taler-merchant-httpd_private-post-instances-ID-auth.c (11645B)
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 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 const char *auth_pw = NULL; 60 json_t *jauth = hc->request_body; 61 62 { 63 enum GNUNET_GenericReturnValue ret; 64 65 ret = TMH_check_auth_config (connection, 66 jauth, 67 &auth_pw); 68 if (GNUNET_OK != ret) 69 return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; 70 } 71 72 if ( (0 != (tcs & TEH_TCS_SMS) && 73 ( (NULL == mi->settings.phone) || 74 (NULL == TMH_helper_sms) || 75 (! mi->settings.phone_validated) ) ) ) 76 { 77 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 78 "Cannot reset password: SMS factor not available\n"); 79 return TALER_MHD_reply_with_error ( 80 connection, 81 MHD_HTTP_FORBIDDEN, 82 TALER_EC_MERCHANT_GENERIC_MFA_MISSING, 83 "phone_number"); 84 } 85 if ( (0 != (tcs & TEH_TCS_EMAIL) && 86 ( (NULL == mi->settings.email) || 87 (NULL == TMH_helper_email) || 88 (! mi->settings.email_validated) ) ) ) 89 { 90 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 91 "Cannot reset password: E-mail factor not available\n"); 92 return TALER_MHD_reply_with_error ( 93 connection, 94 MHD_HTTP_FORBIDDEN, 95 TALER_EC_MERCHANT_GENERIC_MFA_MISSING, 96 "email"); 97 } 98 if (! auth_override) 99 { 100 enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; // fix -Wmaybe-uninitialized 101 102 switch (tcs) 103 { 104 case TEH_TCS_NONE: 105 ret = GNUNET_OK; 106 break; 107 case TEH_TCS_SMS: 108 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 break; 115 case TEH_TCS_EMAIL: 116 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 break; 123 case TEH_TCS_EMAIL_AND_SMS: 124 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 break; 133 } 134 if (GNUNET_OK != ret) 135 { 136 return (GNUNET_NO == ret) 137 ? MHD_YES 138 : MHD_NO; 139 } 140 } 141 142 if (NULL == auth_pw) 143 { 144 memset (&ias.auth_salt, 145 0, 146 sizeof (ias.auth_salt)); 147 memset (&ias.auth_hash, 148 0, 149 sizeof (ias.auth_hash)); 150 } 151 else 152 { 153 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 for (unsigned int i = 0; i<MAX_RETRIES; i++) 163 { 164 if (GNUNET_OK != 165 TMH_db->start (TMH_db->cls, 166 "post /instances/$ID/auth")) 167 { 168 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 qs = TMH_db->lookup_instance_auth (TMH_db->cls, 185 mi->settings.id, 186 &db_ias); 187 188 switch (qs) 189 { 190 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 191 /* Instance got purged. */ 192 TMH_db->rollback (TMH_db->cls); 193 return TALER_MHD_reply_with_error (connection, 194 MHD_HTTP_NOT_FOUND, 195 TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN, 196 NULL); 197 case GNUNET_DB_STATUS_SOFT_ERROR: 198 TMH_db->rollback (TMH_db->cls); 199 goto retry; 200 case GNUNET_DB_STATUS_HARD_ERROR: 201 TMH_db->rollback (TMH_db->cls); 202 return TALER_MHD_reply_with_error (connection, 203 MHD_HTTP_INTERNAL_SERVER_ERROR, 204 TALER_EC_GENERIC_DB_FETCH_FAILED, 205 NULL); 206 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 207 /* Success! */ 208 break; 209 } 210 211 if (! auth_override) 212 { 213 // FIXME are we sure what the scope here is? 214 ec = TMH_check_token (hc->auth_token, 215 mi->settings.id, 216 &hc->auth_scope); 217 if (TALER_EC_NONE != ec) 218 { 219 TMH_db->rollback (TMH_db->cls); 220 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 221 "Refusing auth change: `%s'\n", 222 TALER_ErrorCode_get_hint (ec)); 223 return TALER_MHD_reply_with_error (connection, 224 MHD_HTTP_UNAUTHORIZED, 225 TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED, 226 NULL); 227 } 228 } 229 } 230 231 qs = TMH_db->update_instance_auth (TMH_db->cls, 232 mi->settings.id, 233 &ias); 234 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 235 { 236 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 237 TMH_db->rollback (TMH_db->cls); 238 if (GNUNET_DB_STATUS_HARD_ERROR == qs) 239 { 240 return TALER_MHD_reply_with_error (connection, 241 MHD_HTTP_INTERNAL_SERVER_ERROR, 242 TALER_EC_GENERIC_DB_FETCH_FAILED, 243 NULL); 244 } 245 goto retry; 246 } 247 qs = TMH_db->commit (TMH_db->cls); 248 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 249 qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 250 retry: 251 if (GNUNET_DB_STATUS_SOFT_ERROR != qs) 252 break; /* success! -- or hard failure */ 253 } /* for .. MAX_RETRIES */ 254 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 255 { 256 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 mi->auth = ias; 263 } 264 TMH_reload_instances (mi->settings.id); 265 return TALER_MHD_reply_static (connection, 266 MHD_HTTP_NO_CONTENT, 267 NULL, 268 NULL, 269 0); 270 } 271 272 273 MHD_RESULT 274 TMH_private_post_instances_ID_auth (const struct TMH_RequestHandler *rh, 275 struct MHD_Connection *connection, 276 struct TMH_HandlerContext *hc) 277 { 278 struct TMH_MerchantInstance *mi = hc->instance; 279 280 return post_instances_ID_auth (mi, 281 connection, 282 hc, 283 false, 284 TEH_TCS_NONE); 285 } 286 287 288 MHD_RESULT 289 TMH_public_post_instances_ID_auth (const struct TMH_RequestHandler *rh, 290 struct MHD_Connection *connection, 291 struct TMH_HandlerContext *hc) 292 { 293 struct TMH_MerchantInstance *mi = hc->instance; 294 295 return post_instances_ID_auth (mi, 296 connection, 297 hc, 298 false, 299 TEH_mandatory_tan_channels); 300 } 301 302 303 MHD_RESULT 304 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 if ( (NULL == hc->infix) || 313 (0 == strcmp ("admin", 314 hc->infix)) ) 315 { 316 GNUNET_break_op (0); 317 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 mi = TMH_lookup_instance (hc->infix); 324 if (NULL == mi) 325 { 326 return TALER_MHD_reply_with_error ( 327 connection, 328 MHD_HTTP_NOT_FOUND, 329 TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN, 330 hc->infix); 331 } 332 ret = post_instances_ID_auth (mi, 333 connection, 334 hc, 335 true, 336 TEH_TCS_NONE); 337 return ret; 338 } 339 340 341 /* end of taler-merchant-httpd_private-post-instances-ID-auth.c */