Skip to content
Snippets Groups Projects
Commit b5eaa6b6 authored by Flavio TARSETTI's avatar Flavio TARSETTI
Browse files

[accounts/ui-registration/ui-views-urls/utils-management] added view to handle...

[accounts/ui-registration/ui-views-urls/utils-management] added view to handle blocked users requests for supervision and management to cleanup expired requests
parent 2d69197c
No related branches found
No related tags found
1 merge request!224Security accounts
Pipeline #
Showing
with 403 additions and 8 deletions
...@@ -93,8 +93,10 @@ class GodfatherListView(generics.ListAPIView): ...@@ -93,8 +93,10 @@ class GodfatherListView(generics.ListAPIView):
#A godfather can validate an account of: #A godfather can validate an account of:
#1) a new user requesting validation #1) a new user requesting validation
#2) an existing validated user rejected by a previous supervisor #2) an existing validated user rejected by a previous supervisor
#On both cases check the current key in supervisee profile match the supervisiontrack key as this is the current supervision request/track from the supervisee #3) an existing validated user requesting a change of supervisor
queryset = SupervisionTrack.objects.filter(godfather=request.user).filter(Q(supervisee__profile__status=Profile.WAITINGVALIDATION)|Q(supervisee__profile__status=Profile.REJECTED)|Q(supervisee__profile__status=Profile.ACCEPTED)).filter(Q(supervisee__profile__supervision_key=models.F('supervision_key'))) #4) a blocked user requesting a supervision
#On all cases check the current key in supervisee profile match the supervisiontrack key as this is the current supervision request/track from the supervisee
queryset = SupervisionTrack.objects.filter(godfather=request.user).filter(Q(supervisee__profile__status=Profile.WAITINGVALIDATION)|Q(supervisee__profile__status=Profile.REJECTED)|Q(supervisee__profile__status=Profile.ACCEPTED)|Q(supervisee__profile__status=Profile.BLOCKED)).filter(Q(supervisee__profile__supervision_key=models.F('supervision_key')))
serializer = FullSupervisionTrackSerializer(queryset, many=True, context ={'request': request}) serializer = FullSupervisionTrackSerializer(queryset, many=True, context ={'request': request})
return Response(serializer.data) return Response(serializer.data)
...@@ -215,14 +217,17 @@ class GodfatherRemoveSuperviseeView(BaseUpdateSupervisionTrackView): ...@@ -215,14 +217,17 @@ class GodfatherRemoveSuperviseeView(BaseUpdateSupervisionTrackView):
now = datetime.datetime.now() now = datetime.datetime.now()
expiration_date_delta = datetime.timedelta(days=settings.ACCOUNT_BLOCKAGE_AFTER_FIRST_REJECTION_DAYS) expiration_date_delta = datetime.timedelta(days=settings.ACCOUNT_BLOCKAGE_AFTER_FIRST_REJECTION_DAYS)
supervisiontrack.expiration_date = now supervisiontrack.expiration_date = now
supervisiontrack.is_valid = False supervisiontrack.is_valid = False
supervisee.profile.status = Profile.REJECTED if supervisee.profile.status != Profile.BLOCKED:
supervisee.profile.status = Profile.REJECTED
if supervisee.profile.rejection_date == None:
supervisee.profile.rejection_date = now + expiration_date_delta
else:
supervisee.profile.rejection_date = None
supervisee.profile.supervision_key = None supervisee.profile.supervision_key = None
if supervisee.profile.rejection_date == None:
supervisee.profile.rejection_date = now + expiration_date_delta
supervisiontrack.save() supervisiontrack.save()
supervisee.profile.save() supervisee.profile.save()
......
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
# encoding: utf-8
###############################################################################
# #
# Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ #
# Contact: beat.support@idiap.ch #
# #
# This file is part of the beat.web module of the BEAT platform. #
# #
# Commercial License Usage #
# Licensees holding valid commercial BEAT licenses may use this file in #
# accordance with the terms contained in a written agreement between you #
# and Idiap. For further information contact tto@idiap.ch #
# #
# Alternatively, this file may be used under the terms of the GNU Affero #
# Public License version 3 as published by the Free Software and appearing #
# in the file LICENSE.AGPL included in the packaging of this file. #
# The BEAT platform is distributed in the hope that it will be useful, but #
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
# or FITNESS FOR A PARTICULAR PURPOSE. #
# #
# You should have received a copy of the GNU Affero Public License along #
# with the BEAT platform. If not, see http://www.gnu.org/licenses/. #
# #
###############################################################################
from django.core.management.base import BaseCommand, CommandError
from datetime import date
import datetime
from django.contrib.auth.models import User
from django.conf import settings
from ...models import SupervisionTrack
from ...models import Profile
from ....ui.registration.models import RegistrationProfile
import sys
import random
class Command(BaseCommand):
help = 'Clean blocked users expired requests for supervisor validation'
def add_arguments(self, parser):
parser.add_argument('--noinput', action='store_false', dest='interactive', default=False,
help=('Tells Django to NOT prompt the user for input of any kind.'))
def handle(self, *args, **options):
block = True
if options['interactive']:
try:
answer = self.get_input_data('Clean blocked user(s) that have not been validated by a supervisor? (y/n)? ', 'y').lower()
except KeyboardInterrupt:
self.stderr.write("\nOperation canceled.")
sys.exit(1)
if answer != 'y':
self.stdout.write('Clean blocked users operation canceled')
sys.exit(1)
if block:
blocked_profiles = Profile.objects.filter(status=Profile.BLOCKED)
count = 0
for blocked_profile in blocked_profiles:
user = blocked_profile.user
if user.profile.rejection_date is not None:
if user.profile.rejection_date < datetime.datetime.now():
count +=1
user.profile.status = Profile.BLOCKED
user.profile.rejection_date = None
user.is_active = False
if user.profile.supervision_key != None:
supervisiontrack = SupervisionTrack.objects.get(supervision_key=blocked_profile.supervision_key)
if supervisiontrack.is_valid == False and supervisiontrack.start_date is None:
user.profile.supervision_key = None
supervisiontrack.delete()
user.profile.save()
user.save()
self.stdout.write('{} Blocked user(s) successfully cleaned from expired supervision/'.format(count) + '{} Total user(s) checked'.format(blocked_profiles.count()))
def get_input_data(self, message, default=None):
"""
Override this method if you want to customize data inputs or
validation exceptions.
"""
raw_value = raw_input(message)
if default and raw_value == '':
raw_value = default
return raw_value
...@@ -250,3 +250,72 @@ class RegistrationFormNoFreeEmail(RegistrationForm): ...@@ -250,3 +250,72 @@ class RegistrationFormNoFreeEmail(RegistrationForm):
if email_domain in self.bad_domains: if email_domain in self.bad_domains:
raise forms.ValidationError(_(u'Registration using free email addresses is prohibited. Please supply a different email address.')) raise forms.ValidationError(_(u'Registration using free email addresses is prohibited. Please supply a different email address.'))
return self.cleaned_data['email'] return self.cleaned_data['email']
class BlockedUserRevalidationForm(forms.Form):
"""
Form for registering a new user account.
Validates that the requested username is not already in use, and
requires the password to be entered twice to catch typos.
Subclasses should feel free to add any additional validation they
need, but should either preserve the base ``save()`` or implement
a ``save()`` method which returns a ``User``.
"""
username = forms.RegexField(regex=r'^\w+$',
max_length=30,
widget=forms.TextInput(attrs=attrs_dict),
label=_(u'Username'))
password = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
label=_(u'Password'))
godfather = forms.RegexField(regex=r'^\w+$',
max_length=30,
widget=forms.TextInput(attrs=attrs_dict),
label=_(u'Godfather Username (Your account needs to be re-validated by a recognized person)'))
def clean_username(self):
"""
Validate that the username is alphanumeric and user exists
"""
try:
user = User.objects.get(username__iexact=self.cleaned_data['username'])
return self.cleaned_data['username']
except User.DoesNotExist:
raise forms.ValidationError(_(u'This username has not been recognized'))
def clean_godfather(self):
"""
Validate that the username is alphanumeric and exists.
"""
try:
user = User.objects.get(username__iexact=self.cleaned_data['godfather'])
except User.DoesNotExist:
raise forms.ValidationError(_(u'This godfather username does not exist. Please choose another.'))
if not user.profile.is_godfather:
raise forms.ValidationError(_(u'This user is not a recognized godfather. Please choose another.'))
return self.cleaned_data['godfather']
def clean(self):
"""
Verifiy that the values entered into the two password fields
match. Note that an error here will end up in
``non_field_errors()`` because it doesn't apply to a single
field.
"""
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
self._errors["password2"] = self.error_class([_(u'You must type the same password each time')])
del self.cleaned_data["password1"]
del self.cleaned_data["password2"]
return self.cleaned_data
{% extends "base.html" %}
{% comment %}
* Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/
* Contact: beat.support@idiap.ch
*
* This file is part of the beat.web module of the BEAT platform.
*
* Commercial License Usage
* Licensees holding valid commercial BEAT licenses may use this file in
* accordance with the terms contained in a written agreement between you
* and Idiap. For further information contact tto@idiap.ch
*
* Alternatively, this file may be used under the terms of the GNU Affero
* Public License version 3 as published by the Free Software and appearing
* in the file LICENSE.AGPL included in the packaging of this file.
* The BEAT platform is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.
*
* You should have received a copy of the GNU Affero Public License along
* with the BEAT platform. If not, see http://www.gnu.org/licenses/.
{% endcomment %}
{% load registration_tags %}
{% block content %}
<div class="row">
<div class="col-sm-4 col-sm-offset-4">
<div class="panel panel-default login-panel">
<div class="panel-heading">Account Reactivation</div>
<div class="panel-body">
<form method="post" action="" class="form">
{% csrf_token %}
{% for error in form.non_field_errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
<div class="form-group{% if form.username.errors %} has-error{% endif %}">
{{ form.username.label_tag }}
{{ form.username|tabindex:1|addclass:"form-control" }}
{% for error in form.username.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
</div>
<div class="form-group{% if form.password.errors %} has-error{% endif %}">
{{ form.password.label_tag }}
{{ form.password|tabindex:2|addclass:"form-control" }}
{% for error in form.password.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
</div>
<div class="form-group{% if form.godfather.errors %} has-error{% endif %}">
{{ form.godfather.label_tag }}
{{ form.godfather|tabindex:2|addclass:"form-control" }}
{% for error in form.godfather.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
</div>
<div class="form-group">
<button type="submit" class="btn btn-success" tabindex="3"><i class="fa fa-plug fa-fw fa-lg"></i> Reactivation request</button>
</div>
</form>
<p class="comment">In order to sign in, you must be <a href="{% url 'registration' %}">registered</a></p>
<p class="comment">Already registered and not blocked? <a href="{% url 'login' %}">login</a></p>
</div>
</div>
</div>
</div>
{% endblock content %}
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
</form> </form>
<p class="comment">In order to sign in, you must be <a href="{% url 'registration' %}">registered</a></p> <p class="comment">In order to sign in, you must be <a href="{% url 'registration' %}">registered</a></p>
<p class="comment">Inactive/Blocked user need to get their account reactivated <a href="{% url 'blocked_user_reactivation' %}">reactivation</a></p>
</div> </div>
</div> </div>
......
...@@ -2,7 +2,7 @@ Dear {{ supervisor.first_name }}, ...@@ -2,7 +2,7 @@ Dear {{ supervisor.first_name }},
Thank you in advance for validating the request of one your supervisee at the Idiap Research Institute's Biometric Thank you in advance for validating the request of one your supervisee at the Idiap Research Institute's Biometric
Evaluation and Testing (BEAT) platform. Before we can activate this Evaluation and Testing (BEAT) platform. Before we can activate this
supervision requesti, you must login to your account and under supervision tab validate the following supervisee: supervision request, you must login to your account and under supervision tab validate the following supervisee:
First Name: {{ supervisee.first_name }} First Name: {{ supervisee.first_name }}
Last Name: {{ supervisee.last_name }} Last Name: {{ supervisee.last_name }}
......
Dear {{ supervisee.first_name }},
Your account is currently blocked due to a lack of supervisor.
We acknowledge your request made to re-activate your account via a supervision request to {{ supervisor.username }}.
You will be informed if your supervisor has accepted or rejected your request.
If no information is given by your supervisor, you will be allowed to make a new supervision request after a week.
If you are having problems to activate your account, contact a member of our
support staff at {{ prefix }}{% url 'contact' %}.
BEAT Administrators at the Idiap Research Institute
Blocked Account re-validation - Supervision request confirmation
...@@ -48,6 +48,11 @@ urlpatterns = [ ...@@ -48,6 +48,11 @@ urlpatterns = [
name='login', name='login',
), ),
url(r'^blocked_user_reactivation/$',
views.blocked_user_reactivation,
name='blocked_user_reactivation',
),
url(r'^events/(?P<author_name>\w+)/$', url(r'^events/(?P<author_name>\w+)/$',
views.activity_stream, views.activity_stream,
name='activity-stream', name='activity-stream',
......
...@@ -29,6 +29,8 @@ ...@@ -29,6 +29,8 @@
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.shortcuts import render_to_response from django.shortcuts import render_to_response
from django.template import RequestContext from django.template import RequestContext
from django.template import loader
from django.template import Context
from django.contrib.auth.views import login as django_login from django.contrib.auth.views import login as django_login
from django.contrib.auth.forms import PasswordChangeForm from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -45,6 +47,11 @@ from rest_framework.authtoken.models import Token ...@@ -45,6 +47,11 @@ from rest_framework.authtoken.models import Token
from ..import __version__ from ..import __version__
from ..common.models import Shareable from ..common.models import Shareable
from registration.forms import BlockedUserRevalidationForm
from ..accounts.models import Profile, SupervisionTrack
import datetime
from urlparse import urlparse
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -71,6 +78,113 @@ def login(request): ...@@ -71,6 +78,113 @@ def login(request):
return response return response
def blocked_user_reactivation(request):
'''Reactivation page'''
if request.method == "POST":
form = BlockedUserRevalidationForm(request.POST)
if form.is_valid():
try:
user = User.objects.get(username=request.POST["username"])
if user.check_password(request.POST["password"]):
# Check if user is a blocked user
if user.profile.status == Profile.BLOCKED:
godfather = User.objects.get(username=request.POST["godfather"])
# Check the godfather
if godfather.profile.status == Profile.ACCEPTED:
# Check if supervision track already exists
if user.profile.supervision_key is None:
# Create and assign key
supervisee = user
supervisee.profile.supervision_key = supervisee.profile._generate_current_supervision_key()
supervisiontrack = SupervisionTrack.objects.create(
supervisee = supervisee,
godfather = godfather,
is_valid = False,
)
# Assign key to supervision track
supervisiontrack.supervision_key = supervisee.profile.supervision_key
supervisiontrack.save()
supervisee.profile.supervision.add(supervisiontrack)
# Add a rejection date to the supervisee profile
now = datetime.datetime.now()
expiration_date_delta = datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS_FROM_GODFATHER)
if supervisee.profile.rejection_date == None:
supervisee.profile.rejection_date = now + expiration_date_delta
supervisee.profile.save()
supervisee.save()
#Inform by email the supervisor that he has a new supervisee request
from django.core.mail import send_mail
parsed_url = urlparse(settings.URL_PREFIX)
server_address = '%s://%s' % (parsed_url.scheme, parsed_url.hostname)
c = Context({ 'supervisor': godfather,
'supervisee': supervisee,
'prefix': server_address,
})
try:
t = loader.get_template('registration/mail.godfather_validation.subject.txt')
subject = t.render(c)
# Note: e-mail subject *must not* contain newlines
subject = settings.EMAIL_SUBJECT_PREFIX + ''.join(subject.splitlines())
t = loader.get_template('registration/mail.godfather_validation_supervisee_add_request.message.txt')
message = t.render(c)
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [godfather.email])
except:
pass
# inform the supervisee of his request
c = Context({ 'supervisor': godfather,
'supervisee': supervisee,
'prefix': server_address,
})
try:
t = loader.get_template('registration/mail.supervisee_blocked_validation_wait.subject.txt')
subject = t.render(c)
# Note: e-mail subject *must not* contain newlines
subject = settings.EMAIL_SUBJECT_PREFIX + ''.join(subject.splitlines())
t = loader.get_template('registration/mail.supervisee_blocked_state_wait_for_activation.message.txt')
message = t.render(c)
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [supervisee.email])
except:
pass
messages.success(request, "Your supervision request has been successfully processed.")
else:
messages.error(request, "A supervision request already exists for this account, you need to wait for your supervisor's decision.")
else:
messages.error(request, "The selected godfather is not valid.")
else:
path = request.GET.get('next', '/')
messages.error(request, "Your profile is not blocked, you can go to the login page instead.")
else:
# Don't give too much details though we know the problem is the password only at this step
messages.error(request, "Wrong user or password combination!")
except User.DoesNotExist:
# Don't give too much details though we know the problem is the password only at this step
messages.error(request, "Wrong user or password combination!")
pass
#return redirect('blocked_user_reactivation', pk=post.pk)
else:
form = BlockedUserRevalidationForm()
return render_to_response('registration/blocked_user_reactivate.html', {'form': form},
context_instance=RequestContext(request))
def gather_contributions(requestor, author): def gather_contributions(requestor, author):
'''Gather contributions that are accessible to a certain requestor''' '''Gather contributions that are accessible to a certain requestor'''
......
...@@ -58,3 +58,6 @@ class Command(BaseCommand): ...@@ -58,3 +58,6 @@ class Command(BaseCommand):
# Block rejected users with no supervision after specific rejection date # Block rejected users with no supervision after specific rejection date
call_command('block_rejected_users') call_command('block_rejected_users')
# Clean blocked users with no supervision after specific rejection date
call_command('clean_blocked_users_expired_requests')
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment