/* * This file is part of Cockpit. * * Copyright (C) 2013 Red Hat, Inc. * * Cockpit is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * Cockpit is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Cockpit; If not, see . */ #include "config.h" #include "cockpitauth.h" #include "cockpitws.h" #include "mock-auth.h" #include "common/cockpitconf.h" #include "common/cockpiterror.h" #include "common/cockpittest.h" #include "websocket/websocket.h" #include /* Mock override these from other files */ extern const gchar *cockpit_config_file; extern const gchar *cockpit_ws_max_startups; typedef struct { CockpitAuth *auth; } Test; static void setup (Test *test, gconstpointer data) { test->auth = cockpit_auth_new (FALSE); } static void teardown (Test *test, gconstpointer data) { g_object_unref (test->auth); } static void setup_normal (Test *test, gconstpointer data) { cockpit_config_file = SRCDIR "/src/ws/mock-config/cockpit/cockpit.conf"; test->auth = cockpit_auth_new (FALSE); } static void setup_alt_config (Test *test, gconstpointer data) { cockpit_config_file = SRCDIR "/src/ws/mock-config/cockpit/cockpit-alt.conf"; test->auth = cockpit_auth_new (FALSE); } static void teardown_normal (Test *test, gconstpointer data) { cockpit_assert_expected (); g_object_unref (test->auth); cockpit_conf_cleanup (); } static void on_ready_get_result (GObject *source, GAsyncResult *result, gpointer user_data) { GAsyncResult **retval = user_data; g_assert (retval != NULL); g_assert (*retval == NULL); *retval = g_object_ref (result); } static void test_application (Test *test, gconstpointer data) { gchar *application = NULL; gboolean is_host = FALSE; application = cockpit_auth_parse_application ("/", &is_host); g_assert_cmpstr ("cockpit", ==, application); g_assert_false (is_host); g_clear_pointer (&application, g_free); application = cockpit_auth_parse_application ("/=", &is_host); g_assert_cmpstr ("cockpit", ==, application); g_assert_false (is_host); g_clear_pointer (&application, g_free); application = cockpit_auth_parse_application ("/other/other", &is_host); g_assert_cmpstr ("cockpit", ==, application); g_assert_false (is_host); g_clear_pointer (&application, g_free); application = cockpit_auth_parse_application ("/=other/other", &is_host); g_assert_true (is_host); g_assert_cmpstr ("cockpit+=other", ==, application); g_clear_pointer (&application, g_free); application = cockpit_auth_parse_application ("/=other", &is_host); g_assert_true (is_host); g_assert_cmpstr ("cockpit+=other", ==, application); g_clear_pointer (&application, g_free); application = cockpit_auth_parse_application ("/=other/", &is_host); g_assert_true (is_host); g_assert_cmpstr ("cockpit+=other", ==, application); g_clear_pointer (&application, g_free); application = cockpit_auth_parse_application ("/cockpit", &is_host); g_assert_cmpstr ("cockpit", ==, application); g_assert_false (is_host); g_clear_pointer (&application, g_free); application = cockpit_auth_parse_application ("/cockpit/login", &is_host); g_assert_cmpstr ("cockpit", ==, application); g_assert_false (is_host); g_clear_pointer (&application, g_free); application = cockpit_auth_parse_application ("/cockpit+application", &is_host); g_assert_cmpstr ("cockpit+application", ==, application); g_assert_false (is_host); g_clear_pointer (&application, g_free); application = cockpit_auth_parse_application ("/cockpit+application/", &is_host); g_assert_false (is_host); g_assert_cmpstr ("cockpit+application", ==, application); g_clear_pointer (&application, g_free); application = cockpit_auth_parse_application ("/cockpit+application/other/other", &is_host); g_assert_false (is_host); g_assert_cmpstr ("cockpit+application", ==, application); g_clear_pointer (&application, g_free); } static void test_userpass_cookie_check (Test *test, gconstpointer data) { GAsyncResult *result = NULL; CockpitWebService *service; CockpitWebService *prev_service; CockpitCreds *creds; CockpitCreds *prev_creds; JsonObject *response = NULL; GError *error = NULL; GHashTable *headers; headers = mock_auth_basic_header ("me", "this is the password"); g_hash_table_insert (headers, g_strdup ("X-Authorize"), g_strdup ("password")); cockpit_auth_login_async (test->auth, "/cockpit/", NULL, headers, on_ready_get_result, &result); g_hash_table_unref (headers); while (result == NULL) g_main_context_iteration (NULL, TRUE); headers = web_socket_util_new_headers (); response = cockpit_auth_login_finish (test->auth, result, NULL, headers, &error); g_assert_no_error (error); /* Get the service */ mock_auth_include_cookie_as_if_client (headers, headers, "cockpit"); service = cockpit_auth_check_cookie (test->auth, "/cockpit", headers); g_object_unref (result); g_assert (service != NULL); g_assert (response != NULL); creds = cockpit_web_service_get_creds (service); g_assert_cmpstr ("me", ==, cockpit_creds_get_user (creds)); g_assert_cmpstr ("cockpit", ==, cockpit_creds_get_application (creds)); g_assert_cmpstr ("this is the password", ==, g_bytes_get_data (cockpit_creds_get_password (creds), NULL)); prev_service = service; g_object_unref (service); service = NULL; prev_creds = creds; creds = NULL; mock_auth_include_cookie_as_if_client (headers, headers, "cockpit"); service = cockpit_auth_check_cookie (test->auth, "/cockpit", headers); g_assert (prev_service == service); creds = cockpit_web_service_get_creds (service); g_assert (prev_creds == creds); g_assert_cmpstr ("me", ==, cockpit_creds_get_user (creds)); g_assert_cmpstr ("this is the password", ==, g_bytes_get_data (cockpit_creds_get_password (creds), NULL)); g_hash_table_destroy (headers); g_object_unref (service); json_object_unref (response); } static void test_userpass_bad (Test *test, gconstpointer data) { GAsyncResult *result = NULL; GError *error = NULL; JsonObject *response; GHashTable *headers; headers = mock_auth_basic_header ("me", "bad"); cockpit_auth_login_async (test->auth, "/cockpit", NULL, headers, on_ready_get_result, &result); g_hash_table_unref (headers); while (result == NULL) g_main_context_iteration (NULL, TRUE); headers = web_socket_util_new_headers (); response = cockpit_auth_login_finish (test->auth, result, NULL, headers, &error); g_object_unref (result); g_assert (response == NULL); g_assert_error (error, COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED); g_clear_error (&error); g_hash_table_destroy (headers); } static void test_userpass_emptypass (Test *test, gconstpointer data) { GAsyncResult *result = NULL; JsonObject *response; GError *error = NULL; GHashTable *headers; headers = mock_auth_basic_header ("aaaaaa", ""); cockpit_auth_login_async (test->auth, "/cockpit", NULL, headers, on_ready_get_result, &result); g_hash_table_unref (headers); while (result == NULL) g_main_context_iteration (NULL, TRUE); headers = web_socket_util_new_headers (); response = cockpit_auth_login_finish (test->auth, result, NULL, headers, &error); g_object_unref (result); g_assert (response == NULL); g_assert_error (error, COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED); g_clear_error (&error); g_hash_table_destroy (headers); } static void test_headers_bad (Test *test, gconstpointer data) { GHashTable *headers; headers = web_socket_util_new_headers (); /* Bad version */ g_hash_table_insert (headers, g_strdup ("Cookie"), g_strdup ("CockpitAuth=v=1;k=blah")); if (cockpit_auth_check_cookie (test->auth, "/cockpit", headers)) g_assert_not_reached (); /* Bad hash */ g_hash_table_remove_all (headers); g_hash_table_insert (headers, g_strdup ("Cookie"), g_strdup ("CockpitAuth=v=2;k=blah")); if (cockpit_auth_check_cookie (test->auth, "/cockpit", headers)) g_assert_not_reached (); g_hash_table_destroy (headers); } static gboolean on_timeout_set_flag (gpointer data) { gboolean *flag = data; g_assert (*flag == FALSE); *flag = TRUE; return FALSE; } static gboolean on_idling_set_flag (CockpitAuth *auth, gpointer data) { gboolean *flag = data; *flag = TRUE; return FALSE; } static void test_idle_timeout (Test *test, gconstpointer data) { GAsyncResult *result = NULL; CockpitWebService *service; JsonObject *login_response; GError *error = NULL; GHashTable *headers; gboolean flag = FALSE; gboolean idling = FALSE; /* The idle timeout is one second */ g_assert (cockpit_ws_service_idle == 1); headers = mock_auth_basic_header ("me", "this is the password"); cockpit_auth_login_async (test->auth, "/cockpit", NULL, headers, on_ready_get_result, &result); g_hash_table_unref (headers); while (result == NULL) g_main_context_iteration (NULL, TRUE); headers = web_socket_util_new_headers (); login_response = cockpit_auth_login_finish (test->auth, result, NULL, headers, &error); g_assert (login_response != NULL); json_object_unref (login_response); g_object_unref (result); g_assert_no_error (error); /* Logged in ... the webservice is idle though */ mock_auth_include_cookie_as_if_client (headers, headers, "cockpit"); service = cockpit_auth_check_cookie (test->auth, "/cockpit", headers); g_assert (service != NULL); g_assert (cockpit_web_service_get_idling (service)); g_object_unref (service); g_assert (cockpit_ws_process_idle == 2); g_signal_connect (test->auth, "idling", G_CALLBACK (on_idling_set_flag), &idling); /* Now wait for 2 seconds, and the service should be gone */ g_timeout_add_seconds (2, on_timeout_set_flag, &flag); while (!flag) g_main_context_iteration (NULL, TRUE); /* Timeout, no longer logged in */ service = cockpit_auth_check_cookie (test->auth, "/cockpit", headers); g_assert (service == NULL); /* Now wait for 3 seconds, and the auth should have said its idling */ flag = FALSE; g_timeout_add_seconds (3, on_timeout_set_flag, &flag); while (!flag) g_main_context_iteration (NULL, TRUE); g_assert (idling == TRUE); g_hash_table_destroy (headers); } static void test_process_timeout (Test *test, gconstpointer data) { gboolean idling = FALSE; g_assert (cockpit_ws_process_idle == 2); g_signal_connect (test->auth, "idling", G_CALLBACK (on_idling_set_flag), &idling); while (!idling) g_main_context_iteration (NULL, TRUE); } static void test_max_startups (Test *test, gconstpointer data) { GAsyncResult *result1 = NULL; GAsyncResult *result2 = NULL; GAsyncResult *result3 = NULL; JsonObject *response; GHashTable *headers_slow; GHashTable *headers_fail; GError *error1 = NULL; GError *error2 = NULL; GError *error3 = NULL; cockpit_expect_message ("Request dropped; too many startup connections: 2"); headers_slow = web_socket_util_new_headers (); headers_fail = web_socket_util_new_headers (); g_hash_table_insert (headers_slow, g_strdup ("Authorization"), g_strdup ("testscheme failslow")); g_hash_table_insert (headers_fail, g_strdup ("Authorization"), g_strdup ("testscheme fail")); /* Slow request that takes a while to complete */ cockpit_auth_login_async (test->auth, "/cockpit", NULL, headers_slow, on_ready_get_result, &result1); /* Request that gets dropped */ cockpit_auth_login_async (test->auth, "/cockpit", NULL, headers_fail, on_ready_get_result, &result2); while (result2 == NULL) g_main_context_iteration (NULL, TRUE); response = cockpit_auth_login_finish (test->auth, result2, NULL, NULL, &error2); g_object_unref (result2); g_assert (response == NULL); g_assert_cmpstr ("Connection closed by host", ==, error2->message); /* Wait for first request to finish */ while (result1 == NULL) g_main_context_iteration (NULL, TRUE); response = cockpit_auth_login_finish (test->auth, result1, NULL, NULL, &error1); g_object_unref (result1); g_assert (response == NULL); g_assert_cmpstr ("Authentication failed", ==, error1->message); /* Now that first is finished we can successfully run another one */ g_hash_table_insert (headers_fail, g_strdup ("Authorization"), g_strdup ("testscheme fail")); cockpit_auth_login_async (test->auth, "/cockpit", NULL, headers_fail, on_ready_get_result, &result3); while (result3 == NULL) g_main_context_iteration (NULL, TRUE); response = cockpit_auth_login_finish (test->auth, result3, NULL, NULL, &error3); g_object_unref (result3); g_assert (response == NULL); g_assert_cmpstr ("Authentication failed", ==, error3->message); g_clear_error (&error1); g_clear_error (&error2); g_clear_error (&error3); g_hash_table_destroy (headers_fail); g_hash_table_destroy (headers_slow); } typedef struct { const gchar *header; const gchar *error_message; const gchar *warning; const gchar *path; int error_code; } ErrorFixture; typedef struct { const gchar *data; const gchar *warning; const gchar *header; const gchar *path; const gchar *user; const gchar *password; const gchar *application; const gchar *cookie_name; gboolean authorized; } SuccessFixture; static void test_custom_fail (Test *test, gconstpointer data) { GAsyncResult *result = NULL; JsonObject *response; GError *error = NULL; GHashTable *headers; const ErrorFixture *fix = data; const gchar *path = fix->path ? fix->path : "/cockpit"; if (fix->warning) cockpit_expect_warning (fix->warning); headers = web_socket_util_new_headers (); g_hash_table_insert (headers, g_strdup ("Authorization"), g_strdup (fix->header)); cockpit_auth_login_async (test->auth, path, NULL, headers, on_ready_get_result, &result); g_hash_table_unref (headers); while (result == NULL) g_main_context_iteration (NULL, TRUE); headers = web_socket_util_new_headers (); response = cockpit_auth_login_finish (test->auth, result, NULL, headers, &error); g_object_unref (result); g_assert (response == NULL); if (fix->error_code) g_assert_error (error, COCKPIT_ERROR, fix->error_code); else g_assert (error != NULL); g_assert_cmpstr (fix->error_message, ==, error->message); g_clear_error (&error); g_hash_table_destroy (headers); } static void test_custom_timeout (Test *test, gconstpointer data) { cockpit_expect_message ("*session timed out*"); test_custom_fail (test, data); } static void test_bad_command (Test *test, gconstpointer data) { cockpit_expect_possible_log ("cockpit-protocol", G_LOG_LEVEL_WARNING, "*couldn't recv*"); cockpit_expect_possible_log ("cockpit-ws", G_LOG_LEVEL_WARNING, "*Auth pipe closed: internal-error*"); cockpit_expect_possible_log ("cockpit-ws", G_LOG_LEVEL_WARNING, "*Auth pipe closed: not-found*"); cockpit_expect_possible_log ("cockpit-ws", G_LOG_LEVEL_WARNING, "*Auth pipe closed: terminated*"); cockpit_expect_possible_log ("cockpit-ws", G_LOG_LEVEL_WARNING, "*couldn't write: Connection refused*"); cockpit_expect_possible_log ("cockpit-protocol", G_LOG_LEVEL_MESSAGE, "*couldn't write: Connection refused*"); cockpit_expect_possible_log ("cockpit-protocol", G_LOG_LEVEL_MESSAGE, "*couldn't send: Connection refused*"); test_custom_fail (test, data); } static void test_custom_success (Test *test, gconstpointer data) { GAsyncResult *result = NULL; CockpitWebService *service; JsonObject *response; CockpitCreds *creds; GError *error = NULL; GHashTable *headers; JsonObject *login_data; const SuccessFixture *fix = data; const gchar *path = fix->path ? fix->path : "/cockpit"; const gchar *password = fix->password ? fix->password : "this is the password"; const gchar *application = fix->application ? fix->application : "cockpit"; if (fix->warning) cockpit_expect_warning (fix->warning); headers = web_socket_util_new_headers (); g_hash_table_insert (headers, g_strdup ("Authorization"), g_strdup (fix->header)); if (fix->authorized) g_hash_table_insert (headers, g_strdup ("X-Authorize"), g_strdup ("password")); cockpit_auth_login_async (test->auth, path, NULL, headers, on_ready_get_result, &result); g_hash_table_unref (headers); while (result == NULL) g_main_context_iteration (NULL, TRUE); headers = web_socket_util_new_headers (); response = cockpit_auth_login_finish (test->auth, result, NULL, headers, &error); g_object_unref (result); g_assert_no_error (error); g_assert (response != NULL); json_object_unref (response); mock_auth_include_cookie_as_if_client (headers, headers, fix->cookie_name ? fix->cookie_name : "cockpit"); service = cockpit_auth_check_cookie (test->auth, path, headers); creds = cockpit_web_service_get_creds (service); g_assert_cmpstr (application, ==, cockpit_creds_get_application (creds)); if (fix->authorized && g_str_has_prefix (fix->header, "Basic")) { if (password == NULL) g_assert_null (cockpit_creds_get_password (creds)); else g_assert_cmpstr (g_bytes_get_data (cockpit_creds_get_password (creds), NULL), ==, password); } else { g_assert_null (cockpit_creds_get_password (creds)); } login_data = cockpit_creds_get_login_data (creds); if (fix->data) g_assert_cmpstr (json_object_get_string_member (login_data, "login"), ==, fix->data); else g_assert_null (login_data); g_hash_table_destroy (headers); g_object_unref (service); } static const SuccessFixture fixture_ssh_basic = { .warning = NULL, .data = NULL, .header = "Basic bWU6dGhpcyBpcyB0aGUgcGFzc3dvcmQ=", .authorized = TRUE }; static const SuccessFixture fixture_ssh_not_authorized = { .warning = NULL, .data = NULL, .header = "Basic bWU6dGhpcyBpcyB0aGUgcGFzc3dvcmQ=", .authorized = FALSE }; static const SuccessFixture fixture_ssh_remote_basic = { .warning = NULL, .data = NULL, .header = "Basic cmVtb3RlLXVzZXI6dGhpcyBpcyB0aGUgbWFjaGluZSBwYXNzd29yZA==", .path = "/cockpit+=machine", .user = "remote-user", .password = "this is the machine password", .application = "cockpit+=machine", .cookie_name = "machine-cockpit+machine" }; static const SuccessFixture fixture_ssh_no_data = { .warning = NULL, .data = NULL, .header = "testsshscheme success" }; static const SuccessFixture fixture_ssh_remote_switched = { .data = NULL, .header = "testscheme ssh-remote-switch", .path = "/cockpit+=machine", .application = "cockpit+=machine", .cookie_name = "machine-cockpit+machine" }; static const SuccessFixture fixture_ssh_alt_default = { .data = NULL, .header = "testsshscheme ssh-alt-default", }; static const SuccessFixture fixture_ssh_alt = { .data = NULL, .path = "/cockpit+=machine", .application = "cockpit+=machine", .header = "testsshscheme ssh-alt-machine", .cookie_name = "machine-cockpit+machine" }; static const ErrorFixture fixture_bad_conversation = { .header = "X-Conversation conversation-id xxx", .error_message = "Invalid conversation token" }; static const ErrorFixture fixture_ssh_basic_failed = { .error_message = "Authentication failed", .header = "Basic dXNlcjp0aGlzIGlzIHRoZSBwYXNzd29yZA==" }; static const ErrorFixture fixture_ssh_remote_basic_failed = { .error_message = "Authentication failed", .header = "Basic d3Jvbmc6dGhpcyBpcyB0aGUgbWFjaGluZSBwYXNzd29yZA==", .path = "/cockpit+=machine" }; static const ErrorFixture fixture_ssh_not_supported = { .error_code = COCKPIT_ERROR_AUTHENTICATION_FAILED, .error_message = "Authentication failed: authentication-not-supported", .header = "testsshscheme not-supported", }; static const ErrorFixture fixture_ssh_auth_failed = { .error_code = COCKPIT_ERROR_AUTHENTICATION_FAILED, .error_message = "Authentication failed", .header = "testsshscheme ssh-fail", }; static const ErrorFixture fixture_ssh_auth_with_error = { .error_code = COCKPIT_ERROR_FAILED, .error_message = "Authentication failed: unknown: detail for error", .header = "testsshscheme with-error", }; static const SuccessFixture fixture_no_cookie = { .warning = NULL, .data = NULL, .header = "testscheme no-cookie" }; static const SuccessFixture fixture_no_data = { .warning = NULL, .data = NULL, .header = "testscheme success" }; static const SuccessFixture fixture_data_then_success = { .warning = NULL, .data = "data", .header = "testscheme data-then-success" }; static const ErrorFixture fixture_bad_command = { .error_code = COCKPIT_ERROR_FAILED, .error_message = "Internal error in login process", .header = "badcommand bad", }; static const ErrorFixture fixture_auth_failed = { .error_code = COCKPIT_ERROR_AUTHENTICATION_FAILED, .error_message = "Authentication failed", .header = "testscheme fail", }; static const ErrorFixture fixture_auth_denied = { .error_code = COCKPIT_ERROR_PERMISSION_DENIED, .error_message = "Permission denied", .header = "testscheme denied", }; static const ErrorFixture fixture_auth_with_error = { .error_code = COCKPIT_ERROR_FAILED, .error_message = "Authentication failed: unknown: detail for error", .header = "testscheme with-error", }; static const ErrorFixture fixture_auth_none = { .error_code = COCKPIT_ERROR_AUTHENTICATION_FAILED, .error_message = "Authentication disabled", .header = "none invalid", }; static const ErrorFixture fixture_auth_timeout = { .error_message = "Authentication failed: Timeout", .header = "timeout-scheme too-slow", }; typedef struct { const gchar **headers; const gchar **prompts; const gchar *error_message; const gchar *message; int error_code; int pause; } ErrorMultiFixture; typedef struct { const gchar **headers; const gchar **prompts; } SuccessMultiFixture; static inline gchar * str_skip (gchar *v, gchar c) { while (v[0] == c) v++; return v; } static gboolean parse_login_reply_challenge (GHashTable *headers, gchar **out_id, gchar **out_prompt) { gchar *original = NULL; gchar *line; gchar *next; gchar *id = NULL; gchar *prompt = NULL; gboolean ret = FALSE; gpointer key = NULL; gsize length; if (!g_hash_table_lookup_extended (headers, "WWW-Authenticate", &key, (gpointer *)&original)) goto out; line = original; // Check challenge type line = str_skip (line, ' '); if (g_ascii_strncasecmp (line, "X-Conversation ", strlen("X-Conversation ")) != 0) goto out; next = strchr (line, ' '); if (!next) goto out; // Get id line = next; line = str_skip (line, ' '); next = strchr (line, ' '); if (!next) goto out; id = g_strndup (line, next - line); // Rest should be the base64 prompt next = str_skip (next, ' '); prompt = g_strdup (next); if (g_base64_decode_inplace (prompt, &length) == NULL) goto out; prompt[length] = '\0'; ret = TRUE; out: if (ret) { *out_id = id; *out_prompt = prompt; } else { g_warning ("Got invalid WWW-Authenticate header: %s", original); g_free (id); g_free (prompt); } return ret; } static void test_multi_step_success (Test *test, gconstpointer data) { CockpitWebService *service; CockpitCreds *creds; GHashTable *headers = NULL; gint spot = 0; gchar *id = NULL; const SuccessMultiFixture *fix = data; for (spot = 0; fix->headers[spot]; spot++) { GAsyncResult *result = NULL; JsonObject *response = NULL; GError *error = NULL; const gchar *header = fix->headers[spot]; const gchar *expect_prompt = fix->prompts[spot]; gchar *out = NULL; gchar *prompt = NULL; headers = web_socket_util_new_headers (); if (id) { g_assert (id != NULL); out = g_base64_encode ((guint8 *)header, strlen (header)); g_hash_table_insert (headers, g_strdup ("Authorization"), g_strdup_printf ("X-Conversation %s %s", id, out)); g_free (id); g_free (out); out = NULL; id = NULL; } else { g_hash_table_insert (headers, g_strdup ("Authorization"), g_strdup (header)); } cockpit_auth_login_async (test->auth, "/cockpit/", NULL, headers, on_ready_get_result, &result); g_hash_table_unref (headers); while (result == NULL) g_main_context_iteration (NULL, TRUE); headers = web_socket_util_new_headers (); response = cockpit_auth_login_finish (test->auth, result, NULL, headers, &error); g_object_unref (result); /* Confirm we got the right prompt */ if (expect_prompt) { g_assert (prompt == NULL); g_assert (id == NULL); g_assert (parse_login_reply_challenge (headers, &id, &prompt)); g_assert_cmpstr (expect_prompt, ==, prompt); g_assert (id != NULL); g_free (prompt); prompt = NULL; g_assert_error (error, COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED); g_clear_error (&error); g_hash_table_unref (headers); } else { g_assert_no_error (error); } if (response) json_object_unref (response); } mock_auth_include_cookie_as_if_client (headers, headers, "cockpit"); service = cockpit_auth_check_cookie (test->auth, "/cockpit", headers); creds = cockpit_web_service_get_creds (service); g_assert_cmpstr ("cockpit", ==, cockpit_creds_get_application (creds)); g_assert_null (cockpit_creds_get_password (creds)); if (headers) g_hash_table_destroy (headers); g_object_unref (service); } static void test_multi_step_fail (Test *test, gconstpointer data) { GHashTable *headers = NULL; gint spot = 0; gchar *id = NULL; gchar *prompt = NULL; const ErrorMultiFixture *fix = data; if (fix->message) cockpit_expect_message (fix->message); for (spot = 0; fix->headers[spot]; spot++) { GAsyncResult *result = NULL; JsonObject *response = NULL; GError *error = NULL; const gchar *header = fix->headers[spot]; const gchar *expect_prompt = fix->prompts[spot]; gchar *out = NULL; gboolean ready_for_next = TRUE; headers = web_socket_util_new_headers (); if (id) { g_assert (id != NULL); out = g_base64_encode ((guint8 *)header, strlen (header)); g_hash_table_insert (headers, g_strdup ("Authorization"), g_strdup_printf ("X-Conversation %s %s", id, out)); g_free (id); g_free (out); out = NULL; id = NULL; } else { g_hash_table_insert (headers, g_strdup ("Authorization"), g_strdup (header)); } cockpit_auth_login_async (test->auth, "/cockpit/", NULL, headers, on_ready_get_result, &result); g_hash_table_unref (headers); while (result == NULL) g_main_context_iteration (NULL, TRUE); headers = web_socket_util_new_headers (); response = cockpit_auth_login_finish (test->auth, result, NULL, headers, &error); g_object_unref (result); g_assert (error != NULL); /* Confirm we got the right prompt */ if (expect_prompt) { g_assert (prompt == NULL); g_assert (id == NULL); g_assert (parse_login_reply_challenge (headers, &id, &prompt)); g_assert_cmpstr (expect_prompt, ==, prompt); g_assert (id != NULL); g_free (prompt); prompt = NULL; if (fix->pause) { ready_for_next = FALSE; g_timeout_add_seconds (fix->pause, on_timeout_set_flag, &ready_for_next); } while (ready_for_next == FALSE) g_main_context_iteration (NULL, TRUE); if (response) json_object_unref (response); g_hash_table_unref (headers); g_assert_error (error, COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED); g_clear_error (&error); } else { g_assert (response == NULL); if (fix->error_code) g_assert_error (error, COCKPIT_ERROR, fix->error_code); else g_assert (error != NULL); g_assert_cmpstr (fix->error_message, ==, error->message); g_clear_error (&error); break; } } if (headers) g_hash_table_destroy (headers); } const gchar *two_steps[3] = { "testscheme two-step", "two", NULL }; const gchar *two_prompts[2] = { "type two", NULL }; const gchar *three_steps[4] = { "testscheme three-step", "two", "three", NULL }; const gchar *three_steps_ssh[4] = { "testsshscheme three-step", "two", "three", NULL }; const gchar *three_prompts[3] = { "type two", "type three", NULL }; static const SuccessMultiFixture fixture_two_steps = { .headers = two_steps, .prompts = two_prompts, }; static const SuccessMultiFixture fixture_three_steps = { .headers = three_steps, .prompts = three_prompts, }; static const SuccessMultiFixture fixture_ssh_three_steps = { .headers = three_steps_ssh, .prompts = three_prompts, }; const gchar *two_steps_ssh_wrong[3] = { "testsshscheme two-step", "bad", NULL }; const gchar *two_steps_wrong[3] = { "testscheme two-step", "bad", NULL }; const gchar *three_steps_wrong[4] = { "testscheme three-step", "two", "bad", NULL }; static const ErrorMultiFixture fixture_fail_three_steps = { .headers = three_steps_wrong, .prompts = three_prompts, .error_code = COCKPIT_ERROR_AUTHENTICATION_FAILED, .error_message = "Authentication failed", }; static const ErrorMultiFixture fixture_fail_two_steps = { .headers = two_steps_wrong, .prompts = two_prompts, .error_code = COCKPIT_ERROR_AUTHENTICATION_FAILED, .error_message = "Authentication failed", }; static const ErrorMultiFixture fixture_fail_ssh_two_steps = { .headers = two_steps_ssh_wrong, .prompts = two_prompts, .error_code = COCKPIT_ERROR_AUTHENTICATION_FAILED, .error_message = "Authentication failed", }; static const ErrorMultiFixture fixture_fail_step_timeout = { .headers = two_steps, .prompts = two_prompts, .error_code = COCKPIT_ERROR_AUTHENTICATION_FAILED, .error_message = "Invalid conversation token", .message = "*session timed out during authentication*", .pause = 3, }; typedef struct { const gchar *str; guint max_startups; guint max_startups_rate; guint max_startups_begin; gboolean warn; } StartupFixture; static void setup_startups (Test *test, gconstpointer data) { const StartupFixture *fix = data; cockpit_config_file = SRCDIR "does-not-exist"; cockpit_ws_max_startups = fix->str; if (fix->warn) cockpit_expect_warning ("Illegal MaxStartups spec*"); test->auth = cockpit_auth_new (FALSE); } static void teardown_startups (Test *test, gconstpointer data) { cockpit_assert_expected (); g_object_unref (test->auth); } static const StartupFixture fixture_normal = { .str = "20:50:200", .max_startups = 200, .max_startups_begin = 20, .max_startups_rate = 50, .warn = FALSE, }; static const StartupFixture fixture_single = { .str = "20", .max_startups = 20, .max_startups_begin = 20, .max_startups_rate = 100, .warn = FALSE, }; static const StartupFixture fixture_double = { .str = "20:50", .max_startups = 20, .max_startups_begin = 20, .max_startups_rate = 100, .warn = FALSE, }; static const StartupFixture fixture_unlimited = { .str = "0", .max_startups = 0, .max_startups_begin = 0, .max_startups_rate = 100, .warn = FALSE, }; static const StartupFixture fixture_bad = { .str = "bad", .max_startups = 10, .max_startups_begin = 10, .max_startups_rate = 100, .warn = TRUE, }; static const StartupFixture fixture_bad_rate = { .str = "20:101:40", .max_startups = 10, .max_startups_begin = 10, .max_startups_rate = 100, .warn = TRUE, }; static const StartupFixture fixture_bad_startups = { .str = "40:101:20", .max_startups = 10, .max_startups_begin = 10, .max_startups_rate = 100, .warn = TRUE, }; static const StartupFixture fixture_bad_negative = { .str = "-40:101:20", .max_startups = 10, .max_startups_begin = 10, .max_startups_rate = 100, .warn = TRUE, }; static const StartupFixture fixture_bad_too_many = { .str = "40:101:20:50:50", .max_startups = 10, .max_startups_begin = 10, .max_startups_rate = 100, .warn = TRUE, }; static void test_max_startups_conf (Test *test, gconstpointer data) { const StartupFixture *fix = data; g_assert_cmpuint (fix->max_startups_begin, ==, test->auth->max_startups_begin); g_assert_cmpuint (fix->max_startups, ==, test->auth->max_startups); g_assert_cmpuint (fix->max_startups_rate, ==, test->auth->max_startups_rate); } int main (int argc, char *argv[]) { cockpit_ws_session_program = BUILDDIR "/mock-auth-command"; cockpit_ws_service_idle = 1; cockpit_ws_process_idle = 2; cockpit_test_init (&argc, &argv); g_test_add ("/auth/application", Test, NULL, NULL, test_application, NULL); g_test_add ("/auth/userpass-header-check", Test, NULL, setup, test_userpass_cookie_check, teardown); g_test_add ("/auth/userpass-bad", Test, NULL, setup, test_userpass_bad, teardown); g_test_add ("/auth/userpass-emptypass", Test, NULL, setup, test_userpass_emptypass, teardown); g_test_add ("/auth/headers-bad", Test, NULL, setup, test_headers_bad, teardown); g_test_add ("/auth/idle-timeout", Test, NULL, setup, test_idle_timeout, teardown); g_test_add ("/auth/process-timeout", Test, NULL, setup, test_process_timeout, teardown); g_test_add ("/auth/bad-coversation", Test, &fixture_bad_conversation, setup_normal, test_custom_fail, teardown_normal); g_test_add ("/auth/custom-success", Test, &fixture_no_data, setup_normal, test_custom_success, teardown_normal); g_test_add ("/auth/custom-no-cookie-success", Test, &fixture_no_cookie, setup_normal, test_custom_success, teardown_normal); g_test_add ("/auth/custom-data-then-success", Test, &fixture_data_then_success, setup_normal, test_custom_success, teardown_normal); g_test_add ("/auth/custom-fail-auth", Test, &fixture_auth_failed, setup_normal, test_custom_fail, teardown_normal); g_test_add ("/auth/custom-denied-auth", Test, &fixture_auth_denied, setup_normal, test_custom_fail, teardown_normal); g_test_add ("/auth/custom-with-error", Test, &fixture_auth_with_error, setup_normal, test_custom_fail, teardown_normal); g_test_add ("/auth/custom-timeout", Test, &fixture_auth_timeout, setup_normal, test_custom_timeout, teardown_normal); g_test_add ("/auth/custom-ssh-basic-success", Test, &fixture_ssh_basic, setup_normal, test_custom_success, teardown_normal); g_test_add ("/auth/custom-ssh-basic-success-not-authorized", Test, &fixture_ssh_not_authorized, setup_normal, test_custom_success, teardown_normal); g_test_add ("/auth/custom-ssh-remote-basic-success", Test, &fixture_ssh_remote_basic, setup_normal, test_custom_success, teardown_normal); g_test_add ("/auth/custom-ssh-remote-switched", Test, &fixture_ssh_remote_switched, setup_normal, test_custom_success, teardown_normal); g_test_add ("/auth/custom-ssh-with-conf-default", Test, &fixture_ssh_alt_default, setup_alt_config, test_custom_success, teardown_normal); g_test_add ("/auth/custom-ssh-with-conf-allow", Test, &fixture_ssh_alt, setup_alt_config, test_custom_success, teardown_normal); g_test_add ("/auth/custom-ssh-success", Test, &fixture_ssh_no_data, setup_normal, test_custom_success, teardown_normal); g_test_add ("/auth/custom-ssh-fail-auth", Test, &fixture_ssh_auth_failed, setup_normal, test_custom_fail, teardown_normal); g_test_add ("/auth/custom-ssh-fail-basic-auth", Test, &fixture_ssh_basic_failed, setup_normal, test_custom_fail, teardown_normal); g_test_add ("/auth/custom-ssh-remote-fail-basic-auth", Test, &fixture_ssh_remote_basic_failed, setup_normal, test_custom_fail, teardown_normal); g_test_add ("/auth/custom-ssh-not-supported", Test, &fixture_ssh_not_supported, setup_normal, test_custom_fail, teardown_normal); g_test_add ("/auth/custom-ssh-with-error", Test, &fixture_ssh_auth_with_error, setup_normal, test_custom_fail, teardown_normal); g_test_add ("/auth/success-ssh-multi-step-three", Test, &fixture_ssh_three_steps, setup_normal, test_multi_step_success, teardown_normal); g_test_add ("/auth/fail-ssh-multi-step-two", Test, &fixture_fail_ssh_two_steps, setup_normal, test_multi_step_fail, teardown_normal); g_test_add ("/auth/none", Test, &fixture_auth_none, setup_normal, test_custom_fail, teardown_normal); g_test_add ("/auth/bad-command", Test, &fixture_bad_command, setup_normal, test_bad_command, teardown_normal); g_test_add ("/auth/success-multi-step-two", Test, &fixture_two_steps, setup_normal, test_multi_step_success, teardown_normal); g_test_add ("/auth/success-multi-step-three", Test, &fixture_three_steps, setup_normal, test_multi_step_success, teardown_normal); g_test_add ("/auth/fail-multi-step-two", Test, &fixture_fail_two_steps, setup_normal, test_multi_step_fail, teardown_normal); g_test_add ("/auth/fail-multi-step-three", Test, &fixture_fail_three_steps, setup_normal, test_multi_step_fail, teardown_normal); g_test_add ("/auth/fail-multi-step-timeout", Test, &fixture_fail_step_timeout, setup_normal, test_multi_step_fail, teardown_normal); g_test_add ("/auth/max-startups", Test, NULL, setup_normal, test_max_startups, teardown_normal); g_test_add ("/auth/max-startups-normal", Test, &fixture_normal, setup_startups, test_max_startups_conf, teardown_startups); g_test_add ("/auth/max-startups-single", Test, &fixture_single, setup_startups, test_max_startups_conf, teardown_startups); g_test_add ("/auth/max-startups-double", Test, &fixture_double, setup_startups, test_max_startups_conf, teardown_startups); g_test_add ("/auth/max-startups-unlimited", Test, &fixture_unlimited, setup_startups, test_max_startups_conf, teardown_startups); g_test_add ("/auth/max-startups-bad", Test, &fixture_bad, setup_startups, test_max_startups_conf, teardown_startups); g_test_add ("/auth/max-startups-bad-rate", Test, &fixture_bad_rate, setup_startups, test_max_startups_conf, teardown_startups); g_test_add ("/auth/max-startups-bad-startups", Test, &fixture_bad_startups, setup_startups, test_max_startups_conf, teardown_startups); g_test_add ("/auth/max-startups-bad-negative", Test, &fixture_bad_negative, setup_startups, test_max_startups_conf, teardown_startups); g_test_add ("/auth/max-startups-too-many", Test, &fixture_bad_too_many, setup_startups, test_max_startups_conf, teardown_startups); return g_test_run (); }