fix: updated captcha api to use enterprise assessment (#37079)
* fix: updated captcha api to use enterprise assessment
This commit is contained in:
@@ -25,7 +25,7 @@ def is_forum_daily_digest_enabled():
|
||||
# .. toggle_name: discussion.enable_captcha
|
||||
# .. toggle_implementation: CourseWaffleFlag
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: Waffle flag to enable account level preferences for notifications
|
||||
# .. toggle_description: When the flag is ON, users will be able to see captcha for discussion
|
||||
# .. toggle_use_cases: temporary, open_edx
|
||||
# .. toggle_creation_date: 2025-07-12
|
||||
# .. toggle_target_removal_date: 2025-07-29
|
||||
|
||||
@@ -130,7 +130,7 @@ from .utils import (
|
||||
get_usernames_from_search_string,
|
||||
set_attribute,
|
||||
is_posting_allowed,
|
||||
can_user_notify_all_learners, is_captcha_enabled
|
||||
can_user_notify_all_learners, is_captcha_enabled, get_captcha_site_key_by_platform
|
||||
)
|
||||
|
||||
User = get_user_model()
|
||||
@@ -383,7 +383,7 @@ def get_course(request, course_key, check_tab=True):
|
||||
),
|
||||
'captcha_settings': {
|
||||
'enabled': is_captcha_enabled(course_key),
|
||||
'site_key': settings.RECAPTCHA_SITE_KEY,
|
||||
'site_key': get_captcha_site_key_by_platform('web'),
|
||||
},
|
||||
"is_email_verified": request.user.is_active,
|
||||
"only_verified_users_can_post": ONLY_VERIFIED_USERS_CAN_POST.is_enabled(course_key),
|
||||
|
||||
@@ -218,7 +218,7 @@ class GetCourseTest(ForumsEnableMixin, UrlResetMixin, SharedModuleStoreTestCase)
|
||||
'is_notify_all_learners_enabled': False,
|
||||
'captcha_settings': {
|
||||
'enabled': False,
|
||||
'site_key': '',
|
||||
'site_key': None,
|
||||
},
|
||||
"is_email_verified": True,
|
||||
"only_verified_users_can_post": False,
|
||||
|
||||
@@ -570,7 +570,7 @@ class CourseViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
'is_notify_all_learners_enabled': False,
|
||||
'captcha_settings': {
|
||||
'enabled': False,
|
||||
'site_key': '',
|
||||
'site_key': None,
|
||||
},
|
||||
"is_email_verified": True,
|
||||
"only_verified_users_can_post": False,
|
||||
|
||||
@@ -6,6 +6,7 @@ from datetime import datetime
|
||||
from typing import Dict, List
|
||||
|
||||
import requests
|
||||
from crum import get_current_request
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
|
||||
from django.core.paginator import Paginator
|
||||
@@ -413,26 +414,6 @@ def can_user_notify_all_learners(user_roles, is_course_staff, is_course_admin):
|
||||
return is_staff_or_instructor
|
||||
|
||||
|
||||
def verify_recaptcha_token(token):
|
||||
"""
|
||||
Helper function to verify reCAPTCHA token
|
||||
"""
|
||||
verify_url = settings.RECAPTCHA_VERIFY_URL
|
||||
verify_data = {
|
||||
'secret': settings.RECAPTCHA_PRIVATE_KEY,
|
||||
'response': token,
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(verify_url, data=verify_data, timeout=10)
|
||||
result = response.json()
|
||||
log.info("reCAPTCHA verification result: %s", result)
|
||||
return result.get('success', False)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
log.error("Error verifying reCAPTCHA token: %s", e)
|
||||
return False
|
||||
|
||||
|
||||
def is_captcha_enabled(course_id) -> bool:
|
||||
"""
|
||||
Check if reCAPTCHA is enabled for discussion posts in the given course.
|
||||
@@ -463,3 +444,55 @@ def is_only_student(course_key, user) -> bool:
|
||||
is_user_admin = user.is_staff
|
||||
user_roles = get_user_role_names(user, course_key)
|
||||
return user_roles == {FORUM_ROLE_STUDENT} and not (is_course_staff_or_admin or is_user_admin)
|
||||
|
||||
|
||||
def verify_recaptcha_token(token: str) -> bool:
|
||||
"""
|
||||
Assess the reCAPTCHA token using Google reCAPTCHA Enterprise API.
|
||||
Logs success or error and returns True if an error occurs, along with logging the error.
|
||||
"""
|
||||
try:
|
||||
site_key = get_captcha_site_key_by_platform(get_platform_from_request())
|
||||
url = (f"https://recaptchaenterprise.googleapis.com/v1/projects/{settings.RECAPTCHA_PROJECT_ID}/assessments"
|
||||
f"?key={settings.RECAPTCHA_PRIVATE_KEY}")
|
||||
data = {
|
||||
"event": {
|
||||
"token": token,
|
||||
"siteKey": site_key,
|
||||
}
|
||||
}
|
||||
|
||||
response = requests.post(url, json=data, timeout=10).json()
|
||||
|
||||
if response.get('tokenProperties', {}).get('valid'):
|
||||
logging.info("reCAPTCHA token assessment successful. Token is valid.")
|
||||
return True
|
||||
elif response.get('error'):
|
||||
logging.error(f"reCAPTCHA token assessment failed: {response['error']}.")
|
||||
return True
|
||||
else:
|
||||
logging.error(f"reCAPTCHA token assessment failed: Invalid token.{response}.")
|
||||
return False
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error(f"Network or API error during reCAPTCHA assessment: {e}")
|
||||
return True
|
||||
except KeyError as e:
|
||||
logging.error(f"Unexpected response format from reCAPTCHA API. Missing key: {e}. Full response: {response}")
|
||||
return True
|
||||
except Exception as e: # lint-amnesty, pylint: disable=broad-except
|
||||
logging.error(f"An unexpected error occurred during reCAPTCHA assessment: {e}", exc_info=True)
|
||||
return True
|
||||
|
||||
|
||||
def get_platform_from_request():
|
||||
"""
|
||||
get Mobile-Platform-Identifier header value from request
|
||||
"""
|
||||
return get_current_request().headers.get('Mobile-Platform-Identifier', 'web')
|
||||
|
||||
|
||||
def get_captcha_site_key_by_platform(platform: str) -> str | None:
|
||||
"""
|
||||
Get reCAPTCHA site key based on the platform.
|
||||
"""
|
||||
return settings.RECAPTCHA_SITE_KEYS.get(platform, None)
|
||||
|
||||
@@ -5001,12 +5001,28 @@ LMS_COMM_DEFAULT_FROM_EMAIL = "no-reply@example.com"
|
||||
# .. setting_description: Add recaptcha private key to use captcha feature in discussion app.
|
||||
RECAPTCHA_PRIVATE_KEY = ""
|
||||
|
||||
# .. setting_name: RECAPTCHA_VERIFY_URL
|
||||
# .. setting_default: empty string
|
||||
# .. setting_description: Add recaptcha verification api url to verify capthca tokens.
|
||||
RECAPTCHA_VERIFY_URL = ""
|
||||
# .. setting_name: RECAPTCHA_SITE_KEYS
|
||||
# .. setting_default: empty dictionary
|
||||
# .. setting_description: Add recaptcha site keys to use captcha feature in discussion app.
|
||||
# .. setting_warning: This setting is used to configure the reCAPTCHA keys for web,
|
||||
# iOS, and Android platforms.
|
||||
# The keys are expected to be in the format:
|
||||
# {
|
||||
# 'web': 'your-web-site-key',
|
||||
# 'ios': 'your-ios-site-key',
|
||||
# 'android': 'your-android-site-key',
|
||||
# }
|
||||
RECAPTCHA_SITE_KEYS = {
|
||||
'web': None,
|
||||
'ios': None,
|
||||
'android': None,
|
||||
}
|
||||
|
||||
# .. setting_name: RECAPTCHA_SITE_KEY
|
||||
# .. setting_default: empty string
|
||||
# .. setting_description: Add recaptcha site key to use captcha feature in discussion MFE.
|
||||
RECAPTCHA_SITE_KEY = ""
|
||||
# .. setting_name: RECAPTCHA_PROJECT_ID
|
||||
# .. setting_default: None
|
||||
# .. setting_description: Add recaptcha project id to use captcha feature in discussion app.
|
||||
# .. setting_warning: This setting is used to configure the reCAPTCHA project ID for the discussion app.
|
||||
# The project ID is used to identify the reCAPTCHA project in the Google Cloud Console
|
||||
# and is required for the reCAPTCHA service to function correctly.
|
||||
# The project ID should be obtained from the Google Cloud Console when creating a reCAPTCHA
|
||||
RECAPTCHA_PROJECT_ID = None
|
||||
|
||||
Reference in New Issue
Block a user