/* wocky-auth-registry.c */

#include "wocky-auth-registry.h"
#include "wocky-auth-handler.h"
#include "wocky-sasl-scram.h"
#include "wocky-sasl-digest-md5.h"
#include "wocky-sasl-plain.h"
#include "wocky-jabber-auth-password.h"
#include "wocky-jabber-auth-digest.h"
#include "wocky-utils.h"

#define WOCKY_DEBUG_FLAG WOCKY_DEBUG_AUTH
#include "wocky-debug-internal.h"

G_DEFINE_TYPE (WockyAuthRegistry, wocky_auth_registry, G_TYPE_OBJECT)

/* private structure */
struct _WockyAuthRegistryPrivate
{
  gboolean dispose_has_run;

  WockyAuthHandler *handler;
  GSList *handlers;
};

static void wocky_auth_registry_start_auth_async_func (WockyAuthRegistry *self,
    const GSList *mechanisms,
    gboolean allow_plain,
    gboolean is_secure_channel,
    const gchar *username,
    const gchar *password,
    const gchar *server,
    const gchar *session_id,
    GAsyncReadyCallback callback,
    gpointer user_data);

static gboolean wocky_auth_registry_start_auth_finish_func (
    WockyAuthRegistry *self,
    GAsyncResult *result,
    WockyAuthRegistryStartData **start_data,
    GError **error);

static void wocky_auth_registry_challenge_async_func (WockyAuthRegistry *self,
    const GString *challenge_data,
    GAsyncReadyCallback callback,
    gpointer user_data);

static gboolean wocky_auth_registry_challenge_finish_func (
    WockyAuthRegistry *self,
    GAsyncResult *result,
    GString **response,
    GError **error);

static void wocky_auth_registry_success_async_func (WockyAuthRegistry *self,
    GAsyncReadyCallback callback,
    gpointer user_data);

static gboolean wocky_auth_registry_success_finish_func (
    WockyAuthRegistry *self,
    GAsyncResult *result,
    GError **error);

GQuark
wocky_auth_error_quark (void) {
  static GQuark quark = 0;

  if (!quark)
    quark = g_quark_from_static_string ("wocky_auth_error");

  return quark;
}

static void
wocky_auth_registry_constructed (GObject *object)
{
}

static void
wocky_auth_registry_get_property (GObject    *object,
    guint       property_id,
    GValue     *value,
    GParamSpec *pspec)
{
  switch (property_id)
    {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
wocky_auth_registry_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);
    }
}

static void
wocky_auth_registry_dispose (GObject *object)
{
  WockyAuthRegistry *self = WOCKY_AUTH_REGISTRY (object);
  WockyAuthRegistryPrivate *priv = self->priv;

  if (priv->dispose_has_run)
    return;

  priv->dispose_has_run = TRUE;

  /* release any references held by the object here */
  if (priv->handler != NULL)
    {
      g_object_unref (priv->handler);
    }

  if (priv->handlers != NULL)
    {
      g_slist_foreach (priv->handlers, (GFunc) g_object_unref, NULL);
      g_slist_free (priv->handlers);
    }

  G_OBJECT_CLASS (wocky_auth_registry_parent_class)->dispose (object);
}

static void
wocky_auth_registry_finalize (GObject *object)
{
  G_OBJECT_CLASS (wocky_auth_registry_parent_class)->finalize (object);
}

static void
wocky_auth_registry_class_init (WockyAuthRegistryClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  g_type_class_add_private (klass, sizeof (WockyAuthRegistryPrivate));

  object_class->constructed = wocky_auth_registry_constructed;
  object_class->get_property = wocky_auth_registry_get_property;
  object_class->set_property = wocky_auth_registry_set_property;
  object_class->dispose = wocky_auth_registry_dispose;
  object_class->finalize = wocky_auth_registry_finalize;

  klass->start_auth_async_func = wocky_auth_registry_start_auth_async_func;
  klass->start_auth_finish_func = wocky_auth_registry_start_auth_finish_func;

  klass->challenge_async_func = wocky_auth_registry_challenge_async_func;
  klass->challenge_finish_func = wocky_auth_registry_challenge_finish_func;

  klass->success_async_func = wocky_auth_registry_success_async_func;
  klass->success_finish_func = wocky_auth_registry_success_finish_func;

  klass->failure_func = NULL;
}

static void
wocky_auth_registry_init (WockyAuthRegistry *self)
{
  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, WOCKY_TYPE_AUTH_REGISTRY,
      WockyAuthRegistryPrivate);
}

WockyAuthRegistry *
wocky_auth_registry_new (void)
{
  return g_object_new (WOCKY_TYPE_AUTH_REGISTRY, NULL);
}

static gboolean
wocky_auth_registry_has_mechanism (const GSList *list, const gchar *mech) {
  GSList *t;

  t = g_slist_find_custom ((GSList *) list, mech, (GCompareFunc) g_strcmp0);

  return (t != NULL);
}

WockyAuthRegistryStartData *
wocky_auth_registry_start_data_new (const gchar *mechanism,
    const GString *initial_response)
{
  WockyAuthRegistryStartData *start_data = g_slice_new0 (
      WockyAuthRegistryStartData);

  start_data->mechanism = g_strdup (mechanism);
  start_data->initial_response = wocky_g_string_dup (initial_response);

  return start_data;
}

WockyAuthRegistryStartData *
wocky_auth_registry_start_data_dup (WockyAuthRegistryStartData *start_data)
{
  return wocky_auth_registry_start_data_new (
      start_data->mechanism,
      start_data->initial_response);
}

void
wocky_auth_registry_start_data_free (WockyAuthRegistryStartData *start_data)
{
  g_free (start_data->mechanism);

  if (start_data->initial_response != NULL)
    g_string_free (start_data->initial_response, TRUE);

  g_slice_free (WockyAuthRegistryStartData, start_data);
}

static gboolean
wocky_auth_registry_select_handler (WockyAuthRegistry *self,
    const GSList *mechanisms,
    gboolean allow_plain,
    const gchar *username,
    const gchar *password,
    const gchar *server,
    const gchar *session_id,
    WockyAuthHandler **out_handler)
{
  WockyAuthRegistryPrivate *priv = self->priv;
  GSList *k;

  for (k = priv->handlers; k != NULL; k = k->next)
    {
      WockyAuthHandler *handler = k->data;
      const gchar *handler_mech = wocky_auth_handler_get_mechanism (handler);

      if (wocky_auth_handler_is_plain (handler) && !allow_plain)
        continue;

      if (wocky_auth_registry_has_mechanism (mechanisms, handler_mech))
        {
          if (out_handler != NULL)
            *out_handler = g_object_ref (handler);

          return TRUE;
        }
    }

  if (wocky_auth_registry_has_mechanism (mechanisms,
          WOCKY_AUTH_MECH_SASL_SCRAM_SHA_1))
    {
      if (out_handler != NULL)
        {
          /* XXX: check for username and password here? */
          DEBUG ("Choosing SCRAM-SHA-1 as auth mechanism");
          *out_handler = WOCKY_AUTH_HANDLER (wocky_sasl_scram_new (
              server, username, password));
        }
      return TRUE;
    }

  if (wocky_auth_registry_has_mechanism (mechanisms,
          WOCKY_AUTH_MECH_SASL_DIGEST_MD5))
    {
      if (out_handler != NULL)
        {
          /* XXX: check for username and password here? */
          *out_handler = WOCKY_AUTH_HANDLER (wocky_sasl_digest_md5_new (
                  server, username, password));
        }
      return TRUE;
    }

  if (wocky_auth_registry_has_mechanism (mechanisms,
          WOCKY_AUTH_MECH_JABBER_DIGEST))
    {
      if (out_handler != NULL)
        {
          *out_handler = WOCKY_AUTH_HANDLER (wocky_jabber_auth_digest_new (
                  session_id, password));
        }
      return TRUE;
    }

  if (allow_plain && wocky_auth_registry_has_mechanism (mechanisms,
          WOCKY_AUTH_MECH_SASL_PLAIN))
    {
      if (out_handler != NULL)
        {
          /* XXX: check for username and password here? */
          DEBUG ("Choosing PLAIN as auth mechanism");
          *out_handler = WOCKY_AUTH_HANDLER (wocky_sasl_plain_new (
                  username, password));
        }
      return TRUE;
    }

  if (allow_plain && wocky_auth_registry_has_mechanism (mechanisms,
          WOCKY_AUTH_MECH_JABBER_PASSWORD))
    {
      if (out_handler != NULL)
        {
          *out_handler = WOCKY_AUTH_HANDLER (wocky_jabber_auth_password_new (
                  password));
        }
      return TRUE;
    }

  if (out_handler)
    *out_handler = NULL;

  return FALSE;
}


static void
wocky_auth_registry_start_auth_async_func (WockyAuthRegistry *self,
    const GSList *mechanisms,
    gboolean allow_plain,
    gboolean is_secure_channel,
    const gchar *username,
    const gchar *password,
    const gchar *server,
    const gchar *session_id,
    GAsyncReadyCallback callback,
    gpointer user_data)
{
  WockyAuthRegistryPrivate *priv = self->priv;
  GSimpleAsyncResult *result;

  result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
      wocky_auth_registry_start_auth_async);

  g_assert (priv->handler == NULL);

  if (!wocky_auth_registry_select_handler (self, mechanisms,
          allow_plain, username, password, server, session_id,
          &priv->handler))
    {
      g_simple_async_result_set_error (result, WOCKY_AUTH_ERROR,
          WOCKY_AUTH_ERROR_NO_SUPPORTED_MECHANISMS,
          "No supported mechanisms found");
    }
  else
    {
      GString *initial_data;
      GError *error = NULL;

      if (!wocky_auth_handler_get_initial_response (priv->handler,
             &initial_data, &error))
        {
          g_simple_async_result_set_from_error (result, error);
          g_error_free (error);
        }
      else
        {
          WockyAuthRegistryStartData *start_data =
            wocky_auth_registry_start_data_new (
                wocky_auth_handler_get_mechanism (priv->handler),
                initial_data);

          g_simple_async_result_set_op_res_gpointer (result, start_data,
              (GDestroyNotify) wocky_auth_registry_start_data_free);

          wocky_g_string_free (initial_data);
        }
    }

  g_simple_async_result_complete_in_idle (result);
  g_object_unref (result);
}

void
wocky_auth_registry_start_auth_async (WockyAuthRegistry *self,
    const GSList *mechanisms,
    gboolean allow_plain,
    gboolean is_secure_channel,
    const gchar *username,
    const gchar *password,
    const gchar *server,
    const gchar *session_id,
    GAsyncReadyCallback callback,
    gpointer user_data)
{
  WockyAuthRegistryClass *cls = WOCKY_AUTH_REGISTRY_GET_CLASS (self);

  cls->start_auth_async_func (self, mechanisms, allow_plain, is_secure_channel,
      username, password, server, session_id, callback, user_data);
}

gboolean
wocky_auth_registry_start_auth_finish (WockyAuthRegistry *self,
    GAsyncResult *result,
    WockyAuthRegistryStartData **start_data,
    GError **error)
{
  WockyAuthRegistryClass *cls = WOCKY_AUTH_REGISTRY_GET_CLASS (self);

  return cls->start_auth_finish_func (self, result, start_data, error);
}

static gboolean
wocky_auth_registry_start_auth_finish_func (WockyAuthRegistry *self,
    GAsyncResult *result,
    WockyAuthRegistryStartData **start_data,
    GError **error)
{
  wocky_implement_finish_copy_pointer (self,
      wocky_auth_registry_start_auth_async,
      wocky_auth_registry_start_data_dup,
      start_data);
}

static void
wocky_auth_registry_challenge_async_func (WockyAuthRegistry *self,
    const GString *challenge_data,
    GAsyncReadyCallback callback,
    gpointer user_data)
{
  WockyAuthRegistryPrivate *priv = self->priv;
  GString *response = NULL;
  GError *error = NULL;
  GSimpleAsyncResult *result = g_simple_async_result_new (G_OBJECT (self),
      callback, user_data, wocky_auth_registry_challenge_async);

  g_assert (priv->handler != NULL);

  if (!wocky_auth_handler_handle_auth_data (priv->handler, challenge_data,
          &response, &error))
    {
      g_simple_async_result_set_from_error (result, error);
      g_error_free (error);
    }
  else
    {
      g_simple_async_result_set_op_res_gpointer (result, response,
          (GDestroyNotify) wocky_g_string_free);
    }

  g_simple_async_result_complete_in_idle (result);
  g_object_unref (result);
}

void
wocky_auth_registry_challenge_async (WockyAuthRegistry *self,
    const GString *challenge_data,
    GAsyncReadyCallback callback,
    gpointer user_data)
{
  WockyAuthRegistryClass *cls = WOCKY_AUTH_REGISTRY_GET_CLASS (self);

  cls->challenge_async_func (self, challenge_data, callback, user_data);
}

gboolean
wocky_auth_registry_challenge_finish (WockyAuthRegistry *self,
    GAsyncResult *result,
    GString **response,
    GError **error)
{
  WockyAuthRegistryClass *cls = WOCKY_AUTH_REGISTRY_GET_CLASS (self);

  return cls->challenge_finish_func (self, result, response, error);
}

static gboolean
wocky_auth_registry_challenge_finish_func (WockyAuthRegistry *self,
    GAsyncResult *result,
    GString **response,
    GError **error)
{
  wocky_implement_finish_copy_pointer (self,
      wocky_auth_registry_challenge_async,
      wocky_g_string_dup,
      response);
}

static void
wocky_auth_registry_success_async_func (WockyAuthRegistry *self,
    GAsyncReadyCallback callback,
    gpointer user_data)
{
  WockyAuthRegistryPrivate *priv = self->priv;
  GError *error = NULL;
  GSimpleAsyncResult *result = g_simple_async_result_new (G_OBJECT (self),
      callback, user_data, wocky_auth_registry_success_async);

  g_assert (priv->handler != NULL);

  if (!wocky_auth_handler_handle_success (priv->handler, &error))
    {
      g_simple_async_result_set_from_error (result, error);
      g_error_free (error);
    }

  g_simple_async_result_complete_in_idle (result);
  g_object_unref (result);
}


void
wocky_auth_registry_success_async (WockyAuthRegistry *self,
    GAsyncReadyCallback callback,
    gpointer user_data)
{
  WockyAuthRegistryClass *cls = WOCKY_AUTH_REGISTRY_GET_CLASS (self);

  cls->success_async_func (self, callback, user_data);
}

gboolean
wocky_auth_registry_success_finish (WockyAuthRegistry *self,
    GAsyncResult *result,
    GError **error)
{
  WockyAuthRegistryClass *cls = WOCKY_AUTH_REGISTRY_GET_CLASS (self);

  return cls->success_finish_func (self, result, error);
}

static gboolean
wocky_auth_registry_success_finish_func (WockyAuthRegistry *self,
    GAsyncResult *result,
    GError **error)
{
  wocky_implement_finish_void (self, wocky_auth_registry_success_async);
}

void
wocky_auth_registry_failure (WockyAuthRegistry *self,
    GError *error)
{
  WockyAuthRegistryClass *cls = WOCKY_AUTH_REGISTRY_GET_CLASS (self);

  if (cls->failure_func != NULL)
    cls->failure_func (self, error);
}

void
wocky_auth_registry_add_handler (WockyAuthRegistry *self,
    WockyAuthHandler *handler)
{
  WockyAuthRegistryPrivate *priv = self->priv;

  g_object_ref (handler);
  priv->handlers = g_slist_append (priv->handlers, handler);
}

/**
 * wocky_auth_registry_supports_one_of:
 * @self: a #WockyAuthRegistry
 * @allow_plain: Whether auth in plain text is allowed
 * @mechanisms: a #GSList of gchar* of auth mechanisms
 *
 * Checks whether at least one of @mechanisms is supported by Wocky. At present,
 * Wocky itself only implements password-based authentication mechanisms.
 *
 * Returns: %TRUE if one of the @mechanisms is supported by wocky,
 *  %FALSE otherwise.
 */
gboolean
wocky_auth_registry_supports_one_of (WockyAuthRegistry *self,
    const GSList *mechanisms,
    gboolean allow_plain)
{
  return wocky_auth_registry_select_handler (self, mechanisms,
          allow_plain, NULL, NULL, NULL, NULL, NULL);
}
