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