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

[accounts/ui/settings/navigation] added first registration step for supervisee

parent b82be6c2
No related branches found
No related tags found
1 merge request!224Security accounts
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2017-05-18 12:32
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('accounts', '0002_profile'),
]
operations = [
migrations.CreateModel(
name='SupervisionTrack',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_valid', models.BooleanField(default=False)),
('start_date', models.DateTimeField(blank=True, null=True)),
('expiration_date', models.DateTimeField(blank=True, null=True)),
('last_validation_date', models.DateTimeField(blank=True, null=True)),
('godfather', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='supervisees', to=settings.AUTH_USER_MODEL)),
('supervisee', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='supervisors', to=settings.AUTH_USER_MODEL)),
],
),
migrations.RemoveField(
model_name='profile',
name='supervisees',
),
migrations.AddField(
model_name='profile',
name='supervision',
field=models.ManyToManyField(blank=True, related_name='profiles', to='accounts.SupervisionTrack'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2017-05-23 17:36
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0003_auto_20170518_1232'),
]
operations = [
migrations.AddField(
model_name='profile',
name='supervision_key',
field=models.CharField(blank=True, max_length=40, null=True),
),
migrations.AddField(
model_name='supervisiontrack',
name='supervision_key',
field=models.CharField(blank=True, max_length=40, null=True),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2017-06-08 09:35
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0004_auto_20170523_1736'),
]
operations = [
migrations.AddField(
model_name='profile',
name='registration_date',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AlterField(
model_name='profile',
name='status',
field=models.CharField(choices=[(b'N', b'New User'), (b'W', b'Waiting Validation'), (b'A', b'Accepted'), (b'R', b'Rejected'), (b'Y', b'Yearly revalidation'), (b'B', b'Blocked no supervisor')], default=b'B', max_length=1),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2017-06-08 14:51
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('accounts', '0005_auto_20170608_0935'),
]
operations = [
migrations.AlterField(
model_name='supervisiontrack',
name='godfather',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='supervisees', to=settings.AUTH_USER_MODEL),
),
]
......@@ -30,6 +30,9 @@ from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
import random
import string
class AccountSettings(models.Model):
class Meta:
......@@ -42,19 +45,44 @@ class AccountSettings(models.Model):
database_notifications_enabled = models.BooleanField(default=True)
environment_notifications_enabled = models.BooleanField(default=True)
class SupervisionTrack(models.Model):
#_____ Fields __________
supervisee = models.OneToOneField(User, on_delete=models.CASCADE,
related_name='supervisors')
godfather = models.ForeignKey(User, on_delete=models.CASCADE,
related_name='supervisees')
is_valid = models.BooleanField(default=False)
start_date = models.DateTimeField(null=True, blank=True)
expiration_date = models.DateTimeField(null=True, blank=True)
last_validation_date = models.DateTimeField(null=True, blank=True)
supervision_key = models.CharField(max_length=40, null=True, blank=True)
def __unicode__(self):
return u'Godfather: %s, Supervisee, %s, Validity: %s' % (self.godfather.username, self.supervisee.username, self.is_valid)
class Profile(models.Model):
#_____ Constants __________
#New account creation 'N'/'W'
#Godfather acceptance/rejection 'A'/'R'
#Yearly revalidation/blockage 'A'/'B'
NEWUSER = 'N'
WAITINGVALIDATION = 'W'
ACCEPTED = 'A'
REJECTED = 'R'
WAITINGVALIDATION = 'W'
YEARREVALIDATION = 'Y'
BLOCKED = 'B'
PROFILE_STATUS = (
(NEWUSER, 'New User'),
(WAITINGVALIDATION, 'Waiting Validation'),
(ACCEPTED, 'Accepted'),
(REJECTED, 'Rejected'),
(WAITINGVALIDATION, 'Waiting Validation'),
(BLOCKED, 'Blocked'),
(YEARREVALIDATION, 'Yearly revalidation'),
(BLOCKED, 'Blocked no supervisor'),
)
#_____ Fields __________
......@@ -65,11 +93,18 @@ class Profile(models.Model):
# Other fields here
status = models.CharField(max_length=1, choices=PROFILE_STATUS, default=BLOCKED)
is_godfather = models.BooleanField(default=False)
supervisees = models.ManyToManyField(User, related_name='users', blank=True)
supervision = models.ManyToManyField(SupervisionTrack, related_name='profiles', blank=True)
supervision_key = models.CharField(max_length=40, null=True, blank=True)
registration_date = models.DateTimeField(null=True, blank=True)
def __unicode__(self):
return u'User: %s' % self.user.username
def _generate_current_supervision_key(self):
length = 40
return ''.join(random.choice(string.ascii_letters + string.digits) for _
in range(length))
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
......
......@@ -31,6 +31,7 @@ from django.contrib.auth.models import User
from .models import Agreement
from ..accounts.models import AccountSettings
from ..accounts.models import SupervisionTrack
from ..accounts.models import Profile
......@@ -51,6 +52,14 @@ class AccountSettingsInline(admin.StackedInline):
#----------------------------------------------------------
class SupervisionTrackInline(admin.StackedInline):
model = SupervisionTrack
fk_name = 'godfather'
#----------------------------------------------------------
class ProfileInline(admin.StackedInline):
model = Profile
......@@ -76,8 +85,19 @@ class UserAdmin(UserAdmin):
def status(self, obj):
return obj.profile.status
def supervisees(self, obj):
return obj.profile.supervisees
def supervision(self, obj):
if obj.is_staff:
return "Staff account"
if obj.profile.is_godfather:
return "Godfather account"
else:
supervisiontrack = SupervisionTrack.objects.get(supervisee=obj,
is_valid=True)
godfather = supervisiontrack.godfather
return godfather
def supervision_key(self, obj):
return obj.profile.supervision_key
......@@ -90,7 +110,8 @@ class UserAdmin(UserAdmin):
'last_login',
'godfather',
'status',
'supervisees',
'supervision',
'supervision_key',
)
ordering = (
......@@ -102,8 +123,53 @@ class UserAdmin(UserAdmin):
inlines = (
AgreementInline,
AccountSettingsInline,
SupervisionTrackInline,
ProfileInline,
)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
#----------------------------------------------------------
class SupervisionTrackAdmin(admin.ModelAdmin):
list_display = (
'godfather',
'supervisee',
'is_valid',
'start_date',
'expiration_date',
'last_validation_date',
'supervision_key',
)
ordering = (
'godfather',
)
admin.site.register(SupervisionTrack, SupervisionTrackAdmin)
#----------------------------------------------------------
class ProfileAdmin(admin.ModelAdmin):
list_display = (
'user',
'status',
'registration_date',
'is_godfather',
'supervision_key',
)
ordering = (
'user',
)
admin.site.register(Profile, ProfileAdmin)
......@@ -216,6 +216,7 @@ DATASETS_ROOT_PATH = None
##############################################################################
ACCOUNT_ACTIVATION_DAYS = 2
ACCOUNT_ACTIVATION_DAYS_FROM_GODFATHER = 7
LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/login/'
SYSTEM_ACCOUNT = 'system'
......
......@@ -30,6 +30,7 @@ Forms and validation code for user registration.
"""
import datetime
from django.contrib.auth.models import User
from django import forms
......@@ -39,6 +40,8 @@ from django.utils.translation import ugettext_lazy as _
from .models import RegistrationProfile
from .models import PreregistrationProfile
from ...accounts.models import SupervisionTrack
from ...accounts.models import Profile
# I put this on all required fields, because it's easier to pick up
# on them with CSS or JavaScript if they have a class of "required"
......@@ -79,6 +82,11 @@ class RegistrationForm(forms.Form):
label=_(u'Password'))
password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
label=_(u'Password (again)'))
godfather = forms.RegexField(regex=r'^\w+$',
max_length=30,
widget=forms.TextInput(attrs=attrs_dict),
label=_(u'Godfather Username (Your account needs to be validated by a recognized person)'))
def clean_username(self):
"""
......@@ -92,6 +100,22 @@ class RegistrationForm(forms.Form):
return self.cleaned_data['username']
raise forms.ValidationError(_(u'This username is already taken. Please choose another.'))
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
......@@ -123,6 +147,26 @@ class RegistrationForm(forms.Form):
password=self.cleaned_data['password1'],
email=self.cleaned_data['email'],
)
#Create and assign key
new_user.profile.supervision_key = new_user.profile._generate_current_supervision_key()
godfather = User.objects.get(username = self.cleaned_data['godfather'])
supervisiontrack = SupervisionTrack.objects.create(
supervisee = new_user,
godfather = godfather,
is_valid = False,
)
#Assign key to supervision track
supervisiontrack.supervision_key = new_user.profile.supervision_key
supervisiontrack.save()
new_user.profile.is_godfather = False
new_user.profile.status = Profile.NEWUSER
new_user.profile.registration_date = datetime.datetime.now()
new_user.profile.supervision.add(supervisiontrack)
new_user.save()
return new_user
......
......@@ -39,6 +39,9 @@ from django.template import loader
from django.template import Context
from django.utils.translation import ugettext_lazy as _
from ...accounts.models import SupervisionTrack
from ...accounts.models import Profile
SHA1_RE = re.compile('^[a-f0-9]{40}$')
......@@ -93,6 +96,43 @@ class RegistrationManager(models.Manager):
profile.activation_key = self.model.ACTIVATED
profile.save()
user_activated.send(sender=self.model, user=user)
# The user activated via the link but activation from godfather or admin
# is requested now to have access to the platform (so set it
# inactive again)
user.is_active = False
user.profile.status = Profile.WAITINGVALIDATION
user.profile.registration_date = datetime.datetime.now()
user.save()
# Send email to godfather
# Fetch Godfather user from supervision track
supervisiontrack = SupervisionTrack.objects.get(supervision_key=user.profile.supervision_key)
godfather_user = supervisiontrack.godfather
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_user,
'supervisee': user,
'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.message.txt')
message = t.render(c)
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [godfather_user.email])
except:
pass
return user
return False
......
......@@ -2,21 +2,21 @@
{% 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 %}
......@@ -26,13 +26,19 @@
<div class="row">
<div class="col-sm-4 col-sm-offset-4">
<h4>{% if account %}Account activated{% else %}Error{% endif %}</h4>
<h4>{% if account %}Account almost activated{% else %}Error{% endif %}</h4>
{% if account %}
<p class="text">Your account was successfully activated. Welcome {{ account.username }}.
You can now <a href="{% url 'login' %}">sign in</a> and start your own experiments!</p>
<p class="text">Your account is almost activated. Welcome {{ account.username }}.
We have asked your "godfather" to validate your account and accept you as a
supervisee. You will be informed by email once this is done.
In the meantime you can remind your "godfather"(supervisor) to validate
your account. You will then be able to <a href="{% url 'login' %}">sign in</a> and start your own experiments!</p>
{% else %}
<p class="text">Your account wasn't activated. Check that the URL you entered is correct. If you still have problems to activate your account, <a href="{% url 'contact' %}">contact a member of our support staff</a>.</p>
<p class="text">Your account wasn't activated. Check that the URL you entered is correct.
If you already clicked on the link sent to you by email, you need to wait
for your "godfather"(supervisor) to accept your account activation request.
If you still have problems to activate your account, <a href="{% url 'contact' %}">contact a member of our support staff</a>.</p>
{% endif %}
</div>
......
......@@ -2,21 +2,21 @@
{% 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 %}
......@@ -91,6 +91,14 @@
{% endfor %}
</div>
<div class="form-group{% if form.godfather.errors %} has-error{% endif %}">
{{ form.godfather.label_tag }}
{{ form.godfather|tabindex:7|addclass:"form-control" }}
{% for error in form.godfather.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
</div>
<div class="form-group{% if form.tos.errors %} has-error{% endif %}">
{{ form.tos }} I have carefully read the <a href="{% url 'terms-of-service' %}">Terms of Service</a>, which include the Privacy and Data Protection Terms of Use, and fully agree and undertake to comply with all provisions therein by checking this box.
{% for error in form.tos.errors %}
......
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