diff --git a/common/djangoapps/student/management/commands/recover_account.py b/common/djangoapps/student/management/commands/recover_account.py index 78e45cdbb3..1cb1249ff6 100644 --- a/common/djangoapps/student/management/commands/recover_account.py +++ b/common/djangoapps/student/management/commands/recover_account.py @@ -16,6 +16,7 @@ from django.urls import reverse from django.utils.http import int_to_base36 from edx_ace import ace from edx_ace.recipient import Recipient +from eventtracking import tracker from common.djangoapps.student.models import AccountRecoveryConfiguration from openedx.core.djangoapps.user_authn.toggles import should_redirect_to_authn_microfrontend @@ -27,6 +28,7 @@ from openedx.core.djangoapps.user_authn.message_types import PasswordReset from openedx.core.lib.celery.task_utils import emulate_http_request logger = logging.getLogger(__name__) # pylint: disable=invalid-name +PASSWORD_RESET_INITIATED = 'edx.user.passwordreset.initiated' class Command(BaseCommand): @@ -84,6 +86,15 @@ class Command(BaseCommand): user = get_user_model().objects.get(Q(username__iexact=username) | Q(email__iexact=current_email)) user.email = desired_email user.save() + tracker.emit( + PASSWORD_RESET_INITIATED, + { + "email": user.email, + "user_id": user.id, + "old_email": current_email, + "source": "Account Recovery Management Command", + } + ) self.send_password_reset_email(user, site) successful_updates.append(desired_email) except Exception as exc: # pylint: disable=broad-except diff --git a/openedx/core/djangoapps/user_authn/views/login.py b/openedx/core/djangoapps/user_authn/views/login.py index 7d04987126..6c4eaf0bbd 100644 --- a/openedx/core/djangoapps/user_authn/views/login.py +++ b/openedx/core/djangoapps/user_authn/views/login.py @@ -24,6 +24,7 @@ from django.views.decorators.debug import sensitive_post_parameters from django.views.decorators.http import require_http_methods from django_ratelimit.decorators import ratelimit from edx_django_utils.monitoring import set_custom_attribute +from eventtracking import tracker from openedx_events.learning.data import UserData, UserPersonalData from openedx_events.learning.signals import SESSION_LOGIN_COMPLETED from openedx_filters.learning.filters import StudentLoginRequested @@ -61,6 +62,7 @@ from openedx.features.enterprise_support.api import activate_learner_enterprise, log = logging.getLogger("edx.student") AUDIT_LOG = logging.getLogger("audit") USER_MODEL = get_user_model() +PASSWORD_RESET_INITIATED = 'edx.user.passwordreset.initiated' def _do_third_party_auth(request): @@ -194,6 +196,14 @@ def _enforce_password_policy_compliance(request, user): # lint-amnesty, pylint: LoginFailures.increment_lockout_counter(user) AUDIT_LOG.info("Password reset initiated for email %s.", user.email) + tracker.emit( + PASSWORD_RESET_INITIATED, + { + "email": user.email, + "user_id": user.id, + "source": "Policy Compliance", + } + ) send_password_reset_email_for_user(user, request) # Prevent the login attempt. diff --git a/openedx/core/djangoapps/user_authn/views/password_reset.py b/openedx/core/djangoapps/user_authn/views/password_reset.py index f8049d4d91..57bde03c45 100644 --- a/openedx/core/djangoapps/user_authn/views/password_reset.py +++ b/openedx/core/djangoapps/user_authn/views/password_reset.py @@ -52,6 +52,7 @@ from common.djangoapps.util.password_policy_validators import normalize_password POST_EMAIL_KEY = 'openedx.core.djangoapps.util.ratelimit.request_post_email' REAL_IP_KEY = 'openedx.core.djangoapps.util.ratelimit.real_ip' SETTING_CHANGE_INITIATED = 'edx.user.settings.change_initiated' +PASSWORD_RESET_INITIATED = 'edx.user.passwordreset.initiated' # Maintaining this naming for backwards compatibility. log = logging.getLogger("edx.student") @@ -289,6 +290,14 @@ def password_reset(request): user = request.user # Prefer logged-in user's email email = user.email if user.is_authenticated else request.POST.get('email') + tracker.emit( + PASSWORD_RESET_INITIATED, + { + "email": email, + "user_id": user.id, + "source": "Logistration Page", + } + ) AUDIT_LOG.info("Password reset initiated for email %s.", email) if getattr(request, 'limited', False): @@ -608,6 +617,14 @@ def password_change_request_handler(request): # Prefer logged-in user's email email = user.email if user.is_authenticated else request.POST.get('email') AUDIT_LOG.info("Password reset initiated for email %s.", email) + tracker.emit( + PASSWORD_RESET_INITIATED, + { + "email": email, + "user_id": user.id, + "source": "Account API", + } + ) if getattr(request, 'limited', False) and not request_from_support_tools: AUDIT_LOG.warning("Password reset rate limit exceeded for email %s.", email)