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