/*
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
*/
/**
* @file lib/anastasis_api_redux.c
* @brief anastasis reducer api
* @author Christian Grothoff
* @author Dominik Meister
* @author Dennis Neufeld
*/
#include
#include
#include "anastasis_redux.h"
#include "anastasis_error_codes.h"
#include
#include "anastasis_api_redux.h"
#include
/**
* 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; imethods_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; imethods_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; imethods_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 (®ex,
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 (®ex,
input,
0,
NULL,
0))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Input `%s' does not match regex `%s'\n",
input,
regexp);
regfree (®ex);
return false;
}
regfree (®ex);
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",
®exp)),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("validation-logic",
®log)),
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);
}