# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import logging import requests from odoo import api, models, _ from odoo.http import request from odoo.exceptions import UserError, ValidationError logger = logging.getLogger(__name__) class Http(models.AbstractModel): _inherit = 'ir.http' def session_info(self): session_info = super().session_info() return self._add_public_key_to_session_info(session_info) @api.model def get_frontend_session_info(self): frontend_session_info = super().get_frontend_session_info() return self._add_public_key_to_session_info(frontend_session_info) @api.model def _add_public_key_to_session_info(self, session_info): """Add the ReCaptcha public key to the given session_info object""" public_key = self.env['ir.config_parameter'].sudo().get_param('recaptcha_public_key') if public_key: session_info['recaptcha_public_key'] = public_key return session_info @api.model def _verify_request_recaptcha_token(self, action): """ Verify the recaptcha token for the current request. If no recaptcha private key is set the recaptcha verification is considered inactive and this method will return True. """ ip_addr = request.httprequest.remote_addr token = request.params.pop('recaptcha_token_response', False) recaptcha_result = request.env['ir.http']._verify_recaptcha_token(ip_addr, token, action) if recaptcha_result in ['is_human', 'no_secret']: return True if recaptcha_result == 'wrong_secret': raise ValidationError(_("The reCaptcha private key is invalid.")) elif recaptcha_result == 'wrong_token': raise ValidationError(_("The reCaptcha token is invalid.")) elif recaptcha_result == 'timeout': raise UserError(_("Your request has timed out, please retry.")) elif recaptcha_result == 'bad_request': raise UserError(_("The request is invalid or malformed.")) else: return False @api.model def _verify_recaptcha_token(self, ip_addr, token, action=False): """ Verify a recaptchaV3 token and returns the result as a string. RecaptchaV3 verify DOC: https://developers.google.com/recaptcha/docs/verify :return: The result of the call to the google API: is_human: The token is valid and the user trustworthy. is_bot: The user is not trustworthy and most likely a bot. no_secret: No reCaptcha secret set in settings. wrong_action: the action performed to obtain the token does not match the one we are verifying. wrong_token: The token provided is invalid or empty. wrong_secret: The private key provided in settings is invalid. timeout: The request has timout or the token provided is too old. bad_request: The request is invalid or malformed. :rtype: str """ private_key = request.env['ir.config_parameter'].sudo().get_param('recaptcha_private_key') if not private_key: return 'no_secret' min_score = request.env['ir.config_parameter'].sudo().get_param('recaptcha_min_score') try: r = requests.post('https://www.recaptcha.net/recaptcha/api/siteverify', { 'secret': private_key, 'response': token, 'remoteip': ip_addr, }, timeout=2) # it takes ~50ms to retrieve the response result = r.json() res_success = result['success'] res_action = res_success and action and result['action'] except requests.exceptions.Timeout: logger.error("Trial captcha verification timeout for ip address %s", ip_addr) return 'timeout' except Exception: logger.error("Trial captcha verification bad request response") return 'bad_request' if res_success: score = result.get('score', False) if score < float(min_score): logger.warning("Trial captcha verification for ip address %s failed with score %f.", ip_addr, score) return 'is_bot' if res_action and res_action != action: logger.warning("Trial captcha verification for ip address %s failed with action %f, expected: %s.", ip_addr, score, action) return 'wrong_action' logger.info("Trial captcha verification for ip address %s succeeded with score %f.", ip_addr, score) return 'is_human' errors = result.get('error-codes', []) logger.warning("Trial captcha verification for ip address %s failed error codes %r. token was: [%s]", ip_addr, errors, token) for error in errors: if error in ['missing-input-secret', 'invalid-input-secret']: return 'wrong_secret' if error in ['missing-input-response', 'invalid-input-response']: return 'wrong_token' if error == 'timeout-or-duplicate': return 'timeout' if error == 'bad-request': return 'bad_request' return 'is_bot'