ReCaptcha Enterprise в формах django без django-recaptcha

Как я могу реализовать новые reCaptcha Enterprise в django на форме без использования старых django-recaptcha?

Я удалил django-recaptcha = "==2.0.6", которые использовали v2 или v3 reCaptcha не Enterprise (. Но я использовал логику интеграции reCaptcha оттуда). Для этого:

  • install google-cloud-recaptcha-enterprise = "==1.4.1", предоставил учетные данные через строку, которую получил из google-console как key.json и установил ее в env; получили учетные данные

RECAPTCHA_CREDENTIALS_JSON = ast.literal_eval(
    os.getenv("RECAPTCHA_CREDENTIALS_JSON", "{}",)
    .replace("\r", "\\r")
    .replace("\n", "\\n")
)

RECAPTCHA_CREDENTIALS_JSON должен быть представлен в виде строки, как:

RECAPTCHA_CREDENTIALS_JSON="{ "type": "service_account", "project_id":..., "private_key": "..." ...., }"
  • получил sitekey для Enterprise RECAPTCHA_PUBLIC_KEY;

    RECAPTCHA_PUBLIC_KEY = os.getenv("RECAPTCHA_PUBLIC_KEY", "...")

  • добавлен в INSTALLED_APPS "django.forms",;

  • добавлен в настройки FORM_RENDERER = "django.forms.renderers.TemplatesSetting";

В Forms.py:

from google.cloud import recaptchaenterprise_v1

RECAPTCHA_ACTION = "login"

if settings.RECAPTCHA_CREDENTIALS_JSON and settings.RECAPTCHA_PUBLIC_KEY:
    client = recaptchaenterprise_v1.RecaptchaEnterpriseServiceClient.from_service_account_info(
        settings.RECAPTCHA_CREDENTIALS_JSON
    )
else:
    client = None

Метод проверки результата для поля captcha:

def create_assessment(token):
    """Create an assessment to analyze the risk of a UI action.
    Args:
    projectID: GCloud Project ID
    recaptchaSiteKey: Site key obtained by registering a domain/app to use recaptcha services.
    token: The token obtained from the client on passing the recaptchaSiteKey.
    recaptchaAction: Action name corresponding to the token.
    Return:
    bool: True if successful.
    """
    project_id = "....."
    recaptcha_site_key = settings.RECAPTCHA_PUBLIC_KEY
    recaptcha_action = RECAPTCHA_ACTION

    # Set the properties of the event to be tracked.
    event = recaptchaenterprise_v1.Event(expected_action=recaptcha_action)
    event.site_key = recaptcha_site_key
    event.token = token

    assessment = recaptchaenterprise_v1.Assessment()
    assessment.event = event

    project_name = f"projects/{project_id}"

    # Build the assessment request.
    request = recaptchaenterprise_v1.CreateAssessmentRequest()
    request.assessment = assessment
    request.parent = project_name

    response = client.create_assessment(request)

    # Check if the token is valid.
    if not response.token_properties.valid:
        logger.info(
            "The CreateAssessment call failed because the token was "
            + "invalid for for the following reasons: "
            + str(response.token_properties.invalid_reason)
        )
        return None
    return True

Вышеуказанный метод получает token, который мы должны получить из "виджета". Код виджета:

class ReCaptchaBase(forms.widgets.Widget):
    """
    Base widget to be used for Google ReCAPTCHA.
    public_key -- String value: can optionally be passed to not make use of the
        project wide Google Site Key.
    """
    recaptcha_response_name = "g-recaptcha-response"
    template_name = "oauth2_provider/widget_v2_checkbox.html"

    def value_from_datadict(self, data, files, name):
        return data.get(self.recaptcha_response_name, None)

    def build_attrs(self, base_attrs, extra_attrs=None):
        attrs = super(ReCaptchaBase, self).build_attrs(base_attrs, extra_attrs)
        attrs["data-callback"] = base_attrs.get("data-callback", "onloadCallback")
        attrs["sitekey"] = base_attrs.get("sitekey", settings.RECAPTCHA_PUBLIC_KEY)
        attrs["theme"] = base_attrs.get("theme", "light")
        attrs["action"] = base_attrs.get("action", RECAPTCHA_ACTION)
        return attrs
  • value_from_datadict: помогает мне получить client токен;
  • build_attrs: атрибуты, которые передаются в шаблон;

Добавьте поле в нашу форму, я переписывал PasswordResetForm. Я добавил его как CharFiled в наш виджет.

class ReCaptchaField(forms.CharField):

    widget = ReCaptchaBase()

    def validate(self, value):
        if not create_assessment(value):
            raise forms.ValidationError(
                self.error_messages["required"], code="required"
            )
        return value

Форма:

class CustomPasswordResetForm(PasswordResetForm):
    """Reset password form override"""

    def __init__(self, *args, **kwargs):
        super(CustomPasswordResetForm, self).__init__(*args, **kwargs)
        if not settings.RECAPTCHA_CREDENTIALS_JSON or not settings.RECAPTCHA_PUBLIC_KEY:
            self.fields.pop("captcha")

    captcha = ReCaptchaField()
  • __init__: удалять капчу всякий раз, когда учетные данные не предоставлены

Добавьте captcha block к форме в основном шаблоне password_reset_form.html:

{% block captcha %}
<div class="justify-center">
    <div class="w-full flex mt-3 justify-center">
        {{ form.captcha }}
    </div>
    <div class="w-full flex mt-3 justify-center">
        <p>{{ form.errors.captcha }}</p>
    </div>
</div>
{% endblock captcha %}

шаблон для виджета widget_v2_checkbox.html:

<script src="https://www.google.com/recaptcha/enterprise.js?onload=onloadCallback&render=explicit" async defer></script>
<script type="text/javascript">
    // Submit function to be called, after reCAPTCHA was successful.
    var onloadCallback = function () {
        grecaptcha.enterprise.render('id_captcha', {
            'sitekey': '{{ widget.attrs.sitekey }}',
            'action': '{{ widget.attrs.action }}',
            'theme': '{{ widget.attrs.theme }}',
        });
    };
</script>
<div class="captcha" id="id_captcha" 
{% for name, value in widget.attrs.items %} 
    {% if value is not False %} {{name}} 
    {% if value is not True %}="{{ value|stringformat:'s' }}"
    {% endif %}{% endif %}
{% endfor %}>
</div> 
Вернуться на верх