[age restriction] progress 10/n
More work towards support for extensions: - Prepared statements and DB-plugin-functions for setting and retrieving configurations from the database added. - primitive "registry" of extensions for age restrictions and peer2peer (stub) - TALER_Extensions now with FP for parsing, setting and converting a configuration. - /management/extensions handler now verifies signature of the (opaque) json object for all extensions. - /management/extensions handler calls the FP in the corrensponding TALER_Extension for parsing and setting the configuration of a particular extension More work towards age restriction: - TALER_Extensions interfaces for config-parser, -setter and converter implemented for age restriction - DB event handler now retrieves config from database, parses it and sets it (the age mask) in the global extension. - load_age_mask now loads age mask from the global extension (and not from the config file)
This commit is contained in:
parent
ef4238874f
commit
120c6d53e2
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 2014-2021 Taler Systems SA
|
||||
This file is part of TALER
|
||||
Copyright (C) 2014-2021 Taler Systems SA
|
||||
|
||||
TALER is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
TALER is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
TALER 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 Affero General Public License for more details.
|
||||
TALER 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
You should have received a copy of the GNU Affero General Public License along with
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
/**
|
||||
* @file taler-exchange-httpd.c
|
||||
* @brief Serve the HTTP interface of the exchange
|
||||
@ -148,23 +148,9 @@ int TEH_check_invariants_flag;
|
||||
bool TEH_suicide;
|
||||
|
||||
/**
|
||||
* The global manifest with the list supported extensions, sorted by
|
||||
* TALER_Extension_Type.
|
||||
**/
|
||||
const struct TALER_Extension TEH_extensions[TALER_Extension_Max] = {
|
||||
[TALER_Extension_Peer2Peer] = {
|
||||
.type = TALER_Extension_Peer2Peer,
|
||||
.name = "peer2peer",
|
||||
.critical = false,
|
||||
.config = NULL, // disabled per default
|
||||
},
|
||||
[TALER_Extension_AgeRestriction] = {
|
||||
.type = TALER_Extension_AgeRestriction,
|
||||
.name = "age_restriction",
|
||||
.critical = false,
|
||||
.config = NULL, // disabled per default
|
||||
},
|
||||
};
|
||||
* Global register of extensions
|
||||
*/
|
||||
struct TALER_Extension **TEH_extensions;
|
||||
|
||||
/**
|
||||
* Value to return from main()
|
||||
@ -485,7 +471,7 @@ proceed_with_handler (struct TEH_RequestContext *rc,
|
||||
if (GNUNET_SYSERR == res)
|
||||
{
|
||||
GNUNET_assert (NULL == root);
|
||||
return MHD_NO; /* bad upload, could not even generate error */
|
||||
return MHD_NO; /* bad upload, could not even generate error */
|
||||
}
|
||||
if ( (GNUNET_NO == res) ||
|
||||
(NULL == root) )
|
||||
@ -528,8 +514,8 @@ proceed_with_handler (struct TEH_RequestContext *rc,
|
||||
sizeof (emsg),
|
||||
"Got %u/%u segments for %s request ('%s')",
|
||||
(NULL == args[i - 1])
|
||||
? i - 1
|
||||
: i + ((NULL != fin) ? 1 : 0),
|
||||
? i - 1
|
||||
: i + ((NULL != fin) ? 1 : 0),
|
||||
rh->nargs,
|
||||
rh->url,
|
||||
url);
|
||||
@ -553,7 +539,7 @@ proceed_with_handler (struct TEH_RequestContext *rc,
|
||||
root,
|
||||
args);
|
||||
else /* We also only have "POST" or "GET" in the API for at this point
|
||||
(OPTIONS/HEAD are taken care of earlier) */
|
||||
(OPTIONS/HEAD are taken care of earlier) */
|
||||
ret = rh->handler.get (rc,
|
||||
args);
|
||||
}
|
||||
@ -1120,7 +1106,7 @@ handle_mhd_request (void *cls,
|
||||
|
||||
if (0 == strcasecmp (method,
|
||||
MHD_HTTP_METHOD_HEAD))
|
||||
method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */
|
||||
method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */
|
||||
|
||||
/* parse first part of URL */
|
||||
{
|
||||
@ -1954,8 +1940,8 @@ run (void *cls,
|
||||
MHD_OPTION_CONNECTION_TIMEOUT,
|
||||
connection_timeout,
|
||||
(0 == allow_address_reuse)
|
||||
? MHD_OPTION_END
|
||||
: MHD_OPTION_LISTENING_ADDRESS_REUSE,
|
||||
? MHD_OPTION_END
|
||||
: MHD_OPTION_LISTENING_ADDRESS_REUSE,
|
||||
(unsigned int) allow_address_reuse,
|
||||
MHD_OPTION_END);
|
||||
if (NULL == mhd)
|
||||
|
@ -202,9 +202,9 @@ extern volatile bool MHD_terminating;
|
||||
extern struct GNUNET_CURL_Context *TEH_curl_ctx;
|
||||
|
||||
/**
|
||||
* The manifest of the available extensions
|
||||
* The manifest of the available extensions, NULL terminated
|
||||
*/
|
||||
extern const struct TALER_Extension TEH_extensions[TALER_Extension_Max];
|
||||
extern struct TALER_Extension **TEH_extensions;
|
||||
|
||||
/**
|
||||
* @brief Struct describing an URL and the handler for it.
|
||||
|
@ -24,8 +24,107 @@
|
||||
#include "taler-exchange-httpd_extensions.h"
|
||||
#include "taler_json_lib.h"
|
||||
#include "taler_mhd_lib.h"
|
||||
#include "taler_extensions.h"
|
||||
#include <jansson.h>
|
||||
|
||||
/**
|
||||
* @brief implements the TALER_Extension.parse_and_set_config interface.
|
||||
*/
|
||||
static enum GNUNET_GenericReturnValue
|
||||
age_restriction_parse_and_set_config (struct TALER_Extension *this,
|
||||
const json_t *config)
|
||||
{
|
||||
enum GNUNET_GenericReturnValue ret;
|
||||
struct TALER_AgeMask mask = {0};
|
||||
|
||||
ret = TALER_agemask_parse_json (config, &mask);
|
||||
if (GNUNET_OK != ret)
|
||||
return ret;
|
||||
|
||||
if (this != NULL && TALER_Extension_AgeRestriction == this->type)
|
||||
{
|
||||
if (NULL != this->config)
|
||||
{
|
||||
GNUNET_free (this->config);
|
||||
}
|
||||
this->config = GNUNET_malloc (sizeof(struct TALER_AgeMask));
|
||||
GNUNET_memcpy (this->config, &mask, sizeof(struct TALER_AgeMask));
|
||||
}
|
||||
|
||||
return GNUNET_OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief implements the TALER_Extension.test_config interface.
|
||||
*/
|
||||
static enum GNUNET_GenericReturnValue
|
||||
age_restriction_test_config (const json_t *config)
|
||||
{
|
||||
return age_restriction_parse_and_set_config (NULL, config);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief implements the TALER_Extension.config_to_json interface.
|
||||
*/
|
||||
static json_t *
|
||||
age_restriction_config_to_json (const struct TALER_Extension *this)
|
||||
{
|
||||
const struct TALER_AgeMask *mask;
|
||||
if (NULL == this || TALER_Extension_AgeRestriction != this->type)
|
||||
return NULL;
|
||||
|
||||
mask = (struct TALER_AgeMask *) this->config;
|
||||
json_t *config = GNUNET_JSON_PACK (
|
||||
GNUNET_JSON_pack_string ("extension", this->name),
|
||||
GNUNET_JSON_pack_string ("mask",
|
||||
TALER_age_mask_to_string (mask))
|
||||
);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
/* The extension for age restriction */
|
||||
static struct TALER_Extension extension_age_restriction = {
|
||||
.type = TALER_Extension_AgeRestriction,
|
||||
.name = "age_restriction",
|
||||
.critical = false,
|
||||
.config = NULL, // disabled per default
|
||||
.test_config = &age_restriction_test_config,
|
||||
.parse_and_set_config = &age_restriction_parse_and_set_config,
|
||||
.config_to_json = &age_restriction_config_to_json,
|
||||
};
|
||||
|
||||
/* TODO: The extension for peer2peer */
|
||||
static struct TALER_Extension extension_peer2peer = {
|
||||
.type = TALER_Extension_Peer2Peer,
|
||||
.name = "peer2peer",
|
||||
.critical = false,
|
||||
.config = NULL, // disabled per default
|
||||
.test_config = NULL, // TODO
|
||||
.parse_and_set_config = NULL, // TODO
|
||||
.config_to_json = NULL, // TODO
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create a list with the extensions for Age Restriction and Peer2Peer
|
||||
*/
|
||||
static struct TALER_Extension **
|
||||
get_known_extensions ()
|
||||
{
|
||||
|
||||
struct TALER_Extension **list = GNUNET_new_array (TALER_Extension_Max + 1,
|
||||
struct TALER_Extension *);
|
||||
list[TALER_Extension_AgeRestriction] = &extension_age_restriction;
|
||||
list[TALER_Extension_Peer2Peer] = &extension_peer2peer;
|
||||
list[TALER_Extension_Max] = NULL;
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler listening for extensions updates by other exchange
|
||||
@ -33,7 +132,6 @@
|
||||
*/
|
||||
static struct GNUNET_DB_EventHandler *extensions_eh;
|
||||
|
||||
|
||||
/**
|
||||
* Function called whenever another exchange process has updated
|
||||
* the extensions data in the database.
|
||||
@ -48,30 +146,99 @@ extension_update_event_cb (void *cls,
|
||||
size_t extra_size)
|
||||
{
|
||||
(void) cls;
|
||||
(void) extra;
|
||||
(void) extra_size;
|
||||
enum TALER_Extension_Type type;
|
||||
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"Received /management/extensions update event\n");
|
||||
"Received extensions update event\n");
|
||||
|
||||
if (sizeof(enum TALER_Extension_Type) != extra_size)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Oops, incorrect size of extra for TALER_Extension_type\n");
|
||||
return;
|
||||
}
|
||||
|
||||
type = *(enum TALER_Extension_Type *) extra;
|
||||
if (type <0 || type >= TALER_Extension_Max)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Oops, incorrect type for TALER_Extension_type\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the config from the database as string
|
||||
{
|
||||
char *config_str;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
struct TALER_Extension *extension;
|
||||
json_error_t err;
|
||||
json_t *config;
|
||||
enum GNUNET_GenericReturnValue ret;
|
||||
|
||||
// TODO: make this a safe lookup
|
||||
extension = TEH_extensions[type];
|
||||
|
||||
qs = TEH_plugin->get_extension_config (TEH_plugin->cls,
|
||||
extension->name,
|
||||
&config_str);
|
||||
|
||||
if (qs < 0)
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Couldn't get extension config\n");
|
||||
GNUNET_break (0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the string as JSON
|
||||
config = json_loads (config_str, JSON_DECODE_ANY, &err);
|
||||
if (NULL == config)
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Failed to parse config for extension `%s' as JSON: %s (%s)\n",
|
||||
extension->name,
|
||||
err.text,
|
||||
err.source);
|
||||
GNUNET_break (0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Call the parser for the extension
|
||||
ret = extension->parse_and_set_config (extension, config);
|
||||
if (GNUNET_OK != ret)
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Couldn't parse configuration for extension %s from the database",
|
||||
extension->name);
|
||||
GNUNET_break (0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum GNUNET_GenericReturnValue
|
||||
TEH_extensions_init ()
|
||||
{
|
||||
struct GNUNET_DB_EventHeaderP es = {
|
||||
.size = htons (sizeof (es)),
|
||||
.type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED),
|
||||
};
|
||||
TEH_extensions = get_known_extensions ();
|
||||
|
||||
extensions_eh = TEH_plugin->event_listen (TEH_plugin->cls,
|
||||
GNUNET_TIME_UNIT_FOREVER_REL,
|
||||
&es,
|
||||
&extension_update_event_cb,
|
||||
NULL);
|
||||
if (NULL == extensions_eh)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
return GNUNET_SYSERR;
|
||||
struct GNUNET_DB_EventHeaderP ev = {
|
||||
.size = htons (sizeof (ev)),
|
||||
.type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED),
|
||||
};
|
||||
|
||||
extensions_eh = TEH_plugin->event_listen (TEH_plugin->cls,
|
||||
GNUNET_TIME_UNIT_FOREVER_REL,
|
||||
&ev,
|
||||
&extension_update_event_cb,
|
||||
NULL);
|
||||
if (NULL == extensions_eh)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
}
|
||||
return GNUNET_OK;
|
||||
}
|
||||
@ -89,22 +256,4 @@ TEH_extensions_done ()
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TEH_extensions_update_state (void)
|
||||
{
|
||||
/* TODO */
|
||||
#if 0
|
||||
struct GNUNET_DB_EventHeaderP es = {
|
||||
.size = htons (sizeof (es)),
|
||||
.type = htons (TALER_DBEVENT_EXCHANGE_WIRE_UPDATED),
|
||||
};
|
||||
|
||||
TEH_plugin->event_notify (TEH_plugin->cls,
|
||||
&es,
|
||||
NULL,
|
||||
0);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/* end of taler-exchange-httpd_extensions.c */
|
||||
|
@ -40,12 +40,4 @@ TEH_extensions_init (void);
|
||||
void
|
||||
TEH_extensions_done (void);
|
||||
|
||||
/**
|
||||
* Something changed in the database. Rebuild the extension state metadata.
|
||||
* This function should be called if the exchange learns about a new signature
|
||||
* from our master key.
|
||||
*/
|
||||
void
|
||||
TEH_extensions_update_state (void);
|
||||
|
||||
#endif
|
||||
|
@ -736,10 +736,6 @@ destroy_key_helpers (struct HelperState *hs)
|
||||
* Looks up the AGE_RESTRICTED setting for a denomination in the config and
|
||||
* returns the age restriction (mask) accordingly.
|
||||
*
|
||||
* FIXME: The mask is currently taken from the config. However, It MUST come
|
||||
* from the database where it has been persisted after a signed call to the
|
||||
* /management/extension API (TODO).
|
||||
*
|
||||
* @param section_name Section in the configuration for the particular
|
||||
* denomination.
|
||||
*/
|
||||
@ -748,15 +744,13 @@ load_age_mask (const char*section_name)
|
||||
{
|
||||
static const struct TALER_AgeMask null_mask = {0};
|
||||
struct TALER_AgeMask age_mask = {0};
|
||||
struct TALER_Extension *age_ext =
|
||||
TEH_extensions[TALER_Extension_AgeRestriction];
|
||||
|
||||
/* FIXME-oec: get age_mask from database, not from config */
|
||||
if (TALER_Extension_OK != TALER_get_age_mask (TEH_cfg, &age_mask))
|
||||
// Get the age mask from the extension, if configured
|
||||
if (NULL != age_ext->config)
|
||||
{
|
||||
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
|
||||
TALER_EXTENSION_SECTION_AGE_RESTRICTION,
|
||||
"AGE_GROUPS",
|
||||
"must be of form a:b:...:n:m, where 0<a<b<...<n<m<32\n");
|
||||
return null_mask;
|
||||
age_mask = *(struct TALER_AgeMask *) age_ext->config;
|
||||
}
|
||||
|
||||
if (age_mask.mask == 0)
|
||||
|
@ -29,21 +29,17 @@
|
||||
#include "taler-exchange-httpd_management.h"
|
||||
#include "taler-exchange-httpd_responses.h"
|
||||
#include "taler_extensions.h"
|
||||
#include "taler_dbevents.h"
|
||||
|
||||
|
||||
/**
|
||||
* Extension carries the necessary data for a particular extension.
|
||||
*
|
||||
*/
|
||||
struct Extension
|
||||
{
|
||||
enum TALER_Extension_Type type;
|
||||
json_t *config_json;
|
||||
|
||||
// This union contains the parsed configuration for each extension.
|
||||
union
|
||||
{
|
||||
// configuration for the age restriction
|
||||
struct TALER_AgeMask mask;
|
||||
|
||||
/* TODO oec - peer2peer config */
|
||||
};
|
||||
json_t *config;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -56,6 +52,38 @@ struct SetExtensionsContext
|
||||
struct TALER_MasterSignatureP *extensions_sigs;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief verifies the signature a configuration with the offline master key.
|
||||
*
|
||||
* @param config configuration of an extension given as JSON object
|
||||
* @param master_priv offline master public key of the exchange
|
||||
* @param[out] master_sig signature
|
||||
* @return GNUNET_OK on success, GNUNET_SYSERR otherwise
|
||||
*/
|
||||
static enum GNUNET_GenericReturnValue
|
||||
config_verify (
|
||||
const json_t *config,
|
||||
const struct TALER_MasterPublicKeyP *master_pub,
|
||||
const struct TALER_MasterSignatureP *master_sig
|
||||
)
|
||||
{
|
||||
enum GNUNET_GenericReturnValue ret;
|
||||
struct TALER_ExtensionConfigHash h_config;
|
||||
|
||||
ret = TALER_extension_config_hash (config, &h_config);
|
||||
if (GNUNET_OK != ret)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return TALER_exchange_offline_extension_config_hash_verify (h_config,
|
||||
master_pub,
|
||||
master_sig);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function implementing database transaction to set the configuration of
|
||||
* extensions. It runs the transaction logic.
|
||||
@ -77,9 +105,68 @@ set_extensions (void *cls,
|
||||
struct MHD_Connection *connection,
|
||||
MHD_RESULT *mhd_ret)
|
||||
{
|
||||
// struct SetExtensionContext *sec = cls;
|
||||
struct SetExtensionsContext *sec = cls;
|
||||
|
||||
/* save the configurations of all extensions */
|
||||
for (uint32_t i = 0; i<sec->num_extensions; i++)
|
||||
{
|
||||
struct Extension *ext = &sec->extensions[i];
|
||||
struct TALER_MasterSignatureP *sig = &sec->extensions_sigs[i];
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
char *config;
|
||||
|
||||
/* Sanity check.
|
||||
* TODO: replace with general API to retrieve the extension-handler
|
||||
*/
|
||||
if (0 > ext->type || TALER_Extension_Max <= ext->type)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
config = json_dumps (ext->config, JSON_COMPACT | JSON_SORT_KEYS);
|
||||
if (NULL == config)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_JSON_INVALID,
|
||||
"convert configuration to string");
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
qs = TEH_plugin->set_extension_config (
|
||||
TEH_plugin->cls,
|
||||
TEH_extensions[ext->type]->name,
|
||||
config,
|
||||
sig);
|
||||
|
||||
if (qs < 0)
|
||||
{
|
||||
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
|
||||
return qs;
|
||||
GNUNET_break (0);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_STORE_FAILED,
|
||||
"save extension configuration");
|
||||
}
|
||||
|
||||
/* Success, trigger event */
|
||||
{
|
||||
enum TALER_Extension_Type *type = &sec->extensions[i].type;
|
||||
struct GNUNET_DB_EventHeaderP ev = {
|
||||
.size = htons (sizeof (ev)),
|
||||
.type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED)
|
||||
};
|
||||
TEH_plugin->event_notify (TEH_plugin->cls,
|
||||
&ev,
|
||||
type,
|
||||
sizeof(*type));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO oec
|
||||
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* only 'success', so >=0, matters here */
|
||||
}
|
||||
|
||||
@ -92,50 +179,51 @@ TEH_handler_management_post_extensions (
|
||||
struct SetExtensionsContext sec = {0};
|
||||
json_t *extensions;
|
||||
json_t *extensions_sigs;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
struct GNUNET_JSON_Specification top_spec[] = {
|
||||
GNUNET_JSON_spec_json ("extensions",
|
||||
&extensions),
|
||||
GNUNET_JSON_spec_json ("extensions_sigs",
|
||||
&extensions_sigs),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
bool ok;
|
||||
MHD_RESULT ret;
|
||||
|
||||
// Parse the top level json structure
|
||||
{
|
||||
enum GNUNET_GenericReturnValue res;
|
||||
|
||||
res = TALER_MHD_parse_json_data (connection,
|
||||
root,
|
||||
spec);
|
||||
top_spec);
|
||||
if (GNUNET_SYSERR == res)
|
||||
return MHD_NO; /* hard failure */
|
||||
if (GNUNET_NO == res)
|
||||
return MHD_YES; /* failure */
|
||||
}
|
||||
|
||||
// Ensure we have two arrays of the same size
|
||||
if (! (json_is_array (extensions) &&
|
||||
json_is_array (extensions_sigs)) )
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
GNUNET_JSON_parse_free (top_spec);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_GENERIC_PARAMETER_MALFORMED,
|
||||
"array expected for extensions and extensions_sig");
|
||||
"array expected for extensions and extensions_sigs");
|
||||
}
|
||||
|
||||
sec.num_extensions = json_array_size (extensions_sigs);
|
||||
if (json_array_size (extensions) != sec.num_extensions)
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
GNUNET_JSON_parse_free (top_spec);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_GENERIC_PARAMETER_MALFORMED,
|
||||
"arrays extensions and extensions_sig are not of equal size");
|
||||
"arrays extensions and extensions_sigs are not of the same size");
|
||||
}
|
||||
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
@ -145,116 +233,59 @@ TEH_handler_management_post_extensions (
|
||||
struct Extension);
|
||||
sec.extensions_sigs = GNUNET_new_array (sec.num_extensions,
|
||||
struct TALER_MasterSignatureP);
|
||||
ok = true;
|
||||
|
||||
// Now parse individual extensions and signatures from those arrays.
|
||||
for (unsigned int i = 0; i<sec.num_extensions; i++)
|
||||
{
|
||||
// 1. parse the extension out of the json
|
||||
enum GNUNET_GenericReturnValue res;
|
||||
const struct TALER_Extension *extension;
|
||||
const char *name;
|
||||
struct GNUNET_JSON_Specification ext_spec[] = {
|
||||
GNUNET_JSON_spec_string ("extension",
|
||||
&name),
|
||||
GNUNET_JSON_spec_json ("config",
|
||||
&sec.extensions[i].config),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
|
||||
// 1. parse the extension
|
||||
res = TALER_MHD_parse_json_array (connection,
|
||||
extensions,
|
||||
ext_spec,
|
||||
i,
|
||||
-1);
|
||||
if (GNUNET_SYSERR == res)
|
||||
{
|
||||
enum GNUNET_GenericReturnValue res;
|
||||
const char *name;
|
||||
struct GNUNET_JSON_Specification ispec[] = {
|
||||
GNUNET_JSON_spec_string ("extension",
|
||||
&name),
|
||||
GNUNET_JSON_spec_json ("config",
|
||||
&sec.extensions[i].config_json),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
|
||||
res = TALER_MHD_parse_json_array (connection,
|
||||
extensions,
|
||||
ispec,
|
||||
i,
|
||||
-1);
|
||||
if (GNUNET_SYSERR == res)
|
||||
{
|
||||
ret = MHD_NO; /* hard failure */
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
if (GNUNET_NO == res)
|
||||
{
|
||||
ret = MHD_YES;
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Make sure name refers to a supported extension
|
||||
{
|
||||
bool found = false;
|
||||
for (unsigned int k = 0; k < TALER_Extension_Max; k++)
|
||||
{
|
||||
if (0 == strncmp (name,
|
||||
TEH_extensions[k].name,
|
||||
strlen (TEH_extensions[k].name)))
|
||||
{
|
||||
sec.extensions[i].type = TEH_extensions[k].type;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! found)
|
||||
{
|
||||
GNUNET_free (sec.extensions);
|
||||
GNUNET_free (sec.extensions_sigs);
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
GNUNET_JSON_parse_free (ispec);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_GENERIC_PARAMETER_MALFORMED,
|
||||
"invalid extension type");
|
||||
}
|
||||
}
|
||||
|
||||
// We have a JSON object for the extension. Increment its refcount and
|
||||
// free the parser.
|
||||
// TODO: is this correct?
|
||||
json_incref (sec.extensions[i].config_json);
|
||||
GNUNET_JSON_parse_free (ispec);
|
||||
|
||||
// Make sure the config is sound
|
||||
{
|
||||
switch (sec.extensions[i].type)
|
||||
{
|
||||
case TALER_Extension_AgeRestriction:
|
||||
if (GNUNET_OK != TALER_agemask_parse_json (
|
||||
sec.extensions[i].config_json,
|
||||
&sec.extensions[i].mask))
|
||||
{
|
||||
GNUNET_free (sec.extensions);
|
||||
GNUNET_free (sec.extensions_sigs);
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_GENERIC_PARAMETER_MALFORMED,
|
||||
"invalid mask for age restriction");
|
||||
}
|
||||
break;
|
||||
|
||||
case TALER_Extension_Peer2Peer: /* TODO */
|
||||
ok = false;
|
||||
ret = MHD_NO;
|
||||
goto BREAK;
|
||||
|
||||
default:
|
||||
/* not reachable */
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"shouldn't be reached in handler for /management/extensions\n");
|
||||
ok = false;
|
||||
ret = MHD_NO;
|
||||
goto BREAK;
|
||||
}
|
||||
}
|
||||
ret = MHD_NO; /* hard failure */
|
||||
goto CLEANUP;
|
||||
}
|
||||
if (GNUNET_NO == res)
|
||||
{
|
||||
ret = MHD_YES;
|
||||
goto CLEANUP;
|
||||
}
|
||||
|
||||
// 2. parse the signature
|
||||
/* 2. Make sure name refers to a supported extension */
|
||||
if (GNUNET_OK != TALER_extension_get_by_name (name,
|
||||
(const struct
|
||||
TALER_Extension **)
|
||||
TEH_extensions,
|
||||
&extension))
|
||||
{
|
||||
ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_GENERIC_PARAMETER_MALFORMED,
|
||||
"invalid extension type");
|
||||
goto CLEANUP;
|
||||
}
|
||||
|
||||
sec.extensions[i].type = extension->type;
|
||||
|
||||
/* 3. Extract the signature out of the json array */
|
||||
{
|
||||
enum GNUNET_GenericReturnValue res;
|
||||
struct GNUNET_JSON_Specification ispec[] = {
|
||||
struct GNUNET_JSON_Specification sig_spec[] = {
|
||||
GNUNET_JSON_spec_fixed_auto (NULL,
|
||||
&sec.extensions_sigs[i]),
|
||||
GNUNET_JSON_spec_end ()
|
||||
@ -262,81 +293,61 @@ TEH_handler_management_post_extensions (
|
||||
|
||||
res = TALER_MHD_parse_json_array (connection,
|
||||
extensions_sigs,
|
||||
ispec,
|
||||
sig_spec,
|
||||
i,
|
||||
-1);
|
||||
if (GNUNET_SYSERR == res)
|
||||
{
|
||||
ret = MHD_NO; /* hard failure */
|
||||
ok = false;
|
||||
break;
|
||||
goto CLEANUP;
|
||||
}
|
||||
if (GNUNET_NO == res)
|
||||
{
|
||||
ret = MHD_YES;
|
||||
ok = false;
|
||||
break;
|
||||
goto CLEANUP;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. verify the signature
|
||||
{
|
||||
enum GNUNET_GenericReturnValue res;
|
||||
|
||||
switch (sec.extensions[i].type)
|
||||
{
|
||||
case TALER_Extension_AgeRestriction:
|
||||
res = TALER_exchange_offline_extension_agemask_verify (
|
||||
sec.extensions[i].mask,
|
||||
/* 4. Verify the signature of the config */
|
||||
if (GNUNET_OK != config_verify (
|
||||
sec.extensions[i].config,
|
||||
&TEH_master_public_key,
|
||||
&sec.extensions_sigs[i]);
|
||||
if (GNUNET_OK != res)
|
||||
{
|
||||
GNUNET_free (sec.extensions);
|
||||
GNUNET_free (sec.extensions_sigs);
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_GENERIC_PARAMETER_MALFORMED,
|
||||
"invalid signature for age mask");
|
||||
}
|
||||
break;
|
||||
|
||||
case TALER_Extension_Peer2Peer: /* TODO */
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Peer2peer not yet supported in handler for /management/extensions\n");
|
||||
ok = false;
|
||||
ret = MHD_NO;
|
||||
goto BREAK;
|
||||
|
||||
default:
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"shouldn't be reached in handler for /management/extensions\n");
|
||||
ok = false;
|
||||
ret = MHD_NO;
|
||||
/* not reachable */
|
||||
goto BREAK;
|
||||
}
|
||||
&sec.extensions_sigs[i]))
|
||||
{
|
||||
ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_GENERIC_PARAMETER_MALFORMED,
|
||||
"invalid signature for extension");
|
||||
goto CLEANUP;
|
||||
}
|
||||
}
|
||||
|
||||
BREAK:
|
||||
if (! ok)
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Failure to handle /management/extensions\n");
|
||||
GNUNET_free (sec.extensions);
|
||||
GNUNET_free (sec.extensions_sigs);
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return ret;
|
||||
}
|
||||
/* 5. Make sure the config is sound */
|
||||
if (GNUNET_OK != extension->test_config (sec.extensions[i].config))
|
||||
{
|
||||
GNUNET_JSON_parse_free (ext_spec);
|
||||
ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_GENERIC_PARAMETER_MALFORMED,
|
||||
"invalid configuration for extension");
|
||||
goto CLEANUP;
|
||||
|
||||
}
|
||||
|
||||
/* We have a validly signed JSON object for the extension.
|
||||
* Increment its refcount and free the parser for the extension.
|
||||
*/
|
||||
json_incref (sec.extensions[i].config);
|
||||
GNUNET_JSON_parse_free (ext_spec);
|
||||
|
||||
} /* for-loop */
|
||||
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"Received %u extensions\n",
|
||||
sec.num_extensions);
|
||||
|
||||
// now run the transaction to persist the configurations
|
||||
{
|
||||
enum GNUNET_GenericReturnValue res;
|
||||
|
||||
@ -347,19 +358,29 @@ BREAK:
|
||||
&set_extensions,
|
||||
&sec);
|
||||
|
||||
GNUNET_free (sec.extensions);
|
||||
GNUNET_free (sec.extensions_sigs);
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
if (GNUNET_SYSERR == res)
|
||||
return ret;
|
||||
goto CLEANUP;
|
||||
}
|
||||
|
||||
return TALER_MHD_reply_static (
|
||||
ret = TALER_MHD_reply_static (
|
||||
connection,
|
||||
MHD_HTTP_NO_CONTENT,
|
||||
NULL,
|
||||
NULL,
|
||||
0);
|
||||
|
||||
CLEANUP:
|
||||
for (unsigned int i = 0; i < sec.num_extensions; i++)
|
||||
{
|
||||
if (NULL != sec.extensions[i].config)
|
||||
{
|
||||
json_decref (sec.extensions[i].config);
|
||||
}
|
||||
}
|
||||
GNUNET_free (sec.extensions);
|
||||
GNUNET_free (sec.extensions_sigs);
|
||||
GNUNET_JSON_parse_free (top_spec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 2014--2021 Taler Systems SA
|
||||
This file is part of TALER
|
||||
Copyright (C) 2014--2021 Taler Systems SA
|
||||
|
||||
TALER 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 3, or (at your option) any later version.
|
||||
TALER 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 3, or (at your option) any later version.
|
||||
|
||||
TALER 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.
|
||||
TALER 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
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file plugin_exchangedb_postgres.c
|
||||
@ -211,7 +211,7 @@ prepare_statements (struct PostgresClosure *pg)
|
||||
enum GNUNET_GenericReturnValue ret;
|
||||
struct GNUNET_PQ_PreparedStatement ps[] = {
|
||||
/* Used in #postgres_insert_denomination_info() and
|
||||
#postgres_add_denomination_key() */
|
||||
#postgres_add_denomination_key() */
|
||||
GNUNET_PQ_make_prepare (
|
||||
"denomination_insert",
|
||||
"INSERT INTO denominations "
|
||||
@ -222,8 +222,8 @@ prepare_statements (struct PostgresClosure *pg)
|
||||
",expire_withdraw"
|
||||
",expire_deposit"
|
||||
",expire_legal"
|
||||
",coin_val" /* value of this denom */
|
||||
",coin_frac" /* fractional value of this denom */
|
||||
",coin_val" /* value of this denom */
|
||||
",coin_frac" /* fractional value of this denom */
|
||||
",fee_withdraw_val"
|
||||
",fee_withdraw_frac"
|
||||
",fee_deposit_val"
|
||||
@ -245,8 +245,8 @@ prepare_statements (struct PostgresClosure *pg)
|
||||
",expire_withdraw"
|
||||
",expire_deposit"
|
||||
",expire_legal"
|
||||
",coin_val" /* value of this denom */
|
||||
",coin_frac" /* fractional value of this denom */
|
||||
",coin_val" /* value of this denom */
|
||||
",coin_frac" /* fractional value of this denom */
|
||||
",fee_withdraw_val"
|
||||
",fee_withdraw_frac"
|
||||
",fee_deposit_val"
|
||||
@ -268,8 +268,8 @@ prepare_statements (struct PostgresClosure *pg)
|
||||
",expire_withdraw"
|
||||
",expire_deposit"
|
||||
",expire_legal"
|
||||
",coin_val" /* value of this denom */
|
||||
",coin_frac" /* fractional value of this denom */
|
||||
",coin_val" /* value of this denom */
|
||||
",coin_frac" /* fractional value of this denom */
|
||||
",fee_withdraw_val"
|
||||
",fee_withdraw_frac"
|
||||
",fee_deposit_val"
|
||||
@ -332,8 +332,8 @@ prepare_statements (struct PostgresClosure *pg)
|
||||
",expire_withdraw"
|
||||
",expire_deposit"
|
||||
",expire_legal"
|
||||
",coin_val" /* value of this denom */
|
||||
",coin_frac" /* fractional value of this denom */
|
||||
",coin_val" /* value of this denom */
|
||||
",coin_frac" /* fractional value of this denom */
|
||||
",fee_withdraw_val"
|
||||
",fee_withdraw_frac"
|
||||
",fee_deposit_val"
|
||||
@ -766,7 +766,7 @@ prepare_statements (struct PostgresClosure *pg)
|
||||
|
||||
See also:
|
||||
https://stackoverflow.com/questions/34708509/how-to-use-returning-with-on-conflict-in-postgresql/37543015#37543015
|
||||
*/
|
||||
*/
|
||||
GNUNET_PQ_make_prepare (
|
||||
"insert_known_coin",
|
||||
"WITH dd"
|
||||
@ -2743,6 +2743,23 @@ prepare_statements (struct PostgresClosure *pg)
|
||||
" AND start_row=$2"
|
||||
" AND end_row=$3",
|
||||
3),
|
||||
/* Used in #postgres_set_extension_config */
|
||||
GNUNET_PQ_make_prepare (
|
||||
"set_extension_config",
|
||||
"WITH upsert AS "
|
||||
" (UPDATE extensions "
|
||||
" SET config=$2 "
|
||||
" config_sig=$3 "
|
||||
" WHERE name=$1 RETURNING *) "
|
||||
"INSERT INTO extensions (config, config_sig) VALUES ($2, $3) "
|
||||
"WHERE NOT EXISTS (SELECT * FROM upsert);",
|
||||
3),
|
||||
/* Used in #postgres_get_extension_config */
|
||||
GNUNET_PQ_make_prepare (
|
||||
"get_extension_config",
|
||||
"SELECT (config) FROM extensions"
|
||||
" WHERE name=$1;",
|
||||
1),
|
||||
GNUNET_PQ_PREPARED_STATEMENT_END
|
||||
};
|
||||
|
||||
@ -3396,11 +3413,11 @@ dominations_cb_helper (void *cls,
|
||||
|
||||
|
||||
/**
|
||||
* Function called to invoke @a cb on every known denomination key (revoked
|
||||
* and non-revoked) that has been signed by the master key. Runs in its own
|
||||
* read-only transaction.
|
||||
*
|
||||
*
|
||||
* Function called to invoke @a cb on every known denomination key (revoked
|
||||
* and non-revoked) that has been signed by the master key. Runs in its own
|
||||
* read-only transaction.
|
||||
*
|
||||
*
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param cb function to call on each denomination key
|
||||
* @param cb_cls closure for @a cb
|
||||
@ -3513,7 +3530,7 @@ postgres_iterate_active_signkeys (void *cls,
|
||||
void *cb_cls)
|
||||
{
|
||||
struct PostgresClosure *pg = cls;
|
||||
struct GNUNET_TIME_Absolute now;
|
||||
struct GNUNET_TIME_Absolute now = {0};
|
||||
struct GNUNET_PQ_QueryParam params[] = {
|
||||
GNUNET_PQ_query_param_absolute_time (&now),
|
||||
GNUNET_PQ_query_param_end
|
||||
@ -3600,7 +3617,7 @@ auditors_cb_helper (void *cls,
|
||||
/**
|
||||
* Function called to invoke @a cb on every active auditor. Disabled
|
||||
* auditors are skipped. Runs in its own read-only transaction.
|
||||
*
|
||||
*
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param cb function to call on each active auditor
|
||||
* @param cb_cls closure for @a cb
|
||||
@ -4470,6 +4487,8 @@ compute_shard (const struct TALER_MerchantPublicKeyP *merchant_pub)
|
||||
* Perform deposit operation, checking for sufficient balance
|
||||
* of the coin and possibly persisting the deposit details.
|
||||
*
|
||||
* FIXME: parameters missing in description!
|
||||
*
|
||||
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
||||
* @param deposit deposit operation details
|
||||
* @param known_coin_id row of the coin in the known_coins table
|
||||
@ -4908,7 +4927,7 @@ add_bank_to_exchange (void *cls,
|
||||
tail = append_rh (rhc);
|
||||
tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE;
|
||||
tail->details.bank = bt;
|
||||
} /* end of 'while (0 < rows)' */
|
||||
} /* end of 'while (0 < rows)' */
|
||||
}
|
||||
|
||||
|
||||
@ -5033,7 +5052,7 @@ add_recoup (void *cls,
|
||||
tail = append_rh (rhc);
|
||||
tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN;
|
||||
tail->details.recoup = recoup;
|
||||
} /* end of 'while (0 < rows)' */
|
||||
} /* end of 'while (0 < rows)' */
|
||||
}
|
||||
|
||||
|
||||
@ -5093,7 +5112,7 @@ add_exchange_to_bank (void *cls,
|
||||
tail = append_rh (rhc);
|
||||
tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK;
|
||||
tail->details.closing = closing;
|
||||
} /* end of 'while (0 < rows)' */
|
||||
} /* end of 'while (0 < rows)' */
|
||||
}
|
||||
|
||||
|
||||
@ -5361,7 +5380,7 @@ postgres_get_ready_deposit (void *cls,
|
||||
void *deposit_cb_cls)
|
||||
{
|
||||
struct PostgresClosure *pg = cls;
|
||||
struct GNUNET_TIME_Absolute now;
|
||||
struct GNUNET_TIME_Absolute now = {0};
|
||||
struct GNUNET_PQ_QueryParam params[] = {
|
||||
GNUNET_PQ_query_param_absolute_time (&now),
|
||||
GNUNET_PQ_query_param_uint64 (&start_shard_row),
|
||||
@ -6260,7 +6279,7 @@ postgres_get_refresh_reveal (void *cls,
|
||||
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
|
||||
goto cleanup;
|
||||
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
|
||||
default: /* can have more than one result */
|
||||
default: /* can have more than one result */
|
||||
break;
|
||||
}
|
||||
switch (grctx.qs)
|
||||
@ -6269,7 +6288,7 @@ postgres_get_refresh_reveal (void *cls,
|
||||
case GNUNET_DB_STATUS_SOFT_ERROR:
|
||||
goto cleanup;
|
||||
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
|
||||
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* should be impossible */
|
||||
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* should be impossible */
|
||||
break;
|
||||
}
|
||||
|
||||
@ -11394,6 +11413,68 @@ postgres_delete_shard_locks (void *cls)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function called to save the configuration of an extension
|
||||
* (age-restriction, peer2peer, ...). After succesfull storage of the
|
||||
* configuration it triggers the corresponding event.
|
||||
*
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param extension_name the name of the extension
|
||||
* @param config JSON object of the configuration as string
|
||||
* @param config_sig signature of the configuration by the offline master key
|
||||
* @return transaction status code
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
postgres_set_extension_config (void *cls,
|
||||
const char *extension_name,
|
||||
const char *config,
|
||||
const struct TALER_MasterSignatureP *config_sig)
|
||||
{
|
||||
struct PostgresClosure *pg = cls;
|
||||
struct GNUNET_PQ_QueryParam params[] = {
|
||||
GNUNET_PQ_query_param_string (extension_name),
|
||||
GNUNET_PQ_query_param_string (config),
|
||||
GNUNET_PQ_query_param_auto_from_type (config_sig),
|
||||
GNUNET_PQ_query_param_end
|
||||
};
|
||||
|
||||
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
|
||||
"set_extension_config",
|
||||
params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function called to get the configuration of an extension
|
||||
* (age-restriction, peer2peer, ...)
|
||||
*
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param extension_name the name of the extension
|
||||
* @param[out] config JSON object of the configuration as string
|
||||
* @return transaction status code
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
postgres_get_extension_config (void *cls,
|
||||
const char *extension_name,
|
||||
char **config)
|
||||
{
|
||||
struct PostgresClosure *pg = cls;
|
||||
struct GNUNET_PQ_QueryParam params[] = {
|
||||
GNUNET_PQ_query_param_string (extension_name),
|
||||
GNUNET_PQ_query_param_end
|
||||
};
|
||||
struct GNUNET_PQ_ResultSpec rs[] = {
|
||||
GNUNET_PQ_result_spec_string ("config", config),
|
||||
GNUNET_PQ_result_spec_end
|
||||
};
|
||||
|
||||
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
|
||||
"get_extension_config",
|
||||
params,
|
||||
rs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize Postgres database subsystem.
|
||||
*
|
||||
@ -11628,6 +11709,10 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
|
||||
= &postgres_release_revolving_shard;
|
||||
plugin->delete_shard_locks
|
||||
= &postgres_delete_shard_locks;
|
||||
plugin->set_extension_config
|
||||
= &postgres_set_extension_config;
|
||||
plugin->get_extension_config
|
||||
= &postgres_get_extension_config;
|
||||
return plugin;
|
||||
}
|
||||
|
||||
|
@ -542,6 +542,19 @@ struct TALER_PickupIdentifierP
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Salted hash over the JSON object representing the configuration of an
|
||||
* extension.
|
||||
*/
|
||||
struct TALER_ExtensionConfigHash
|
||||
{
|
||||
/**
|
||||
* Actual hash value.
|
||||
*/
|
||||
struct GNUNET_HashCode hash;
|
||||
};
|
||||
|
||||
|
||||
GNUNET_NETWORK_STRUCT_END
|
||||
|
||||
|
||||
@ -2521,30 +2534,31 @@ TALER_merchant_wire_signature_make (
|
||||
/* **************** /management/extensions offline signing **************** */
|
||||
|
||||
/**
|
||||
* Create a signature for age restriction groups
|
||||
* Create a signature for the hash of the configuration of an extension
|
||||
*
|
||||
* @param mask The bitmask representing age groups
|
||||
* @param h_config hash of the JSON object representing the configuration
|
||||
* @param master_priv private key to sign with
|
||||
* @param[out] master_sig where to write the signature
|
||||
*/
|
||||
void
|
||||
TALER_exchange_offline_extension_agemask_sign (
|
||||
const struct TALER_AgeMask mask,
|
||||
TALER_exchange_offline_extension_config_hash_sign (
|
||||
const struct TALER_ExtensionConfigHash h_config,
|
||||
const struct TALER_MasterPrivateKeyP *master_priv,
|
||||
struct TALER_MasterSignatureP *master_sig);
|
||||
|
||||
|
||||
/**
|
||||
* Verify the signature in @a master_sig.
|
||||
* Verify the signature in @a master_sig of the given hash, taken over the JSON
|
||||
* blob representing the configuration of an extension
|
||||
*
|
||||
* @param mask bit mask representing an age group for age restriction
|
||||
* @param h_config hash of the JSON blob of a configuration of an extension
|
||||
* @param master_pub master public key of the exchange
|
||||
* @param master_sig signature of the exchange
|
||||
* @return #GNUNET_OK if signature is valid
|
||||
*/
|
||||
enum GNUNET_GenericReturnValue
|
||||
TALER_exchange_offline_extension_agemask_verify (
|
||||
const struct TALER_AgeMask mask,
|
||||
TALER_exchange_offline_extension_config_hash_verify (
|
||||
const struct TALER_ExtensionConfigHash h_config,
|
||||
const struct TALER_MasterPublicKeyP *master_pub,
|
||||
const struct TALER_MasterSignatureP *master_sig
|
||||
);
|
||||
|
@ -4025,8 +4025,35 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
(*delete_shard_locks)(void *cls);
|
||||
|
||||
/**
|
||||
* TODO-oec: add function for adding extension config
|
||||
* Function called to save the configuration of an extension
|
||||
* (age-restriction, peer2peer, ...)
|
||||
*
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param extension_name the name of the extension
|
||||
* @param config JSON object of the configuration as string
|
||||
* @param config_sig signature of the configuration by the offline master key
|
||||
* @return transaction status code
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*set_extension_config)(void *cls,
|
||||
const char *extension_name,
|
||||
const char *config,
|
||||
const struct TALER_MasterSignatureP *config_sig);
|
||||
|
||||
/**
|
||||
* Function called to retrieve the configuration of an extension
|
||||
* (age-restriction, peer2peer, ...)
|
||||
*
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param extension_name the name of the extension
|
||||
* @param[out] config JSON object of the configuration as string
|
||||
* @param[out] config_sig signature of the configuration by the master key
|
||||
* @return transaction status code
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*get_extension_config)(void *cls,
|
||||
const char *extension_name,
|
||||
char **config);
|
||||
|
||||
};
|
||||
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
#include <gnunet/gnunet_util_lib.h>
|
||||
#include "taler_crypto_lib.h"
|
||||
#include "taler_json_lib.h"
|
||||
|
||||
|
||||
#define TALER_EXTENSION_SECTION_PREFIX "exchange-extension-"
|
||||
@ -39,22 +40,42 @@ enum TALER_Extension_Type
|
||||
{
|
||||
TALER_Extension_AgeRestriction = 0,
|
||||
TALER_Extension_Peer2Peer = 1,
|
||||
TALER_Extension_Max = 2
|
||||
TALER_Extension_Max = 2 // Must be last
|
||||
};
|
||||
|
||||
/*
|
||||
* TODO oec: documentation
|
||||
*/
|
||||
struct TALER_Extension
|
||||
{
|
||||
enum TALER_Extension_Type type;
|
||||
char *name;
|
||||
bool critical;
|
||||
void *config;
|
||||
|
||||
enum GNUNET_GenericReturnValue (*test_config)(const json_t *config);
|
||||
enum GNUNET_GenericReturnValue (*parse_and_set_config)(struct
|
||||
TALER_Extension *this,
|
||||
const json_t *config);
|
||||
json_t *(*config_to_json)(const struct TALER_Extension *this);
|
||||
};
|
||||
|
||||
/*
|
||||
* TALER Peer2Peer Extension
|
||||
* FIXME oec
|
||||
/**
|
||||
* Generic functions for extensions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Finds and returns a supported extension by a given name.
|
||||
*
|
||||
* @param name name of the extension to lookup
|
||||
* @param extensions list of TALER_Extensions as haystack, terminated by an entry of type TALER_Extension_Max
|
||||
* @param[out] ext set to the extension, if found, NULL otherwise
|
||||
* @return GNUNET_OK if extension was found, GNUNET_NO otherwise
|
||||
*/
|
||||
enum GNUNET_GenericReturnValue
|
||||
TALER_extension_get_by_name (const char *name,
|
||||
const struct TALER_Extension **extensions,
|
||||
const struct TALER_Extension **ext);
|
||||
|
||||
/*
|
||||
* TALER Age Restriction Extension
|
||||
@ -72,7 +93,19 @@ struct TALER_Extension
|
||||
<< 21)
|
||||
|
||||
/**
|
||||
* @param groups String representation of age groups, like: "8:10:12:14:16:18:21"
|
||||
* @brief Parses a string as a list of age groups.
|
||||
*
|
||||
* The string must consist of a colon-separated list of increasing integers
|
||||
* between 0 and 31. Each entry represents the beginning of a new age group.
|
||||
* F.e. the string "8:10:12:14:16:18:21" parses into the following list of age
|
||||
* groups
|
||||
* 0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-20, 21-...
|
||||
* which then is represented as bit mask with the corresponding bits set:
|
||||
* 31 24 16 8 0
|
||||
* | | | | |
|
||||
* oooooooo oo1oo1o1 o1o1o1o1 ooooooo1
|
||||
*
|
||||
* @param groups String representation of age groups
|
||||
* @param[out] mask Mask representation for age restriction.
|
||||
* @return Error, if age groups were invalid, OK otherwise.
|
||||
*/
|
||||
@ -81,6 +114,19 @@ TALER_parse_age_group_string (char *groups,
|
||||
struct TALER_AgeMask *mask);
|
||||
|
||||
/**
|
||||
* Encodes the age mask into a string, like "8:10:12:14:16:18:21"
|
||||
*
|
||||
* @param mask Age mask
|
||||
* @return String representation of the age mask, allocated by GNUNET_malloc.
|
||||
* Can be used as value in the TALER config.
|
||||
*/
|
||||
char *
|
||||
TALER_age_mask_to_string (const struct TALER_AgeMask *mask);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Reads the age groups from the configuration and sets the
|
||||
* corresponding age mask.
|
||||
*
|
||||
* @param cfg
|
||||
* @param[out] mask for age restriction, will be set to 0 if age restriction is disabled.
|
||||
@ -90,4 +136,11 @@ TALER_parse_age_group_string (char *groups,
|
||||
enum TALER_Extension_ReturnValue
|
||||
TALER_get_age_mask (const struct GNUNET_CONFIGURATION_Handle *cfg,
|
||||
struct TALER_AgeMask *mask);
|
||||
|
||||
|
||||
/*
|
||||
* TALER Peer2Peer Extension
|
||||
* TODO oec
|
||||
*/
|
||||
|
||||
#endif
|
||||
|
@ -532,7 +532,7 @@ TALER_JSON_wire_to_payto (const json_t *wire_s);
|
||||
|
||||
|
||||
/**
|
||||
* Hash @a extensions.
|
||||
* Hash @a extensions in deposits.
|
||||
*
|
||||
* @param extensions contract extensions to hash
|
||||
* @param[out] ech where to write the extension hash
|
||||
@ -541,6 +541,16 @@ void
|
||||
TALER_deposit_extension_hash (const json_t *extensions,
|
||||
struct TALER_ExtensionContractHash *ech);
|
||||
|
||||
/**
|
||||
* Hash the @a config of an extension, given as JSON
|
||||
*
|
||||
* @param config configuration of the extension
|
||||
* @param[out] eh where to write the extension hash
|
||||
* @return GNUNET_OK on success, GNUNET_SYSERR on failure
|
||||
*/
|
||||
enum GNUNET_GenericReturnValue
|
||||
TALER_extension_config_hash (const json_t *config,
|
||||
struct TALER_ExtensionConfigHash *eh);
|
||||
|
||||
/**
|
||||
* Parses a JSON object { "extension": "age_restriction", "mask": <uint32> }.
|
||||
@ -553,7 +563,6 @@ enum GNUNET_GenericReturnValue
|
||||
TALER_agemask_parse_json (const json_t *root,
|
||||
struct TALER_AgeMask *mask);
|
||||
|
||||
|
||||
#endif /* TALER_JSON_LIB_H_ */
|
||||
|
||||
/* End of taler_json_lib.h */
|
||||
|
@ -967,9 +967,9 @@ struct TALER_MasterDelWirePS
|
||||
|
||||
/*
|
||||
* @brief Signature made by the exchange offline key over the
|
||||
* configuration of the age restriction extension.
|
||||
* configuration of an extension.
|
||||
*/
|
||||
struct TALER_MasterExtensionAgeRestrictionPS
|
||||
struct TALER_MasterExtensionConfigurationPS
|
||||
{
|
||||
/**
|
||||
* Purpose is #TALER_SIGNATURE_MASTER_EXTENSION. Signed
|
||||
@ -978,29 +978,11 @@ struct TALER_MasterExtensionAgeRestrictionPS
|
||||
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
|
||||
|
||||
/**
|
||||
* Bit mask representing the lits of age groups, see TALER_AgeMask for a
|
||||
* description.
|
||||
* Hash of the JSON object that represents the configuration of an extension.
|
||||
*/
|
||||
struct TALER_AgeMask mask;
|
||||
struct TALER_ExtensionConfigHash h_config GNUNET_PACKED;
|
||||
};
|
||||
|
||||
#if 0
|
||||
/*
|
||||
* @brief Signature made by the exchange offline key over the
|
||||
* configuration of the peer2peer extension.
|
||||
*/
|
||||
struct TALER_MasterExtensionPeer2PeerPS
|
||||
{
|
||||
/**
|
||||
* Purpose is #TALER_SIGNATURE_MASTER_EXTENSION. Signed
|
||||
* by a `struct TALER_MasterPublicKeyP` using EdDSA.
|
||||
*/
|
||||
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
|
||||
|
||||
// TODO oec
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Information about a denomination key. Denomination keys
|
||||
* are used to sign coins of a certain value into existence.
|
||||
|
@ -1009,4 +1009,14 @@ TALER_deposit_extension_hash (const json_t *extensions,
|
||||
}
|
||||
|
||||
|
||||
enum GNUNET_GenericReturnValue
|
||||
TALER_extension_config_hash (const json_t *config,
|
||||
struct TALER_ExtensionConfigHash *ech)
|
||||
{
|
||||
return dump_and_hash (config,
|
||||
"taler-extension-configuration",
|
||||
&ech->hash);
|
||||
}
|
||||
|
||||
|
||||
/* End of json/json.c */
|
||||
|
@ -153,32 +153,17 @@ TALER_EXCHANGE_management_post_extensions (
|
||||
GNUNET_assert (NULL != extensions);
|
||||
for (unsigned int i = 0; i<pkd->num_extensions; i++)
|
||||
{
|
||||
json_t *config;
|
||||
const struct TALER_AgeMask *mask;
|
||||
const json_t *config;
|
||||
const struct TALER_Extension *ext = &pkd->extensions[i];
|
||||
|
||||
switch (ext->type)
|
||||
{
|
||||
// TODO: case TALER_Extension_Peer2Peer
|
||||
case TALER_Extension_AgeRestriction:
|
||||
mask = (const struct TALER_AgeMask *) (&ext->config);
|
||||
config = GNUNET_JSON_PACK (
|
||||
GNUNET_JSON_pack_string ("extension",
|
||||
ext->name),
|
||||
GNUNET_JSON_pack_data_auto ("mask",
|
||||
&mask->mask));
|
||||
GNUNET_assert (NULL != config);
|
||||
break;
|
||||
default:
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Extension not supported.\n");
|
||||
}
|
||||
config = ext->config_to_json (ext);
|
||||
|
||||
GNUNET_assert (NULL != config);
|
||||
GNUNET_assert (0 ==
|
||||
json_array_append_new (
|
||||
extensions,
|
||||
GNUNET_JSON_PACK (
|
||||
GNUNET_JSON_pack_data_auto ("name",
|
||||
GNUNET_JSON_pack_data_auto ("extension",
|
||||
&ext->name),
|
||||
GNUNET_JSON_pack_data_auto ("config",
|
||||
config)
|
||||
|
@ -72,6 +72,7 @@ libtalerutil_la_SOURCES = \
|
||||
crypto_wire.c \
|
||||
denom.c \
|
||||
exchange_signatures.c \
|
||||
extensions.c \
|
||||
extension_age_restriction.c \
|
||||
getopt.c \
|
||||
lang.c \
|
||||
|
@ -23,7 +23,6 @@
|
||||
#include "taler_extensions.h"
|
||||
#include "stdint.h"
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param cfg Handle to the GNUNET configuration
|
||||
@ -137,12 +136,14 @@ TALER_parse_age_group_string (char *groups,
|
||||
|
||||
|
||||
/**
|
||||
* Encodes the age mask into a string, like "8:10:12:14:16:18:21"
|
||||
*
|
||||
* @param mask Age mask
|
||||
* @return String representation of the age mask, allocated by GNUNET_malloc.
|
||||
* Can be used as value in the TALER config.
|
||||
*/
|
||||
char *
|
||||
TALER_age_mask_to_string (struct TALER_AgeMask *m)
|
||||
TALER_age_mask_to_string (const struct TALER_AgeMask *m)
|
||||
{
|
||||
uint32_t mask = m->mask;
|
||||
unsigned int n = 0;
|
||||
|
49
src/util/extensions.c
Normal file
49
src/util/extensions.c
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 2014-2021 Taler Systems SA
|
||||
|
||||
TALER 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 3, or (at your option) any later version.
|
||||
|
||||
TALER 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
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
/**
|
||||
* @file extensions.c
|
||||
* @brief Utility functions for extensions
|
||||
* @author Özgür Kesim
|
||||
*/
|
||||
#include "platform.h"
|
||||
#include "taler_util.h"
|
||||
#include "taler_extensions.h"
|
||||
#include "stdint.h"
|
||||
|
||||
enum GNUNET_GenericReturnValue
|
||||
TALER_extension_get_by_name (const char *name,
|
||||
const struct TALER_Extension **extensions,
|
||||
const struct TALER_Extension **ext)
|
||||
{
|
||||
|
||||
const struct TALER_Extension *it = *extensions;
|
||||
|
||||
for (; NULL != it; it++)
|
||||
{
|
||||
if (0 == strncmp (name,
|
||||
it->name,
|
||||
strlen (it->name)))
|
||||
{
|
||||
*ext = it;
|
||||
return GNUNET_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return GNUNET_NO;
|
||||
}
|
||||
|
||||
|
||||
/* end of extensions.c */
|
@ -491,66 +491,40 @@ TALER_exchange_offline_wire_fee_verify (
|
||||
|
||||
|
||||
void
|
||||
TALER_exchange_offline_extension_agemask_sign (
|
||||
const struct TALER_AgeMask mask,
|
||||
TALER_exchange_offline_extension_config_hash_sign (
|
||||
const struct TALER_ExtensionConfigHash h_config,
|
||||
const struct TALER_MasterPrivateKeyP *master_priv,
|
||||
struct TALER_MasterSignatureP *master_sig)
|
||||
{
|
||||
struct TALER_MasterExtensionAgeRestrictionPS ar = {
|
||||
struct TALER_MasterExtensionConfigurationPS ec = {
|
||||
.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION),
|
||||
.purpose.size = htonl (sizeof(ar)),
|
||||
.mask = mask
|
||||
.purpose.size = htonl (sizeof(ec)),
|
||||
.h_config = h_config
|
||||
};
|
||||
GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
|
||||
&ar,
|
||||
&ec,
|
||||
&master_sig->eddsa_signature);
|
||||
}
|
||||
|
||||
|
||||
enum GNUNET_GenericReturnValue
|
||||
TALER_exchange_offline_extension_agemask_verify (
|
||||
const struct TALER_AgeMask mask,
|
||||
TALER_exchange_offline_extension_config_hash_verify (
|
||||
const struct TALER_ExtensionConfigHash h_config,
|
||||
const struct TALER_MasterPublicKeyP *master_pub,
|
||||
const struct TALER_MasterSignatureP *master_sig
|
||||
)
|
||||
{
|
||||
struct TALER_MasterExtensionAgeRestrictionPS ar = {
|
||||
struct TALER_MasterExtensionConfigurationPS ec = {
|
||||
.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION),
|
||||
.purpose.size = htonl (sizeof(ar)),
|
||||
.mask = mask
|
||||
.purpose.size = htonl (sizeof(ec)),
|
||||
.h_config = h_config
|
||||
};
|
||||
return
|
||||
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_EXTENSION,
|
||||
&ar,
|
||||
&master_sig->eddsa_signature,
|
||||
&master_pub->eddsa_pub);
|
||||
|
||||
return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_EXTENSION,
|
||||
&ec,
|
||||
&master_sig->eddsa_signature,
|
||||
&master_pub->eddsa_pub);
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
/* TODO peer2peer */
|
||||
void
|
||||
TALER_exchange_offline_extension_p2p_sign (
|
||||
// TODO
|
||||
const struct TALER_MasterPrivateKeyP *master_priv,
|
||||
struct TALER_MasterSignatureP *master_sig)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
enum GNUNET_GenericReturnValue
|
||||
TALER_exchange_offline_extension_p2p_verify (
|
||||
// TODO
|
||||
const struct TALER_MasterPublicKeyP *master_pub,
|
||||
const struct TALER_MasterSignatureP *master_sig,
|
||||
)
|
||||
{
|
||||
// TODO
|
||||
return GNUNET_FALSE;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
/* end of offline_signatures.c */
|
||||
|
Loading…
Reference in New Issue
Block a user