plugins: added digest plugin along with unit tests
authorImran Zaman <imran.zaman@linux.intel.com>
Wed, 27 Mar 2013 10:17:07 +0000 (12:17 +0200)
committerJussi Laako <jussi.laako@linux.intel.com>
Wed, 27 Mar 2013 16:26:01 +0000 (18:26 +0200)
configure.ac
src/plugins/Makefile.am
src/plugins/digest/Makefile.am [new file with mode: 0644]
src/plugins/digest/gsignond-digest-plugin.c [new file with mode: 0644]
src/plugins/digest/gsignond-digest-plugin.h [new file with mode: 0644]
test/plugins/Makefile.am
test/plugins/digestplugintest.c [new file with mode: 0644]

index 4fbf744..0bac4ad 100644 (file)
@@ -76,6 +76,7 @@ src/extensions/test/Makefile
 src/plugins/Makefile
 src/plugins/password/Makefile
 src/plugins/ssotest/Makefile
+src/plugins/digest/Makefile
 test/Makefile
 test/common/Makefile
 test/db/Makefile
index 2c5162e..0d23ac6 100644 (file)
@@ -1 +1 @@
-SUBDIRS=password ssotest
+SUBDIRS=password ssotest digest
diff --git a/src/plugins/digest/Makefile.am b/src/plugins/digest/Makefile.am
new file mode 100644 (file)
index 0000000..7213883
--- /dev/null
@@ -0,0 +1,22 @@
+include $(top_srcdir)/common.mk
+plugins_LTLIBRARIES = libdigest.la
+NULL=
+
+libdigest_la_CPPFLAGS = \
+    -I$(top_srcdir) \
+    -I$(top_srcdir)/src \
+    -I$(top_srcdir)/include \
+    $(GSIGNOND_CFLAGS) \
+    $(NULL)
+
+libdigest_la_LIBADD = \
+    $(top_builddir)/src/common/libgsignond-common.la \
+    $(GSIGNOND_LIBS) \
+    -lgcrypt \
+    $(NULL)
+
+libdigest_la_SOURCES = \
+    gsignond-digest-plugin.c \
+    $(NULL)
+
+CLEANFILES = 
diff --git a/src/plugins/digest/gsignond-digest-plugin.c b/src/plugins/digest/gsignond-digest-plugin.c
new file mode 100644 (file)
index 0000000..871d0a4
--- /dev/null
@@ -0,0 +1,415 @@
+/* vi: set et sw=4 ts=4 cino=t0,(0: */
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of gsignond
+ *
+ * Copyright (C) 2012-2013 Intel Corporation.
+ *
+ * Contact: Imran Zaman <imran.zaman@intel.com>
+ *
+ * This library 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.
+ *
+ * This library 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <gsignond/gsignond-plugin-interface.h>
+#include "gsignond-digest-plugin.h"
+#include <gsignond/gsignond-error.h>
+#include <gsignond/gsignond-log.h>
+
+static void gsignond_plugin_interface_init (GSignondPluginInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GSignondDigestPlugin, gsignond_digest_plugin,
+                         G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GSIGNOND_TYPE_PLUGIN,
+                                                gsignond_plugin_interface_init));
+
+#define GSIGNOND_DIGEST_PLUGIN_GET_PRIVATE(obj) \
+                                       (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
+                                        GSIGNOND_TYPE_DIGEST_PLUGIN, \
+                                        GSignondDigestPluginPrivate))
+
+#define DATA_SET_VALUE(data, key, value) \
+    if (value) { \
+        gsignond_dictionary_set_string(data, key, value); \
+    }
+#define TO_GUCHAR(data) ((const guchar*)data)
+
+struct _GSignondDigestPluginPrivate
+{
+    GSignondSessionData *session_data;
+};
+
+static gchar *
+_gsignond_digest_plugin_compute_md5_digest(
+        const gchar* algo,
+        const gchar* username,
+        const gchar* realm,
+        const gchar* secret,
+        const gchar* nonce,
+        const gchar* nonce_count,
+        const gchar* cnonce,
+        const gchar* qop,
+        const gchar* method,
+        const gchar* digest_uri,
+        const gchar* hentity)
+{
+    GChecksum *a1 = NULL, *a2 = NULL, *response = NULL;
+    const gchar *ha1 = NULL, *ha2 = NULL;
+    gchar *hresponse = NULL;
+
+    a1 = g_checksum_new (G_CHECKSUM_MD5);
+    g_checksum_update (a1, TO_GUCHAR(username), strlen(username));
+    g_checksum_update (a1, TO_GUCHAR(":"), 1);
+    g_checksum_update (a1, TO_GUCHAR(realm), strlen(realm));
+    g_checksum_update (a1, TO_GUCHAR(":"), 1);
+    g_checksum_update (a1, TO_GUCHAR(secret), strlen(secret));
+
+    if (g_strcmp0 (algo, "md5-sess") == 0) {
+        GChecksum *a1_sess = NULL;
+        a1_sess = g_checksum_new (G_CHECKSUM_MD5);
+        ha1 = g_checksum_get_string (a1);
+        g_checksum_update (a1_sess, TO_GUCHAR(ha1), strlen(ha1));
+        g_checksum_update (a1_sess, TO_GUCHAR(":"), 1);
+        g_checksum_update (a1_sess, TO_GUCHAR(nonce), strlen(nonce));
+        g_checksum_update (a1_sess, TO_GUCHAR(":"), 1);
+        g_checksum_update (a1_sess, TO_GUCHAR(cnonce), strlen(cnonce));
+        g_checksum_free (a1);
+        a1 = a1_sess;
+    }
+
+    a2 = g_checksum_new (G_CHECKSUM_MD5);
+    g_checksum_update (a2, TO_GUCHAR(method), strlen(method));
+    g_checksum_update (a2, TO_GUCHAR(":"), 1);
+    g_checksum_update (a2, TO_GUCHAR(digest_uri), strlen(digest_uri));
+    if (qop && g_strcmp0 (qop, "auth-int") == 0) {
+        g_checksum_update (a2, TO_GUCHAR(":"), 1);
+        g_checksum_update (a2, TO_GUCHAR(hentity), strlen(hentity));
+    }
+    ha1 = g_checksum_get_string (a1);
+    ha2 = g_checksum_get_string (a2);
+
+    response = g_checksum_new (G_CHECKSUM_MD5);
+    g_checksum_update (response, TO_GUCHAR(ha1), strlen(ha1));
+    g_checksum_update (response, TO_GUCHAR(":"), 1);
+    g_checksum_update (response, TO_GUCHAR(nonce), strlen(nonce));
+    g_checksum_update (response, TO_GUCHAR(":"), 1);
+    if (qop) {
+        g_checksum_update (response, TO_GUCHAR(nonce_count),
+                strlen(nonce_count));
+        g_checksum_update (response, TO_GUCHAR(":"), 1);
+        g_checksum_update (response, TO_GUCHAR(cnonce), strlen(cnonce));
+        g_checksum_update (response, TO_GUCHAR(":"), 1);
+        g_checksum_update (response, TO_GUCHAR(qop), strlen(qop));
+        g_checksum_update (response, TO_GUCHAR(":"), 1);
+    }
+    g_checksum_update (response, TO_GUCHAR(ha2), strlen(ha2));
+    hresponse = g_strdup(g_checksum_get_string (response));
+    g_checksum_free (response);
+    g_checksum_free (a2);
+    g_checksum_free (a1);
+    return hresponse;
+}
+
+static gchar *
+_gsignond_digest_plugin_generate_nonce(void)
+{
+    GChecksum *hash = NULL;
+    gchar *nonce = NULL, *timestr = NULL;
+    struct timespec ts;
+    gint fd;
+
+    hash = g_checksum_new (G_CHECKSUM_MD5);
+    fd = open("/dev/urandom", O_RDONLY);
+    if (fd != -1) {
+        guint8 buf[32];
+        if (read (fd, buf, sizeof(buf) > 0)) {
+            g_checksum_update (hash, buf, 32);
+            g_checksum_update (hash, TO_GUCHAR(":"), 1);
+        }
+        close(fd);
+    }
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    timestr = g_strdup_printf ("%p:%d:%lu",
+                    hash,
+                    g_random_int (),
+                    (unsigned long) ts.tv_nsec);
+    g_checksum_update (hash, TO_GUCHAR(timestr), strlen(timestr));
+    g_free (timestr);
+    nonce = g_strdup (g_checksum_get_string (hash));
+    g_checksum_free (hash);
+    return nonce;
+}
+
+static void
+gsignond_digest_plugin_cancel (GSignondPlugin *self)
+{
+    GError* error = g_error_new(GSIGNOND_ERROR,
+                                GSIGNOND_ERROR_SESSION_CANCELED,
+                                "Session cancelled");
+    gsignond_plugin_error (self, error);
+    g_error_free(error);
+}
+
+static void
+gsignond_digest_plugin_abort (GSignondPlugin *self)
+{
+
+}
+
+static void
+gsignond_digest_plugin_request (
+    GSignondPlugin *self,
+    GSignondSessionData *session_data)
+{
+
+}
+
+static void
+gsignond_digest_plugin_request_initial (
+    GSignondPlugin *plugin,
+    GSignondSessionData *session_data,
+    const gchar *mechanism)
+{
+
+    GSignondDigestPlugin *self = NULL;
+    const gchar *username = gsignond_session_data_get_username(session_data);
+    const gchar *secret = gsignond_session_data_get_secret(session_data);
+    const gchar *realm = gsignond_session_data_get_realm (session_data);
+    const gchar *algo = gsignond_dictionary_get_string (session_data, "Algo");
+    const gchar *nonce = gsignond_dictionary_get_string (session_data,
+            "Nonce");
+    const gchar *nonce_count = gsignond_dictionary_get_string (session_data,
+            "NonceCount");
+    const gchar *qop = gsignond_dictionary_get_string (session_data,
+            "Qop");
+    const gchar *method = gsignond_dictionary_get_string (session_data,
+            "Method");
+    const gchar *digest_uri = gsignond_dictionary_get_string (session_data,
+            "DigestUri");
+    const gchar *hentity = gsignond_dictionary_get_string (session_data,
+            "HEntity");
+
+    if ((!realm || !algo  || !nonce  || !method  || !digest_uri)
+        || (qop && g_strcmp0 (qop, "auth-int") == 0 && !hentity)
+        || (qop && !nonce_count)) {
+        GError* error = g_error_new(GSIGNOND_ERROR, GSIGNOND_ERROR_MISSING_DATA,
+                "Missing Session Data");
+        gsignond_plugin_error (plugin, error);
+        g_error_free(error);
+        return;
+    }
+
+    if (username != NULL && secret != NULL) {
+        gchar *cnonce = _gsignond_digest_plugin_generate_nonce ();
+        GSignondSessionData *response = gsignond_dictionary_new ();
+        gsignond_session_data_set_username (response, username);
+        gsignond_dictionary_set_string (response, "CNonce", cnonce);
+        gchar *digest = _gsignond_digest_plugin_compute_md5_digest (algo,
+                username,realm, secret, nonce, nonce_count, cnonce, qop, method,
+                digest_uri, hentity);
+        g_free (cnonce);
+        gsignond_dictionary_set_string (response, "Response", digest);
+        g_free (digest);
+        gsignond_plugin_response_final (plugin, response);
+        gsignond_dictionary_unref (response);
+        return;
+    }
+
+    self = GSIGNOND_DIGEST_PLUGIN (plugin);
+    if (self->priv->session_data) {
+        gsignond_dictionary_unref (self->priv->session_data);
+        self->priv->session_data = NULL;
+    }
+    gsignond_dictionary_ref (session_data);
+    self->priv->session_data = session_data;
+
+    GSignondSignonuiData *user_action_data = gsignond_signonui_data_new ();
+    DATA_SET_VALUE (user_action_data, "Realm", realm);
+    DATA_SET_VALUE (user_action_data, "DigestUri", digest_uri);
+    gsignond_signonui_data_set_query_username (user_action_data, TRUE);
+    gsignond_signonui_data_set_query_password (user_action_data, TRUE);
+    gsignond_plugin_user_action_required (plugin, user_action_data);
+    gsignond_dictionary_unref (user_action_data);
+}
+
+static void
+gsignond_digest_plugin_user_action_finished (
+    GSignondPlugin *plugin,
+    GSignondSignonuiData *signonui_data)
+{
+    GSignondSessionData *session_data = NULL;
+    GSignondSignonuiError query_error;
+    gboolean res = gsignond_signonui_data_get_query_error(signonui_data,
+            &query_error);
+    g_assert(res == TRUE);
+
+    const gchar* username = gsignond_signonui_data_get_username(signonui_data);
+    const gchar* secret = gsignond_signonui_data_get_password(signonui_data);
+    
+    session_data = GSIGNOND_DIGEST_PLUGIN (plugin)->priv->session_data;
+
+    if (query_error == SIGNONUI_ERROR_NONE &&
+        username != NULL && 
+        secret != NULL &&
+        session_data != NULL) {
+        GSignondSessionData *response = NULL;
+        const gchar* realm = gsignond_session_data_get_realm (session_data);
+        const gchar* algo = gsignond_dictionary_get_string (session_data,
+                "Algo");
+        const gchar* nonce = gsignond_dictionary_get_string (session_data,
+                "Nonce");
+        const gchar* nonce_count = gsignond_dictionary_get_string (session_data,
+                "NonceCount");
+        const gchar* qop = gsignond_dictionary_get_string (session_data,
+                "Qop");
+        const gchar* method = gsignond_dictionary_get_string (session_data,
+                "Method");
+        const gchar* digest_uri = gsignond_dictionary_get_string (session_data,
+                "DigestUri");
+        const gchar* hentity = gsignond_dictionary_get_string (session_data,
+                "HEntity");
+        gchar *cnonce = _gsignond_digest_plugin_generate_nonce ();
+        gchar *digest = _gsignond_digest_plugin_compute_md5_digest(algo,
+                username,realm, secret, nonce, nonce_count, cnonce, qop, method,
+                digest_uri, hentity);
+
+        response = gsignond_dictionary_new();
+        gsignond_session_data_set_username(response, username);
+        gsignond_dictionary_set_string(response, "CNonce", cnonce);
+        g_free (cnonce);
+        gsignond_dictionary_set_string(response, "Response", digest);
+        g_free(digest);
+
+        gsignond_plugin_response_final(plugin, response);
+        gsignond_dictionary_unref(response);
+        return;
+    } else if (query_error == SIGNONUI_ERROR_CANCELED) {
+        gsignond_digest_plugin_cancel (plugin);
+    } else {
+        GError* error = g_error_new(GSIGNOND_ERROR, 
+                GSIGNOND_ERROR_USER_INTERACTION, "userActionFinished error: %d",
+                query_error);
+        gsignond_plugin_error (plugin, error);
+        g_error_free(error);
+    }
+}
+
+static void
+gsignond_digest_plugin_refresh (
+    GSignondPlugin *self, 
+    GSignondSessionData *session_data)
+{
+    gsignond_plugin_refreshed(self, session_data);
+}
+
+static void
+gsignond_plugin_interface_init (GSignondPluginInterface *iface)
+{
+    iface->cancel = gsignond_digest_plugin_cancel;
+    iface->abort = gsignond_digest_plugin_abort;
+    iface->request_initial = gsignond_digest_plugin_request_initial;
+    iface->request = gsignond_digest_plugin_request;
+    iface->user_action_finished = gsignond_digest_plugin_user_action_finished;
+    iface->refresh = gsignond_digest_plugin_refresh;
+}
+
+static void
+gsignond_digest_plugin_init (GSignondDigestPlugin *self)
+{
+    self->priv = GSIGNOND_DIGEST_PLUGIN_GET_PRIVATE (self);
+    self->priv->session_data = NULL;
+}
+
+enum
+{
+    PROP_0,
+    PROP_TYPE,
+    PROP_MECHANISMS
+};
+
+static void
+gsignond_digest_plugin_set_property (
+        GObject      *object,
+        guint         property_id,
+        const GValue *value,
+        GParamSpec   *pspec)
+{
+    switch (property_id)
+    {
+        default:
+            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+            break;
+    }
+}
+
+static void
+gsignond_digest_plugin_get_property (
+        GObject    *object,
+        guint       prop_id,
+        GValue     *value,
+        GParamSpec *pspec)
+{
+    GSignondDigestPlugin *digest_plugin = GSIGNOND_DIGEST_PLUGIN (object);
+    (void) digest_plugin;
+    gchar *mechanisms[] = { "digest", NULL };
+    
+    switch (prop_id)
+    {
+        case PROP_TYPE:
+            g_value_set_string (value, "digest");
+            break;
+        case PROP_MECHANISMS:
+            g_value_set_boxed (value, mechanisms);
+            break;
+        default:
+            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+            break;
+    }
+}
+
+static void
+gsignond_digest_plugin_dispose (GObject *gobject)
+{
+    g_return_if_fail (GSIGNOND_IS_DIGEST_PLUGIN (gobject));
+    GSignondDigestPlugin *self = GSIGNOND_DIGEST_PLUGIN (gobject);
+
+    if (self->priv->session_data) {
+        gsignond_dictionary_unref (self->priv->session_data);
+        self->priv->session_data = NULL;
+    }
+
+    /* Chain up to the parent class */
+    G_OBJECT_CLASS (gsignond_digest_plugin_parent_class)->dispose (
+            gobject);
+}
+
+static void
+gsignond_digest_plugin_class_init (GSignondDigestPluginClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+    
+    gobject_class->set_property = gsignond_digest_plugin_set_property;
+    gobject_class->get_property = gsignond_digest_plugin_get_property;
+    gobject_class->dispose = gsignond_digest_plugin_dispose;
+    
+    g_object_class_override_property (gobject_class, PROP_TYPE, "type");
+    g_object_class_override_property (gobject_class, PROP_MECHANISMS,
+            "mechanisms");
+
+    g_type_class_add_private (klass, sizeof (GSignondDigestPluginPrivate));
+}
diff --git a/src/plugins/digest/gsignond-digest-plugin.h b/src/plugins/digest/gsignond-digest-plugin.h
new file mode 100644 (file)
index 0000000..f3df796
--- /dev/null
@@ -0,0 +1,66 @@
+/* vi: set et sw=4 ts=4 cino=t0,(0: */
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of gsignond
+ *
+ * Copyright (C) 2012-2013 Intel Corporation.
+ *
+ * Contact: Imran Zaman <imran.zaman@intel.com>
+ *
+ * This library 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.
+ *
+ * This library 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef __GSIGNOND_DIGEST_PLUGIN_H__
+#define __GSIGNOND_DIGEST_PLUGIN_H__
+
+#include <glib-object.h>
+
+#define GSIGNOND_TYPE_DIGEST_PLUGIN         (gsignond_digest_plugin_get_type ())
+#define GSIGNOND_DIGEST_PLUGIN(obj)         \
+    (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSIGNOND_TYPE_DIGEST_PLUGIN, \
+            GSignondDigestPlugin))
+#define GSIGNOND_IS_DIGEST_PLUGIN(obj)          \
+    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSIGNOND_TYPE_DIGEST_PLUGIN))
+#define GSIGNOND_DIGEST_PLUGIN_CLASS(klass)     \
+    (G_TYPE_CHECK_CLASS_CAST ((klass), GSIGNOND_TYPE_DIGEST_PLUGIN, \
+            GSignondDigestPluginClass))
+#define GSIGNOND_IS_DIGEST_PLUGIN_CLASS(klass)  \
+    (G_TYPE_CHECK_CLASS_TYPE ((klass), GSIGNOND_TYPE_DIGEST_PLUGIN))
+#define GSIGNOND_DIGEST_PLUGIN_GET_CLASS(obj)   \
+    (G_TYPE_INSTANCE_GET_CLASS ((obj), GSIGNOND_TYPE_DIGEST_PLUGIN, \
+            GSignondDigestPluginClass))
+
+
+typedef struct _GSignondDigestPlugin        GSignondDigestPlugin;
+typedef struct _GSignondDigestPluginClass   GSignondDigestPluginClass;
+typedef struct _GSignondDigestPluginPrivate GSignondDigestPluginPrivate;
+
+struct _GSignondDigestPlugin
+{
+    GObject parent_instance;
+    
+    GSignondDigestPluginPrivate *priv;
+    int instance_member;
+};
+
+struct _GSignondDigestPluginClass
+{
+    GObjectClass parent_class;
+};
+
+GType gsignond_digest_plugin_get_type (void);
+
+#endif /* __GSIGNOND_DIGEST_PLUGIN_H__ */
index c3a4d54..bef8056 100644 (file)
@@ -1,7 +1,20 @@
-TESTS = passwordplugintest pluginproxytest
+TESTS = digestplugintest passwordplugintest pluginproxytest
 TESTS_ENVIRONMENT= SSO_PLUGINS_DIR=$(top_builddir)/src/plugins/password/.libs
 
-check_PROGRAMS = passwordplugintest pluginproxytest
+check_PROGRAMS = digestplugintest passwordplugintest pluginproxytest
+digestplugintest_SOURCES = digestplugintest.c
+digestplugintest_CFLAGS = \
+    $(GSIGNOND_CFLAGS) \
+    $(CHECK_CFLAGS) \
+    -I$(top_srcdir)/src/plugins/digest \
+    -I$(top_srcdir)/include/
+
+digestplugintest_LDADD = \
+    $(top_builddir)/src/common/libgsignond-common.la \
+    $(top_builddir)/src/plugins/digest/libdigest.la \
+    $(GSIGNOND_LIBS) \
+    $(CHECK_LIBS)
+
 passwordplugintest_SOURCES = passwordplugintest.c
 passwordplugintest_CFLAGS = \
     $(GSIGNOND_CFLAGS) \
diff --git a/test/plugins/digestplugintest.c b/test/plugins/digestplugintest.c
new file mode 100644 (file)
index 0000000..fc871d9
--- /dev/null
@@ -0,0 +1,371 @@
+/* vi: set et sw=4 ts=4 cino=t0,(0: */
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of gsignond
+ *
+ * Copyright (C) 2012-2013 Intel Corporation.
+ *
+ * Contact: Imran Zaman <imran.zaman@intel.com>
+ *
+ * This library 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.
+ *
+ * This library 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <check.h>
+#include <stdlib.h>
+#include "gsignond-digest-plugin.h"
+#include <gsignond/gsignond-session-data.h>
+#include <gsignond/gsignond-plugin-interface.h>
+#include <gsignond/gsignond-error.h>
+#include <gsignond/gsignond-log.h>
+#include <gsignond/gsignond-plugin-loader.h>
+#include <gsignond/gsignond-config.h>
+
+START_TEST (test_session_data)
+{
+    GSignondSessionData* data;
+    GSignondSessionData* data_from_variant;
+    GSignondSessionData* data_from_copy;
+    GVariant* variant;
+
+    data = gsignond_dictionary_new();
+    fail_if(data == NULL);
+
+    fail_unless(gsignond_session_data_get_username(data) == NULL);
+    fail_unless(gsignond_session_data_get_secret(data) == NULL);
+
+    gsignond_session_data_set_username(data, "megauser");
+    gsignond_session_data_set_secret(data, "megapassword");
+
+    fail_unless(g_strcmp0(gsignond_session_data_get_username(data),
+                          "megauser") == 0);
+    fail_unless(g_strcmp0(gsignond_session_data_get_secret(data),
+                          "megapassword") == 0);
+
+    gsignond_session_data_set_username(data, "usermega");
+    fail_unless(g_strcmp0(gsignond_session_data_get_username(data),
+                          "usermega") == 0);
+
+    data_from_copy = gsignond_dictionary_copy(data);
+    fail_if(data_from_copy == NULL);
+
+    fail_unless(g_strcmp0(gsignond_session_data_get_username(data_from_copy),
+                          "usermega") == 0);
+    fail_unless(g_strcmp0(gsignond_session_data_get_secret(data_from_copy),
+                          "megapassword") == 0);
+
+    variant = gsignond_dictionary_to_variant(data);
+    fail_if(variant == NULL);
+    data_from_variant = gsignond_dictionary_new_from_variant(variant);
+    fail_if(data_from_variant == NULL);
+
+    fail_unless(g_strcmp0(gsignond_session_data_get_username(data_from_variant),
+                          "usermega") == 0);
+    fail_unless(g_strcmp0(gsignond_session_data_get_secret(data_from_variant),
+                          "megapassword") == 0);
+
+    g_variant_unref(variant);
+    gsignond_dictionary_unref(data_from_variant);
+    gsignond_dictionary_unref(data_from_copy);
+    gsignond_dictionary_unref(data);
+}
+END_TEST
+
+static void check_plugin(GSignondPlugin* plugin)
+{
+    gchar* type;
+    gchar** mechanisms;
+
+    fail_if(plugin == NULL);
+
+    g_object_get(plugin, "type", &type, "mechanisms", &mechanisms, NULL);
+
+    fail_unless(g_strcmp0(type, "digest") == 0);
+    fail_unless(g_strcmp0(mechanisms[0], "digest") == 0);
+    fail_unless(mechanisms[1] == NULL);
+
+    g_free(type);
+    g_strfreev(mechanisms);
+}
+
+START_TEST (test_digestplugin_create)
+{
+    gpointer plugin;
+
+    plugin = g_object_new(GSIGNOND_TYPE_DIGEST_PLUGIN, NULL);
+    check_plugin(plugin);
+    g_object_unref(plugin);
+}
+END_TEST
+
+static void
+response_callback(
+        GSignondPlugin* plugin,
+        GSignondSessionData* result,
+        gpointer user_data)
+{
+    GSignondSessionData** user_data_p = user_data;
+    *user_data_p = gsignond_dictionary_copy(result);
+}
+
+static void
+user_action_required_callback(
+        GSignondPlugin* plugin,
+        GSignondSignonuiData* ui_request,
+        gpointer user_data)
+{
+    GSignondSignonuiData** user_data_p = user_data;
+    *user_data_p = gsignond_dictionary_copy(ui_request);
+    gsignond_signonui_data_set_username(*user_data_p, "user1");
+    gsignond_signonui_data_set_password(*user_data_p, "password1");
+}
+
+static void
+error_callback(
+        GSignondPlugin* plugin,
+        GError* error,
+        gpointer user_data)
+{
+    GError** user_data_p = user_data;
+    *user_data_p = g_error_copy(error);
+}
+
+
+START_TEST (test_digestplugin_request)
+{
+    gpointer plugin;
+    gboolean query_res;
+
+    plugin = g_object_new(GSIGNOND_TYPE_DIGEST_PLUGIN, NULL);
+    fail_if(plugin == NULL);
+
+    GSignondSessionData* result = NULL;
+    GSignondSignonuiData* ui_action = NULL;
+    GError* error = NULL;
+
+    g_signal_connect(plugin, "response-final", G_CALLBACK(response_callback),
+            &result);
+    g_signal_connect(plugin, "user-action-required",
+            G_CALLBACK(user_action_required_callback), &ui_action);
+    g_signal_connect(plugin, "error", G_CALLBACK(error_callback), &error);
+
+    GSignondSessionData* data = gsignond_dictionary_new();
+
+    // set only username and password
+    gsignond_session_data_set_username(data, "user1");
+    gsignond_session_data_set_secret(data, "password1");
+
+    gsignond_plugin_request_initial(plugin, data, "digest");
+    fail_if(result != NULL);
+    fail_if(ui_action != NULL);
+    fail_if(error == NULL);
+    fail_unless(g_error_matches(error, GSIGNOND_ERROR,
+                                GSIGNOND_ERROR_MISSING_DATA));
+    g_error_free(error);
+    error = NULL;
+
+    // set all the required stuff so that no ui-action is required
+    gsignond_session_data_set_realm(data, "realm1");
+    gsignond_dictionary_set_string(data, "Algo", "md5-sess");
+    gsignond_dictionary_set_string(data, "Nonce",
+            "abg10b1234ee1f0e8b11d0f600bfb0c093");
+    gsignond_dictionary_set_string(data, "Method", "GET");
+    gsignond_dictionary_set_string(data, "DigestUri", "/test/index.html");
+
+    gsignond_plugin_request_initial(plugin, data, "digest");
+    fail_if(result == NULL);
+    fail_if(ui_action != NULL);
+    fail_if(error != NULL);
+    fail_if(g_strcmp0(gsignond_session_data_get_username(result),
+            "user1") != 0);
+    fail_if(gsignond_dictionary_get_string(result, "Response") == NULL);
+    fail_if(gsignond_dictionary_get_string(result, "CNonce") == NULL);
+    gsignond_dictionary_unref(result);
+    result = NULL;
+
+    //remove secret so that ui action is required
+    gsignond_dictionary_remove (data, "Secret");
+    gsignond_plugin_request_initial(plugin, data, "digest");
+    fail_if(result != NULL);
+    fail_if(ui_action == NULL);
+    fail_if(error != NULL);
+    fail_if(g_strcmp0(gsignond_signonui_data_get_username(ui_action),
+            "user1") != 0);
+    fail_if(g_strcmp0(gsignond_signonui_data_get_password(ui_action),
+            "password1") != 0);
+    fail_if(gsignond_dictionary_get_string(ui_action, "Realm") == NULL);
+    fail_if(gsignond_dictionary_get_string(ui_action, "DigestUri") == NULL);
+    gsignond_signonui_data_get_query_username(ui_action, &query_res);
+    fail_if(query_res == FALSE);
+    gsignond_signonui_data_get_query_password(ui_action, &query_res);
+    fail_if(query_res == FALSE);
+    gsignond_dictionary_unref(ui_action);
+    ui_action = NULL;
+
+    gsignond_dictionary_unref(data);
+    g_object_unref(plugin);
+}
+END_TEST
+
+START_TEST (test_digestplugin_user_action_finished)
+{
+    gpointer plugin;
+
+    plugin = g_object_new(GSIGNOND_TYPE_DIGEST_PLUGIN, NULL);
+    fail_if(plugin == NULL);
+
+    GSignondSessionData *result = NULL, *data = NULL;
+    GSignondSignonuiData *ui_action = NULL, *ui_data = NULL;
+    GError* error = NULL;
+
+    g_signal_connect(plugin, "response-final", G_CALLBACK(response_callback),
+            &result);
+    g_signal_connect(plugin, "user-action-required",
+            G_CALLBACK(user_action_required_callback), &ui_action);
+    g_signal_connect(plugin, "error", G_CALLBACK(error_callback), &error);
+
+    ui_data = gsignond_signonui_data_new();
+    gsignond_signonui_data_set_query_error(ui_data, SIGNONUI_ERROR_NONE);
+
+    //empty data
+    gsignond_plugin_user_action_finished(plugin, ui_data);
+    fail_if(result != NULL);
+    fail_if(ui_action != NULL);
+    fail_if(error == NULL);
+    fail_unless(g_error_matches(error, GSIGNOND_ERROR,
+            GSIGNOND_ERROR_USER_INTERACTION));
+    g_error_free(error);
+    error = NULL;
+
+    // user cancelled
+    gsignond_signonui_data_set_query_error(ui_data, SIGNONUI_ERROR_CANCELED);
+    gsignond_plugin_user_action_finished(plugin, ui_data);
+    fail_if(result != NULL);
+    fail_if(ui_action != NULL);
+    fail_if(error == NULL);
+    fail_unless(g_error_matches(error, GSIGNOND_ERROR,
+                                GSIGNOND_ERROR_SESSION_CANCELED));
+    g_error_free(error);
+    error = NULL;
+
+    // error in ui request
+    gsignond_signonui_data_set_query_error(ui_data, SIGNONUI_ERROR_GENERAL);
+    gsignond_plugin_user_action_finished(plugin, ui_data);
+    fail_if(result != NULL);
+    fail_if(ui_action != NULL);
+    fail_if(error == NULL);
+    fail_unless(g_error_matches(error, GSIGNOND_ERROR,
+                                GSIGNOND_ERROR_USER_INTERACTION));
+    g_error_free(error);
+    error = NULL;
+
+    // correct values but no session data
+    gsignond_signonui_data_set_username (ui_data, "user1");
+    gsignond_signonui_data_set_password (ui_data, "password1");
+    gsignond_signonui_data_set_query_error (ui_data, SIGNONUI_ERROR_NONE);
+    gsignond_plugin_user_action_finished (plugin, ui_data);
+    fail_if(result != NULL);
+    fail_if(ui_action != NULL);
+    fail_if(error == NULL);
+    fail_unless(g_error_matches(error, GSIGNOND_ERROR,
+            GSIGNOND_ERROR_USER_INTERACTION));
+    g_error_free(error);
+    error = NULL;
+
+    //correct values
+    data = gsignond_dictionary_new ();
+    gsignond_session_data_set_username (data, "user1");
+    gsignond_session_data_set_realm (data, "realm1");
+    gsignond_dictionary_set_string (data, "Algo", "md5-sess");
+    gsignond_dictionary_set_string (data, "Nonce",
+            "abg10b1234ee1f0e8b11d0f600bfb0c093");
+    gsignond_dictionary_set_string (data, "Method", "GET");
+    gsignond_dictionary_set_string (data, "DigestUri", "/test/index.html");
+    gsignond_plugin_request_initial (plugin, data, "digest");
+    gsignond_dictionary_unref (data); data = NULL;
+
+    gsignond_plugin_user_action_finished (plugin, ui_data);
+    fail_if (result == NULL);
+    fail_if (error != NULL);
+    fail_if(ui_action == NULL);
+    fail_if(g_strcmp0(gsignond_session_data_get_username(result),
+            "user1") != 0);
+    fail_if(gsignond_dictionary_get_string(result, "Response") == NULL);
+    fail_if(gsignond_dictionary_get_string(result, "CNonce") == NULL);
+    gsignond_dictionary_unref(result);
+    result = NULL;
+    gsignond_dictionary_unref(ui_action);
+    ui_action = NULL;
+
+    gsignond_dictionary_unref (ui_data);
+    g_object_unref (plugin);
+}
+END_TEST
+
+START_TEST (test_digestplugin_refresh)
+{
+    gpointer plugin;
+
+    plugin = g_object_new(GSIGNOND_TYPE_DIGEST_PLUGIN, NULL);
+    fail_if(plugin == NULL);
+
+    GSignondSessionData* result = NULL;
+    GError* error = NULL;
+
+    g_signal_connect(plugin, "refreshed", G_CALLBACK(response_callback),
+            &result);
+    g_signal_connect(plugin, "error", G_CALLBACK(error_callback), &error);
+
+    GSignondSessionData* data = gsignond_dictionary_new();
+    gsignond_plugin_refresh(plugin, data);
+    fail_if(result == NULL);
+    fail_if(error != NULL);
+    gsignond_dictionary_unref(result);
+    result = NULL;
+
+    gsignond_dictionary_unref(data);
+    g_object_unref(plugin);
+}
+END_TEST
+
+Suite* digestplugin_suite (void)
+{
+    Suite *s = suite_create ("Digest plugin");
+
+    /* Core test case */
+    TCase *tc_core = tcase_create ("Tests");
+    tcase_add_test (tc_core, test_session_data);
+    tcase_add_test (tc_core, test_digestplugin_create);
+    tcase_add_test (tc_core, test_digestplugin_request);
+    tcase_add_test (tc_core, test_digestplugin_user_action_finished);
+    tcase_add_test (tc_core, test_digestplugin_refresh);
+    suite_add_tcase (s, tc_core);
+    return s;
+}
+
+int main (void)
+{
+    int number_failed;
+
+    g_type_init();
+
+    Suite *s = digestplugin_suite();
+    SRunner *sr = srunner_create(s);
+    srunner_run_all(sr, CK_NORMAL);
+    number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+