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