taler-merchant-httpd_post-private-otp-devices.c (6823B)
1 /* 2 This file is part of TALER 3 (C) 2022 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU Affero General Public License as 7 published by the Free Software Foundation; either version 3, 8 or (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, 17 see <http://www.gnu.org/licenses/> 18 */ 19 20 /** 21 * @file src/backend/taler-merchant-httpd_post-private-otp-devices.c 22 * @brief implementing POST /otp-devices request handling 23 * @author Christian Grothoff 24 */ 25 #include "platform.h" 26 #include "taler-merchant-httpd_post-private-otp-devices.h" 27 #include "taler-merchant-httpd_helper.h" 28 #include <taler/taler_json_lib.h> 29 #include "merchant-database/insert_otp.h" 30 #include "merchant-database/select_otp.h" 31 #include "merchant-database/start.h" 32 33 34 /** 35 * How often do we retry the simple INSERT database transaction? 36 */ 37 #define MAX_RETRIES 3 38 39 40 /** 41 * Check if the two otp-devices are identical. 42 * 43 * @param t1 device to compare 44 * @param t2 other device to compare 45 * @return true if they are 'equal', false if not or of payto_uris is not an array 46 */ 47 static bool 48 otp_devices_equal (const struct TALER_MERCHANTDB_OtpDeviceDetails *t1, 49 const struct TALER_MERCHANTDB_OtpDeviceDetails *t2) 50 { 51 return ( (0 == strcmp (t1->otp_description, 52 t2->otp_description)) && 53 (0 == strcmp (t1->otp_key, 54 t2->otp_key) ) && 55 (t1->otp_ctr == t2->otp_ctr) && 56 (t1->otp_algorithm == t2->otp_algorithm) ); 57 } 58 59 60 enum MHD_Result 61 TMH_private_post_otp_devices (const struct TMH_RequestHandler *rh, 62 struct MHD_Connection *connection, 63 struct TMH_HandlerContext *hc) 64 { 65 struct TMH_MerchantInstance *mi = hc->instance; 66 struct TALER_MERCHANTDB_OtpDeviceDetails tp = { 0 }; 67 const char *device_id; 68 enum GNUNET_DB_QueryStatus qs; 69 struct GNUNET_JSON_Specification spec[] = { 70 GNUNET_JSON_spec_string ("otp_device_id", 71 &device_id), 72 GNUNET_JSON_spec_string ("otp_device_description", 73 (const char **) &tp.otp_description), 74 TALER_JSON_spec_otp_type ("otp_algorithm", 75 &tp.otp_algorithm), 76 GNUNET_JSON_spec_mark_optional ( 77 GNUNET_JSON_spec_uint64 ("otp_ctr", 78 &tp.otp_ctr), 79 NULL), 80 TALER_JSON_spec_otp_key ("otp_key", 81 (const char **) &tp.otp_key), 82 GNUNET_JSON_spec_end () 83 }; 84 85 GNUNET_assert (NULL != mi); 86 { 87 enum GNUNET_GenericReturnValue res; 88 89 res = TALER_MHD_parse_json_data (connection, 90 hc->request_body, 91 spec); 92 if (GNUNET_OK != res) 93 { 94 GNUNET_break_op (0); 95 return (GNUNET_NO == res) 96 ? MHD_YES 97 : MHD_NO; 98 } 99 } 100 101 /* finally, interact with DB until no serialization error */ 102 for (unsigned int i = 0; i<MAX_RETRIES; i++) 103 { 104 /* Test if a OTP device of this id is known */ 105 struct TALER_MERCHANTDB_OtpDeviceDetails etp; 106 107 if (GNUNET_OK != 108 TALER_MERCHANTDB_start (TMH_db, 109 "/post otp-devices")) 110 { 111 GNUNET_break (0); 112 GNUNET_JSON_parse_free (spec); 113 return TALER_MHD_reply_with_error (connection, 114 MHD_HTTP_INTERNAL_SERVER_ERROR, 115 TALER_EC_GENERIC_DB_START_FAILED, 116 NULL); 117 } 118 qs = TALER_MERCHANTDB_select_otp (TMH_db, 119 mi->settings.id, 120 device_id, 121 &etp); 122 switch (qs) 123 { 124 case GNUNET_DB_STATUS_HARD_ERROR: 125 /* Clean up and fail hard */ 126 GNUNET_break (0); 127 TALER_MERCHANTDB_rollback (TMH_db); 128 GNUNET_JSON_parse_free (spec); 129 return TALER_MHD_reply_with_error (connection, 130 MHD_HTTP_INTERNAL_SERVER_ERROR, 131 TALER_EC_GENERIC_DB_FETCH_FAILED, 132 NULL); 133 case GNUNET_DB_STATUS_SOFT_ERROR: 134 /* restart transaction */ 135 goto retry; 136 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 137 /* Good, we can proceed! */ 138 break; 139 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 140 /* idempotency check: is etp == tp? */ 141 { 142 bool eq; 143 144 eq = otp_devices_equal (&tp, 145 &etp); 146 GNUNET_free (etp.otp_description); 147 GNUNET_free (etp.otp_key); 148 TALER_MERCHANTDB_rollback (TMH_db); 149 GNUNET_JSON_parse_free (spec); 150 return eq 151 ? TALER_MHD_reply_static (connection, 152 MHD_HTTP_NO_CONTENT, 153 NULL, 154 NULL, 155 0) 156 : TALER_MHD_reply_with_error (connection, 157 MHD_HTTP_CONFLICT, 158 TALER_EC_MERCHANT_PRIVATE_POST_OTP_DEVICES_CONFLICT_OTP_DEVICE_EXISTS, 159 device_id); 160 } 161 } /* end switch (qs) */ 162 163 qs = TALER_MERCHANTDB_insert_otp (TMH_db, 164 mi->settings.id, 165 device_id, 166 &tp); 167 if (GNUNET_DB_STATUS_HARD_ERROR == qs) 168 { 169 TALER_MERCHANTDB_rollback (TMH_db); 170 break; 171 } 172 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 173 { 174 qs = TALER_MERCHANTDB_commit (TMH_db); 175 if (GNUNET_DB_STATUS_SOFT_ERROR != qs) 176 break; 177 } 178 retry: 179 GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == qs); 180 TALER_MERCHANTDB_rollback (TMH_db); 181 } /* for RETRIES loop */ 182 GNUNET_JSON_parse_free (spec); 183 if (qs < 0) 184 { 185 GNUNET_break (0); 186 return TALER_MHD_reply_with_error ( 187 connection, 188 MHD_HTTP_INTERNAL_SERVER_ERROR, 189 (GNUNET_DB_STATUS_SOFT_ERROR == qs) 190 ? TALER_EC_GENERIC_DB_SOFT_FAILURE 191 : TALER_EC_GENERIC_DB_COMMIT_FAILED, 192 NULL); 193 } 194 return TALER_MHD_reply_static (connection, 195 MHD_HTTP_NO_CONTENT, 196 NULL, 197 NULL, 198 0); 199 } 200 201 202 /* end of taler-merchant-httpd_post-private-otp-devices.c */