commit 41e05e290d1c2fbf6edec1699ba97cea10887957 parent 47000119f9521ab0a925e18310e0e427f4b2264e Author: Martin Schanzenbach <schanzen@gnunet.org> Date: Sun, 22 Jun 2025 12:49:06 +0200 Add /tokens API Diffstat:
19 files changed, 737 insertions(+), 7 deletions(-)
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am @@ -111,7 +111,9 @@ taler_merchant_httpd_SOURCES = \ taler-merchant-httpd_private-get-instances-ID.h \ taler-merchant-httpd_private-get-instances-ID-kyc.c \ taler-merchant-httpd_private-get-instances-ID-kyc.h \ - taler-merchant-httpd_private-get-pos.c \ + taler-merchant-httpd_private-get-instances-ID-tokens.c \ + taler-merchant-httpd_private-get-instances-ID-tokens.h \ + taler-merchant-httpd_private-get-pos.c \ taler-merchant-httpd_private-get-pos.h \ taler-merchant-httpd_private-get-products.c \ taler-merchant-httpd_private-get-products.h \ diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c @@ -53,6 +53,7 @@ #include "taler-merchant-httpd_private-get-instances.h" #include "taler-merchant-httpd_private-get-instances-ID.h" #include "taler-merchant-httpd_private-get-instances-ID-kyc.h" +#include "taler-merchant-httpd_private-get-instances-ID-tokens.h" #include "taler-merchant-httpd_private-get-pos.h" #include "taler-merchant-httpd_private-get-products.h" #include "taler-merchant-httpd_private-get-products-ID.h" @@ -474,6 +475,21 @@ TMH_get_scope_by_name (const char *name) } +const char* +TMH_get_name_by_scope (enum TMH_AuthScope scope, bool *refreshable) +{ + *refreshable = scope & TMH_AS_REFRESHABLE; + for (unsigned int i = 0; TMH_AS_NONE != scope_permissions[i].as; i++) + { + /* We ignore the TMH_AS_REFRESHABLE bit */ + if ( (scope & ~TMH_AS_REFRESHABLE) == + (scope_permissions[i].as & ~TMH_AS_REFRESHABLE) ) + return scope_permissions[i].name; + } + return NULL; +} + + enum GNUNET_GenericReturnValue TMH_check_auth (const char *password, struct TALER_MerchantAuthenticationSaltP *salt, @@ -1715,6 +1731,13 @@ url_handler (void *cls, .handler = &TMH_private_delete_account_ID, .have_id_segment = true }, + /* GET /tokens: */ + { + .url_prefix = "/tokens", + .permission = "tokens-read", + .method = MHD_HTTP_METHOD_GET, + .handler = &TMH_private_get_instances_ID_tokens, + }, /* POST /token: */ { .url_prefix = "/token", @@ -1724,6 +1747,14 @@ url_handler (void *cls, /* Body should be tiny. */ .max_upload = 1024 }, + /* DELETE /tokens/$SERIAL: */ + { + .url_prefix = "/tokens/", + .permission = "tokens-write", + .method = MHD_HTTP_METHOD_DELETE, + .handler = &TMH_private_delete_instances_ID_token_SERIAL, + .have_id_segment = true + }, /* DELETE /token: */ { .url_prefix = "/token", diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h @@ -875,4 +875,14 @@ TMH_scope_is_subset (enum TMH_AuthScope as, enum TMH_AuthScope candidate); enum TMH_AuthScope TMH_get_scope_by_name (const char *name); +/** + * Return the name corresponding to @a scop. + * + * @param scope the scope to look for + * @param[out] refreshable outputs if scope value was refreshable + * @return the name corresponding to the scope, or NULL. + */ +const char* +TMH_get_name_by_scope (enum TMH_AuthScope scope, bool *refreshable); + #endif diff --git a/src/backend/taler-merchant-httpd_private-delete-instances-ID-token.c b/src/backend/taler-merchant-httpd_private-delete-instances-ID-token.c @@ -29,6 +29,60 @@ MHD_RESULT +TMH_private_delete_instances_ID_token_SERIAL (const struct TMH_RequestHandler * + rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) +{ + struct TMH_MerchantInstance *mi = hc->instance; + enum GNUNET_DB_QueryStatus qs; + unsigned long long serial; + char dummy; + + GNUNET_assert (NULL != mi); + GNUNET_assert (NULL != hc->infix); + if (1 != sscanf (hc->infix, + "%llu%c", + &serial, + &dummy)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "serial must be a number"); + } + + + qs = TMH_db->delete_login_token_serial (TMH_db->cls, + mi->settings.id, + serial); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_ec (connection, + TALER_EC_GENERIC_DB_STORE_FAILED, + "delete_login_token_by_serial"); + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN, + hc->infix); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + return TALER_MHD_reply_static (connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); + } + GNUNET_break (0); + return MHD_NO; +} + + +MHD_RESULT TMH_private_delete_instances_ID_token (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) diff --git a/src/backend/taler-merchant-httpd_private-delete-instances-ID-token.h b/src/backend/taler-merchant-httpd_private-delete-instances-ID-token.h @@ -26,6 +26,20 @@ #define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_INSTANCES_ID_TOKEN_H #include "taler-merchant-httpd.h" +/** + * Delete login token for an instance by serial. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_private_delete_instances_ID_token_SERIAL ( + const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc); + /** * Delete login token for an instance. diff --git a/src/backend/taler-merchant-httpd_private-get-instances-ID-tokens.c b/src/backend/taler-merchant-httpd_private-get-instances-ID-tokens.c @@ -0,0 +1,114 @@ +/* + This file is part of TALER + (C) 2025 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 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 taler-merchant-httpd_private-get-instances-ID-tokens.c + * @brief implement GET /tokens + * @author Martin Schanzenbach + */ +#include "platform.h" +#include "taler-merchant-httpd_private-get-instances-ID-tokens.h" + + +/** + * Add token details to our JSON array. + * + * @param cls a `json_t *` JSON array to build + * @param product_serial serial (row) number of the product in the database + * @param product_id ID of the product + */ +static void +add_token (void *cls, + struct GNUNET_TIME_Timestamp creation_time, + struct GNUNET_TIME_Timestamp expiration_time, + uint32_t scope, + const char *description, + uint64_t serial) +{ + json_t *pa = cls; + bool refreshable; + const char*as; + + as = TMH_get_name_by_scope (scope, &refreshable); + if (NULL == as) + { + GNUNET_break (0); + return; + } + GNUNET_assert (0 == + json_array_append_new ( + pa, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_timestamp ("creation_time", + creation_time), + GNUNET_JSON_pack_timestamp ("expiration_time", + expiration_time), + GNUNET_JSON_pack_string ("scope", + as), + GNUNET_JSON_pack_bool ("refreshable", + refreshable), + GNUNET_JSON_pack_string ("description", + description), + GNUNET_JSON_pack_uint64 ("serial", + serial)))); +} + + +MHD_RESULT +TMH_private_get_instances_ID_tokens (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) +{ + json_t *ta; + enum GNUNET_DB_QueryStatus qs; + int64_t limit; + uint64_t offset; + + limit = -20; /* default */ + TALER_MHD_parse_request_snumber (connection, + "limit", + &limit); + if (limit < 0) + offset = INT64_MAX; + else + offset = 0; + TALER_MHD_parse_request_number (connection, + "offset", + &offset); + ta = json_array (); + GNUNET_assert (NULL != ta); + qs = TMH_db->lookup_login_tokens (TMH_db->cls, + hc->instance->settings.id, + offset, + limit, + &add_token, + ta); + if (0 > qs) + { + GNUNET_break (0); + json_decref (ta); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + NULL); + } + return TALER_MHD_REPLY_JSON_PACK (connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_array_steal ("tokens", + ta)); +} + + +/* end of taler-merchant-httpd_private-get-products.c */ diff --git a/src/backend/taler-merchant-httpd_private-get-instances-ID-tokens.h b/src/backend/taler-merchant-httpd_private-get-instances-ID-tokens.h @@ -0,0 +1,41 @@ +/* + This file is part of TALER + (C) 2025 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 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 taler-merchant-httpd_private-get-instances-ID-tokens.h + * @brief implement GET /tokens + * @author Martin Schanzenbach + */ +#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_INSTANCES_ID_TOKENS_H +#define TALER_MERCHANT_HTTPD_PRIVATE_GET_INSTANCES_ID_TOKENS_H + +#include "taler-merchant-httpd.h" + + +/** + * Handle a GET "/tokens" request. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_private_get_instances_ID_tokens (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc); + +/* end of taler-merchant-httpd_private-get-instances-ID-tokens.h */ +#endif diff --git a/src/backend/taler-merchant-httpd_private-post-instances-ID-token.c b/src/backend/taler-merchant-httpd_private-post-instances-ID-token.c @@ -41,6 +41,7 @@ TMH_private_post_instances_ID_token (const struct TMH_RequestHandler *rh, struct TMH_MerchantInstance *mi = hc->instance; json_t *jtoken = hc->request_body; const char *scope; + const char *description; uint32_t iscope = TMH_AS_NONE; bool refreshable = false; struct TALER_MERCHANTDB_LoginTokenP btoken; @@ -58,6 +59,10 @@ TMH_private_post_instances_ID_token (const struct TMH_RequestHandler *rh, GNUNET_JSON_spec_bool ("refreshable", &refreshable), NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("description", + &description), + NULL), GNUNET_JSON_spec_end () }; enum GNUNET_DB_QueryStatus qs; @@ -107,12 +112,17 @@ TMH_private_post_instances_ID_token (const struct TMH_RequestHandler *rh, TALER_EC_GENERIC_TOKEN_PERMISSION_INSUFFICIENT, NULL); } + if (NULL == description) + { + description = ""; + } qs = TMH_db->insert_login_token (TMH_db->cls, mi->settings.id, &btoken, GNUNET_TIME_timestamp_get (), expiration_time, - iscope); + iscope, + description); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am @@ -36,6 +36,7 @@ sql_DATA = \ merchant-0017.sql \ merchant-0018.sql \ merchant-0019.sql \ + merchant-0020.sql \ drop.sql BUILT_SOURCES = \ @@ -142,6 +143,7 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \ pg_lookup_deposits_by_order.h pg_lookup_deposits_by_order.c \ pg_lookup_instance_auth.h pg_lookup_instance_auth.c \ pg_lookup_instances.h pg_lookup_instances.c \ + pg_lookup_login_tokens.h pg_lookup_login_tokens.c \ pg_lookup_order.h pg_lookup_order.c \ pg_lookup_order_by_fulfillment.h pg_lookup_order_by_fulfillment.c \ pg_lookup_order_status.h pg_lookup_order_status.c \ diff --git a/src/backenddb/merchant-0020.sql b/src/backenddb/merchant-0020.sql @@ -0,0 +1,38 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2025 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 merchant-0020.sql +-- @brief Add token descriptions +-- @author Martin Schanzenbach + + +BEGIN; + +-- Check patch versioning is in place. +SELECT _v.register_patch('merchant-0020', NULL, NULL); + +SET search_path TO merchant; + +ALTER TABLE merchant_login_tokens + ADD description TEXT NOT NULL; + +COMMENT ON COLUMN merchant_login_tokens.description + IS 'Description of the login token'; + +ALTER TABLE merchant_login_tokens +ADD COLUMN serial BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY; + +COMMIT; diff --git a/src/backenddb/pg_delete_login_token.c b/src/backenddb/pg_delete_login_token.c @@ -25,6 +25,33 @@ #include "pg_delete_login_token.h" #include "pg_helper.h" +enum GNUNET_DB_QueryStatus +TMH_PG_delete_login_token_serial ( + void *cls, + const char *id, + uint64_t serial) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (id), + GNUNET_PQ_query_param_uint64 (&serial), + GNUNET_PQ_query_param_end + }; + + check_connection (pg); + PREPARE (pg, + "delete_login_token_serial", + "DELETE FROM merchant_login_tokens" + " WHERE serial=$2" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1)"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "delete_login_token_serial", + params); +} + enum GNUNET_DB_QueryStatus TMH_PG_delete_login_token ( @@ -52,4 +79,3 @@ TMH_PG_delete_login_token ( "delete_login_token", params); } - diff --git a/src/backenddb/pg_delete_login_token.h b/src/backenddb/pg_delete_login_token.h @@ -25,6 +25,20 @@ #include <taler/taler_json_lib.h> #include "taler_merchantdb_plugin.h" +/** + * Delete login token from database by serial. + * + * @param cls closure + * @param id identifier of the instance + * @param serial serial of the token + * @return database result code + */ +enum GNUNET_DB_QueryStatus +TMH_PG_delete_login_token_serial ( + void *cls, + const char *id, + uint64_t serial); + /** * Delete login token from database. diff --git a/src/backenddb/pg_insert_login_token.c b/src/backenddb/pg_insert_login_token.c @@ -33,7 +33,8 @@ TMH_PG_insert_login_token ( const struct TALER_MERCHANTDB_LoginTokenP *token, struct GNUNET_TIME_Timestamp creation_time, struct GNUNET_TIME_Timestamp expiration_time, - uint32_t validity_scope) + uint32_t validity_scope, + const char *description) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { @@ -42,6 +43,7 @@ TMH_PG_insert_login_token ( GNUNET_PQ_query_param_timestamp (&creation_time), GNUNET_PQ_query_param_timestamp (&expiration_time), GNUNET_PQ_query_param_uint32 (&validity_scope), + GNUNET_PQ_query_param_string (description), GNUNET_PQ_query_param_end }; @@ -53,9 +55,10 @@ TMH_PG_insert_login_token ( ",creation_time" ",expiration_time" ",validity_scope" + ",description" ",merchant_serial" ")" - "SELECT $2, $3, $4, $5, merchant_serial" + "SELECT $2, $3, $4, $5, $6, merchant_serial" " FROM merchant_instances" " WHERE merchant_id=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, diff --git a/src/backenddb/pg_insert_login_token.h b/src/backenddb/pg_insert_login_token.h @@ -44,7 +44,8 @@ TMH_PG_insert_login_token ( const struct TALER_MERCHANTDB_LoginTokenP *token, struct GNUNET_TIME_Timestamp creation_time, struct GNUNET_TIME_Timestamp expiration_time, - uint32_t validity_scope); + uint32_t validity_scope, + const char*description); #endif diff --git a/src/backenddb/pg_lookup_login_tokens.c b/src/backenddb/pg_lookup_login_tokens.c @@ -0,0 +1,182 @@ +/* + 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 backenddb/pg_lookup_products.c + * @brief Implementation of the lookup_products function for Postgres + * @author Iván Ávalos + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_lookup_products.h" +#include "pg_helper.h" + +/** + * Context used for TMH_PG_lookup_products(). + */ +struct LookupLoginTokensContext +{ + /** + * Function to call with the results. + */ + TALER_MERCHANTDB_LoginTokensCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Did database result extraction fail? + */ + bool extract_failed; +}; + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results about products. + * + * @param[in,out] cls of type `struct LookupProductsContext *` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +lookup_login_tokens_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct LookupLoginTokensContext *plc = cls; + + for (unsigned int i = 0; i < num_results; i++) + { + uint32_t validity_scope; + uint64_t serial; + struct GNUNET_TIME_Timestamp expiration_time; + struct GNUNET_TIME_Timestamp creation_time; + char *description; + struct TALER_MERCHANTDB_LoginTokenP token; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("token", + &token), + GNUNET_PQ_result_spec_uint32 ("validity_scope", + &validity_scope), + GNUNET_PQ_result_spec_string ("description", + &description), + GNUNET_PQ_result_spec_timestamp ("creation_time", + &creation_time), + GNUNET_PQ_result_spec_timestamp ("expiration_time", + &expiration_time), + GNUNET_PQ_result_spec_uint64 ("serial", + &serial), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + plc->extract_failed = true; + return; + } + plc->cb (plc->cb_cls, + creation_time, + expiration_time, + validity_scope, + description, + serial); + GNUNET_PQ_cleanup_result (rs); + } +} + + +enum GNUNET_DB_QueryStatus +TMH_PG_lookup_login_tokens (void *cls, + const char *instance_id, + uint64_t offset, + int64_t limit, + TALER_MERCHANTDB_LoginTokensCallback cb, + void *cb_cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); + uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit); + struct LookupLoginTokensContext plc = { + .cb = cb, + .cb_cls = cb_cls, + /* Can be overwritten by the lookup_products_cb */ + .extract_failed = false, + }; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_timestamp (&now), + GNUNET_PQ_query_param_uint64 (&offset), + GNUNET_PQ_query_param_uint64 (&plimit), + GNUNET_PQ_query_param_end + }; + enum GNUNET_DB_QueryStatus qs; + + check_connection (pg); + PREPARE (pg, + "lookup_login_tokens_asc", + "SELECT" + " token" + ",serial" + ",creation_time" + ",expiration_time" + ",validity_scope" + ",description" + " FROM merchant_login_tokens" + " JOIN merchant_instances" + " USING (merchant_serial)" + " WHERE merchant_instances.merchant_id=$1" + " AND expiration_time < $2" + " ORDER BY serial ASC" + " OFFSET $3" + " LIMIT $4"); + PREPARE (pg, + "lookup_login_tokens_desc", + "SELECT" + " token" + ",serial" + ",creation_time" + ",expiration_time" + ",validity_scope" + ",description" + " FROM merchant_login_tokens" + " JOIN merchant_instances" + " USING (merchant_serial)" + " WHERE merchant_instances.merchant_id=$1" + " AND expiration_time < $2" + " ORDER BY serial DESC" + " OFFSET $3" + " LIMIT $4"); + qs = GNUNET_PQ_eval_prepared_multi_select ( + pg->conn, + (limit > 0) + ? "lookup_login_tokens_asc" + : "lookup_login_tokens_desc", + params, + &lookup_login_tokens_cb, + &plc); + /* If there was an error inside lookup_products_cb, return a hard error. */ + if (plc.extract_failed) + return GNUNET_DB_STATUS_HARD_ERROR; + return qs; +} diff --git a/src/backenddb/pg_lookup_login_tokens.h b/src/backenddb/pg_lookup_login_tokens.h @@ -0,0 +1,48 @@ +/* + This file is part of TALER + Copyright (C) 2025 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 backenddb/pg_lookup_login_tokens.h + * @brief implementation of the lookup_login_tokens function for Postgres + * @author Martin Schanzenbach + */ +#ifndef PG_LOOKUP_LOGIN_TOKENS_H +#define PG_LOOKUP_LOGIN_TOKENS_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Lookup all of the tokens the given instance has configured. + * + * @param cls closure + * @param instance_id instance to lookup tokens for + * @param offset transfer_serial number of the transfer we want to offset from + * @param limit number of entries to return, negative for descending, + * positive for ascending + * @param cb function to call on all products found + * @param cb_cls closure for @a cb + * @return database result code + */ +enum GNUNET_DB_QueryStatus +TMH_PG_lookup_login_tokens (void *cls, + const char *instance_id, + uint64_t offset, + int64_t limit, + TALER_MERCHANTDB_LoginTokensCallback cb, + void *cb_cls); + +#endif diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c @@ -43,6 +43,7 @@ #include "pg_insert_login_token.h" #include "pg_delete_login_token.h" #include "pg_select_login_token.h" +#include "pg_lookup_login_tokens.h" #include "pg_insert_account.h" #include "pg_update_account.h" #include "pg_lookup_instances.h" @@ -387,8 +388,12 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) = &TMH_PG_insert_login_token; plugin->delete_login_token = &TMH_PG_delete_login_token; + plugin->delete_login_token_serial + = &TMH_PG_delete_login_token_serial; plugin->select_login_token = &TMH_PG_select_login_token; + plugin->lookup_login_tokens + = &TMH_PG_lookup_login_tokens; plugin->select_account_by_uri = &TMH_PG_select_account_by_uri; plugin->lookup_instance_auth diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h @@ -386,6 +386,26 @@ typedef void /** + * Typically called by `lookup_login_tokens`. + * + * @param cls a `json_t *` JSON array to build + * @param creation_time creation time of the token + * @param expiration_time expiration time of the token + * @param scope validity scope of the token + * @param description description of the token + * @param serial serial number of the token + */ +typedef void +(*TALER_MERCHANTDB_LoginTokensCallback)( + void *cls, + struct GNUNET_TIME_Timestamp creation_time, + struct GNUNET_TIME_Timestamp expiration_time, + uint32_t scope, + const char *description, + uint64_t serial); + + +/** * Typically called by `lookup_templates`. * * @param cls closure @@ -1659,6 +1679,7 @@ struct TALER_MERCHANTDB_Plugin * @param creation_time the current time * @param expiration_time when does the token expire * @param validity_scope scope of the token + * @param description description of the token * @return database result code */ enum GNUNET_DB_QueryStatus @@ -1668,7 +1689,8 @@ struct TALER_MERCHANTDB_Plugin const struct TALER_MERCHANTDB_LoginTokenP *token, struct GNUNET_TIME_Timestamp creation_time, struct GNUNET_TIME_Timestamp expiration_time, - uint32_t validity_scope); + uint32_t validity_scope, + const char *description); /** @@ -1689,6 +1711,25 @@ struct TALER_MERCHANTDB_Plugin struct GNUNET_TIME_Timestamp *expiration_time, uint32_t *validity_scope); + /** + * Lookup login tokens for instance. + * + * @param cls closure + * @param instance_id instance to lookup tokens for + * @param offset transfer_serial number of the transfer we want to offset from + * @param limit number of entries to return, negative for descending, + * positive for ascending + * @param cb function to call on all products found + * @param cb_cls closure for @a cb + * @return database result code + */ + enum GNUNET_DB_QueryStatus + (*lookup_login_tokens)(void *cls, + const char *instance_id, + uint64_t offset, + int64_t limit, + TALER_MERCHANTDB_LoginTokensCallback cb, + void *cb_cls); /** * Delete login token from database. @@ -1704,6 +1745,20 @@ struct TALER_MERCHANTDB_Plugin const char *id, const struct TALER_MERCHANTDB_LoginTokenP *token); + /** + * Delete login token from database by serial. + * + * @param cls closure + * @param id identifier of the instance + * @param serial serial of the token + * @return database result code + */ + enum GNUNET_DB_QueryStatus + (*delete_login_token_serial)( + void *cls, + const char *id, + uint64_t serial); + /** * Update information about an instance's account into our database. diff --git a/src/testing/test_merchant_instance_auth.sh b/src/testing/test_merchant_instance_auth.sh @@ -286,6 +286,8 @@ STATUS=$(curl -H "Content-Type: application/json" -X POST \ -d '{"method":"token","password":"again"}' \ -w "%{http_code}" -s -o "$LAST_RESPONSE") +BASIC_AUTH2=$(echo -n second:again | base64) + if [ "$STATUS" != "204" ] then cat $LAST_RESPONSE @@ -325,6 +327,84 @@ fi echo " OK" >&2 +echo -n "Requesting another login token... (read)" >&2 + +STATUS=$(curl -H "Content-Type: application/json" -X POST \ + -H 'Authorization: Basic '"$BASIC_AUTH2" \ + http://localhost:9966/instances/second/private/token \ + -d '{"scope":"readonly", "refreshable": false}' \ + -w "%{http_code}" -s -o "$LAST_RESPONSE") + +if [ "$STATUS" != "200" ] +then + jq < "$LAST_RESPONSE" >&2 + exit_fail "Expected 200, login token created. got: $STATUS" +fi + +RTOKEN=$(jq -e -r .access_token < "$LAST_RESPONSE") + +echo " OK" >&2 + +echo -n "Requesting another login token... (read:refreshable)" >&2 + +STATUS=$(curl -H "Content-Type: application/json" -X POST \ + -H 'Authorization: Basic '"$BASIC_AUTH2" \ + http://localhost:9966/instances/second/private/token \ + -d '{"scope":"readonly:refreshable", "description": "readonly but refreshable"}' \ + -w "%{http_code}" -s -o "$LAST_RESPONSE") + +if [ "$STATUS" != "200" ] +then + jq < "$LAST_RESPONSE" >&2 + exit_fail "Expected 200, login token created. got: $STATUS" +fi + +RTOKEN=$(jq -e -r .access_token < "$LAST_RESPONSE") + +echo " OK" >&2 + +echo "Getting 2 login tokens with offset 2." >&2 + +STATUS=$(curl -H "Content-Type: application/json" \ + -H "Authorization: Bearer $RWTOKEN" \ + 'http://localhost:9966/instances/second/private/tokens?limit=2&offset=2' \ + -w "%{http_code}" -s -o $LAST_RESPONSE) + +if [ "$STATUS" != "200" ] +then + exit_fail "Expected 200 OK. Got: $STATUS" +fi + +TOKEN_SERIAL=$(jq -e -r .tokens[1].serial < "$LAST_RESPONSE") + +echo -n "Deleting second login token by serial..." >&2 + +STATUS=$(curl -H "Content-Type: application/json" -X DELETE \ + -H 'Authorization: Bearer '"$RWTOKEN" \ + http://localhost:9966/instances/second/private/tokens/$TOKEN_SERIAL \ + -w "%{http_code}" -s -o "$LAST_RESPONSE") + +if [ "$STATUS" != "204" ] +then + jq < "$LAST_RESPONSE" >&2 + exit_fail "Expected 204, login token deleted. got: $STATUS" +fi +echo " OK" >&2 + +echo -n "Using deleted login token..." >&2 + +STATUS=$(curl "http://localhost:9966/instances/second/private/orders" \ + -H 'Authorization: Bearer '"$RTOKEN" \ + -w "%{http_code}" -s -o "$LAST_RESPONSE") + +if [ "$STATUS" != "401" ] +then + jq < "$LAST_RESPONSE" >&2 + exit_fail "Expected 401, token was deleted. got: $STATUS" +fi + +echo " OK" >&2 + echo -n "Deleting login token..." >&2