Line data Source code
1 : /*
2 : This file is part of GNU Taler
3 : (C) 2021 Taler Systems SA
4 :
5 : GNU 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 : GNU 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-instances-ID-auth.c
22 : * @brief implementing POST /instances/$ID/auth request handling
23 : * @author Christian Grothoff
24 : * @author Florian Dold
25 : */
26 : #include "platform.h"
27 : #include "taler-merchant-httpd_private-post-instances-ID-auth.h"
28 : #include "taler-merchant-httpd_auth.h"
29 : #include "taler-merchant-httpd_helper.h"
30 : #include "taler-merchant-httpd_mfa.h"
31 : #include <taler/taler_json_lib.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 : * Change the authentication settings of an instance.
42 : *
43 : * @param mi instance to modify settings of
44 : * @param connection the MHD connection to handle
45 : * @param[in,out] hc context with further information about the request
46 : * @param auth_override The authentication settings for this instance
47 : * do not apply due to administrative action. Do not check
48 : * against the DB value when updating the auth token.
49 : * @param tcs set of multi-factor authorizations required
50 : * @return MHD result code
51 : */
52 : static MHD_RESULT
53 9 : post_instances_ID_auth (struct TMH_MerchantInstance *mi,
54 : struct MHD_Connection *connection,
55 : struct TMH_HandlerContext *hc,
56 : bool auth_override,
57 : enum TEH_TanChannelSet tcs)
58 : {
59 : struct TALER_MERCHANTDB_InstanceAuthSettings ias;
60 9 : const char *auth_pw = NULL;
61 9 : json_t *jauth = hc->request_body;
62 :
63 : {
64 : enum GNUNET_GenericReturnValue ret;
65 :
66 9 : ret = TMH_check_auth_config (connection,
67 : jauth,
68 : &auth_pw);
69 9 : if (GNUNET_OK != ret)
70 0 : return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
71 : }
72 :
73 9 : if ( (0 != (tcs & TEH_TCS_SMS) &&
74 0 : ( (NULL == mi->settings.phone) ||
75 0 : (NULL == TMH_helper_sms) ||
76 0 : (! mi->settings.phone_validated) ) ) )
77 : {
78 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
79 : "Cannot reset password: SMS factor not available\n");
80 0 : return TALER_MHD_reply_with_error (
81 : connection,
82 : MHD_HTTP_FORBIDDEN,
83 : TALER_EC_MERCHANT_GENERIC_MFA_MISSING,
84 : "phone_number");
85 : }
86 9 : if ( (0 != (tcs & TEH_TCS_EMAIL) &&
87 0 : ( (NULL == mi->settings.email) ||
88 0 : (NULL == TMH_helper_email) ||
89 0 : (! mi->settings.email_validated) ) ) )
90 : {
91 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
92 : "Cannot reset password: E-mail factor not available\n");
93 0 : return TALER_MHD_reply_with_error (
94 : connection,
95 : MHD_HTTP_FORBIDDEN,
96 : TALER_EC_MERCHANT_GENERIC_MFA_MISSING,
97 : "email");
98 : }
99 9 : if (! auth_override)
100 : {
101 6 : enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; // fix -Wmaybe-uninitialized
102 :
103 6 : switch (tcs)
104 : {
105 6 : case TEH_TCS_NONE:
106 6 : ret = GNUNET_OK;
107 6 : break;
108 0 : case TEH_TCS_SMS:
109 0 : ret = TMH_mfa_challenges_do (hc,
110 : TALER_MERCHANT_MFA_CO_AUTH_CONFIGURATION,
111 : true,
112 : TALER_MERCHANT_MFA_CHANNEL_SMS,
113 : mi->settings.phone,
114 : TALER_MERCHANT_MFA_CHANNEL_NONE);
115 0 : break;
116 0 : case TEH_TCS_EMAIL:
117 0 : ret = TMH_mfa_challenges_do (hc,
118 : TALER_MERCHANT_MFA_CO_AUTH_CONFIGURATION,
119 : true,
120 : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
121 : mi->settings.email,
122 : TALER_MERCHANT_MFA_CHANNEL_NONE);
123 0 : break;
124 0 : case TEH_TCS_EMAIL_AND_SMS:
125 0 : ret = TMH_mfa_challenges_do (hc,
126 : TALER_MERCHANT_MFA_CO_AUTH_CONFIGURATION,
127 : true,
128 : TALER_MERCHANT_MFA_CHANNEL_SMS,
129 : mi->settings.phone,
130 : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
131 : mi->settings.email,
132 : TALER_MERCHANT_MFA_CHANNEL_NONE);
133 0 : break;
134 : }
135 6 : if (GNUNET_OK != ret)
136 : {
137 : return (GNUNET_NO == ret)
138 : ? MHD_YES
139 0 : : MHD_NO;
140 : }
141 : }
142 :
143 9 : if (NULL == auth_pw)
144 : {
145 2 : memset (&ias.auth_salt,
146 : 0,
147 : sizeof (ias.auth_salt));
148 2 : memset (&ias.auth_hash,
149 : 0,
150 : sizeof (ias.auth_hash));
151 : }
152 : else
153 : {
154 7 : TMH_compute_auth (auth_pw,
155 : &ias.auth_salt,
156 : &ias.auth_hash);
157 : }
158 :
159 : /* Store the new auth information in the database */
160 : {
161 : enum GNUNET_DB_QueryStatus qs;
162 :
163 9 : for (unsigned int i = 0; i<MAX_RETRIES; i++)
164 : {
165 9 : if (GNUNET_OK !=
166 9 : TMH_db->start (TMH_db->cls,
167 : "post /instances/$ID/auth"))
168 : {
169 0 : return TALER_MHD_reply_with_error (connection,
170 : MHD_HTTP_INTERNAL_SERVER_ERROR,
171 : TALER_EC_GENERIC_DB_START_FAILED,
172 : NULL);
173 : }
174 :
175 : /* Make the authentication update a serializable operation.
176 : We first check that the authentication information
177 : that the caller's request authenticated with
178 : is still up to date.
179 : Otherwise, we've detected a conflicting update
180 : to the authentication. */
181 : {
182 : struct TALER_MERCHANTDB_InstanceAuthSettings db_ias;
183 : enum TALER_ErrorCode ec;
184 :
185 9 : qs = TMH_db->lookup_instance_auth (TMH_db->cls,
186 9 : mi->settings.id,
187 : &db_ias);
188 :
189 9 : switch (qs)
190 : {
191 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
192 : /* Instance got purged. */
193 0 : TMH_db->rollback (TMH_db->cls);
194 0 : return TALER_MHD_reply_with_error (connection,
195 : MHD_HTTP_NOT_FOUND,
196 : TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
197 : NULL);
198 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
199 0 : TMH_db->rollback (TMH_db->cls);
200 0 : goto retry;
201 0 : case GNUNET_DB_STATUS_HARD_ERROR:
202 0 : TMH_db->rollback (TMH_db->cls);
203 0 : return TALER_MHD_reply_with_error (connection,
204 : MHD_HTTP_INTERNAL_SERVER_ERROR,
205 : TALER_EC_GENERIC_DB_FETCH_FAILED,
206 : NULL);
207 9 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
208 : /* Success! */
209 9 : break;
210 : }
211 :
212 9 : if (! auth_override)
213 : {
214 : // FIXME are we sure what the scope here is?
215 6 : ec = TMH_check_token (hc->auth_token,
216 6 : mi->settings.id,
217 : &hc->auth_scope);
218 6 : if (TALER_EC_NONE != ec)
219 : {
220 0 : TMH_db->rollback (TMH_db->cls);
221 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
222 : "Refusing auth change: `%s'\n",
223 : TALER_ErrorCode_get_hint (ec));
224 0 : return TALER_MHD_reply_with_error (connection,
225 : MHD_HTTP_UNAUTHORIZED,
226 : TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
227 : NULL);
228 : }
229 : }
230 : }
231 :
232 9 : qs = TMH_db->update_instance_auth (TMH_db->cls,
233 9 : mi->settings.id,
234 : &ias);
235 9 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
236 : {
237 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
238 0 : TMH_db->rollback (TMH_db->cls);
239 0 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
240 : {
241 0 : return TALER_MHD_reply_with_error (connection,
242 : MHD_HTTP_INTERNAL_SERVER_ERROR,
243 : TALER_EC_GENERIC_DB_FETCH_FAILED,
244 : NULL);
245 : }
246 0 : goto retry;
247 : }
248 9 : qs = TMH_db->commit (TMH_db->cls);
249 9 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
250 9 : qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
251 0 : retry:
252 9 : if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
253 9 : break; /* success! -- or hard failure */
254 : } /* for .. MAX_RETRIES */
255 9 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
256 : {
257 0 : return TALER_MHD_reply_with_error (connection,
258 : MHD_HTTP_INTERNAL_SERVER_ERROR,
259 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
260 : NULL);
261 : }
262 : /* Finally, also update our running process */
263 9 : mi->auth = ias;
264 : }
265 9 : TMH_reload_instances (mi->settings.id);
266 9 : return TALER_MHD_reply_static (connection,
267 : MHD_HTTP_NO_CONTENT,
268 : NULL,
269 : NULL,
270 : 0);
271 : }
272 :
273 :
274 : MHD_RESULT
275 6 : TMH_private_post_instances_ID_auth (const struct TMH_RequestHandler *rh,
276 : struct MHD_Connection *connection,
277 : struct TMH_HandlerContext *hc)
278 : {
279 6 : struct TMH_MerchantInstance *mi = hc->instance;
280 :
281 6 : return post_instances_ID_auth (mi,
282 : connection,
283 : hc,
284 : false,
285 : TEH_TCS_NONE);
286 : }
287 :
288 :
289 : MHD_RESULT
290 0 : TMH_public_post_instances_ID_auth (const struct TMH_RequestHandler *rh,
291 : struct MHD_Connection *connection,
292 : struct TMH_HandlerContext *hc)
293 : {
294 0 : struct TMH_MerchantInstance *mi = hc->instance;
295 :
296 0 : return post_instances_ID_auth (mi,
297 : connection,
298 : hc,
299 : false,
300 : TEH_mandatory_tan_channels);
301 : }
302 :
303 :
304 : MHD_RESULT
305 3 : TMH_private_post_instances_default_ID_auth (
306 : const struct TMH_RequestHandler *rh,
307 : struct MHD_Connection *connection,
308 : struct TMH_HandlerContext *hc)
309 : {
310 : struct TMH_MerchantInstance *mi;
311 : MHD_RESULT ret;
312 :
313 3 : if ( (NULL == hc->infix) ||
314 3 : (0 == strcmp ("admin",
315 3 : hc->infix)) )
316 : {
317 0 : GNUNET_break_op (0);
318 0 : return TALER_MHD_reply_with_error (
319 : connection,
320 : MHD_HTTP_FORBIDDEN,
321 : TALER_EC_MERCHANT_GENERIC_MFA_MISSING,
322 : "not allowed for 'admin' account");
323 : }
324 3 : mi = TMH_lookup_instance (hc->infix);
325 3 : if (NULL == mi)
326 : {
327 0 : return TALER_MHD_reply_with_error (
328 : connection,
329 : MHD_HTTP_NOT_FOUND,
330 : TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
331 0 : hc->infix);
332 : }
333 3 : ret = post_instances_ID_auth (mi,
334 : connection,
335 : hc,
336 : true,
337 : TEH_TCS_NONE);
338 3 : return ret;
339 : }
340 :
341 :
342 : /* end of taler-merchant-httpd_private-post-instances-ID-auth.c */
|