Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2020-2024 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 taler-merchant-httpd_private-post-account.c
22 : * @brief implementing POST /private/accounts request handling
23 : * @author Christian Grothoff
24 : */
25 : #include "platform.h"
26 : #include "taler-merchant-httpd_private-post-account.h"
27 : #include "taler-merchant-httpd_helper.h"
28 : #include "taler_merchant_bank_lib.h"
29 : #include <taler/taler_dbevents.h>
30 : #include <taler/taler_json_lib.h>
31 : #include "taler-merchant-httpd_mfa.h"
32 : #include <regex.h>
33 :
34 :
35 : MHD_RESULT
36 21 : TMH_private_post_account (const struct TMH_RequestHandler *rh,
37 : struct MHD_Connection *connection,
38 : struct TMH_HandlerContext *hc)
39 : {
40 21 : struct TMH_MerchantInstance *mi = hc->instance;
41 21 : const char *credit_facade_url = NULL;
42 21 : const json_t *credit_facade_credentials = NULL;
43 : struct TALER_FullPayto uri;
44 : struct GNUNET_JSON_Specification ispec[] = {
45 21 : TALER_JSON_spec_full_payto_uri ("payto_uri",
46 : &uri),
47 21 : GNUNET_JSON_spec_mark_optional (
48 : TALER_JSON_spec_web_url ("credit_facade_url",
49 : &credit_facade_url),
50 : NULL),
51 21 : GNUNET_JSON_spec_mark_optional (
52 : GNUNET_JSON_spec_object_const ("credit_facade_credentials",
53 : &credit_facade_credentials),
54 : NULL),
55 21 : GNUNET_JSON_spec_end ()
56 : };
57 : struct TMH_WireMethod *wm;
58 :
59 : {
60 : enum GNUNET_GenericReturnValue res;
61 :
62 21 : res = TALER_MHD_parse_json_data (connection,
63 21 : hc->request_body,
64 : ispec);
65 21 : if (GNUNET_OK != res)
66 : return (GNUNET_NO == res)
67 : ? MHD_YES
68 0 : : MHD_NO;
69 : }
70 :
71 : {
72 : char *err;
73 :
74 21 : if (NULL !=
75 21 : (err = TALER_payto_validate (uri)))
76 : {
77 : MHD_RESULT mret;
78 :
79 0 : GNUNET_break_op (0);
80 0 : mret = TALER_MHD_reply_with_error (connection,
81 : MHD_HTTP_BAD_REQUEST,
82 : TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
83 : err);
84 0 : GNUNET_free (err);
85 0 : return mret;
86 : }
87 : }
88 : {
89 21 : char *apt = GNUNET_strdup (TMH_allowed_payment_targets);
90 21 : char *method = TALER_payto_get_method (uri.full_payto);
91 : bool ok;
92 :
93 21 : ok = false;
94 21 : for (const char *tok = strtok (apt,
95 : " ");
96 21 : NULL != tok;
97 0 : tok = strtok (NULL,
98 : " "))
99 : {
100 21 : if (0 == strcmp ("*",
101 : tok))
102 21 : ok = true;
103 21 : if (0 == strcmp (method,
104 : tok))
105 0 : ok = true;
106 21 : if (ok)
107 21 : break;
108 : }
109 21 : GNUNET_free (method);
110 21 : GNUNET_free (apt);
111 21 : if (! ok)
112 : {
113 0 : GNUNET_break_op (0);
114 0 : return TALER_MHD_reply_with_error (connection,
115 : MHD_HTTP_BAD_REQUEST,
116 : TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
117 : "The payment target type is forbidden by policy");
118 : }
119 : }
120 :
121 21 : if ( (NULL != TMH_payment_target_regex) &&
122 : (0 !=
123 0 : regexec (&TMH_payment_target_re,
124 0 : uri.full_payto,
125 : 0,
126 : NULL,
127 : 0)) )
128 : {
129 0 : GNUNET_break_op (0);
130 0 : return TALER_MHD_reply_with_error (connection,
131 : MHD_HTTP_BAD_REQUEST,
132 : TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
133 : "The specific account is forbidden by policy");
134 : }
135 :
136 21 : if ( (NULL == credit_facade_url) !=
137 : (NULL == credit_facade_credentials) )
138 : {
139 0 : GNUNET_break_op (0);
140 0 : return TALER_MHD_reply_with_error (connection,
141 : MHD_HTTP_BAD_REQUEST,
142 : TALER_EC_GENERIC_PARAMETER_MISSING,
143 0 : (NULL == credit_facade_url)
144 : ? "credit_facade_url"
145 : : "credit_facade_credentials");
146 : }
147 21 : if ( (NULL != credit_facade_url) ||
148 19 : (NULL != credit_facade_credentials) )
149 : {
150 : struct TALER_MERCHANT_BANK_AuthenticationData auth;
151 :
152 2 : if (GNUNET_OK !=
153 2 : TALER_MERCHANT_BANK_auth_parse_json (credit_facade_credentials,
154 : credit_facade_url,
155 : &auth))
156 : {
157 0 : GNUNET_break_op (0);
158 0 : return TALER_MHD_reply_with_error (connection,
159 : MHD_HTTP_BAD_REQUEST,
160 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
161 : "credit_facade_url or credit_facade_credentials");
162 : }
163 2 : TALER_MERCHANT_BANK_auth_free (&auth);
164 : }
165 :
166 : /* FIXME: might want to do all of the DB interactions below in one transaction... */
167 : {
168 : enum GNUNET_DB_QueryStatus qs;
169 : enum GNUNET_GenericReturnValue ret;
170 :
171 21 : qs = TMH_db->select_accounts (TMH_db->cls,
172 21 : mi->settings.id,
173 : NULL,
174 : NULL);
175 21 : switch (qs)
176 : {
177 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
178 : case GNUNET_DB_STATUS_HARD_ERROR:
179 0 : GNUNET_break (0);
180 0 : return TALER_MHD_reply_with_error (connection,
181 : MHD_HTTP_INTERNAL_SERVER_ERROR,
182 : TALER_EC_GENERIC_DB_FETCH_FAILED,
183 : "select_accounts");
184 18 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
185 : /* skip MFA */
186 18 : break;
187 3 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
188 3 : ret = TMH_mfa_check_simple (hc,
189 : TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
190 : mi);
191 :
192 3 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
193 : "Account creation MFA check returned %d\n",
194 : (int) ret);
195 3 : if (GNUNET_OK != ret)
196 : {
197 : return (GNUNET_NO == ret)
198 : ? MHD_YES
199 0 : : MHD_NO;
200 : }
201 : }
202 : }
203 :
204 : /* convert provided payto URI into internal data structure with salts */
205 21 : wm = TMH_setup_wire_account (uri,
206 : credit_facade_url,
207 : credit_facade_credentials);
208 21 : GNUNET_assert (NULL != wm);
209 : {
210 21 : struct TALER_MERCHANTDB_AccountDetails ad = {
211 : .payto_uri = wm->payto_uri,
212 : .salt = wm->wire_salt,
213 21 : .instance_id = mi->settings.id,
214 : .h_wire = wm->h_wire,
215 21 : .credit_facade_url = wm->credit_facade_url,
216 21 : .credit_facade_credentials = wm->credit_facade_credentials,
217 21 : .active = wm->active
218 : };
219 : enum GNUNET_DB_QueryStatus qs;
220 :
221 21 : qs = TMH_db->insert_account (TMH_db->cls,
222 : &ad);
223 21 : switch (qs)
224 : {
225 21 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
226 21 : break;
227 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
228 : /* conflict: account exists */
229 : {
230 : struct TALER_MERCHANTDB_AccountDetails adx;
231 :
232 0 : qs = TMH_db->select_account_by_uri (TMH_db->cls,
233 0 : mi->settings.id,
234 : ad.payto_uri,
235 : &adx);
236 0 : switch (qs)
237 : {
238 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
239 0 : if ( (0 == TALER_full_payto_cmp (adx.payto_uri,
240 0 : ad.payto_uri) ) &&
241 0 : ( (adx.credit_facade_credentials ==
242 0 : ad.credit_facade_credentials) ||
243 0 : ( (NULL != adx.credit_facade_credentials) &&
244 0 : (NULL != ad.credit_facade_credentials) &&
245 0 : (1 == json_equal (adx.credit_facade_credentials,
246 0 : ad.credit_facade_credentials)) ) ) &&
247 0 : ( (adx.credit_facade_url == ad.credit_facade_url) ||
248 0 : ( (NULL != adx.credit_facade_url) &&
249 0 : (NULL != ad.credit_facade_url) &&
250 0 : (0 == strcmp (adx.credit_facade_url,
251 0 : ad.credit_facade_url)) ) ) )
252 : {
253 0 : TMH_wire_method_free (wm);
254 0 : GNUNET_free (adx.payto_uri.full_payto);
255 0 : GNUNET_free (adx.credit_facade_url);
256 0 : json_decref (adx.credit_facade_credentials);
257 0 : return TALER_MHD_REPLY_JSON_PACK (
258 : connection,
259 : MHD_HTTP_OK,
260 : GNUNET_JSON_pack_data_auto (
261 : "salt",
262 : &adx.salt),
263 : GNUNET_JSON_pack_data_auto (
264 : "h_wire",
265 : &adx.h_wire));
266 : }
267 0 : GNUNET_free (adx.payto_uri.full_payto);
268 0 : GNUNET_free (adx.credit_facade_url);
269 0 : json_decref (adx.credit_facade_credentials);
270 0 : break;
271 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
272 : case GNUNET_DB_STATUS_SOFT_ERROR:
273 : case GNUNET_DB_STATUS_HARD_ERROR:
274 0 : GNUNET_break (0);
275 0 : TMH_wire_method_free (wm);
276 0 : return TALER_MHD_reply_with_error (connection,
277 : MHD_HTTP_INTERNAL_SERVER_ERROR,
278 : TALER_EC_GENERIC_DB_FETCH_FAILED,
279 : "select_account");
280 : }
281 : }
282 0 : TMH_wire_method_free (wm);
283 0 : return TALER_MHD_reply_with_error (connection,
284 : MHD_HTTP_CONFLICT,
285 : TALER_EC_MERCHANT_PRIVATE_ACCOUNT_EXISTS,
286 0 : uri.full_payto);
287 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
288 : case GNUNET_DB_STATUS_HARD_ERROR:
289 0 : GNUNET_break (0);
290 0 : TMH_wire_method_free (wm);
291 0 : return TALER_MHD_reply_with_error (connection,
292 : MHD_HTTP_INTERNAL_SERVER_ERROR,
293 : TALER_EC_GENERIC_DB_STORE_FAILED,
294 : "insert_account");
295 : }
296 : }
297 :
298 : {
299 21 : struct GNUNET_DB_EventHeaderP es = {
300 21 : .size = htons (sizeof (es)),
301 21 : .type = htons (TALER_DBEVENT_MERCHANT_ACCOUNTS_CHANGED)
302 : };
303 :
304 21 : TMH_db->event_notify (TMH_db->cls,
305 : &es,
306 : NULL,
307 : 0);
308 : }
309 : /* Finally, also update our running process */
310 21 : GNUNET_CONTAINER_DLL_insert (mi->wm_head,
311 : mi->wm_tail,
312 : wm);
313 : /* Note: we may not need to do this, as we notified
314 : about the account change above. But also hardly hurts. */
315 21 : TMH_reload_instances (mi->settings.id);
316 21 : return TALER_MHD_REPLY_JSON_PACK (connection,
317 : MHD_HTTP_OK,
318 : GNUNET_JSON_pack_data_auto ("salt",
319 : &wm->wire_salt),
320 : GNUNET_JSON_pack_data_auto ("h_wire",
321 : &wm->h_wire));
322 : }
323 :
324 :
325 : /* end of taler-merchant-httpd_private-post-account.c */
|