/*
* This file is part of Cockpit.
*
* Copyright (C) 2016 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/cockpittest.h"
#include "common/cockpitwebserver.h"
#include "common/cockpiterror.h"
#include
#include
#include
#include
#define PASSWORD "this is the password"
typedef struct {
CockpitAuth *auth;
/* setup_mock_sshd */
GPid mock_sshd;
guint16 ssh_port;
} TestCase;
typedef struct {
const char *header;
} TestFixture;
static GString *
read_all_into_string (int fd)
{
GString *input = g_string_new ("");
gsize len;
gssize ret;
for (;;)
{
len = input->len;
g_string_set_size (input, len + 256);
ret = read (fd, input->str + len, 256);
if (ret < 0)
{
if (errno != EAGAIN)
{
g_critical ("couldn't read from mock input: %s", g_strerror (errno));
g_string_free (input, TRUE);
return NULL;
}
}
else if (ret == 0)
{
return input;
}
else
{
input->len = len + ret;
input->str[input->len] = '\0';
}
}
}
static void
setup_mock_sshd (TestCase *tc)
{
GError *error = NULL;
GString *port;
gchar *endptr;
guint64 value;
gint out_fd;
const gchar *argv[] = {
BUILDDIR "/mock-sshd",
"--user", "me",
"--password", PASSWORD,
NULL
};
g_spawn_async_with_pipes (BUILDDIR, (gchar **)argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL,
&tc->mock_sshd, NULL, &out_fd, NULL, &error);
g_assert_no_error (error);
/*
* mock-sshd prints its port on stdout, and then closes stdout
* This also lets us know when it has initialized.
*/
port = read_all_into_string (out_fd);
g_assert (port != NULL);
close (out_fd);
g_assert_no_error (error);
g_strstrip (port->str);
value = g_ascii_strtoull (port->str, &endptr, 10);
if (!endptr || *endptr != '\0' || value == 0 || value > G_MAXUSHORT)
g_critical ("invalid port printed by mock-sshd: %s", port->str);
tc->ssh_port = (gushort)value;
g_string_free (port, TRUE);
}
static void
setup (TestCase *tc,
gconstpointer data)
{
tc->auth = cockpit_auth_new (TRUE);
setup_mock_sshd (tc);
}
static void
teardown (TestCase *tc,
gconstpointer data)
{
if (tc->mock_sshd)
{
kill (tc->mock_sshd, SIGTERM);
g_assert_cmpint (waitpid (tc->mock_sshd, 0, 0), ==, tc->mock_sshd);
g_spawn_close_pid (tc->mock_sshd);
}
g_clear_object (&tc->auth);
}
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_basic_good (TestCase *test,
gconstpointer data)
{
GHashTable *in_headers;
GHashTable *out_headers;
GAsyncResult *result = NULL;
CockpitCreds *creds;
CockpitWebService *service;
JsonObject *response;
GError *error = NULL;
gchar *path = NULL;
gchar *application = NULL;
gchar *cookie = NULL;
in_headers = mock_auth_basic_header ("me", PASSWORD);
g_hash_table_insert (in_headers, g_strdup ("X-Authorize"), g_strdup ("password"));
out_headers = cockpit_web_server_new_table ();
application = g_strdup_printf ("cockpit+=127.0.0.1:%d", test->ssh_port);
cookie = g_strdup_printf ("machine-cockpit+127.0.0.1:%d", test->ssh_port);
path = g_strdup_printf ("/%s", application);
cockpit_auth_login_async (test->auth, path, NULL, in_headers, on_ready_get_result, &result);
g_hash_table_unref (in_headers);
while (result == NULL)
g_main_context_iteration (NULL, TRUE);
response = cockpit_auth_login_finish (test->auth, result, NULL, out_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 (out_headers, out_headers, cookie);
service = cockpit_auth_check_cookie (test->auth, path, out_headers);
g_assert (service != NULL);
creds = cockpit_web_service_get_creds (service);
g_assert_cmpstr (application, ==, cockpit_creds_get_application (creds));
g_assert_cmpstr (PASSWORD, ==, g_bytes_get_data (cockpit_creds_get_password (creds), NULL));
g_hash_table_unref (out_headers);
g_object_unref (service);
g_free (cookie);
g_free (path);
g_free (application);
}
static const TestFixture fixture_bad_format = {
.header = "Basic d3JvbmctZm9ybWF0Cg=="
};
static const TestFixture fixture_wrong_pw = {
.header = "Basic bWU6d3JvbmcK"
};
static const TestFixture fixture_empty = {
.header = "Basic"
};
static void
test_basic_fail (TestCase *test,
gconstpointer data)
{
GHashTable *headers;
GAsyncResult *result = NULL;
GError *error = NULL;
gchar *path = NULL;
gchar *application = NULL;
const TestFixture *fix = data;
headers = cockpit_web_server_new_table ();
g_hash_table_insert (headers, g_strdup ("Authorization"), g_strdup (fix->header));
g_hash_table_insert (headers, g_strdup ("X-Authorize"), g_strdup ("password"));
application = g_strdup_printf ("cockpit+=127.0.0.1:%d", test->ssh_port);
path = g_strdup_printf ("/%s", application);
cockpit_auth_login_async (test->auth, path, NULL, headers, on_ready_get_result, &result);
g_hash_table_unref (headers);
headers = cockpit_web_server_new_table ();
while (result == NULL)
g_main_context_iteration (NULL, TRUE);
g_assert_null (cockpit_auth_login_finish (test->auth, result, NULL, headers, &error));
g_object_unref (result);
g_assert_error (error, COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED);
g_assert (g_str_has_prefix (error->message, "Authentication failed"));
g_clear_error (&error);
g_hash_table_unref (headers);
g_free (path);
g_free (application);
}
int
main (int argc,
char *argv[])
{
cockpit_ws_ssh_program = BUILDDIR "/cockpit-ssh";
g_setenv ("COCKPIT_SSH_KNOWN_HOSTS_FILE", SRCDIR "/src/ssh/mock_known_hosts", TRUE);
g_setenv ("COCKPIT_SSH_BRIDGE_COMMAND", BUILDDIR "/cockpit-bridge", TRUE);
cockpit_test_init (&argc, &argv);
g_test_add ("/auth-ssh/basic-good", TestCase, NULL,
setup, test_basic_good, teardown);
g_test_add ("/auth-ssh/basic-bad-password", TestCase, &fixture_wrong_pw,
setup, test_basic_fail, teardown);
g_test_add ("/auth-ssh/basic-bad-format", TestCase, &fixture_bad_format,
setup, test_basic_fail, teardown);
g_test_add ("/auth-ssh/basic-empty", TestCase, &fixture_empty,
setup, test_basic_fail, teardown);
return g_test_run ();
}