taler-merchant-httpd_post-management-instances-INSTANCE-auth.c (11940B)
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 TALER_MERCHANT_MFA_CO_AUTH_CONFIGURATION, 114 true, 115 TALER_MERCHANT_MFA_CHANNEL_SMS, 116 mi->settings.phone, 117 TALER_MERCHANT_MFA_CHANNEL_NONE); 118 break; 119 case TMH_TCS_EMAIL: 120 ret = TMH_mfa_challenges_do (hc, 121 TALER_MERCHANT_MFA_CO_AUTH_CONFIGURATION, 122 true, 123 TALER_MERCHANT_MFA_CHANNEL_EMAIL, 124 mi->settings.email, 125 TALER_MERCHANT_MFA_CHANNEL_NONE); 126 break; 127 case TMH_TCS_EMAIL_AND_SMS: 128 ret = TMH_mfa_challenges_do (hc, 129 TALER_MERCHANT_MFA_CO_AUTH_CONFIGURATION, 130 true, 131 TALER_MERCHANT_MFA_CHANNEL_EMAIL, 132 mi->settings.email, 133 TALER_MERCHANT_MFA_CHANNEL_SMS, 134 mi->settings.phone, 135 TALER_MERCHANT_MFA_CHANNEL_NONE); 136 break; 137 } 138 if (GNUNET_OK != ret) 139 { 140 return (GNUNET_NO == ret) 141 ? MHD_YES 142 : MHD_NO; 143 } 144 } 145 146 if (NULL == auth_pw) 147 { 148 memset (&ias.auth_salt, 149 0, 150 sizeof (ias.auth_salt)); 151 memset (&ias.auth_hash, 152 0, 153 sizeof (ias.auth_hash)); 154 } 155 else 156 { 157 TMH_compute_auth (auth_pw, 158 &ias.auth_salt, 159 &ias.auth_hash); 160 } 161 162 /* Store the new auth information in the database */ 163 { 164 enum GNUNET_DB_QueryStatus qs; 165 166 for (unsigned int i = 0; i<MAX_RETRIES; i++) 167 { 168 if (GNUNET_OK != 169 TALER_MERCHANTDB_start (TMH_db, 170 "post /instances/$ID/auth")) 171 { 172 return TALER_MHD_reply_with_error (connection, 173 MHD_HTTP_INTERNAL_SERVER_ERROR, 174 TALER_EC_GENERIC_DB_START_FAILED, 175 NULL); 176 } 177 178 /* Make the authentication update a serializable operation. 179 We first check that the authentication information 180 that the caller's request authenticated with 181 is still up to date. 182 Otherwise, we've detected a conflicting update 183 to the authentication. */ 184 { 185 struct TALER_MERCHANTDB_InstanceAuthSettings db_ias; 186 enum TALER_ErrorCode ec; 187 188 qs = TALER_MERCHANTDB_lookup_instance_auth (TMH_db, 189 mi->settings.id, 190 &db_ias); 191 192 switch (qs) 193 { 194 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 195 /* Instance got purged. */ 196 TALER_MERCHANTDB_rollback (TMH_db); 197 return TALER_MHD_reply_with_error (connection, 198 MHD_HTTP_NOT_FOUND, 199 TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN, 200 NULL); 201 case GNUNET_DB_STATUS_SOFT_ERROR: 202 TALER_MERCHANTDB_rollback (TMH_db); 203 goto retry; 204 case GNUNET_DB_STATUS_HARD_ERROR: 205 TALER_MERCHANTDB_rollback (TMH_db); 206 return TALER_MHD_reply_with_error (connection, 207 MHD_HTTP_INTERNAL_SERVER_ERROR, 208 TALER_EC_GENERIC_DB_FETCH_FAILED, 209 NULL); 210 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 211 /* Success! */ 212 break; 213 } 214 215 if (! auth_override) 216 { 217 // FIXME are we sure what the scope here is? 218 ec = TMH_check_token (hc->auth_token, 219 mi->settings.id, 220 &hc->auth_scope); 221 if (TALER_EC_NONE != ec) 222 { 223 TALER_MERCHANTDB_rollback (TMH_db); 224 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 225 "Refusing auth change: `%s'\n", 226 TALER_ErrorCode_get_hint (ec)); 227 return TALER_MHD_reply_with_error (connection, 228 MHD_HTTP_UNAUTHORIZED, 229 TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED, 230 NULL); 231 } 232 } 233 } 234 235 qs = TALER_MERCHANTDB_update_instance_auth (TMH_db, 236 mi->settings.id, 237 &ias); 238 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 239 { 240 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 241 TALER_MERCHANTDB_rollback (TMH_db); 242 if (GNUNET_DB_STATUS_HARD_ERROR == qs) 243 { 244 return TALER_MHD_reply_with_error (connection, 245 MHD_HTTP_INTERNAL_SERVER_ERROR, 246 TALER_EC_GENERIC_DB_FETCH_FAILED, 247 NULL); 248 } 249 goto retry; 250 } 251 qs = TALER_MERCHANTDB_commit (TMH_db); 252 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 253 qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 254 retry: 255 if (GNUNET_DB_STATUS_SOFT_ERROR != qs) 256 break; /* success! -- or hard failure */ 257 } /* for .. MAX_RETRIES */ 258 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 259 { 260 return TALER_MHD_reply_with_error (connection, 261 MHD_HTTP_INTERNAL_SERVER_ERROR, 262 TALER_EC_GENERIC_DB_COMMIT_FAILED, 263 NULL); 264 } 265 /* Finally, also update our running process */ 266 mi->auth = ias; 267 } 268 TMH_reload_instances (mi->settings.id); 269 return TALER_MHD_reply_static (connection, 270 MHD_HTTP_NO_CONTENT, 271 NULL, 272 NULL, 273 0); 274 } 275 276 277 enum MHD_Result 278 TMH_private_post_instances_ID_auth (const struct TMH_RequestHandler *rh, 279 struct MHD_Connection *connection, 280 struct TMH_HandlerContext *hc) 281 { 282 struct TMH_MerchantInstance *mi = hc->instance; 283 284 return post_instances_ID_auth (mi, 285 connection, 286 hc, 287 false, 288 TMH_TCS_NONE); 289 } 290 291 292 enum MHD_Result 293 TMH_public_post_instances_ID_auth (const struct TMH_RequestHandler *rh, 294 struct MHD_Connection *connection, 295 struct TMH_HandlerContext *hc) 296 { 297 struct TMH_MerchantInstance *mi = hc->instance; 298 299 if (0 == strcmp ("admin", 300 mi->settings.id)) 301 { 302 GNUNET_break_op (0); 303 return TALER_MHD_reply_with_error ( 304 connection, 305 MHD_HTTP_FORBIDDEN, 306 TALER_EC_MERCHANT_GENERIC_MFA_MISSING, 307 "not allowed for 'admin' account"); 308 } 309 return post_instances_ID_auth (mi, 310 connection, 311 hc, 312 false, 313 TEH_mandatory_tan_channels); 314 } 315 316 317 enum MHD_Result 318 TMH_private_post_instances_default_ID_auth ( 319 const struct TMH_RequestHandler *rh, 320 struct MHD_Connection *connection, 321 struct TMH_HandlerContext *hc) 322 { 323 struct TMH_MerchantInstance *mi; 324 enum MHD_Result ret; 325 326 mi = TMH_lookup_instance (hc->infix); 327 if (NULL == mi) 328 { 329 return TALER_MHD_reply_with_error ( 330 connection, 331 MHD_HTTP_NOT_FOUND, 332 TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN, 333 hc->infix); 334 } 335 ret = post_instances_ID_auth (mi, 336 connection, 337 hc, 338 true, 339 TMH_TCS_NONE); 340 return ret; 341 } 342 343 344 /* end of taler-merchant-httpd_post-management-instances-INSTANCE-auth.c */