diff options
| author | Christian Grothoff <christian@grothoff.org> | 2022-12-29 10:10:25 +0100 | 
|---|---|---|
| committer | Christian Grothoff <christian@grothoff.org> | 2022-12-29 10:10:25 +0100 | 
| commit | fa840f7071da56c4794c887b813ca2a6f491f836 (patch) | |
| tree | 7b126d737aa1fdc3df2ea60f76540af6011ea049 /src | |
| parent | 5828eead705965b5ac87cfad78636b1363b16396 (diff) | |
| parent | 915d6ddfaa638a9759766eaf69dfef7e8e17474b (diff) | |
Merge branch 'master' of git+ssh://git.taler.net/exchange
Diffstat (limited to 'src')
24 files changed, 1037 insertions, 434 deletions
| diff --git a/src/auditor/taler-helper-auditor-purses.c b/src/auditor/taler-helper-auditor-purses.c index 0136a9ec..13327ccb 100644 --- a/src/auditor/taler-helper-auditor-purses.c +++ b/src/auditor/taler-helper-auditor-purses.c @@ -308,6 +308,14 @@ struct PurseSummary     */    bool had_pi; +  /** +   * Was the purse deleted?  FIXME: Not yet handled (do we need to? purse +   * might just appear as expired eventually; but in the meantime, exchange +   * may seem to have refunded the coins for no good reason...), also we do +   * not yet check the deletion signature. +   */ +  bool purse_deleted; +  }; @@ -407,7 +415,8 @@ setup_purse (struct PurseContext *pc,                                      &ps->total_value,                                      &ps->exchange_balance,                                      &ps->h_contract_terms, -                                    &ps->merge_timestamp); +                                    &ps->merge_timestamp, +                                    &ps->purse_deleted);    if (0 >= qs)    {      GNUNET_free (ps); diff --git a/src/exchange/exchange.conf b/src/exchange/exchange.conf index d662cdd0..758e77c9 100644 --- a/src/exchange/exchange.conf +++ b/src/exchange/exchange.conf @@ -113,32 +113,3 @@ PRIVACY_DIR = $DATADIR/exchange/pp/  # Etag / filename for the privacy policy.  PRIVACY_ETAG = pp-v0 - -# Set to NONE to disable KYC checks. -# Set to "OAUTH2" to use OAuth 2.0 for KYC authorization. -KYC_MODE = NONE - -# Balance threshold above which wallets are told -# to undergo a KYC check at the exchange. Optional, -# if not given there is no limit. -# KYC_WALLET_BALANCE_LIMIT = CURRENCY:150 -# -# KYC_WITHDRAW_PERIOD = 1 month - -[exchange-kyc-oauth2] - -# URL of the OAuth endpoint for KYC checks -# KYC_OAUTH2_URL = - -# URL of the "information" endpoint for KYC checks -# KYC_INFO_URL = - -# KYC Oauth client ID. -# KYC_OAUTH2_CLIENT_ID = - -# KYC Client secret used to obtain access tokens. -# KYC_OAUTH2_CLIENT_SECRET = - -# Where to redirect clients after successful -# authorization? -# KYC_OAUTH2_POST_URL = https://bank.com/ diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index 4b64dfd5..76b38889 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -116,11 +116,6 @@ struct TALER_AgeRestrictionConfig TEH_age_restriction_config = {0};  static struct MHD_Daemon *mhd;  /** - * Our KYC configuration. - */ -struct TEH_KycOptions TEH_kyc_config; - -/**   * How long is caching /keys allowed at most? (global)   */  struct GNUNET_TIME_Relative TEH_max_keys_caching; @@ -732,12 +727,16 @@ proceed_with_handler (struct TEH_RequestContext *rc,      /* Above logic ensures that 'root' is exactly non-NULL for POST operations,         so we test for 'root' to decide which handler to invoke. */ -    if (NULL != root) +    if (0 == strcasecmp (rh->method, +                         MHD_HTTP_METHOD_POST))        ret = rh->handler.post (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) */ +    else if (0 == strcasecmp (rh->method, +                              MHD_HTTP_METHOD_DELETE)) +      ret = rh->handler.delete (rc, +                                args); +    else /* Only GET left */        ret = rh->handler.get (rc,                               args);    } @@ -975,7 +974,7 @@ handle_post_management (struct TEH_RequestContext *rc,  /** - * Handle a get "/management" request. + * Handle a GET "/management" request.   *   * @param rc request context   * @param args array of additional options (must be [0] == "keys") @@ -1225,7 +1224,7 @@ handle_mhd_request (void *cls,        .url = "purses",        .method = MHD_HTTP_METHOD_POST,        .handler.post = &handle_post_purses, -      .nargs = 2 // ?? +      .nargs = 2      },      /* Getting purse status */      { @@ -1234,6 +1233,13 @@ handle_mhd_request (void *cls,        .handler.get = &TEH_handler_purses_get,        .nargs = 2      }, +    /* Deleting purse */ +    { +      .url = "purses", +      .method = MHD_HTTP_METHOD_DELETE, +      .handler.delete = &TEH_handler_purses_delete, +      .nargs = 1 +    },      /* Getting contracts */      {        .url = "contracts", @@ -1526,185 +1532,6 @@ handle_mhd_request (void *cls,  /** - * Load general KYC configuration parameters for the exchange server into the - * #TEH_kyc_config variable. - * - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_kyc_settings (void) -{ -  if (GNUNET_OK != -      GNUNET_CONFIGURATION_get_value_time (TEH_cfg, -                                           "exchange", -                                           "KYC_WITHDRAW_PERIOD", -                                           &TEH_kyc_config.withdraw_period)) -  { -    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, -                               "exchange", -                               "KYC_WITHDRAW_PERIOD", -                               "valid relative time expected"); -    return GNUNET_SYSERR; -  } -  if (GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period)) -    return GNUNET_OK; -  if (GNUNET_OK != -      TALER_config_get_amount (TEH_cfg, -                               "exchange", -                               "KYC_WITHDRAW_LIMIT", -                               &TEH_kyc_config.withdraw_limit)) -    return GNUNET_SYSERR; -  if (0 != strcasecmp (TEH_kyc_config.withdraw_limit.currency, -                       TEH_currency)) -  { -    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, -                               "exchange", -                               "KYC_WITHDRAW_LIMIT", -                               "currency mismatch"); -    return GNUNET_SYSERR; -  } -  return GNUNET_OK; -} - - -/** - * Load OAuth2.0 configuration parameters for the exchange server into the - * #TEH_kyc_config variable. - * - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_kyc_oauth_cfg (void) -{ -  char *s; - -  if (GNUNET_OK != -      GNUNET_CONFIGURATION_get_value_string (TEH_cfg, -                                             "exchange-kyc-oauth2", -                                             "KYC_OAUTH2_AUTH_URL", -                                             &s)) -  { -    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, -                               "exchange-kyc-oauth2", -                               "KYC_OAUTH2_AUTH_URL"); -    return GNUNET_SYSERR; -  } -  if ( (! TALER_url_valid_charset (s)) || -       ( (0 != strncasecmp (s, -                            "http://", -                            strlen ("http://"))) && -         (0 != strncasecmp (s, -                            "https://", -                            strlen ("https://"))) ) ) -  { -    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, -                               "exchange-kyc-oauth2", -                               "KYC_OAUTH2_AUTH_URL", -                               "not a valid URL"); -    GNUNET_free (s); -    return GNUNET_SYSERR; -  } -  TEH_kyc_config.details.oauth2.auth_url = s; - -  if (GNUNET_OK != -      GNUNET_CONFIGURATION_get_value_string (TEH_cfg, -                                             "exchange-kyc-oauth2", -                                             "KYC_OAUTH2_LOGIN_URL", -                                             &s)) -  { -    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, -                               "exchange-kyc-oauth2", -                               "KYC_OAUTH2_LOGIN_URL"); -    return GNUNET_SYSERR; -  } -  if ( (! TALER_url_valid_charset (s)) || -       ( (0 != strncasecmp (s, -                            "http://", -                            strlen ("http://"))) && -         (0 != strncasecmp (s, -                            "https://", -                            strlen ("https://"))) ) ) -  { -    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, -                               "exchange-kyc-oauth2", -                               "KYC_OAUTH2_LOGIN_URL", -                               "not a valid URL"); -    GNUNET_free (s); -    return GNUNET_SYSERR; -  } -  TEH_kyc_config.details.oauth2.login_url = s; - -  if (GNUNET_OK != -      GNUNET_CONFIGURATION_get_value_string (TEH_cfg, -                                             "exchange-kyc-oauth2", -                                             "KYC_INFO_URL", -                                             &s)) -  { -    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, -                               "exchange-kyc-oauth2", -                               "KYC_INFO_URL"); -    return GNUNET_SYSERR; -  } -  if ( (! TALER_url_valid_charset (s)) || -       ( (0 != strncasecmp (s, -                            "http://", -                            strlen ("http://"))) && -         (0 != strncasecmp (s, -                            "https://", -                            strlen ("https://"))) ) ) -  { -    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, -                               "exchange-kyc-oauth2", -                               "KYC_INFO_URL", -                               "not a valid URL"); -    GNUNET_free (s); -    return GNUNET_SYSERR; -  } -  TEH_kyc_config.details.oauth2.info_url = s; - -  if (GNUNET_OK != -      GNUNET_CONFIGURATION_get_value_string (TEH_cfg, -                                             "exchange-kyc-oauth2", -                                             "KYC_OAUTH2_CLIENT_ID", -                                             &s)) -  { -    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, -                               "exchange-kyc-oauth2", -                               "KYC_OAUTH2_CLIENT_ID"); -    return GNUNET_SYSERR; -  } -  TEH_kyc_config.details.oauth2.client_id = s; - -  if (GNUNET_OK != -      GNUNET_CONFIGURATION_get_value_string (TEH_cfg, -                                             "exchange-kyc-oauth2", -                                             "KYC_OAUTH2_CLIENT_SECRET", -                                             &s)) -  { -    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, -                               "exchange-kyc-oauth2", -                               "KYC_OAUTH2_CLIENT_SECRET"); -    return GNUNET_SYSERR; -  } -  TEH_kyc_config.details.oauth2.client_secret = s; - -  if (GNUNET_OK != -      GNUNET_CONFIGURATION_get_value_string (TEH_cfg, -                                             "exchange-kyc-oauth2", -                                             "KYC_OAUTH2_POST_URL", -                                             &s)) -  { -    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, -                               "exchange-kyc-oauth2", -                               "KYC_OAUTH2_POST_URL"); -    return GNUNET_SYSERR; -  } -  TEH_kyc_config.details.oauth2.post_kyc_redirect_url = s; -  return GNUNET_OK; -} - - -/**   * Load configuration parameters for the exchange   * server into the corresponding global variables.   * @@ -1718,47 +1545,6 @@ exchange_serve_process_config (void)    {      return GNUNET_SYSERR;    } -  { -    char *kyc_mode; - -    if (GNUNET_OK != -        GNUNET_CONFIGURATION_get_value_string (TEH_cfg, -                                               "exchange", -                                               "KYC_MODE", -                                               &kyc_mode)) -    { -      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, -                                 "exchange", -                                 "KYC_MODE"); -      return GNUNET_SYSERR; -    } -    if (0 == strcasecmp (kyc_mode, -                         "NONE")) -    { -      TEH_kyc_config.mode = TEH_KYC_NONE; -    } -    else if (0 == strcasecmp (kyc_mode, -                              "OAUTH2")) -    { -      TEH_kyc_config.mode = TEH_KYC_OAUTH2; -      if (GNUNET_OK != -          parse_kyc_oauth_cfg ()) -      { -        GNUNET_free (kyc_mode); -        return GNUNET_SYSERR; -      } -    } -    else -    { -      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, -                                 "exchange", -                                 "KYC_MODE", -                                 "Must be 'NONE' or 'OAUTH2'"); -      GNUNET_free (kyc_mode); -      return GNUNET_SYSERR; -    } -    GNUNET_free (kyc_mode); -  }    if (GNUNET_OK !=        GNUNET_CONFIGURATION_get_value_number (TEH_cfg,                                               "exchange", @@ -1823,35 +1609,6 @@ exchange_serve_process_config (void)      return GNUNET_SYSERR;    } -  if (TEH_KYC_NONE != TEH_kyc_config.mode) -  { -    if (GNUNET_YES == -        GNUNET_CONFIGURATION_have_value (TEH_cfg, -                                         "exchange", -                                         "KYC_WALLET_BALANCE_LIMIT")) -    { -      if ( (GNUNET_OK != -            TALER_config_get_amount (TEH_cfg, -                                     "exchange", -                                     "KYC_WALLET_BALANCE_LIMIT", -                                     &TEH_kyc_config.wallet_balance_limit)) || -           (0 != strcasecmp (TEH_currency, -                             TEH_kyc_config.wallet_balance_limit.currency)) ) -      { -        GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, -                                   "exchange", -                                   "KYC_WALLET_BALANCE_LIMIT", -                                   "valid amount expected"); -        return GNUNET_SYSERR; -      } -    } -    else -    { -      memset (&TEH_kyc_config.wallet_balance_limit, -              0, -              sizeof (TEH_kyc_config.wallet_balance_limit)); -    } -  }    {      char *master_public_key_str; @@ -1882,12 +1639,6 @@ exchange_serve_process_config (void)      }      GNUNET_free (master_public_key_str);    } -  if (TEH_KYC_NONE != TEH_kyc_config.mode) -  { -    if (GNUNET_OK != -        parse_kyc_settings ()) -      return GNUNET_SYSERR; -  }    GNUNET_log (GNUNET_ERROR_TYPE_INFO,                "Launching exchange with public key `%s'...\n",                GNUNET_p2s (&TEH_master_public_key.eddsa_pub)); diff --git a/src/exchange/taler-exchange-httpd.h b/src/exchange/taler-exchange-httpd.h index 67b8e75d..2be26f14 100644 --- a/src/exchange/taler-exchange-httpd.h +++ b/src/exchange/taler-exchange-httpd.h @@ -31,111 +31,6 @@  #include <gnunet/gnunet_mhd_compat.h> -/* ************* NOTE: OLD KYC logic,*********** -   new logic is in taler-exchange-httpd_kyc.h! -   ********************************************* */ - -/** - * Enumeration for our KYC modes. - */ -enum TEH_KycMode -{ -  /** -   * KYC is disabled. -   */ -  TEH_KYC_NONE = 0, - -  /** -   * We use Oauth2.0. -   */ -  TEH_KYC_OAUTH2 = 1 -}; - - -/** - * Structure describing our KYC configuration. - */ -struct TEH_KycOptions -{ -  /** -   * What KYC mode are we in? -   */ -  enum TEH_KycMode mode; - -  /** -   * Maximum amount that can be withdrawn in @e withdraw_period without -   * needing KYC. -   * Only valid if @e mode is not #TEH_KYC_NONE and -   * if @e withdraw_period is non-zero. -   */ -  struct TALER_Amount withdraw_limit; - -  /** -   * Maximum balance a wallet can hold without -   * needing KYC. -   * Only valid if @e mode is not #TEH_KYC_NONE and -   * if the amount specified is valid. -   */ -  struct TALER_Amount wallet_balance_limit; - -  /** -   * Time period over which @e withdraw_limit applies. -   * Only valid if @e mode is not #TEH_KYC_NONE. -   */ -  struct GNUNET_TIME_Relative withdraw_period; - -  /** -   * Details depending on @e mode. -   */ -  union -  { - -    /** -     * Configuration details if @e mode is #TEH_KYC_OAUTH2. -     */ -    struct -    { - -      /** -       * URL of the OAuth2.0 endpoint for KYC checks. -       * (token/auth) -       */ -      char *auth_url; - -      /** -       * URL of the OAuth2.0 endpoint for KYC checks. -       */ -      char *login_url; - -      /** -       * URL of the user info access endpoint. -       */ -      char *info_url; - -      /** -       * Our client ID for OAuth2.0. -       */ -      char *client_id; - -      /** -       * Our client secret for OAuth2.0. -       */ -      char *client_secret; - -      /** -       * Where to redirect clients after the -       * Web-based KYC process is done? -       */ -      char *post_kyc_redirect_url; - -    } oauth2; - -  } details; -}; - - -extern struct TEH_KycOptions TEH_kyc_config; -  /**   * How long is caching /keys allowed at most?   */ @@ -301,11 +196,10 @@ struct TEH_RequestHandler    union    {      /** -     * Function to call to handle a GET requests (and those +     * Function to call to handle GET requests (and those       * with @e method NULL).       *       * @param rc context for the request -     * @param mime_type the @e mime_type for the reply (hint, can be NULL)       * @param args array of arguments, needs to be of length @e args_expected       * @return MHD result code       */ @@ -315,7 +209,7 @@ struct TEH_RequestHandler      /** -     * Function to call to handle a POST request. +     * Function to call to handle POST requests.       *       * @param rc context for the request       * @param json uploaded JSON data @@ -327,6 +221,17 @@ struct TEH_RequestHandler              const json_t *root,              const char *const args[]); +    /** +     * Function to call to handle DELETE requests. +     * +     * @param rc context for the request +     * @param args array of arguments, needs to be of length @e args_expected +     * @return MHD result code +     */ +    MHD_RESULT +      (*delete)(struct TEH_RequestContext *rc, +                const char *const args[]); +    } handler;    /** diff --git a/src/exchange/taler-exchange-httpd_purses_delete.c b/src/exchange/taler-exchange-httpd_purses_delete.c index 34ab11b5..58cc7825 100644 --- a/src/exchange/taler-exchange-httpd_purses_delete.c +++ b/src/exchange/taler-exchange-httpd_purses_delete.c @@ -24,6 +24,7 @@  #include <gnunet/gnunet_json_lib.h>  #include <jansson.h>  #include <microhttpd.h> +#include "taler_dbevents.h"  #include "taler_json_lib.h"  #include "taler_mhd_lib.h"  #include "taler-exchange-httpd_common_deposit.h" @@ -35,13 +36,27 @@  MHD_RESULT  TEH_handler_purses_delete ( -  struct MHD_Connection *connection, -  const struct TALER_PurseContractPublicKeyP *purse_pub) +  struct TEH_RequestContext *rc, +  const char *const args[1])  { +  struct MHD_Connection *connection = rc->connection; +  struct TALER_PurseContractPublicKeyP purse_pub;    struct TALER_PurseContractSignatureP purse_sig;    bool found;    bool decided; +  if (GNUNET_OK != +      GNUNET_STRINGS_string_to_data (args[0], +                                     strlen (args[0]), +                                     &purse_pub, +                                     sizeof (purse_pub))) +  { +    GNUNET_break_op (0); +    return TALER_MHD_reply_with_error (connection, +                                       MHD_HTTP_BAD_REQUEST, +                                       TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED, +                                       args[0]); +  }    {      const char *sig; @@ -66,7 +81,7 @@ TEH_handler_purses_delete (    }    if (GNUNET_OK != -      TALER_wallet_purse_delete_verify (purse_pub, +      TALER_wallet_purse_delete_verify (&purse_pub,                                          &purse_sig))    {      TALER_LOG_WARNING ("Invalid signature on /purses/$PID/delete request\n"); @@ -89,7 +104,7 @@ TEH_handler_purses_delete (      enum GNUNET_DB_QueryStatus qs;      qs = TEH_plugin->do_purse_delete (TEH_plugin->cls, -                                      purse_pub, +                                      &purse_pub,                                        &purse_sig,                                        &decided,                                        &found); @@ -117,6 +132,23 @@ TEH_handler_purses_delete (        TALER_EC_EXCHANGE_PURSE_DELETE_ALREADY_DECIDED,        NULL);    } +  { +    /* Possible minor optimization: integrate notification with +       transaction above... */ +    struct TALER_PurseEventP rep = { +      .header.size = htons (sizeof (rep)), +      .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED), +      .purse_pub = purse_pub +    }; + +    GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                "Notifying about purse deletion %s\n", +                TALER_B2S (&purse_pub)); +    TEH_plugin->event_notify (TEH_plugin->cls, +                              &rep.header, +                              NULL, +                              0); +  }    /* success */    return TALER_MHD_reply_static (connection,                                   MHD_HTTP_NO_CONTENT, diff --git a/src/exchange/taler-exchange-httpd_purses_delete.h b/src/exchange/taler-exchange-httpd_purses_delete.h index 15da2163..912dd43a 100644 --- a/src/exchange/taler-exchange-httpd_purses_delete.h +++ b/src/exchange/taler-exchange-httpd_purses_delete.h @@ -29,14 +29,14 @@  /**   * Handle a DELETE "/purses/$PURSE_PUB" request.   * - * @param connection the MHD connection to handle - * @param purse_pub public key of the purse + * @param rc request details about the request to handle + * @param args argument with the public key of the purse   * @return MHD result code   */  MHD_RESULT  TEH_handler_purses_delete ( -  struct MHD_Connection *connection, -  const struct TALER_PurseContractPublicKeyP *purse_pub); +  struct TEH_RequestContext *rc, +  const char *const args[1]);  #endif diff --git a/src/exchange/taler-exchange-httpd_purses_deposit.c b/src/exchange/taler-exchange-httpd_purses_deposit.c index 4bebebf6..291807aa 100644 --- a/src/exchange/taler-exchange-httpd_purses_deposit.c +++ b/src/exchange/taler-exchange-httpd_purses_deposit.c @@ -374,6 +374,7 @@ TEH_handler_purses_deposit (      enum GNUNET_DB_QueryStatus qs;      struct GNUNET_TIME_Timestamp create_timestamp;      struct GNUNET_TIME_Timestamp merge_timestamp; +    bool was_deleted;      qs = TEH_plugin->select_purse (        TEH_plugin->cls, @@ -383,7 +384,8 @@ TEH_handler_purses_deposit (        &pcc.amount,        &pcc.deposit_total,        &pcc.h_contract_terms, -      &merge_timestamp); +      &merge_timestamp, +      &was_deleted);      switch (qs)      {      case GNUNET_DB_STATUS_HARD_ERROR: @@ -406,12 +408,16 @@ TEH_handler_purses_deposit (      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:        break; /* handled below */      } -    if (GNUNET_TIME_absolute_is_past (pcc.purse_expiration.abs_time)) +    if (GNUNET_TIME_absolute_is_past (pcc.purse_expiration.abs_time) || +        was_deleted)      { -      return TALER_MHD_reply_with_error (connection, -                                         MHD_HTTP_GONE, -                                         TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED, -                                         NULL); +      return TALER_MHD_reply_with_error ( +        connection, +        MHD_HTTP_GONE, +        was_deleted +        ? TALER_EC_EXCHANGE_GENERIC_PURSE_DELETED +        : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED, +        GNUNET_TIME_timestamp2s (pcc.purse_expiration));      }    } diff --git a/src/exchange/taler-exchange-httpd_purses_get.c b/src/exchange/taler-exchange-httpd_purses_get.c index 8384086b..e36b9a10 100644 --- a/src/exchange/taler-exchange-httpd_purses_get.c +++ b/src/exchange/taler-exchange-httpd_purses_get.c @@ -207,6 +207,7 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc,                          const char *const args[2])  {    struct GetContext *gc = rc->rh_ctx; +  bool purse_deleted;    MHD_RESULT res;    if (NULL == gc) @@ -312,7 +313,8 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc,                                     &gc->amount,                                     &gc->deposited,                                     &gc->h_contract, -                                   &gc->merge_timestamp); +                                   &gc->merge_timestamp, +                                   &purse_deleted);      switch (qs)      {      case GNUNET_DB_STATUS_HARD_ERROR: @@ -367,11 +369,14 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc,        gc->eh = eh2;      }    } -  if (GNUNET_TIME_absolute_is_past (gc->purse_expiration.abs_time)) +  if (GNUNET_TIME_absolute_is_past (gc->purse_expiration.abs_time) || +      purse_deleted)    {      return TALER_MHD_reply_with_error (rc->connection,                                         MHD_HTTP_GONE, -                                       TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED, +                                       purse_deleted +                                       ? TALER_EC_EXCHANGE_GENERIC_PURSE_DELETED +                                       : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,                                         GNUNET_TIME_timestamp2s (                                           gc->purse_expiration));    } diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am index 4adb4180..c11828d0 100644 --- a/src/exchangedb/Makefile.am +++ b/src/exchangedb/Makefile.am @@ -284,13 +284,15 @@ check_PROGRAMS = \    bench-db-postgres\    perf-exchangedb-reserves-in-insert-postgres\    test-exchangedb-by-j-postgres\ -  test-exchangedb-batch-reserves-in-insert-postgres +  test-exchangedb-batch-reserves-in-insert-postgres\ +  test-exchangedb-populate-table-postgres  AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;  TESTS = \    test-exchangedb-postgres\    test-exchangedb-by-j-postgres\    perf-exchangedb-reserves-in-insert-postgres\ -  test-exchangedb-batch-reserves-in-insert-postgres +  test-exchangedb-batch-reserves-in-insert-postgres\ +  test-exchangedb-populate-table-postgres  test_exchangedb_postgres_SOURCES = \ @@ -364,6 +366,27 @@ bench_db_postgres_LDADD = \    -lgnunetutil \    $(XLIB) +test_exchangedb_populate_table_postgres_SOURCES = \ +  test_exchangedb_populate_table.c +test_exchangedb_populate_table_postgres_LDADD = \ +  libtalerexchangedb.la \ +  $(top_builddir)/src/json/libtalerjson.la \ +  $(top_builddir)/src/util/libtalerutil.la \ +  $(top_builddir)/src/pq/libtalerpq.la \ +  -ljansson \ +  -lgnunetjson \ +  -lgnunetutil \ +  $(XLIB) + +bench_db_postgres_SOURCES = \ +  bench_db.c +bench_db_postgres_LDADD = \ +  libtalerexchangedb.la \ +  $(top_builddir)/src/util/libtalerutil.la \ +  $(top_builddir)/src/pq/libtalerpq.la \ +  -lgnunetpq \ +  -lgnunetutil \ +  $(XLIB)  EXTRA_test_exchangedb_postgres_DEPENDENCIES = \    libtaler_plugin_exchangedb_postgres.la diff --git a/src/exchangedb/exchange_do_delete_purse.sql b/src/exchangedb/exchange_do_purse_delete.sql index a57f2545..096475b4 100644 --- a/src/exchangedb/exchange_do_delete_purse.sql +++ b/src/exchangedb/exchange_do_purse_delete.sql @@ -14,7 +14,7 @@  -- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>  -- -CREATE OR REPLACE FUNCTION exchange_do_delete_purse( +CREATE OR REPLACE FUNCTION exchange_do_purse_delete(    IN in_purse_pub BYTEA,    IN in_purse_sig BYTEA,    IN in_now INT8, @@ -28,7 +28,7 @@ DECLARE    my_in_reserve_quota BOOLEAN;  BEGIN -SELECT COUNT(*) FROM purse_decision +PERFORM refunded FROM purse_decision    WHERE purse_pub=in_purse_pub;  IF FOUND  THEN @@ -49,7 +49,7 @@ THEN  END IF;  -- store reserve deletion -INSERT INTO purse_deletion +INSERT INTO exchange.purse_deletion    (purse_pub    ,purse_sig)  VALUES @@ -115,5 +115,5 @@ END LOOP;  END $$; -COMMENT ON FUNCTION exchange_do_delete_purse(BYTEA,BYTEA,INT8) +COMMENT ON FUNCTION exchange_do_purse_delete(BYTEA,BYTEA,INT8)    IS 'Delete a previously undecided purse and refund the coins (if any).'; diff --git a/src/exchangedb/pg_select_purse.c b/src/exchangedb/pg_select_purse.c index e2a36c33..9143e872 100644 --- a/src/exchangedb/pg_select_purse.c +++ b/src/exchangedb/pg_select_purse.c @@ -35,7 +35,8 @@ TEH_PG_select_purse (    struct TALER_Amount *amount,    struct TALER_Amount *deposited,    struct TALER_PrivateContractHashP *h_contract_terms, -  struct GNUNET_TIME_Timestamp *merge_timestamp) +  struct GNUNET_TIME_Timestamp *merge_timestamp, +  bool *purse_deleted)  {    struct PostgresClosure *pg = cls;    struct GNUNET_PQ_QueryParam params[] = { @@ -57,6 +58,8 @@ TEH_PG_select_purse (        GNUNET_PQ_result_spec_timestamp ("merge_timestamp",                                         merge_timestamp),        NULL), +    GNUNET_PQ_result_spec_bool ("purse_deleted", +                                purse_deleted),      GNUNET_PQ_result_spec_end    }; @@ -72,8 +75,10 @@ TEH_PG_select_purse (             ",balance_val"             ",balance_frac"             ",merge_timestamp" +           ",purse_sig IS NOT NULL AS purse_deleted"             " FROM purse_requests"             " LEFT JOIN purse_merges USING (purse_pub)" +           " LEFT JOIN purse_deletion USING (purse_pub)"             " WHERE purse_pub=$1;");    *merge_timestamp = GNUNET_TIME_UNIT_FOREVER_TS;    return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, diff --git a/src/exchangedb/pg_select_purse.h b/src/exchangedb/pg_select_purse.h index f522256d..db63f0c9 100644 --- a/src/exchangedb/pg_select_purse.h +++ b/src/exchangedb/pg_select_purse.h @@ -37,6 +37,7 @@   * @param[out] deposited set to actual amount put into the purse so far   * @param[out] h_contract_terms set to hash of the contract for the purse   * @param[out] merge_timestamp set to time when the purse was merged, or NEVER if not + * @param[out] purse_deleted set to true if purse was deleted   * @return transaction status code   */  enum GNUNET_DB_QueryStatus @@ -48,7 +49,8 @@ TEH_PG_select_purse (    struct TALER_Amount *amount,    struct TALER_Amount *deposited,    struct TALER_PrivateContractHashP *h_contract_terms, -  struct GNUNET_TIME_Timestamp *merge_timestamp); +  struct GNUNET_TIME_Timestamp *merge_timestamp, +  bool *purse_deleted);  #endif diff --git a/src/exchangedb/procedures.sql.in b/src/exchangedb/procedures.sql.in index af47bbf6..19483024 100644 --- a/src/exchangedb/procedures.sql.in +++ b/src/exchangedb/procedures.sql.in @@ -28,11 +28,11 @@ SET search_path TO exchange;  #include "exchange_do_recoup_to_reserve.sql"  #include "exchange_do_recoup_to_coin.sql"  #include "exchange_do_gc.sql" +#include "exchange_do_purse_delete.sql"  #include "exchange_do_purse_deposit.sql"  #include "exchange_do_purse_merge.sql"  #include "exchange_do_reserve_purse.sql"  #include "exchange_do_expire_purse.sql" -#include "exchange_do_delete_purse.sql"  #include "exchange_do_history_request.sql"  #include "exchange_do_reserve_open_deposit.sql"  #include "exchange_do_reserve_open.sql" diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c index eb258f00..68f94b6e 100644 --- a/src/exchangedb/test_exchangedb.c +++ b/src/exchangedb/test_exchangedb.c @@ -1351,6 +1351,7 @@ run (void *cls)      RND_BLK (&age_hash);      for (size_t i = 0; i < sizeof(p_ah) / sizeof(p_ah[0]); i++)      { +        RND_BLK (&coin_pub);        GNUNET_assert (GNUNET_OK ==                       TALER_denom_blind (&dkp->pub, diff --git a/src/exchangedb/test_exchangedb_populate_table.c b/src/exchangedb/test_exchangedb_populate_table.c new file mode 100644 index 00000000..ed30894b --- /dev/null +++ b/src/exchangedb/test_exchangedb_populate_table.c @@ -0,0 +1,431 @@ +/* +  This file is part of TALER +  Copyright (C) 2014-2022 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 exchangedb/test_exchangedb_populate_table.c + * @brief test cases for DB interaction functions + * @author Joseph Xu + */ +#include "platform.h" +#include "taler_exchangedb_lib.h" +#include "taler_json_lib.h" +#include "taler_exchangedb_plugin.h" + +/**o + * Global result from the testcase. + */ +static int result; + +/** + * Report line of error if @a cond is true, and jump to label "drop". + */ +#define FAILIF(cond)                            \ +  do {                                          \ +      if (! (cond)) {break;}                    \ +    GNUNET_break (0);                           \ +    goto drop;                                  \ +  } while (0) + + +/** + * Initializes @a ptr with random data. + */ +#define RND_BLK(ptr)                                                    \ +  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr)) + +/** + * Initializes @a ptr with zeros. + */ +#define ZR_BLK(ptr) \ +  memset (ptr, 0, sizeof (*ptr)) + + +/** + * Currency we use.  Must match test-exchange-db-*.conf. + */ +#define CURRENCY "EUR" + + +/** + * Number of newly minted coins to use in the test. + */ +#define MELT_NEW_COINS 5 + + +/** + * How big do we make the RSA keys? + */ +#define RSA_KEY_SIZE 1024 + + +/** + * Database plugin under test. + */ +static struct TALER_EXCHANGEDB_Plugin *plugin; +static struct TALER_DenomFeeSet fees; + + +struct DenomKeyPair +{ +  struct TALER_DenominationPrivateKey priv; +  struct TALER_DenominationPublicKey pub; +}; + + +/** + * Destroy a denomination key pair.  The key is not necessarily removed from the DB. + * + * @param dkp the key pair to destroy + */ +static void +destroy_denom_key_pair (struct DenomKeyPair *dkp) +{ +  TALER_denom_pub_free (&dkp->pub); +  TALER_denom_priv_free (&dkp->priv); +  GNUNET_free (dkp); +} + + +/** + * Create a denomination key pair by registering the denomination in the DB. + * + * @param size the size of the denomination key + * @param now time to use for key generation, legal expiration will be 3h later. + * @param fees fees to use + * @return the denominaiton key pair; NULL upon error + */ +static struct DenomKeyPair * +create_denom_key_pair (unsigned int size, +                       struct GNUNET_TIME_Timestamp now, +                       const struct TALER_Amount *value, +                       const struct TALER_DenomFeeSet *fees) +{ +  struct DenomKeyPair *dkp; +  struct TALER_EXCHANGEDB_DenominationKey dki; +  struct TALER_EXCHANGEDB_DenominationKeyInformation issue2; + +  dkp = GNUNET_new (struct DenomKeyPair); +  GNUNET_assert (GNUNET_OK == +                 TALER_denom_priv_create (&dkp->priv, +                                          &dkp->pub, +                                          TALER_DENOMINATION_RSA, +                                          size)); +  memset (&dki, +          0, +          sizeof (struct TALER_EXCHANGEDB_DenominationKey)); +  dki.denom_pub = dkp->pub; +  dki.issue.start = now; +  dki.issue.expire_withdraw +    = GNUNET_TIME_absolute_to_timestamp ( +        GNUNET_TIME_absolute_add ( +          now.abs_time, +          GNUNET_TIME_UNIT_HOURS)); +  dki.issue.expire_deposit +    = GNUNET_TIME_absolute_to_timestamp ( +        GNUNET_TIME_absolute_add ( +          now.abs_time, +          GNUNET_TIME_relative_multiply ( +            GNUNET_TIME_UNIT_HOURS, 2))); +  dki.issue.expire_legal +    = GNUNET_TIME_absolute_to_timestamp ( +        GNUNET_TIME_absolute_add ( +          now.abs_time, +          GNUNET_TIME_relative_multiply ( +            GNUNET_TIME_UNIT_HOURS, 3))); +  dki.issue.value = *value; +  dki.issue.fees = *fees; +  TALER_denom_pub_hash (&dkp->pub, +                        &dki.issue.denom_hash); +  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != +      plugin->insert_denomination_info (plugin->cls, +                                        &dki.denom_pub, +                                        &dki.issue)) +  { +    GNUNET_break (0); +    destroy_denom_key_pair (dkp); +    return NULL; +  } +  memset (&issue2, 0, sizeof (issue2)); +  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != +      plugin->get_denomination_info (plugin->cls, +                                     &dki.issue.denom_hash, +                                     &issue2)) +  { +    GNUNET_break (0); +    destroy_denom_key_pair (dkp); +    return NULL; +  } +  if (0 != GNUNET_memcmp (&dki.issue, +                          &issue2)) +  { +    GNUNET_break (0); +    destroy_denom_key_pair (dkp); +    return NULL; +  } +  return dkp; +} + + + + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure with config + */ + +static void +run (void *cls) +{ +  struct GNUNET_CONFIGURATION_Handle *cfg = cls; +  const uint32_t num_partitions = 10; +  struct DenomKeyPair *dkp = NULL; +  struct GNUNET_TIME_Timestamp ts; +  struct TALER_EXCHANGEDB_Deposit depos; +  struct GNUNET_TIME_Timestamp ts_deadline; +  struct TALER_Amount value; +  union TALER_DenominationBlindingKeyP bks; +  struct TALER_CoinPubHashP c_hash; +  struct TALER_EXCHANGEDB_CollectableBlindcoin cbc; +  struct TALER_ExchangeWithdrawValues alg_values = { +    .cipher = TALER_DENOMINATION_RSA +    }; +  struct TALER_PlanchetMasterSecretP ps; +  struct TALER_ReservePublicKeyP reserve_pub; + +  ZR_BLK (&cbc); +  RND_BLK (&reserve_pub); + +  memset (&depos, +          0, +          sizeof (depos)); + +  if (NULL == +      (plugin = TALER_EXCHANGEDB_plugin_load (cfg))) +  { +    GNUNET_break (0); +    result = 77; +    return; +  } +  (void) plugin->drop_tables (plugin->cls); +  if (GNUNET_OK != +      plugin->create_tables (plugin->cls, +                             true, +                             num_partitions)) +  { +    GNUNET_break (0); +    result = 77; +    goto cleanup; +  } +  if (GNUNET_OK != +      plugin->preflight (plugin->cls)) +  { +    GNUNET_break (0); +    goto cleanup; +  } + + +  GNUNET_assert (GNUNET_OK == +                 TALER_string_to_amount (CURRENCY ":1.000010", +                                         &value)); +  GNUNET_assert (GNUNET_OK == +                 TALER_string_to_amount (CURRENCY ":0.000010", +                                         &fees.withdraw)); +  GNUNET_assert (GNUNET_OK == +                 TALER_string_to_amount (CURRENCY ":0.000010", +                                         &fees.deposit)); +  GNUNET_assert (GNUNET_OK == +                 TALER_string_to_amount (CURRENCY ":0.000010", +                                         &fees.refresh)); +  GNUNET_assert (GNUNET_OK == +                 TALER_string_to_amount (CURRENCY ":0.000010", +                                         &fees.refund)); + +  ts = GNUNET_TIME_timestamp_get (); + +  dkp = create_denom_key_pair (RSA_KEY_SIZE, +                               ts, +                               &value, +                               &fees); +  GNUNET_assert (NULL != dkp); +  TALER_denom_pub_hash (&dkp->pub, +                        &cbc.denom_pub_hash); +  RND_BLK (&cbc.reserve_sig); +  RND_BLK (&ps); +  TALER_planchet_blinding_secret_create (&ps, +                                         &alg_values, +                                         &bks); + +  { +    struct TALER_PlanchetDetail pd; +    struct TALER_CoinSpendPublicKeyP coin_pub; +    struct TALER_AgeCommitmentHash age_hash; +    struct TALER_AgeCommitmentHash *p_ah[2] = { +      NULL, +      &age_hash +    }; + + +    RND_BLK (&age_hash); +    for (size_t i = 0; i < sizeof(p_ah) / sizeof(p_ah[0]); i++) +    { +      fprintf(stdout, "OPEN\n"); +      RND_BLK (&coin_pub); +      GNUNET_assert (GNUNET_OK == +                     TALER_denom_blind (&dkp->pub, +                                        &bks, +                                        p_ah[i], +                                        &coin_pub, +                                        &alg_values, +                                        &c_hash, +                                        &pd.blinded_planchet)); +      GNUNET_assert (GNUNET_OK == +                     TALER_coin_ev_hash (&pd.blinded_planchet, +                                         &cbc.denom_pub_hash, +                                         &cbc.h_coin_envelope)); +      if (i != 0) +        TALER_blinded_denom_sig_free (&cbc.sig); +      GNUNET_assert ( +        GNUNET_OK == +        TALER_denom_sign_blinded ( +          &cbc.sig, +          &dkp->priv, +          false, +          &pd.blinded_planchet)); +      TALER_blinded_planchet_free (&pd.blinded_planchet); +    } +  } + +  cbc.reserve_pub = reserve_pub; +  cbc.amount_with_fee = value; +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_set_zero (CURRENCY, +                                        &cbc.withdraw_fee)); + + + + +  ts_deadline = GNUNET_TIME_timestamp_get (); + +  depos.deposit_fee = fees.deposit; + +  RND_BLK (&depos.coin.coin_pub); +  TALER_denom_pub_hash (&dkp->pub, +                        &depos.coin.denom_pub_hash); +  GNUNET_assert (GNUNET_OK == +                 TALER_denom_sig_unblind (&depos.coin.denom_sig, +                                          &cbc.sig, +                                          &bks, +                                          &c_hash, +                                          &alg_values, +                                          &dkp->pub)); +  { +    uint64_t known_coin_id; +    struct TALER_DenominationHashP dph; +    struct TALER_AgeCommitmentHash agh; + +    FAILIF (TALER_EXCHANGEDB_CKS_ADDED != +            plugin->ensure_coin_known (plugin->cls, +                                       &depos.coin, +                                       &known_coin_id, +                                       &dph, +                                       &agh)); +  } +  { +    TALER_denom_sig_free (&depos.coin.denom_sig); +    struct GNUNET_TIME_Timestamp now; +    RND_BLK (&depos.merchant_pub); +    RND_BLK (&depos.csig); +    RND_BLK (&depos.h_contract_terms); +    RND_BLK (&depos.wire_salt); +    depos.amount_with_fee = value; +    depos.refund_deadline = ts_deadline; +    depos.wire_deadline = ts_deadline; +    depos.receiver_wire_account = +      "payto://iban/DE67830654080004822650?receiver-name=Test"; +    depos.timestamp = ts; + +    now = GNUNET_TIME_timestamp_get (); +    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != +            plugin->insert_deposit (plugin->cls, +                                    now, +                                    &depos)); +  } + +  result = 0; +  drop: +  GNUNET_break (GNUNET_OK == +  plugin->drop_tables (plugin->cls)); +cleanup: +  if (NULL != dkp) +    destroy_denom_key_pair (dkp); +  TALER_denom_sig_free (&depos.coin.denom_sig); +  TALER_blinded_denom_sig_free (&cbc.sig); +  dkp = NULL; +  TALER_EXCHANGEDB_plugin_unload (plugin); +  plugin = NULL; +} + + +int +main (int argc, +      char *const argv[]) +{ +  const char *plugin_name; +  char *config_filename; +  char *testname; +  struct GNUNET_CONFIGURATION_Handle *cfg; + +  (void) argc; +  result = -1; +  if (NULL == (plugin_name = strrchr (argv[0], (int) '-'))) +  { +    GNUNET_break (0); +    return -1; +  } +  GNUNET_log_setup (argv[0], +                    "WARNING", +                    NULL); +  plugin_name++; +  (void) GNUNET_asprintf (&testname, +                          "test-exchange-db-%s", +                          plugin_name); +  (void) GNUNET_asprintf (&config_filename, +                          "%s.conf", +                          testname); +  fprintf (stdout, +           "Using config: %s\n", +           config_filename); +  cfg = GNUNET_CONFIGURATION_create (); +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_parse (cfg, +                                  config_filename)) +  { +    GNUNET_break (0); +    GNUNET_free (config_filename); +    GNUNET_free (testname); +    return 2; +  } +  GNUNET_SCHEDULER_run (&run, +                        cfg); +  GNUNET_CONFIGURATION_destroy (cfg); +  GNUNET_free (config_filename); +  GNUNET_free (testname); +  return result; +} + + +/* end of test_exchangedb_by_j.c */ diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 0e33f777..938b7445 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -4983,7 +4983,7 @@ struct TALER_EXCHANGE_PurseDeleteHandle;   * @param cb_cls closure for @a cb   * @return the request handle; NULL upon error   */ -struct TALER_EXCHANGE_PurseCreateDepositHandle * +struct TALER_EXCHANGE_PurseDeleteHandle *  TALER_EXCHANGE_purse_delete (    struct TALER_EXCHANGE_Handle *exchange,    const struct TALER_PurseContractPrivateKeyP *purse_priv, diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 5a55b5c9..da28262a 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -5827,6 +5827,7 @@ struct TALER_EXCHANGEDB_Plugin     * @param[out] deposited set to actual amount put into the purse so far     * @param[out] h_contract_terms set to hash of the contract for the purse     * @param[out] merge_timestamp set to time when the purse was merged, or NEVER if not +   * @param[out] purse_deleted set to true if purse was deleted     * @return transaction status code     */    enum GNUNET_DB_QueryStatus @@ -5838,7 +5839,8 @@ struct TALER_EXCHANGEDB_Plugin      struct TALER_Amount *amount,      struct TALER_Amount *deposited,      struct TALER_PrivateContractHashP *h_contract_terms, -    struct GNUNET_TIME_Timestamp *merge_timestamp); +    struct GNUNET_TIME_Timestamp *merge_timestamp, +    bool *purse_deleted);    /** diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h index aa475bd3..55d36b2d 100644 --- a/src/include/taler_testing_lib.h +++ b/src/include/taler_testing_lib.h @@ -2568,6 +2568,21 @@ TALER_TESTING_cmd_purse_create_with_deposit (  /** + * Deletes a purse. + * + * @param label command label + * @param expected_http_status what HTTP status do we expect to get returned from the exchange + * @param purse_cmd command that created the purse + * @return the command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_purse_delete ( +  const char *label, +  unsigned int expected_http_status, +  const char *purse_cmd); + + +/**   * Retrieve contract (also checks that the contract matches   * the upload command).   * diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index e695cf04..b775719e 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -52,6 +52,7 @@ libtalerexchange_la_SOURCES = \    exchange_api_melt.c \    exchange_api_purse_create_with_deposit.c \    exchange_api_purse_create_with_merge.c \ +  exchange_api_purse_delete.c \    exchange_api_purse_deposit.c \    exchange_api_purse_merge.c \    exchange_api_purses_get.c \ diff --git a/src/lib/exchange_api_purse_delete.c b/src/lib/exchange_api_purse_delete.c new file mode 100644 index 00000000..27a9082b --- /dev/null +++ b/src/lib/exchange_api_purse_delete.c @@ -0,0 +1,243 @@ +/* +   This file is part of TALER +   Copyright (C) 2022 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 lib/exchange_api_purse_delete.c + * @brief Implementation of the client to delete a purse + *        into an account + * @author Christian Grothoff + */ +#include "platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_json_lib.h" +#include "taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "exchange_api_common.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A purse delete with deposit handle + */ +struct TALER_EXCHANGE_PurseDeleteHandle +{ + +  /** +   * The connection to exchange this request handle will use +   */ +  struct TALER_EXCHANGE_Handle *exchange; + +  /** +   * The url for this request. +   */ +  char *url; + +  /** +   * Handle for the request. +   */ +  struct GNUNET_CURL_Job *job; + +  /** +   * Function to call with the result. +   */ +  TALER_EXCHANGE_PurseDeleteCallback cb; + +  /** +   * Closure for @a cb. +   */ +  void *cb_cls; + +  /** +   * Header with the purse_sig. +   */ +  struct curl_slist *xhdr; +}; + + +/** + * Function called when we're done processing the + * HTTP DELETE /purse/$PID request. + * + * @param cls the `struct TALER_EXCHANGE_PurseDeleteHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_purse_delete_finished (void *cls, +                              long response_code, +                              const void *response) +{ +  struct TALER_EXCHANGE_PurseDeleteHandle *pdh = cls; +  const json_t *j = response; +  struct TALER_EXCHANGE_PurseDeleteResponse dr = { +    .hr.reply = j, +    .hr.http_status = (unsigned int) response_code +  }; + +  pdh->job = NULL; +  switch (response_code) +  { +  case 0: +    dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; +    break; +  case MHD_HTTP_NO_CONTENT: +    break; +  case MHD_HTTP_BAD_REQUEST: +    /* This should never happen, either us or the exchange is buggy +       (or API version conflict); just pass JSON reply to the application */ +    dr.hr.ec = TALER_JSON_get_error_code (j); +    dr.hr.hint = TALER_JSON_get_error_hint (j); +    break; +  case MHD_HTTP_FORBIDDEN: +    dr.hr.ec = TALER_JSON_get_error_code (j); +    dr.hr.hint = TALER_JSON_get_error_hint (j); +    /* Nothing really to verify, exchange says one of the signatures is +       invalid; as we checked them, this should never happen, we +       should pass the JSON reply to the application */ +    break; +  case MHD_HTTP_NOT_FOUND: +    dr.hr.ec = TALER_JSON_get_error_code (j); +    dr.hr.hint = TALER_JSON_get_error_hint (j); +    /* Nothing really to verify, this should never +       happen, we should pass the JSON reply to the application */ +    break; +  case MHD_HTTP_CONFLICT: +    dr.hr.ec = TALER_JSON_get_error_code (j); +    dr.hr.hint = TALER_JSON_get_error_hint (j); +    break; +  case MHD_HTTP_INTERNAL_SERVER_ERROR: +    dr.hr.ec = TALER_JSON_get_error_code (j); +    dr.hr.hint = TALER_JSON_get_error_hint (j); +    /* Server had an internal issue; we should retry, but this API +       leaves this to the application */ +    break; +  default: +    /* unexpected response code */ +    dr.hr.ec = TALER_JSON_get_error_code (j); +    dr.hr.hint = TALER_JSON_get_error_hint (j); +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u/%d for exchange deposit\n", +                (unsigned int) response_code, +                dr.hr.ec); +    GNUNET_break_op (0); +    break; +  } +  pdh->cb (pdh->cb_cls, +           &dr); +  TALER_EXCHANGE_purse_delete_cancel (pdh); +} + + +struct TALER_EXCHANGE_PurseDeleteHandle * +TALER_EXCHANGE_purse_delete ( +  struct TALER_EXCHANGE_Handle *exchange, +  const struct TALER_PurseContractPrivateKeyP *purse_priv, +  TALER_EXCHANGE_PurseDeleteCallback cb, +  void *cb_cls) +{ +  struct TALER_EXCHANGE_PurseDeleteHandle *pdh; +  struct GNUNET_CURL_Context *ctx; +  CURL *eh; +  struct TALER_PurseContractPublicKeyP purse_pub; +  struct TALER_PurseContractSignatureP purse_sig; +  char arg_str[sizeof (purse_pub) * 2 + 32]; + +  pdh = GNUNET_new (struct TALER_EXCHANGE_PurseDeleteHandle); +  pdh->exchange = exchange; +  pdh->cb = cb; +  pdh->cb_cls = cb_cls; +  GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv, +                                      &purse_pub.eddsa_pub); +  GNUNET_assert (GNUNET_YES == +                 TEAH_handle_is_ready (exchange)); +  { +    char pub_str[sizeof (purse_pub) * 2]; +    char *end; + +    end = GNUNET_STRINGS_data_to_string (&purse_pub, +                                         sizeof (purse_pub), +                                         pub_str, +                                         sizeof (pub_str)); +    *end = '\0'; +    GNUNET_snprintf (arg_str, +                     sizeof (arg_str), +                     "/purses/%s", +                     pub_str); +  } +  pdh->url = TEAH_path_to_url (exchange, +                               arg_str); +  if (NULL == pdh->url) +  { +    GNUNET_break (0); +    GNUNET_free (pdh); +    return NULL; +  } +  TALER_wallet_purse_delete_sign (purse_priv, +                                  &purse_sig); +  { +    char *delete_str; +    char *xhdr; + +    delete_str = +      GNUNET_STRINGS_data_to_string_alloc (&purse_sig, +                                           sizeof (purse_sig)); +    GNUNET_asprintf (&xhdr, +                     "Taler-Purse-Signature: %s", +                     delete_str); +    GNUNET_free (delete_str); +    pdh->xhdr = curl_slist_append (NULL, +                                   xhdr); +    GNUNET_free (xhdr); +  } +  eh = TALER_EXCHANGE_curl_easy_get_ (pdh->url); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_CUSTOMREQUEST, +                                   MHD_HTTP_METHOD_DELETE)); +  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +              "URL for purse delete: `%s'\n", +              pdh->url); +  ctx = TEAH_handle_to_context (exchange); +  pdh->job = GNUNET_CURL_job_add2 (ctx, +                                   eh, +                                   pdh->xhdr, +                                   &handle_purse_delete_finished, +                                   pdh); +  return pdh; +} + + +void +TALER_EXCHANGE_purse_delete_cancel ( +  struct TALER_EXCHANGE_PurseDeleteHandle *pdh) +{ +  if (NULL != pdh->job) +  { +    GNUNET_CURL_job_cancel (pdh->job); +    pdh->job = NULL; +  } +  curl_slist_free_all (pdh->xhdr); +  GNUNET_free (pdh->url); +  GNUNET_free (pdh); +} + + +/* end of exchange_api_purse_delete.c */ diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am index 5ae7de40..db2a54e7 100644 --- a/src/testing/Makefile.am +++ b/src/testing/Makefile.am @@ -77,6 +77,7 @@ libtalertesting_la_SOURCES = \    testing_api_cmd_offline_sign_keys.c \    testing_api_cmd_offline_sign_extensions.c \    testing_api_cmd_purse_create_deposit.c \ +  testing_api_cmd_purse_delete.c \    testing_api_cmd_purse_deposit.c \    testing_api_cmd_purse_get.c \    testing_api_cmd_purse_merge.c \ diff --git a/src/testing/test_exchange_p2p.c b/src/testing/test_exchange_p2p.c index f159b2f1..ad95bf63 100644 --- a/src/testing/test_exchange_p2p.c +++ b/src/testing/test_exchange_p2p.c @@ -159,6 +159,19 @@ run (void *cls,    };    struct TALER_TESTING_Command push[] = {      TALER_TESTING_cmd_purse_create_with_deposit ( +      "purse-with-deposit-for-delete", +      MHD_HTTP_OK, +      "{\"amount\":\"EUR:1\",\"summary\":\"ice cream\"}", +      true, /* upload contract */ +      GNUNET_TIME_UNIT_MINUTES, /* expiration */ +      "withdraw-coin-1", +      "EUR:1.01", +      NULL), +    TALER_TESTING_cmd_purse_delete ( +      "purse-with-deposit-delete", +      MHD_HTTP_NO_CONTENT, +      "purse-with-deposit-for-delete"), +    TALER_TESTING_cmd_purse_create_with_deposit (        "purse-with-deposit",        MHD_HTTP_OK,        "{\"amount\":\"EUR:1\",\"summary\":\"ice cream\"}", diff --git a/src/testing/testing_api_cmd_purse_delete.c b/src/testing/testing_api_cmd_purse_delete.c new file mode 100644 index 00000000..aa085327 --- /dev/null +++ b/src/testing/testing_api_cmd_purse_delete.c @@ -0,0 +1,190 @@ +/* +  This file is part of TALER +  Copyright (C) 2020 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 testing/testing_api_cmd_purse_delete.c + * @brief command for testing /management/purse/disable. + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_testing_lib.h" +#include "taler_signatures.h" +#include "backoff.h" + + +/** + * State for a "purse_delete" CMD. + */ +struct PurseDeleteState +{ + +  /** +   * Purse delete handle while operation is running. +   */ +  struct TALER_EXCHANGE_PurseDeleteHandle *pdh; + +  /** +   * Our interpreter. +   */ +  struct TALER_TESTING_Interpreter *is; + +  /** +   * Expected HTTP response code. +   */ +  unsigned int expected_response_code; + +  /** +   * Command that created the purse we now want to +   * delete. +   */ +  const char *purse_cmd; +}; + + +/** + * Callback to analyze the DELETE /purses/$PID response, just used to check if + * the response code is acceptable. + * + * @param cls closure. + * @param pdr HTTP response details + */ +static void +purse_delete_cb (void *cls, +                 const struct TALER_EXCHANGE_PurseDeleteResponse *pdr) +{ +  struct PurseDeleteState *pds = cls; + +  pds->pdh = NULL; +  if (pds->expected_response_code != pdr->hr.http_status) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s in %s:%u\n", +                pdr->hr.http_status, +                pds->is->commands[pds->is->ip].label, +                __FILE__, +                __LINE__); +    json_dumpf (pdr->hr.reply, +                stderr, +                0); +    TALER_TESTING_interpreter_fail (pds->is); +    return; +  } +  TALER_TESTING_interpreter_next (pds->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +purse_delete_run (void *cls, +                  const struct TALER_TESTING_Command *cmd, +                  struct TALER_TESTING_Interpreter *is) +{ +  struct PurseDeleteState *pds = cls; +  const struct TALER_PurseContractPrivateKeyP *purse_priv; +  const struct TALER_TESTING_Command *ref; + +  (void) cmd; +  ref = TALER_TESTING_interpreter_lookup_command (is, +                                                  pds->purse_cmd); +  if (NULL == ref) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  if (GNUNET_OK != +      TALER_TESTING_get_trait_purse_priv (ref, +                                          &purse_priv)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  pds->is = is; +  pds->pdh = TALER_EXCHANGE_purse_delete ( +    is->exchange, +    purse_priv, +    &purse_delete_cb, +    pds); +  if (NULL == pds->pdh) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +} + + +/** + * Free the state of a "purse_delete" CMD, and possibly cancel a + * pending operation thereof. + * + * @param cls closure, must be a `struct PurseDeleteState`. + * @param cmd the command which is being cleaned up. + */ +static void +purse_delete_cleanup (void *cls, +                      const struct TALER_TESTING_Command *cmd) +{ +  struct PurseDeleteState *pds = cls; + +  if (NULL != pds->pdh) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Command %u (%s) did not complete\n", +                pds->is->ip, +                cmd->label); +    TALER_EXCHANGE_purse_delete_cancel (pds->pdh); +    pds->pdh = NULL; +  } +  GNUNET_free (pds); +} + + +struct TALER_TESTING_Command +TALER_TESTING_cmd_purse_delete (const char *label, +                                unsigned int expected_http_status, +                                const char *purse_cmd) +{ +  struct PurseDeleteState *ds; + +  ds = GNUNET_new (struct PurseDeleteState); +  ds->expected_response_code = expected_http_status; +  ds->purse_cmd = purse_cmd; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ds, +      .label = label, +      .run = &purse_delete_run, +      .cleanup = &purse_delete_cleanup +    }; + +    return cmd; +  } +} + + +/* end of testing_api_cmd_purse_delete.c */ diff --git a/src/testing/testing_api_cmd_withdraw.c b/src/testing/testing_api_cmd_withdraw.c index 7a81d1c3..1bd3c187 100644 --- a/src/testing/testing_api_cmd_withdraw.c +++ b/src/testing/testing_api_cmd_withdraw.c @@ -360,14 +360,12 @@ withdraw_run (void *cls,      = TALER_TESTING_interpreter_lookup_command (          is,          ws->reserve_reference); -    if (NULL == create_reserve)    {      GNUNET_break (0);      TALER_TESTING_interpreter_fail (is);      return;    } -    if (GNUNET_OK !=        TALER_TESTING_get_trait_reserve_priv (create_reserve,                                              &rp)) @@ -376,7 +374,6 @@ withdraw_run (void *cls,      TALER_TESTING_interpreter_fail (is);      return;    } -    if (NULL == ws->exchange_url)      ws->exchange_url        = GNUNET_strdup (TALER_EXCHANGE_get_base_url (is->exchange)); | 
