Line data Source code
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_device.h"
30 : #include "merchant-database/get_otp_device.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 0 : otp_devices_equal (const struct TALER_MERCHANTDB_OtpDeviceDetails *t1,
49 : const struct TALER_MERCHANTDB_OtpDeviceDetails *t2)
50 : {
51 0 : return ( (0 == strcmp (t1->otp_description,
52 0 : t2->otp_description)) &&
53 0 : (0 == strcmp (t1->otp_key,
54 0 : t2->otp_key) ) &&
55 0 : (t1->otp_ctr == t2->otp_ctr) &&
56 0 : (t1->otp_algorithm == t2->otp_algorithm) );
57 : }
58 :
59 :
60 : enum MHD_Result
61 4 : TMH_private_post_otp_devices (const struct TMH_RequestHandler *rh,
62 : struct MHD_Connection *connection,
63 : struct TMH_HandlerContext *hc)
64 : {
65 4 : struct TMH_MerchantInstance *mi = hc->instance;
66 4 : struct TALER_MERCHANTDB_OtpDeviceDetails tp = { 0 };
67 : const char *device_id;
68 : enum GNUNET_DB_QueryStatus qs;
69 : struct GNUNET_JSON_Specification spec[] = {
70 4 : TALER_JSON_spec_slug ("otp_device_id",
71 : &device_id),
72 4 : GNUNET_JSON_spec_string ("otp_device_description",
73 : (const char **) &tp.otp_description),
74 4 : TALER_JSON_spec_otp_type ("otp_algorithm",
75 : &tp.otp_algorithm),
76 4 : GNUNET_JSON_spec_mark_optional (
77 : GNUNET_JSON_spec_uint64 ("otp_ctr",
78 : &tp.otp_ctr),
79 : NULL),
80 4 : TALER_JSON_spec_otp_key ("otp_key",
81 : (const char **) &tp.otp_key),
82 4 : GNUNET_JSON_spec_end ()
83 : };
84 :
85 4 : GNUNET_assert (NULL != mi);
86 : {
87 : enum GNUNET_GenericReturnValue res;
88 :
89 4 : res = TALER_MHD_parse_json_data (connection,
90 4 : hc->request_body,
91 : spec);
92 4 : if (GNUNET_OK != res)
93 : {
94 0 : GNUNET_break_op (0);
95 : return (GNUNET_NO == res)
96 : ? MHD_YES
97 0 : : MHD_NO;
98 : }
99 : }
100 :
101 : /* finally, interact with DB until no serialization error */
102 4 : 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 4 : if (GNUNET_OK !=
108 4 : TALER_MERCHANTDB_start (TMH_db,
109 : "/post otp-devices"))
110 : {
111 0 : GNUNET_break (0);
112 0 : GNUNET_JSON_parse_free (spec);
113 0 : return TALER_MHD_reply_with_error (connection,
114 : MHD_HTTP_INTERNAL_SERVER_ERROR,
115 : TALER_EC_GENERIC_DB_START_FAILED,
116 : NULL);
117 : }
118 4 : qs = TALER_MERCHANTDB_get_otp_device (TMH_db,
119 4 : mi->settings.id,
120 : device_id,
121 : &etp);
122 4 : switch (qs)
123 : {
124 0 : case GNUNET_DB_STATUS_HARD_ERROR:
125 : /* Clean up and fail hard */
126 0 : GNUNET_break (0);
127 0 : TALER_MERCHANTDB_rollback (TMH_db);
128 0 : GNUNET_JSON_parse_free (spec);
129 0 : return TALER_MHD_reply_with_error (connection,
130 : MHD_HTTP_INTERNAL_SERVER_ERROR,
131 : TALER_EC_GENERIC_DB_FETCH_FAILED,
132 : NULL);
133 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
134 : /* restart transaction */
135 0 : goto retry;
136 4 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
137 : /* Good, we can proceed! */
138 4 : break;
139 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
140 : /* idempotency check: is etp == tp? */
141 : {
142 : bool eq;
143 :
144 0 : eq = otp_devices_equal (&tp,
145 : &etp);
146 0 : GNUNET_free (etp.otp_description);
147 0 : GNUNET_free (etp.otp_key);
148 0 : TALER_MERCHANTDB_rollback (TMH_db);
149 0 : GNUNET_JSON_parse_free (spec);
150 : return eq
151 0 : ? TALER_MHD_reply_static (connection,
152 : MHD_HTTP_NO_CONTENT,
153 : NULL,
154 : NULL,
155 : 0)
156 0 : : 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 4 : qs = TALER_MERCHANTDB_insert_otp_device (TMH_db,
164 4 : mi->settings.id,
165 : device_id,
166 : &tp);
167 4 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
168 : {
169 0 : TALER_MERCHANTDB_rollback (TMH_db);
170 4 : break;
171 : }
172 4 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
173 : {
174 : /* A concurrent request created an OTP device with the same ID
175 : between our lookup above and this INSERT. */
176 0 : TALER_MERCHANTDB_rollback (TMH_db);
177 0 : GNUNET_JSON_parse_free (spec);
178 0 : return TALER_MHD_reply_with_error (
179 : connection,
180 : MHD_HTTP_CONFLICT,
181 : TALER_EC_MERCHANT_PRIVATE_POST_OTP_DEVICES_CONFLICT_OTP_DEVICE_EXISTS,
182 : device_id);
183 : }
184 4 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
185 : {
186 4 : qs = TALER_MERCHANTDB_commit (TMH_db);
187 4 : if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
188 4 : break;
189 : }
190 0 : retry:
191 0 : GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == qs);
192 0 : TALER_MERCHANTDB_rollback (TMH_db);
193 : } /* for RETRIES loop */
194 4 : GNUNET_JSON_parse_free (spec);
195 4 : if (qs < 0)
196 : {
197 0 : GNUNET_break (0);
198 0 : return TALER_MHD_reply_with_error (
199 : connection,
200 : MHD_HTTP_INTERNAL_SERVER_ERROR,
201 : (GNUNET_DB_STATUS_SOFT_ERROR == qs)
202 : ? TALER_EC_GENERIC_DB_SOFT_FAILURE
203 : : TALER_EC_GENERIC_DB_COMMIT_FAILED,
204 : NULL);
205 : }
206 4 : return TALER_MHD_reply_static (connection,
207 : MHD_HTTP_NO_CONTENT,
208 : NULL,
209 : NULL,
210 : 0);
211 : }
212 :
213 :
214 : /* end of taler-merchant-httpd_post-private-otp-devices.c */
|