summaryrefslogtreecommitdiff
path: root/src/reducer/anastasis_api_redux.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/reducer/anastasis_api_redux.c')
-rw-r--r--src/reducer/anastasis_api_redux.c1730
1 files changed, 1730 insertions, 0 deletions
diff --git a/src/reducer/anastasis_api_redux.c b/src/reducer/anastasis_api_redux.c
new file mode 100644
index 0000000..eb7e362
--- /dev/null
+++ b/src/reducer/anastasis_api_redux.c
@@ -0,0 +1,1730 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis 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 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/anastasis_api_redux.c
+ * @brief anastasis reducer api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include <platform.h>
+#include <jansson.h>
+#include "anastasis_redux.h"
+#include "anastasis_error_codes.h"
+#include <taler/taler_json_lib.h>
+#include "anastasis_api_redux.h"
+#include <dlfcn.h>
+
+
+/**
+ * How long do we wait at most for a /config reply from an Anastasis provider.
+ * 60s is very generous, given the tiny bandwidth required, even for the most
+ * remote locations.
+ */
+#define CONFIG_GENERIC_TIMEOUT GNUNET_TIME_UNIT_MINUTES
+
+
+#define GENERATE_STRING(STRING) #STRING,
+static const char *generic_strings[] = {
+ ANASTASIS_GENERIC_STATES (GENERATE_STRING)
+};
+#undef GENERATE_STRING
+
+
+/**
+ * #ANASTASIS_REDUX_add_provider_to_state_ waiting for the
+ * configuration request to complete or fail.
+ */
+struct ConfigReduxWaiting
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct ConfigReduxWaiting *prev;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct ConfigReduxWaiting *next;
+
+ /**
+ * Associated redux action.
+ */
+ struct ANASTASIS_ReduxAction ra;
+
+ /**
+ * Config request we are waiting for.
+ */
+ struct ConfigRequest *cr;
+
+ /**
+ * State we are processing.
+ */
+ json_t *state;
+
+ /**
+ * Function to call with updated @e state.
+ */
+ ANASTASIS_ActionCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Anastasis authorization method configuration
+ */
+struct AuthorizationMethodConfig
+{
+ /**
+ * Type of the method, i.e. "question".
+ */
+ char *type;
+
+ /**
+ * Fee charged for accessing key share using this method.
+ */
+ struct TALER_Amount usage_fee;
+};
+
+
+/**
+ * State for a "get config" operation.
+ */
+struct ConfigRequest
+{
+
+ /**
+ * Kept in a DLL, given that we may have multiple backends.
+ */
+ struct ConfigRequest *next;
+
+ /**
+ * Kept in a DLL, given that we may have multiple backends.
+ */
+ struct ConfigRequest *prev;
+
+ /**
+ * Head of DLL of REDUX operations waiting for an answer.
+ */
+ struct ConfigReduxWaiting *w_head;
+
+ /**
+ * Tail of DLL of REDUX operations waiting for an answer.
+ */
+ struct ConfigReduxWaiting *w_tail;
+
+ /**
+ * Obtained status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * The /config GET operation handle.
+ */
+ struct ANASTASIS_ConfigOperation *co;
+
+ /**
+ * URL of the anastasis backend.
+ */
+ char *url;
+
+ /**
+ * Business name of the anastasis backend.
+ */
+ char *business_name;
+
+ /**
+ * currency used by the anastasis backend.
+ */
+ char *currency;
+
+ /**
+ * Array of authorization methods supported by the server.
+ */
+ struct AuthorizationMethodConfig *methods;
+
+ /**
+ * Length of the @e methods array.
+ */
+ unsigned int methods_length;
+
+ /**
+ * Maximum size of an upload in megabytes.
+ */
+ uint32_t storage_limit_in_megabytes;
+
+ /**
+ * Annual fee for an account / policy upload.
+ */
+ struct TALER_Amount annual_fee;
+
+ /**
+ * Fee for a truth upload.
+ */
+ struct TALER_Amount truth_upload_fee;
+
+ /**
+ * Maximum legal liability for data loss covered by the
+ * provider.
+ */
+ struct TALER_Amount liability_limit;
+
+ /**
+ * Server salt.
+ */
+ struct ANASTASIS_CRYPTO_ProviderSaltP salt;
+
+ /**
+ * Task to timeout /config requests.
+ */
+ struct GNUNET_SCHEDULER_Task *tt;
+
+ /**
+ * Status of the /config request.
+ */
+ enum TALER_ErrorCode ec;
+};
+
+
+/**
+ * Reducer API's CURL context handle.
+ */
+struct GNUNET_CURL_Context *ANASTASIS_REDUX_ctx_;
+
+/**
+ * JSON containing country specific identity attributes to ask the user for.
+ */
+static json_t *redux_id_attr;
+
+/**
+ * Head of DLL of Anastasis backend configuration requests.
+ */
+static struct ConfigRequest *cr_head;
+
+/**
+ * Tail of DLL of Anastasis backend configuration requests.
+ */
+static struct ConfigRequest *cr_tail;
+
+/**
+ * JSON containing country specific information.
+ */
+static json_t *redux_countries;
+
+/**
+ * List of Anastasis providers.
+ */
+static json_t *provider_list;
+
+
+/**
+ * Extract the mode of a state from json
+ *
+ * @param state the state to operate on
+ * @return "backup_state" or "recovery_state"
+ */
+static const char *
+get_state_mode (const json_t *state)
+{
+ if (json_object_get (state, "backup_state"))
+ return "backup_state";
+ if (json_object_get (state, "recovery_state"))
+ return "recovery_state";
+ GNUNET_assert (0);
+ return NULL;
+}
+
+
+enum ANASTASIS_GenericState
+ANASTASIS_generic_state_from_string_ (const char *state_string)
+{
+ for (enum ANASTASIS_GenericState i = 0;
+ i < sizeof (generic_strings) / sizeof(*generic_strings);
+ i++)
+ if (0 == strcmp (state_string,
+ generic_strings[i]))
+ return i;
+ return ANASTASIS_GENERIC_STATE_ERROR;
+}
+
+
+const char *
+ANASTASIS_generic_state_to_string_ (enum ANASTASIS_GenericState gs)
+{
+ if ( (gs < 0) ||
+ (gs >= sizeof (generic_strings) / sizeof(*generic_strings)) )
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ return generic_strings[gs];
+}
+
+
+void
+ANASTASIS_redux_fail_ (ANASTASIS_ActionCallback cb,
+ void *cb_cls,
+ enum TALER_ErrorCode ec,
+ const char *detail)
+{
+ json_t *estate;
+
+ estate = json_pack ("{s:s?, s:I, s:s}",
+ "detail", detail,
+ "code", (json_int_t) ec,
+ "hint", TALER_ErrorCode_get_hint (ec));
+ cb (cb_cls,
+ ec,
+ estate);
+ json_decref (estate);
+}
+
+
+/**
+ * Transition the @a state to @a gs.
+ *
+ * @param[in,out] state to transition
+ * @param gs state to transition to
+ */
+static void
+redux_transition (json_t *state,
+ enum ANASTASIS_GenericState gs)
+{
+ const char *s_mode = get_state_mode (state);
+
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ state,
+ s_mode,
+ json_string (
+ ANASTASIS_generic_state_to_string_ (gs))));
+
+}
+
+
+void
+ANASTASIS_redux_init (struct GNUNET_CURL_Context *ctx)
+{
+ ANASTASIS_REDUX_ctx_ = ctx;
+}
+
+
+/**
+ * Function to free a #ConfigRequest, an async operation.
+ *
+ * @param cr state for a "get config" operation
+ */
+static void
+free_config_request (struct ConfigRequest *cr)
+{
+ GNUNET_assert (NULL == cr->w_head);
+ if (NULL != cr->co)
+ ANASTASIS_config_cancel (cr->co);
+ if (NULL != cr->tt)
+ GNUNET_SCHEDULER_cancel (cr->tt);
+ GNUNET_free (cr->currency);
+ GNUNET_free (cr->url);
+ GNUNET_free (cr->business_name);
+ for (unsigned int i = 0; i<cr->methods_length; i++)
+ GNUNET_free (cr->methods[i].type);
+ GNUNET_free (cr->methods);
+ GNUNET_free (cr);
+}
+
+
+void
+ANASTASIS_redux_done ()
+{
+ struct ConfigRequest *cr;
+
+ while (NULL != (cr = cr_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (cr_head,
+ cr_tail,
+ cr);
+ free_config_request (cr);
+ }
+ ANASTASIS_REDUX_ctx_ = NULL;
+ if (NULL != redux_countries)
+ {
+ json_decref (redux_countries);
+ redux_countries = NULL;
+ }
+ if (NULL != redux_id_attr)
+ {
+ json_decref (redux_id_attr);
+ redux_id_attr = NULL;
+ }
+ if (NULL != provider_list)
+ {
+ json_decref (provider_list);
+ provider_list = NULL;
+ }
+}
+
+
+const json_t *
+ANASTASIS_redux_countries_init_ (void)
+{
+ char *dn;
+ json_error_t error;
+
+ if (NULL != redux_countries)
+ return redux_countries;
+
+ {
+ char *path;
+
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ if (NULL == path)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ GNUNET_asprintf (&dn,
+ "%s/redux.countries.json",
+ path);
+ GNUNET_free (path);
+ }
+ redux_countries = json_load_file (dn,
+ JSON_COMPACT,
+ &error);
+ if (NULL == redux_countries)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse `%s': %s at %d:%d (%d)\n",
+ dn,
+ error.text,
+ error.line,
+ error.column,
+ error.position);
+ GNUNET_free (dn);
+ return NULL;
+ }
+ GNUNET_free (dn);
+ return redux_countries;
+}
+
+
+/**
+ * Abort waiting for /config reply.
+ *
+ * @param cls a `struct ConfigReduxWaiting` handle.
+ */
+static void
+abort_provider_config_cb (void *cls)
+{
+ struct ConfigReduxWaiting *w = cls;
+ struct ConfigRequest *cr = w->cr;
+
+ GNUNET_CONTAINER_DLL_remove (cr->w_head,
+ cr->w_tail,
+ w);
+ json_decref (w->state);
+ GNUNET_free (w);
+}
+
+
+/**
+ * Notify anyone waiting on @a cr that the request is done
+ * (successful or failed).
+ *
+ * @param[in,out] cr request that completed
+ */
+static void
+notify_waiting (struct ConfigRequest *cr)
+{
+ struct ConfigReduxWaiting *w;
+
+ while (NULL != (w = cr->w_head))
+ {
+ json_t *provider_list;
+ json_t *prov;
+
+ if (NULL == (provider_list = json_object_get (w->state,
+ "authentication_providers")))
+ {
+ GNUNET_assert (0 ==
+ json_object_set_new (w->state,
+ "authentication_providers",
+ provider_list = json_object ()));
+ }
+ provider_list = json_object_get (w->state,
+ "authentication_providers");
+ GNUNET_assert (NULL != provider_list);
+
+ if (TALER_EC_NONE != cr->ec)
+ {
+ prov = json_pack ("{s:I, s:I}",
+ "error_code",
+ (json_int_t) cr->ec,
+ "http_status",
+ (json_int_t) cr->http_status);
+ }
+ else
+ {
+ json_t *methods_list;
+
+ methods_list = json_array ();
+ GNUNET_assert (NULL != methods_list);
+ for (unsigned int i = 0; i<cr->methods_length; i++)
+ {
+ struct AuthorizationMethodConfig *method = &cr->methods[i];
+ json_t *mj = json_pack ("{s:s, s:o}",
+ "type",
+ method->type,
+ "usage_fee",
+ TALER_JSON_from_amount (&method->usage_fee));
+
+ GNUNET_assert (NULL != mj);
+ GNUNET_assert (0 ==
+ json_array_append_new (methods_list,
+ mj));
+ }
+ prov = json_pack ("{s:o, s:o, s:o, s:o, s:s,"
+ " s:s, s:I, s:o, s:I}",
+ "methods",
+ methods_list,
+ "annual_fee",
+ TALER_JSON_from_amount (&cr->annual_fee),
+ "truth_upload_fee",
+ TALER_JSON_from_amount (&cr->truth_upload_fee),
+ "liability_limit",
+ TALER_JSON_from_amount (&cr->liability_limit),
+ "currency",
+ cr->currency,
+ /* 6 */
+ "business_name",
+ cr->business_name,
+ "storage_limit_in_megabytes",
+ (json_int_t) cr->storage_limit_in_megabytes,
+ "salt",
+ GNUNET_JSON_from_data_auto (&cr->salt),
+ "http_status",
+ (json_int_t) cr->http_status);
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (provider_list,
+ cr->url,
+ prov));
+ w->cb (w->cb_cls,
+ cr->ec,
+ w->state);
+ abort_provider_config_cb (w);
+ }
+
+}
+
+
+/**
+ * Function called with the results of a #ANASTASIS_get_config().
+ *
+ * @param cls closure
+ * @param http_status HTTP status of the request
+ * @param acfg anastasis configuration
+ */
+static void
+config_cb (void *cls,
+ unsigned int http_status,
+ const struct ANASTASIS_Config *acfg)
+{
+ struct ConfigRequest *cr = cls;
+
+ cr->co = NULL;
+ GNUNET_SCHEDULER_cancel (cr->tt);
+ cr->tt = NULL;
+ cr->http_status = http_status;
+ if (MHD_HTTP_OK != http_status)
+ cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED;
+ if ( (MHD_HTTP_OK == http_status) &&
+ (NULL == acfg) )
+ {
+ cr->http_status = MHD_HTTP_NOT_FOUND;
+ cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED;
+ }
+ else if (NULL != acfg)
+ {
+ if (0 == acfg->storage_limit_in_megabytes)
+ {
+ cr->http_status = 0;
+ cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_INVALID_CONFIG;
+ }
+ else
+ {
+ cr->currency = GNUNET_strdup (acfg->currency);
+ cr->business_name = GNUNET_strdup (acfg->business_name);
+ cr->methods = GNUNET_new_array (acfg->methods_length,
+ struct AuthorizationMethodConfig);
+ for (unsigned int i = 0; i<acfg->methods_length; i++)
+ {
+ cr->methods[i].type = GNUNET_strdup (acfg->methods[i].type);
+ cr->methods[i].usage_fee = acfg->methods[i].usage_fee;
+ }
+ cr->methods_length = acfg->methods_length;
+ cr->storage_limit_in_megabytes = acfg->storage_limit_in_megabytes;
+ cr->annual_fee = acfg->annual_fee;
+ cr->truth_upload_fee = acfg->truth_upload_fee;
+ cr->liability_limit = acfg->liability_limit;
+ cr->salt = acfg->salt;
+ }
+ }
+ notify_waiting (cr);
+}
+
+
+/**
+ * Aborts a "get config" after timeout.
+ *
+ * @param cls closure for a "get config" request
+ */
+static void
+config_request_timeout (void *cls)
+{
+ struct ConfigRequest *cr = cls;
+
+ cr->tt = NULL;
+ ANASTASIS_config_cancel (cr->co);
+ cr->co = NULL;
+ cr->http_status = 0;
+ cr->ec = TALER_EC_GENERIC_TIMEOUT;
+ notify_waiting (cr);
+}
+
+
+/**
+ * Schedule job to obtain Anastasis provider configuration at @a url.
+ *
+ * @param url base URL of Anastasis provider
+ * @return check config handle
+ */
+static struct ConfigRequest *
+check_config (const char *url)
+{
+ struct ConfigRequest *cr;
+
+ for (cr = cr_head; NULL != cr; cr = cr->next)
+ {
+ if (0 != strcmp (url,
+ cr->url))
+ continue;
+ if (NULL != cr->co)
+ return cr; /* already on it */
+ break;
+ }
+ if (NULL == cr)
+ {
+ cr = GNUNET_new (struct ConfigRequest);
+ cr->url = GNUNET_strdup (url);
+ GNUNET_CONTAINER_DLL_insert (cr_head,
+ cr_tail,
+ cr);
+ }
+ cr->co = ANASTASIS_get_config (ANASTASIS_REDUX_ctx_,
+ cr->url,
+ &config_cb,
+ cr);
+ if (NULL == cr->co)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ else
+ {
+ cr->tt = GNUNET_SCHEDULER_add_delayed (CONFIG_GENERIC_TIMEOUT,
+ &config_request_timeout,
+ cr);
+ }
+ return cr;
+}
+
+
+/**
+ * Begin asynchronous check for provider configurations.
+ *
+ * @param currencies the currencies to initiate the provider checks for
+ * @param[in,out] state to set provider list for
+ * @return #TALER_EC_NONE on success
+ */
+static enum TALER_ErrorCode
+begin_provider_config_check (const json_t *currencies,
+ json_t *state)
+{
+ if (NULL == provider_list)
+ {
+ json_error_t error;
+ char *dn;
+ char *path;
+
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ if (NULL == path)
+ {
+ GNUNET_break (0);
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+ GNUNET_asprintf (&dn,
+ "%s/provider-list.json",
+ path);
+ GNUNET_free (path);
+ provider_list = json_load_file (dn,
+ JSON_COMPACT,
+ &error);
+ if (NULL == provider_list)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse `%s': %s at %d:%d (%d)\n",
+ dn,
+ error.text,
+ error.line,
+ error.column,
+ error.position);
+ GNUNET_free (dn);
+ return TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED;
+ }
+ GNUNET_free (dn);
+ }
+
+ {
+ size_t index;
+ json_t *provider;
+ const json_t *provider_arr = json_object_get (provider_list,
+ "anastasis_provider");
+ json_t *pl;
+
+ pl = json_object ();
+ json_array_foreach (provider_arr, index, provider)
+ {
+ const char *url;
+ const char *cur;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("url",
+ &url),
+ GNUNET_JSON_spec_string ("currency",
+ &cur),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (provider,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ json_decref (pl);
+ return TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED;
+ }
+
+ {
+ bool found = false;
+ json_t *cu;
+ size_t off;
+
+ json_array_foreach (currencies, off, cu)
+ {
+ const char *currency;
+
+ currency = json_string_value (cu);
+ if (NULL == currency)
+ {
+ json_decref (pl);
+ return TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID;
+ }
+ found = (0 == strcasecmp (currency,
+ cur));
+ }
+ if (! found)
+ continue;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (pl,
+ url,
+ json_object ()));
+ check_config (url);
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "authentication_providers",
+ pl));
+ }
+ return TALER_EC_NONE;
+}
+
+
+/**
+ * Function to validate an input by regular expression ("validation-regex").
+ *
+ * @param input text to validate
+ * @param regexp regular expression to validate
+ * @return true if validation passed, else false
+ */
+static bool
+validate_regex (const char *input,
+ const char *regexp)
+{
+ regex_t regex;
+
+ if (0 != regcomp (&regex,
+ regexp,
+ REG_EXTENDED))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to compile regular expression `%s'.",
+ regexp);
+ return true;
+ }
+ /* check if input has correct form */
+ if (0 != regexec (&regex,
+ input,
+ 0,
+ NULL,
+ 0))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Input `%s' does not match regex `%s'\n",
+ input,
+ regexp);
+ regfree (&regex);
+ return false;
+ }
+ regfree (&regex);
+ return true;
+}
+
+
+/**
+ * Function to load json containing country specific identity
+ * attributes. Uses a single-slot cache to avoid loading
+ * exactly the same attributes twice.
+ *
+ * @param country_code country code (e.g. "de")
+ * @return NULL on error
+ */
+static const json_t *
+redux_id_attr_init (const char *country_code)
+{
+ static char redux_id_cc[3];
+ char *dn;
+ json_error_t error;
+
+ if (0 == strcmp (country_code,
+ redux_id_cc))
+ return redux_id_attr;
+
+ if (NULL != redux_id_attr)
+ {
+ json_decref (redux_id_attr);
+ redux_id_attr = NULL;
+ }
+ {
+ char *path;
+
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ if (NULL == path)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ GNUNET_asprintf (&dn,
+ "%s/redux.%s.json",
+ path,
+ country_code);
+ GNUNET_free (path);
+ }
+ redux_id_attr = json_load_file (dn,
+ JSON_COMPACT,
+ &error);
+ if (NULL == redux_id_attr)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse `%s': %s at %d:%d (%d)\n",
+ dn,
+ error.text,
+ error.line,
+ error.column,
+ error.position);
+ GNUNET_free (dn);
+ return NULL;
+ }
+ GNUNET_free (dn);
+ strncpy (redux_id_cc,
+ country_code,
+ sizeof (redux_id_cc));
+ redux_id_cc[2] = '\0';
+ return redux_id_attr;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "select_continent" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+select_continent (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *redux_countries = ANASTASIS_redux_countries_init_ ();
+ const json_t *root = json_object_get (redux_countries,
+ "countries");
+ const json_t *continent;
+ json_t *countries;
+
+ if (NULL == root)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED,
+ "'countries' missing");
+ return NULL;
+ }
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ continent = json_object_get (arguments,
+ "continent");
+ if (NULL == continent)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'continent' missing");
+ return NULL;
+ }
+ countries = json_array ();
+ GNUNET_assert (NULL != countries);
+ {
+ size_t index;
+ const json_t *country;
+ bool found = false;
+
+ json_array_foreach (root, index, country)
+ {
+ json_t *temp_continent = json_object_get (country,
+ "continent");
+ if (1 == json_equal (continent,
+ temp_continent))
+ {
+ GNUNET_assert (0 ==
+ json_array_append (countries,
+ (json_t *) country));
+ found = true;
+ }
+ }
+ if (! found)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'continent' unknown");
+ return NULL;
+ }
+ }
+ redux_transition (state,
+ ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING);
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "selected_continent",
+ (json_t *) continent));
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "countries",
+ countries));
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "select_country" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return #ANASTASIS_ReduxAction
+ */
+static struct ANASTASIS_ReduxAction *
+select_country (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *required_attrs;
+ const json_t *country_code;
+ const json_t *currencies;
+ const json_t *redux_id_attr;
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ country_code = json_object_get (arguments,
+ "country_code");
+ if (NULL == country_code)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'country_code' missing");
+ return NULL;
+ }
+
+ {
+ json_t *countries = json_object_get (state,
+ "countries");
+ size_t index;
+ json_t *country;
+ bool found = false;
+
+ json_array_foreach (countries, index, country)
+ {
+ json_t *cc = json_object_get (country,
+ "code");
+ if (1 == json_equal (country_code,
+ cc))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (! found)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "specified country not on selected continent");
+ return NULL;
+ }
+ }
+
+ currencies = json_object_get (arguments,
+ "currencies");
+ if ( (NULL == currencies) ||
+ (! json_is_array (currencies)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'currencies' missing");
+ return NULL;
+ }
+ /* We now have an idea of the currency, begin fetching
+ provider /configs (we likely need them later) */
+ {
+ enum TALER_ErrorCode ec;
+
+ ec = begin_provider_config_check (currencies,
+ state);
+ if (TALER_EC_NONE != ec)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ ec,
+ NULL);
+ return NULL;
+ }
+ }
+ redux_id_attr = redux_id_attr_init (json_string_value (country_code));
+ if (NULL == redux_id_attr)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_RESOURCE_MISSING,
+ json_string_value (country_code));
+ return NULL;
+ }
+ required_attrs = json_object_get (redux_id_attr,
+ "required_attributes");
+ if (NULL == required_attrs)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED,
+ "'required_attributes' missing");
+ return NULL;
+ }
+ redux_transition (state,
+ ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING);
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "selected_country",
+ (json_t *) country_code));
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "currencies",
+ (json_t *) currencies));
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "required_attributes",
+ (json_t *) required_attrs));
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "unselect_continent" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+unselect_continent (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ redux_transition (state,
+ ANASTASIS_GENERIC_STATE_CONTINENT_SELECTING);
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+struct ANASTASIS_ReduxAction *
+ANASTASIS_REDUX_add_provider_to_state_ (const char *url,
+ json_t *state,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct ConfigRequest *cr;
+ struct ConfigReduxWaiting *w;
+
+ cr = check_config (url);
+ w = GNUNET_new (struct ConfigReduxWaiting);
+ w->cr = cr;
+ w->state = json_incref (state);
+ w->cb = cb;
+ w->cb_cls = cb_cls;
+ w->ra.cleanup = &abort_provider_config_cb;
+ w->ra.cleanup_cls = w;
+ GNUNET_CONTAINER_DLL_insert (cr->w_head,
+ cr->w_tail,
+ w);
+ if (NULL == cr->co)
+ {
+ notify_waiting (cr);
+ return NULL;
+ }
+ return &w->ra;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "enter_user_attributes" action.
+ * Returns an #ANASTASIS_ReduxAction if operation is async.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+enter_user_attributes (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *attributes;
+ const json_t *required_attributes;
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ attributes = json_object_get (arguments,
+ "identity_attributes");
+ if (NULL == attributes)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'identity_attributes' missing");
+ return NULL;
+ }
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "identity_attributes",
+ (json_t *) attributes));
+
+ /* Verify required attributes are present and well-formed */
+ required_attributes = json_object_get (state,
+ "required_attributes");
+ if ( (NULL == required_attributes) ||
+ (! json_is_array (required_attributes)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'required_attributes' must be an array");
+ return NULL;
+ }
+ {
+ size_t index;
+ json_t *required_attribute;
+
+ json_array_foreach (required_attributes, index, required_attribute)
+ {
+ const char *name;
+ const char *attribute_value;
+ const char *regexp = NULL;
+ const char *reglog = NULL;
+ int optional = false;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("name",
+ &name),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("validation-regex",
+ &regexp)),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("validation-logic",
+ &reglog)),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_boolean ("optional",
+ &optional)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (required_attribute,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'required_attributes' lacks required fields");
+ return NULL;
+ }
+ attribute_value = json_string_value (json_object_get (attributes,
+ name));
+ if (NULL == attribute_value)
+ {
+ if (optional)
+ continue;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Request is missing required attribute `%s'\n",
+ name);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_GENERIC_PARAMETER_MISSING,
+ name);
+ return NULL;
+ }
+ if ( (NULL != regexp) &&
+ (! validate_regex (attribute_value,
+ regexp)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_REGEX_FAILED,
+ name);
+ return NULL;
+ }
+
+ if (NULL != reglog)
+ {
+ bool (*regfun)(const char *);
+
+ regfun = dlsym (RTLD_DEFAULT,
+ reglog);
+ if (NULL == regfun)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Custom validation function `%s' is not available: %s\n",
+ reglog,
+ dlerror ());
+ }
+ else if (! regfun (attribute_value))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_VALIDATION_FAILED,
+ name);
+ return NULL;
+ }
+ }
+ } /* end for all attributes loop */
+ } /* end for all attributes scope */
+
+ /* Transition based on mode */
+ {
+ const char *s_mode = get_state_mode (state);
+
+ if (0 == strcmp (s_mode,
+ "backup_state"))
+ {
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ state,
+ "backup_state",
+ json_string (
+ ANASTASIS_backup_state_to_string_ (
+ ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING))));
+ return ANASTASIS_REDUX_backup_begin_ (state,
+ arguments,
+ cb,
+ cb_cls);
+ }
+ else
+ {
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ state,
+ "recovery_state",
+ json_string (
+ ANASTASIS_recovery_state_to_string_ (
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING))));
+ return ANASTASIS_REDUX_recovery_challenge_begin_ (state,
+ arguments,
+ cb,
+ cb_cls);
+ }
+ }
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "add_provider" action. Adds another Anastasis provider
+ * to the list of available providers for storing information.
+ *
+ * @param state state to operate on
+ * @param arguments arguments with a provider URL to add
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ */
+static struct ANASTASIS_ReduxAction *
+add_provider (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ if (ANASTASIS_add_provider_ (state,
+ arguments,
+ cb,
+ cb_cls))
+ return NULL;
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+bool
+ANASTASIS_add_provider_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ json_t *urls;
+ json_t *tlist;
+
+ tlist = json_object_get (state,
+ "authentication_providers");
+ if (NULL == tlist)
+ {
+ tlist = json_object ();
+ GNUNET_assert (NULL != tlist);
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "authentication_providers",
+ tlist));
+ }
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return true;
+ }
+ urls = json_object_get (arguments,
+ "urls");
+ if (NULL == urls)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'urls' missing");
+ return true;
+ }
+ {
+ size_t index;
+ json_t *url;
+
+ json_array_foreach (urls, index, url)
+ {
+ const char *url_str = json_string_value (url);
+
+ if (NULL == url_str)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'urls' must be strings");
+ return true;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (tlist,
+ url_str,
+ json_object ()));
+ }
+ }
+ return false;
+}
+
+
+struct ANASTASIS_ReduxAction *
+ANASTASIS_back_generic_decrement_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const char *s_mode = get_state_mode (state);
+ const char *state_string = json_string_value (json_object_get (state,
+ s_mode));
+
+ (void) arguments;
+ GNUNET_assert (NULL != state_string);
+ if (0 == strcmp ("backup_state",
+ s_mode))
+ {
+ enum ANASTASIS_BackupState state_index;
+
+ state_index = ANASTASIS_backup_state_from_string_ (state_string);
+ GNUNET_assert (state_index > 0);
+ state_index = state_index - 1;
+
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ state,
+ s_mode,
+ json_string (
+ ANASTASIS_backup_state_to_string_ (state_index))));
+ }
+ else
+ {
+ enum ANASTASIS_RecoveryState state_index;
+
+ state_index = ANASTASIS_recovery_state_from_string_ (state_string);
+ GNUNET_assert (state_index > 0);
+ state_index = state_index - 1;
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ state,
+ s_mode,
+ json_string (
+ ANASTASIS_recovery_state_to_string_ (state_index))));
+ }
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * Callback function which is called by the reducer in dependence of
+ * given state and action.
+ *
+ * @param state the previous state to operate on
+ * @param arguments the arguments needed by operation to operate on state
+ * @param cb Callback function which returns the new state
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel async actions, NULL if @a cb was already called
+ */
+typedef struct ANASTASIS_ReduxAction *
+(*DispatchHandler)(json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+struct ANASTASIS_ReduxAction *
+ANASTASIS_redux_action (const json_t *state,
+ const char *action,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct Dispatcher
+ {
+ enum ANASTASIS_GenericState redux_state;
+ const char *redux_action;
+ DispatchHandler fun;
+ } dispatchers[] = {
+ {
+ ANASTASIS_GENERIC_STATE_CONTINENT_SELECTING,
+ "select_continent",
+ &select_continent
+ },
+ {
+ ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
+ "unselect_continent",
+ &unselect_continent
+ },
+ {
+ ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
+ "select_country",
+ &select_country
+ },
+ {
+ ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
+ "select_continent",
+ &select_continent
+ },
+ {
+ ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
+ "unselect_continent",
+ &unselect_continent
+ },
+ {
+ ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
+ "enter_user_attributes",
+ &enter_user_attributes
+ },
+ {
+ ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
+ "add_provider",
+ &add_provider
+ },
+ {
+ ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
+ "back",
+ &ANASTASIS_back_generic_decrement_
+ },
+ { ANASTASIS_GENERIC_STATE_ERROR, NULL, NULL }
+ };
+ bool recovery_mode = false;
+ const char *s = json_string_value (json_object_get (state,
+ "backup_state"));
+ enum ANASTASIS_GenericState gs;
+
+ if (NULL == s)
+ {
+ s = json_string_value (json_object_get (state,
+ "recovery_state"));
+ if (NULL == s)
+ {
+ GNUNET_break_op (0);
+ cb (cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ NULL);
+ return NULL;
+ }
+ recovery_mode = true;
+ }
+ gs = ANASTASIS_generic_state_from_string_ (s);
+ {
+ json_t *new_state;
+ struct ANASTASIS_ReduxAction *ret;
+
+ new_state = json_deep_copy (state);
+ GNUNET_assert (NULL != new_state);
+ if (gs != ANASTASIS_GENERIC_STATE_ERROR)
+ {
+ for (unsigned int i = 0; NULL != dispatchers[i].fun; i++)
+ {
+ if ( (gs == dispatchers[i].redux_state) &&
+ (0 == strcmp (action,
+ dispatchers[i].redux_action)) )
+ {
+ ret = dispatchers[i].fun (new_state,
+ arguments,
+ cb,
+ cb_cls);
+ json_decref (new_state);
+ return ret;
+ }
+ }
+ }
+ if (recovery_mode)
+ {
+ ret = ANASTASIS_recovery_action_ (new_state,
+ action,
+ arguments,
+ cb,
+ cb_cls);
+ }
+ else
+ {
+ ret = ANASTASIS_backup_action_ (new_state,
+ action,
+ arguments,
+ cb,
+ cb_cls);
+ }
+ json_decref (new_state);
+ return ret;
+ }
+}
+
+
+void
+ANASTASIS_redux_action_cancel (struct ANASTASIS_ReduxAction *ra)
+{
+ ra->cleanup (ra->cleanup_cls);
+}
+
+
+json_t *
+ANASTASIS_REDUX_load_continents_ ()
+{
+ const json_t *countries;
+ json_t *continents;
+ const json_t *redux_countries = ANASTASIS_redux_countries_init_ ();
+
+ if (NULL == redux_countries)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ countries = json_object_get (redux_countries,
+ "countries");
+ if (NULL == countries)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ continents = json_array ();
+ GNUNET_assert (NULL != continents);
+
+ {
+ json_t *country;
+ size_t index;
+
+ json_array_foreach (countries, index, country)
+ {
+ json_t *ex = NULL;
+ const json_t *continent;
+
+ continent = json_object_get (country,
+ "continent");
+ if ( (NULL == continent) ||
+ (! json_is_string (continent)) )
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ {
+ size_t inner_index;
+ json_t *inner_continent;
+
+ json_array_foreach (continents, inner_index, inner_continent)
+ {
+ const json_t *name;
+
+ name = json_object_get (inner_continent,
+ "name");
+ if (1 == json_equal (continent,
+ name))
+ {
+ ex = inner_continent;
+ break;
+ }
+ }
+ }
+ if (NULL == ex)
+ {
+ ex = json_pack ("{s:O}",
+ "name",
+ continent);
+ GNUNET_assert (0 ==
+ json_array_append_new (continents,
+ ex));
+ }
+
+ {
+ json_t *i18n_continent;
+ json_t *name_ex;
+
+ i18n_continent = json_object_get (country,
+ "continent_i18n");
+ name_ex = json_object_get (ex,
+ "name_i18n");
+ if (NULL != i18n_continent)
+ {
+ const char *lang;
+ json_t *trans;
+
+ json_object_foreach (i18n_continent, lang, trans)
+ {
+ if (NULL == name_ex)
+ {
+ name_ex = json_object ();
+ json_object_set_new (ex,
+ "name_i18n",
+ name_ex);
+ }
+ if (NULL == json_object_get (name_ex,
+ lang))
+ {
+ json_object_set (name_ex,
+ lang,
+ trans);
+ }
+ }
+ }
+ }
+ }
+ }
+ return json_pack ("{s:o}",
+ "continents",
+ continents);
+}