/* $Id: idwsf2_data_service.c 3101 2007-05-30 11:40:10Z dlaniel $
 *
 * Lasso - A free implementation of the Liberty Alliance specifications.
 *
 * Copyright (C) 2004-2007 Entr'ouvert
 * http://lasso.entrouvert.org
 *
 * Authors: See AUTHORS file in top-level directory.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include "idwsf2_helper.h"
#include "../xml/id-wsf-2.0/idwsf2_strings.h"
#include "../xml/id-wsf-2.0/disco_abstract.h"
#include "../xml/id-wsf-2.0/disco_service_type.h"
#include "../xml/id-wsf-2.0/disco_provider_id.h"
#include "../xml/id-wsf-2.0/sec_token.h"
#include "../xml/id-wsf-2.0/sbf_framework.h"
#include "../xml/misc_text_node.h"
#include "../utils.h"

/**
 * SECTION: idwsf2-helper
 *
 * Methods to help manipulate EPR elements
 */

/**
 * lasso_wsa_endpoint_reference_get_idwsf2_service_type:
 *
 * Return the disco:ServiceType metadata element content
 *
 * Return value: (transfer none): the content of the first disco:ServiceType metadata, or NULL if
 * none is found.
 */
const char*
lasso_wsa_endpoint_reference_get_idwsf2_service_type(const LassoWsAddrEndpointReference *epr)
{
	LassoIdWsf2DiscoServiceType *disco2_service_type;

	if (! LASSO_IS_WSA_ENDPOINT_REFERENCE (epr) || epr->Metadata == NULL)
		return NULL;
	disco2_service_type = lasso_extract_gobject_from_list (LassoIdWsf2DiscoServiceType,
			LASSO_TYPE_IDWSF2_DISCO_SERVICE_TYPE, epr->Metadata->any);
	if (disco2_service_type) {
		return disco2_service_type->content;
	}
	return NULL;
}

/**
 * lasso_wsa_endpoint_reference_get_idwsf2_provider_id
 * @epr: a #LassoWsAddrEndpointReference object
 *
 * Return the provider ID from the the metadata element of the EPR.
 *
 * Return value: an entityID identifier or NULL if none is found, or the element is empty.
 */
const char*
lasso_wsa_endpoint_reference_get_idwsf2_provider_id(const LassoWsAddrEndpointReference *epr)
{
	LassoIdWsf2DiscoProviderID *disco2_provider_id;

	if (! LASSO_IS_WSA_ENDPOINT_REFERENCE (epr) || epr->Metadata == NULL)
		return NULL;

	/* Get the service type from the EPR */
	disco2_provider_id = lasso_extract_gobject_from_list (LassoIdWsf2DiscoProviderID,
			LASSO_TYPE_IDWSF2_DISCO_PROVIDER_ID, epr->Metadata->any);

	if (disco2_provider_id) {
		return disco2_provider_id->content;
	}

	return NULL;
}

/**
 * lasso_wsa_endpoint_reference_get_idwsf2_security_context_for_security_mechanism:
 * @epr: a #LassoWsAddrEndpointReference object
 * @security_mech_predicate: (allow-none): a predicate to test for security mechanism
 * @security_mech_id: (allow-none): a security mechanism identifier
 * @create: allow to create the element if none if found, @security_mech_id is mandatory when create
 * is TRUE.
 *
 * Return value: (transfer none): a #LassoIdWsf2DiscoSecurityContext, or NULL if none was found and
 * created is FALSE.
 */
LassoIdWsf2DiscoSecurityContext*
lasso_wsa_endpoint_reference_get_idwsf2_security_context_for_security_mechanism(
		const LassoWsAddrEndpointReference *epr,
		gboolean (*sech_mech_predicate)(const char *),
		const char *security_mech_id,
		gboolean create)
{
	LassoIdWsf2DiscoSecurityContext *created = NULL;
	LassoMiscTextNode *new_security_mech_id_declaration;

	if (! LASSO_IS_WSA_ENDPOINT_REFERENCE (epr) || epr->Metadata == NULL)
		return NULL;

	lasso_foreach_full_begin(LassoIdWsf2DiscoSecurityContext*, context, it1, epr->Metadata->any);
	if (LASSO_IS_IDWSF2_DISCO_SECURITY_CONTEXT (context)) {
		lasso_foreach_full_begin(char*, textnode, it2, context->SecurityMechID);
			if (lasso_strisequal(textnode,security_mech_id) || sech_mech_predicate(textnode)) {
				return context;
			}
		lasso_foreach_full_end()
	}
	lasso_foreach_full_end();

	if (create && security_mech_id) {
		created = lasso_idwsf2_disco_security_context_new();
		new_security_mech_id_declaration =
			lasso_misc_text_node_new_with_string(security_mech_id);
		new_security_mech_id_declaration->name = "SecurityMechID";
		new_security_mech_id_declaration->ns_href = LASSO_IDWSF2_DISCOVERY_HREF;
		new_security_mech_id_declaration->ns_prefix = LASSO_IDWSF2_DISCOVERY_PREFIX;
		lasso_list_add_new_gobject (created->SecurityMechID,
				new_security_mech_id_declaration);
		lasso_list_add_new_gobject (epr->Metadata->any, created);
	}
	if (create && ! security_mech_id) {
		message(G_LOG_LEVEL_WARNING, "cannot create a LassoIdWsf2DiscoSecurityContext withou a security_mech_id");
	}

	return created;
}

/**
 * lasso_wsa_endpoint_reference_get_token_by_usage:
 * @epr: a #LassoWsAddrEndpointReference object
 * @security_mech_predicate: (allow-none): a predicate to test for security mechanism
 * @security_mech_id: (allow-none): a security mechanism identifier
 * @usage: the usage to make of the token
 *
 * Try to find a token for the given usage and security mechanism, the security can be chosen by
 * name or by a predicate.
 *
 * Return value: a #LassoNode object or a subclass, representing the token.
 */
static LassoNode*
lasso_wsa_endpoint_reference_get_token_by_usage(
		const LassoWsAddrEndpointReference *epr,
		gboolean (*sec_mech_predicate)(const char *),
		const char *security_mech_id, const char* usage)
{
	LassoIdWsf2DiscoSecurityContext *security_context;

	security_context =
		lasso_wsa_endpoint_reference_get_idwsf2_security_context_for_security_mechanism(
			epr, sec_mech_predicate, security_mech_id, TRUE);
	lasso_foreach_full_begin (LassoIdWsf2SecToken*, token, iter, security_context->Token);
	if (LASSO_IS_IDWSF2_SEC_TOKEN (token)) {
		if (usage && lasso_strisequal(token->usage,usage)) {
			if (LASSO_IS_NODE(token->any)) {
				return (LassoNode*)token->any;
			} else if (token->ref) {
				message(G_LOG_LEVEL_WARNING, "sec:Token ref attribute is not supported");
				return NULL;
			}
		}

	}
	lasso_foreach_full_end();

	return NULL;
}

/**
 * lasso_wsa_endpoint_reference_get_security_token:
 * @epr: a #LassoWsAddrEndpointReference object
 * @sech_mech_predicate:(allow-none): a boolean function to select the security mechanism for which
 * we want the security token
 * @security_mech_id:(allow-none): an optional specific security mechanism identifier to select the
 * security token.
 *
 * Return the first security token found in the metadata of the @epr object which qualify with
 * respect to the predicate or the given security mechanism identifier. It is an error to pass both
 * of @sech_mech_predicate and @security_mech_id as NULL.
 *
 * Return value:(transfer none): a #LassoNode object or NULL if the query cannot be satisfied.
 */
LassoNode*
lasso_wsa_endpoint_reference_get_security_token (const LassoWsAddrEndpointReference *epr,
		gboolean (*sech_mech_predicate)(const char *), const char *security_mech_id)
{
	return lasso_wsa_endpoint_reference_get_token_by_usage (epr, sech_mech_predicate,
			security_mech_id, LASSO_IDWSF2_SEC_TOKEN_USAGE_SECURITY_TOKEN);
}

/**
 * lasso_wsa_endpoint_reference_get_target_identity_token:
 * @epr: a #LassoWsAddrEndpointReference object
 * @sech_mech_predicate:(allow-none): a boolean function to select the security mechanism for which
 * we want the security token
 * @security_mech_id:(allow-none): an optional specific security mechanism identifier to select the
 * security token.
 *
 * Return the first target identity token found in the metadata of the @epr object which qualify
 * with respect to the predicate or the given security mechanism identifier. It is an error to pass
 * both of @sech_mech_predicate and @security_mech_id as NULL.
 *
 * Return value:(transfer none): a #LassoNode object or NULL if the query cannot be satisfied.
 */
LassoNode*
lasso_wsa_endpoint_reference_get_target_identity_token(const LassoWsAddrEndpointReference *epr,
		gboolean (*sech_mech_predicate)(const char *), const char *security_mech_id)
{
	return lasso_wsa_endpoint_reference_get_token_by_usage (epr, sech_mech_predicate,
			security_mech_id, LASSO_IDWSF2_SEC_TOKEN_USAGE_TARGET_IDENTITY);
}

/**
 * lasso_wsa_endpoint_reference_new_for_idwsf2_service:
 * @address: the URL of the SOAP endpoint where the service is anchored
 * @service_type: an URI identifying the ID-WSF 2.0 service type
 * @provider_id: an URI identifying the SAML 2.0 service provider hosting the service, this should
 * help in finding key material for security mechanisms.
 * @abstract: a human description of the service.
 *
 * Create and populate a new #LassoWsAddrEndpointReference object.
 *
 * Return value: a newly created #LassoWsAddrEndpointReference.
 */
LassoWsAddrEndpointReference*
lasso_wsa_endpoint_reference_new_for_idwsf2_service(const char *address,
		const char *service_type, const char *provider_id, const char *abstract)
{
	LassoWsAddrEndpointReference *epr = NULL;
	LassoWsAddrMetadata *metadata = NULL;

	/* Check parameters */
	if (address == NULL || service_type == NULL || provider_id == NULL || abstract == NULL)
		return NULL;

	/* Build EndpointReference */
	epr = lasso_wsa_endpoint_reference_new();

	/* Address */
	epr->Address = lasso_wsa_attributed_uri_new_with_string(address);

	/* Metadatas */
	metadata = lasso_wsa_metadata_new();
	epr->Metadata = metadata;

	/* Abstract */
	lasso_list_add_new_gobject(metadata->any,
			lasso_idwsf2_disco_abstract_new_with_string(abstract));

	/* ProviderID */
	lasso_list_add_new_gobject(metadata->any,
			lasso_idwsf2_disco_provider_id_new_with_string(provider_id));

	/* ServiceType */
	lasso_list_add_new_gobject(metadata->any,
			lasso_idwsf2_disco_service_type_new_with_string(service_type));

	/* Framework */
	lasso_list_add_new_gobject(metadata->any,
			lasso_idwsf2_sbf_framework_new_full("2.0"));

	return epr;
}

/**
 * lasso_wsa_endpoint_reference_add_security_token:
 * @epr: a #LassoWsAddrEndpointReference object
 * @security_token: a security token as a #LassoNode object
 * @security_mechanisms:(element-type utf8): a list of security mechanism
 * for whom the token is made
 *
 * Add a new security context declaration for the given security mechanisms identifiers and populate
 * it with a security token.
 *
 * Return value: 0 if successfull, an error code otherwise.
 */
int
lasso_wsa_endpoint_reference_add_security_token(LassoWsAddrEndpointReference *epr,
		LassoNode *security_token, GList *security_mechanisms)
{
	LassoIdWsf2SecToken *sec_token = NULL;
	LassoWsAddrMetadata *metadata = NULL;
	LassoIdWsf2DiscoSecurityContext *security_context = NULL;
	int rc = 0;

	lasso_bad_param(WSA_ENDPOINT_REFERENCE, epr);
	lasso_bad_param(NODE, security_token);

	lasso_extract_node_or_fail(metadata, epr->Metadata, WSA_METADATA, LASSO_PARAM_ERROR_INVALID_VALUE);

	sec_token = lasso_idwsf2_sec_token_new();
	lasso_assign_gobject(sec_token->any, security_token);
	lasso_assign_string(sec_token->usage, LASSO_IDWSF2_SEC_TOKEN_USAGE_SECURITY_TOKEN);

	security_context = lasso_idwsf2_disco_security_context_new();
	lasso_assign_list_of_strings(security_context->SecurityMechID,
			security_mechanisms);
	lasso_list_add_new_gobject(security_context->Token, sec_token);
	lasso_list_add_new_gobject(metadata->any, security_context);
cleanup:
	return rc;
}

static GHashTable *_mapping = NULL;

static GHashTable *_get_mapping() {
	if (_mapping == NULL) {
		_mapping = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
	}
	return _mapping;
}


/**
 * lasso_wsa_endpoint_reference_associate_service_to_type:
 * @service_type_uri: a service type to associate
 * @g_type: the type of the profile object handling this service type
 *
 * Associate a profile type to a service type.
 *
 * Return value: 0 if successful, an error code otherwise.
 */
int
lasso_wsa_endpoint_reference_associate_service_to_type(
		const char *service_type_uri, GType g_type)
{
	int rc = 0;

	lasso_check_non_empty_string(service_type_uri);
	if (! g_type_is_a(g_type, LASSO_TYPE_IDWSF2_PROFILE)) {
		return LASSO_PARAM_ERROR_INVALID_VALUE;
	}
	g_hash_table_insert(_get_mapping(),
			g_strdup(service_type_uri), (gpointer)g_type);
cleanup:
	return rc;
}

/**
 * lasso_wsa_endpoint_reference_get_service:
 * @epr: a #LassoWsAddrEndpointReference object
 *
 * Get a profile object able to communicate with the service represented by this EPR.
 *
 * Return object: a newly created #LassoIdWsf2Profile instance.
 */
LassoIdWsf2Profile *
lasso_wsa_endpoint_reference_get_service(
		LassoWsAddrEndpointReference *epr)
{
	GType type;
	const char *service_type_uri;

	if (! LASSO_IS_WSA_ENDPOINT_REFERENCE(epr))
		return NULL;

	service_type_uri = lasso_wsa_endpoint_reference_get_idwsf2_service_type(epr);
	type = (GType)g_hash_table_lookup(_get_mapping(), service_type_uri);
	if (type) {
		LassoIdWsf2Profile *profile;

		profile = (LassoIdWsf2Profile*)g_object_new(type, NULL);
		lasso_idwsf2_profile_set_epr(profile, epr);

		return profile;
	}
	return NULL;
}
