diff --git a/cms/djangoapps/course_creators/admin.py b/cms/djangoapps/course_creators/admin.py index 75f29c6ca669..5f9f713f0213 100644 --- a/cms/djangoapps/course_creators/admin.py +++ b/cms/djangoapps/course_creators/admin.py @@ -158,8 +158,11 @@ def send_user_notification_callback(sender, **kwargs): # pylint: disable=unused try: user.email_user(subject, message, studio_request_email) - except: # pylint: disable=bare-except - log.warning("Unable to send course creator status e-mail to %s", user.email) + except: # lint-amnesty, pylint: disable=bare-except + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.warning("Unable to send course creator status e-mail to user ID %s", user.id) + else: + log.warning("Unable to send course creator status e-mail to %s", user.email) @receiver(send_admin_notification, sender=CourseCreator) @@ -185,7 +188,10 @@ def send_admin_notification_callback(sender, **kwargs): # pylint: disable=unuse fail_silently=False ) except SMTPException: - log.warning("Failure sending 'pending state' e-mail for %s to %s", user.email, studio_request_email) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.warning("Failure sending 'pending state' e-mail for user ID %s to [REDACTED]", user.id) + else: + log.warning("Failure sending 'pending state' e-mail for %s to %s", user.email, studio_request_email) @receiver(m2m_changed, sender=CourseCreator.organizations.through) diff --git a/common/djangoapps/student/emails.py b/common/djangoapps/student/emails.py index 6b4641722de6..91cb340fee1c 100644 --- a/common/djangoapps/student/emails.py +++ b/common/djangoapps/student/emails.py @@ -27,8 +27,14 @@ def send_proctoring_requirements_email(context): user_context={'full_name': user.profile.name} ) ace.send(msg) - log.info('Proctoring requirements email sent to user: %r', user.username) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info('Proctoring requirements email sent to user ID: %r', user.id) + else: + log.info('Proctoring requirements email sent to user: %r', user.username) return True except Exception: # pylint: disable=broad-except - log.exception('Could not send email for proctoring requirements to user %s', user.username) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.exception('Could not send email for proctoring requirements to user ID %s', user.id) + else: + log.exception('Could not send email for proctoring requirements to user %s', user.username) return False diff --git a/common/djangoapps/student/models/course_enrollment.py b/common/djangoapps/student/models/course_enrollment.py index cbf257b77f46..f422f627b844 100644 --- a/common/djangoapps/student/models/course_enrollment.py +++ b/common/djangoapps/student/models/course_enrollment.py @@ -731,15 +731,18 @@ def enroll(cls, user, course_key, mode=None, check_access=False, can_upgrade=Fal course_key=course.id, display_name=course.display_name, ) - except CourseOverview.DoesNotExist: + except CourseOverview.DoesNotExist as err: # This is here to preserve legacy behavior which allowed enrollment in courses # announced before the start of content creation. course_data = CourseData( course_key=course_key, ) if check_access: - log.warning("User %s failed to enroll in non-existent course %s", user.username, str(course_key)) - raise NonExistentCourseError # pylint: disable=raise-missing-from # noqa: B904 + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.warning("User %s failed to enroll in non-existent course %s", user.id, str(course_key)) + else: + log.warning("User %s failed to enroll in non-existent course %s", user.username, str(course_key)) + raise NonExistentCourseError from err # lint-amnesty, pylint: disable=raise-missing-from if check_access: if cls.is_enrollment_closed(user, course) and not can_upgrade: @@ -829,7 +832,10 @@ def enroll_by_email(cls, email, course_id, mode=None, ignore_errors=True): return cls.enroll(user, course_id, mode) except User.DoesNotExist: err_msg = "Tried to enroll email {} into course {}, but user not found" - log.error(err_msg.format(email, course_id)) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.error(err_msg.format('[PII_REDACTED]', course_id)) + else: + log.error(err_msg.format(email, course_id)) if ignore_errors: return None raise diff --git a/common/djangoapps/student/models/user.py b/common/djangoapps/student/models/user.py index 4789f0e47c38..c5c4edbd7422 100644 --- a/common/djangoapps/student/models/user.py +++ b/common/djangoapps/student/models/user.py @@ -887,7 +887,10 @@ def activate(self): # pylint: disable=missing-function-docstring self.activation_timestamp = datetime.utcnow() self.save() USER_ACCOUNT_ACTIVATED.send_robust(self.__class__, user=self.user) - log.info('User %s (%s) account is successfully activated.', self.user.username, self.user.email) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info('User %s account is successfully activated.', self.user.id) + else: + log.info('User %s (%s) account is successfully activated.', self.user.username, self.user.email) class PendingNameChange(DeletableByUserValue, models.Model): # noqa: DJ008 diff --git a/common/djangoapps/student/views/management.py b/common/djangoapps/student/views/management.py index 5609afd49ddc..c6441738496d 100644 --- a/common/djangoapps/student/views/management.py +++ b/common/djangoapps/student/views/management.py @@ -489,7 +489,10 @@ def change_enrollment(request, check_access=True): except UnenrollmentNotAllowed as exc: return HttpResponseBadRequest(str(exc)) - log.info("User %s unenrolled from %s; sending REFUND_ORDER", user.username, course_id) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info("User %s unenrolled from %s; sending REFUND_ORDER", user.id, course_id) + else: + log.info("User %s unenrolled from %s; sending REFUND_ORDER", user.username, course_id) REFUND_ORDER.send(sender=None, course_enrollment=enrollment) return HttpResponse() else: @@ -557,11 +560,17 @@ def disable_account_ajax(request): if account_action == 'disable': user_account.account_status = UserStanding.ACCOUNT_DISABLED context['message'] = _("Successfully disabled {}'s account").format(username) - log.info("%s disabled %s's account", request.user, username) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info("%s disabled user %s's account", request.user.id, '[PII_REDACTED]') + else: + log.info("%s disabled %s's account", request.user, username) elif account_action == 'reenable': user_account.account_status = UserStanding.ACCOUNT_ENABLED context['message'] = _("Successfully reenabled {}'s account").format(username) - log.info("%s reenabled %s's account", request.user, username) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info("%s reenabled user %s's account", request.user.id, '[PII_REDACTED]') + else: + log.info("%s reenabled %s's account", request.user, username) else: context['message'] = _("Unexpected account status") return JsonResponse(context, status=400) @@ -847,11 +856,17 @@ def do_email_change_request(user, new_email, activation_key=None, secondary_emai try: ace.send(msg) - log.info("Email activation link sent to user [%s].", new_email) - except Exception: + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info("Email activation link sent for user ID: [%s].", user.id) + else: + log.info("Email activation link sent to user [%s].", new_email) + except Exception as err: from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) - log.error('Unable to send email activation link to user from "%s"', from_address, exc_info=True) - raise ValueError(_('Unable to send email activation link. Please try again later.')) # pylint: disable=raise-missing-from # noqa: B904 + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.error('Unable to send email activation link from a redacted address', exc_info=True) + else: + log.error('Unable to send email activation link to user from "%s"', from_address, exc_info=True) + raise ValueError(_('Unable to send email activation link. Please try again later.')) from err # lint-amnesty, pylint: disable=raise-missing-from if not secondary_email_change_request: # When the email address change is complete, a "edx.user.settings.changed" event will be emitted. @@ -900,7 +915,7 @@ def activate_secondary_email(request, key): @ensure_csrf_cookie -def confirm_email_change(request, key): +def confirm_email_change(request, key): # pylint: disable=too-many-statements """ User requested a new e-mail. This is called when the activation link is clicked. We confirm with the old e-mail, and update @@ -960,7 +975,10 @@ def confirm_email_change(request, key): try: ace.send(msg) except Exception: # pylint: disable=broad-except - log.warning('Unable to send confirmation email to old address', exc_info=True) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.warning('Unable to send confirmation email to old address [REDACTED]', exc_info=True) + else: + log.warning('Unable to send confirmation email to old address', exc_info=True) response = render_to_response("email_change_failed.html", {'email': user.email}) transaction.set_rollback(True) return response @@ -976,7 +994,10 @@ def confirm_email_change(request, key): try: ace.send(msg) except Exception: # pylint: disable=broad-except - log.warning('Unable to send confirmation email to new address', exc_info=True) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.warning('Unable to send confirmation email to new address [REDACTED]', exc_info=True) + else: + log.warning('Unable to send confirmation email to new address', exc_info=True) response = render_to_response("email_change_failed.html", {'email': user.email}) transaction.set_rollback(True) return response diff --git a/lms/djangoapps/bulk_user_retirement/views.py b/lms/djangoapps/bulk_user_retirement/views.py index 28f23651fb65..69219226d067 100644 --- a/lms/djangoapps/bulk_user_retirement/views.py +++ b/lms/djangoapps/bulk_user_retirement/views.py @@ -3,6 +3,7 @@ """ import logging +from django.conf import settings from django.contrib.auth import get_user_model from django.db import transaction from rest_framework import permissions, status @@ -56,11 +57,19 @@ def post(self, request, **kwargs): # pylint: disable=unused-argument user_to_retire = User.objects.get(username=username) with transaction.atomic(): create_retirement_request_and_deactivate_account(user_to_retire) - log.info(f'The user "{username}" has been added to the retirement pipeline \ - by "{request.user}"') + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info('A user has been added to the retirement pipeline') + else: + log.info('The user "%s" has been added to the retirement pipeline by "%s"', + username, + request.user, + ) except User.DoesNotExist: - log.exception(f'The user "{username}" does not exist.') + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.exception('A user does not exist for bulk retirement.') + else: + log.exception(f'The user "{username}" does not exist.') failed_user_retirements.append(username) except Exception as exc: # pylint: disable=broad-except diff --git a/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py b/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py index b9aac5e8efaa..e89f419d46a4 100644 --- a/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py +++ b/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py @@ -50,7 +50,10 @@ def send_ace_message(goal, session_id): """ user = goal.user if not user.has_usable_password(): - log.info(f'Goal Reminder User is disabled {user.username} course {goal.course_key}') + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info(f'Goal Reminder User is disabled user ID {user.id} course {goal.course_key}') + else: + log.info(f'Goal Reminder User is disabled {user.username} course {goal.course_key}') return False try: course = CourseOverview.get_from_id(goal.course_key) diff --git a/lms/djangoapps/courseware/model_data.py b/lms/djangoapps/courseware/model_data.py index ed5cde367ae1..53447dde1b92 100644 --- a/lms/djangoapps/courseware/model_data.py +++ b/lms/djangoapps/courseware/model_data.py @@ -27,6 +27,7 @@ from abc import ABCMeta, abstractmethod from collections import defaultdict, namedtuple +from django.conf import settings from django.db import DatabaseError, IntegrityError, transaction from opaque_keys.edx.asides import AsideUsageKeyV1, AsideUsageKeyV2 from opaque_keys.edx.block_types import BlockTypeKeyV1 @@ -404,9 +405,12 @@ def set_many(self, kv_dict): self.user.username, pending_updates ) - except DatabaseError: - log.exception("Saving user state failed for %s", self.user.username) - raise KeyValueMultiSaveError([]) # pylint: disable=raise-missing-from # noqa: B904 + except DatabaseError as err: + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.exception("Saving user state failed for user ID %s", self.user.id) + else: + log.exception("Saving user state failed for %s", self.user.username) + raise KeyValueMultiSaveError([]) from err # lint-amnesty, pylint: disable=raise-missing-from finally: self._cache.update(pending_updates) diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 960be2fd7849..39b08cb3e53a 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -1479,7 +1479,10 @@ def generate_user_cert(request, course_id): return HttpResponseBadRequest(str(e)) if not is_course_passed(student, course): - log.info("User %s has not passed the course: %s", student.username, course_id) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info("User ID %s has not passed the course: %s", student.id, course_id) + else: + log.info("User %s has not passed the course: %s", student.username, course_id) return HttpResponseBadRequest(_("Your certificate will be available when you pass the course.")) certificate_status = certs_api.certificate_downloadable_status(student, course.id) diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 9b629fd807c7..44dadf23ddec 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -462,7 +462,10 @@ def post(self, request, course_id): # pylint: disable=too-many-statements warnings.append({ 'username': username, 'email': email, 'response': warning_message }) - log.warning('email %s already exist', email) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.warning('email [REDACTED] already exist') + else: + log.warning('email %s already exist', email) else: log.info( "user already exists with username '%s' and email '%s'", diff --git a/lms/djangoapps/instructor/views/instructor_task_helpers.py b/lms/djangoapps/instructor/views/instructor_task_helpers.py index e50687da3a77..f9d17e37e95d 100644 --- a/lms/djangoapps/instructor/views/instructor_task_helpers.py +++ b/lms/djangoapps/instructor/views/instructor_task_helpers.py @@ -7,6 +7,7 @@ import json import logging +from django.conf import settings from django.utils.translation import gettext as _ from django.utils.translation import ngettext @@ -55,7 +56,10 @@ def extract_email_features(email_task): try: task_input_information = json.loads(email_task.task_input) except ValueError: - log.error("Could not parse task input as valid json; task input: %s", email_task.task_input) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.error("Could not parse task input as valid json; task input: [REDACTED]") + else: + log.error("Could not parse task input as valid json; task input: %s", email_task.task_input) return email_error_information() email = CourseEmail.objects.get(id=task_input_information['email_id']) @@ -82,7 +86,10 @@ def extract_email_features(email_task): try: task_output = json.loads(email_task.task_output) except ValueError: - log.error("Could not parse task output as valid json; task output: %s", email_task.task_output) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.error("Could not parse task output as valid json; task output: [REDACTED]") + else: + log.error("Could not parse task output as valid json; task output: %s", email_task.task_output) else: if 'succeeded' in task_output and task_output['succeeded'] > 0: num_emails = task_output['succeeded'] diff --git a/lms/djangoapps/verify_student/management/commands/manual_verifications.py b/lms/djangoapps/verify_student/management/commands/manual_verifications.py index bb84fabb2713..73f3f90bb515 100644 --- a/lms/djangoapps/verify_student/management/commands/manual_verifications.py +++ b/lms/djangoapps/verify_student/management/commands/manual_verifications.py @@ -7,6 +7,7 @@ import os from pprint import pformat +from django.conf import settings from django.contrib.auth.models import User # pylint: disable=imported-auth-user from django.core.management.base import BaseCommand, CommandError @@ -53,7 +54,10 @@ def handle(self, *args, **options): if single_email: successfully_verified = self._add_user_to_manual_verification(single_email) if successfully_verified is False: - log.error(f'Manual verification of {single_email} failed') + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.error('Manual verification of [REDACTED_EMAIL] failed') + else: + log.error(f'Manual verification of {single_email} failed') return email_ids_file = options['email_ids_file'] @@ -70,7 +74,10 @@ def handle(self, *args, **options): len(failed_emails), total_emails )) - log.error(f'Failed emails:{pformat(failed_emails)}') + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.error('Failed emails: [REDACTED]') + else: + log.error(f'Failed emails:{pformat(failed_emails)}') else: log.info(f'Successfully generated manual verification for {total_emails} emails.') @@ -122,7 +129,10 @@ def _add_users_to_manual_verification(self, email_ids): status='approved', )) else: - log.info(f'Skipping email {user.email}, existing verification found.') + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info(f'Skipping user ID {user.id}, existing verification found.') + else: + log.info(f'Skipping email {user.email}, existing verification found.') ManualVerification.objects.bulk_create(verifications_to_create) failed_emails = set(email_ids) - set(users.values_list('email', flat=True)) return list(failed_emails) @@ -147,5 +157,8 @@ def _add_user_to_manual_verification(self, email_id): ) return True except User.DoesNotExist: - log.error(f'Tried to verify email {email_id}, but user not found') + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.error('Tried to verify email [REDACTED_EMAIL], but user not found') + else: + log.error(f'Tried to verify email {email_id}, but user not found') return False diff --git a/openedx/core/djangoapps/user_authn/views/login.py b/openedx/core/djangoapps/user_authn/views/login.py index 6a012d8d212d..9f06322e5af0 100644 --- a/openedx/core/djangoapps/user_authn/views/login.py +++ b/openedx/core/djangoapps/user_authn/views/login.py @@ -199,7 +199,10 @@ def _enforce_password_policy_compliance(request, user): # pylint: disable=missi if LoginFailures.is_feature_enabled(): LoginFailures.increment_lockout_counter(user) - AUDIT_LOG.info("Password reset initiated for email %s.", user.email) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + AUDIT_LOG.info("Password reset initiated for user ID %s.", user.id) + else: + AUDIT_LOG.info("Password reset initiated for email %s.", user.email) tracker.emit( PASSWORD_RESET_INITIATED, {