From 64cd0fc93f00f6cf716e58879660d17095a2ad72 Mon Sep 17 00:00:00 2001 From: Adam Palay Date: Mon, 7 Oct 2013 13:32:25 -0400 Subject: [PATCH 1/2] create UserStanding model, add functionality to disable users --- common/djangoapps/student/models.py | 11 ++++ common/djangoapps/student/views.py | 90 ++++++++++++++++++++++++++++- lms/templates/disable_account.html | 40 +++++++++++++ lms/templates/login.html | 2 - lms/urls.py | 2 + 5 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 lms/templates/disable_account.html diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index da16a2fda227..e3f43980ada3 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -34,6 +34,17 @@ AUDIT_LOG = logging.getLogger("audit") +class UserStanding(models.Model): + """ + This table contains a student's account's status. + Currently, we're only disabling accounts; in the future we can imagine + taking away more specific privileges, like forums access, or adding + more specific karma levels or probationary stages. + """ + user = models.ForeignKey(User, db_index=True, related_name='account_status', unique=True) + account_status = models.CharField(blank=True, max_length=255, default='') + + class UserProfile(models.Model): """This is where we store all the user demographic fields. We have a separate table for this rather than extending the built-in Django auth_user. diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 285509bf5a49..66ceb5dd6cd7 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -16,6 +16,7 @@ from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required from django.contrib.auth.views import password_reset_confirm +from django.contrib.sessions.models import Session from django.core.cache import cache from django.core.context_processors import csrf from django.core.mail import send_mail @@ -30,6 +31,7 @@ from django.utils.http import cookie_date, base36_to_int, urlencode from django.utils.translation import ugettext as _ from django.views.decorators.http import require_POST +from django.contrib.admin.views.decorators import staff_member_required from ratelimitbackend.exceptions import RateLimitException @@ -40,7 +42,8 @@ TestCenterRegistration, TestCenterRegistrationForm, PendingNameChange, PendingEmailChange, CourseEnrollment, unique_id_for_user, - get_testcenter_registration, CourseEnrollmentAllowed) + get_testcenter_registration, CourseEnrollmentAllowed, + UserStanding) from student.forms import PasswordResetFormNoActive from certificates.models import CertificateStatuses, certificate_status_for_student @@ -66,6 +69,8 @@ from dogapi import dog_stats_api from pytz import UTC +from util.json_request import JsonResponse + log = logging.getLogger("mitx.student") AUDIT_LOG = logging.getLogger("audit") @@ -531,6 +536,29 @@ def login_user(request, error=""): return HttpResponse(json.dumps({'success': False, 'value': _('Email or password is incorrect.')})) + if user is not None: + try: + user_account = UserStanding.objects.get(user=user) + if user_account.account_status == u'disabled': + return JsonResponse( + { + 'success': False, + 'value': _( + 'Your account has been disabled. If you believe ' + 'this was done in error, please contact us at ' + '{link_start}{support_email}{link_end}' + ).format( + support_email = settings.CONTACT_EMAIL, + link_start = u''.format( + settings.CONTACT_EMAIL + ), + link_end = u'' + ) + } + ) + except UserStanding.DoesNotExist: + pass + if user is not None and user.is_active: try: # We do not log here, because we have a handler registered @@ -601,6 +629,66 @@ def logout_user(request): domain=settings.SESSION_COOKIE_DOMAIN) return response +@login_required +@ensure_csrf_cookie +def disable_account(request): + if not request.user.is_staff: + raise Http404 + + return render_to_response("disable_account.html") + +@require_POST +@login_required +@ensure_csrf_cookie +def disable_account_ajax(request): + if not request.user.is_staff: + raise Http404 + username = request.POST.get('username') + context = {} + if username is None or username.strip() == '': + context['message'] = _('Please enter a username') + return JsonResponse(context) + + account_action = request.POST.get('account_action') + if account_action is None: + context['message'] = _('Please choose an option') + return JsonResponse(context) + + username = username.strip() + try: + user = User.objects.get(username=username) + except User.DoesNotExist: + context['message'] = _("User with username {} does not exist").format(username) + else: + user_account, created = UserStanding.objects.get_or_create(user=user) + if account_action == 'disable': + user_account.account_status = u'disabled' + context['message'] = _("Successfully disabled {}'s account").format(username) + _remove_user_sessions(user) + context['message'] += _(". Successfully deleted {}'s sessions.").format(username) + elif account_action == 'reenable': + user_account.account_status = '' + context['message'] = _("Successfully reenabled {}'s account").format(username) + print request.user + user_account.save() + return JsonResponse(context) + + +def _remove_user_sessions(user): + """ + hackish and expensive, but as far as I can tell the only way immediately to lock out + a user in django + """ + all_sessions = Session.objects.filter(expire_date__gte=datetime.datetime.now(UTC)) + user_sessions_pks = [ + session.pk for session in all_sessions if session.get_decoded().get( + '_auth_user_id' + ) == user.id + ] + user_sessions_to_delete = Session.objects.filter(pk__in=user_sessions_pks) + user_sessions_to_delete.delete() + + @login_required @ensure_csrf_cookie diff --git a/lms/templates/disable_account.html b/lms/templates/disable_account.html new file mode 100644 index 000000000000..a7736f2fd55f --- /dev/null +++ b/lms/templates/disable_account.html @@ -0,0 +1,40 @@ +<%inherit file="main.html" /> + +<%! from django.core.urlresolvers import reverse %> +<%! from django.utils.translation import ugettext as _ %> + +

Disable or Reenable student accounts

+
+ + +
+ + +
+ + +
+
+
+ + +
+
+

+ + diff --git a/lms/templates/login.html b/lms/templates/login.html index 938b1cc9c835..80dab49365e0 100644 --- a/lms/templates/login.html +++ b/lms/templates/login.html @@ -1,5 +1,3 @@ -<%! from django.utils.translation import ugettext as _ %> - <%inherit file="main.html" /> <%namespace name='static' file='static_content.html'/> diff --git a/lms/urls.py b/lms/urls.py index e624ac9f3431..053e6aae923a 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -30,6 +30,8 @@ url(r'^t/(?P