0001-Registration-refactored-email-validation-done-first-.patch
authentic2/app_settings.py | ||
---|---|---|
90 | 90 |
definition='Root urlconf for the /accounts endpoints'), |
91 | 91 |
A2_REGISTRATION_FORM_CLASS = Setting(default='authentic2.registration_backend.forms.RegistrationForm', |
92 | 92 |
definition='Default registration form'), |
93 |
A2_REGISTRATION_COMPLETION_FORM_CLASS = Setting(default='authentic2.registration_backend.forms.RegistrationCompletionForm', |
|
94 |
definition='Default registration completion form'), |
|
93 | 95 |
A2_REGISTRATION_SET_PASSWORD_FORM_CLASS = Setting(default='authentic2.registration_backend.forms.SetPasswordForm', |
94 | 96 |
definition='Default set password form'), |
95 | 97 |
A2_REGISTRATION_CHANGE_PASSWORD_FORM_CLASS = Setting(default='authentic2.registration_backend.forms.PasswordChangeForm', |
... | ... | |
131 | 133 |
A2_PASSWORD_POLICY_MIN_LENGTH=Setting(default=6, definition='Minimum number of characters in a password'), |
132 | 134 |
A2_AUTH_PASSWORD_ENABLE=Setting(default=True, definition='Activate login/password authentication', names=('AUTH_PASSWORD',)), |
133 | 135 |
PUSH_PROFILE_UPDATES=Setting(default=False, definition='Push profile update to linked services'), |
136 |
A2_REGISTRATION_ID_NAME=Setting(default='registration_id', definition='Unique registration identifier name'), |
|
134 | 137 |
) |
135 | 138 | |
136 | 139 |
app_settings = AppSettings(default_settings) |
authentic2/auth2_auth/auth2_ssl/views.py | ||
---|---|---|
19 | 19 |
from django.contrib.auth import REDIRECT_FIELD_NAME |
20 | 20 | |
21 | 21 | |
22 |
import registration.views |
|
22 |
from authentic2.registration_backend.views import RegistrationView |
|
23 | 23 | |
24 | 24 | |
25 | 25 |
from authentic2.constants import NONCE_FIELD_NAME |
... | ... | |
188 | 188 |
def register(request): |
189 | 189 |
'''Registration page for SSL auth without CA''' |
190 | 190 |
next_url = request.GET.get(REDIRECT_FIELD_NAME, settings.LOGIN_REDIRECT_URL) |
191 |
return registration.views.register(request, success_url=next_url,
|
|
192 |
form_class=functools.partial(forms.RegistrationForm, |
|
193 |
request=request)) |
|
191 |
return RegistrationView.as_view(request, success_url=next_url,
|
|
192 |
form_class=functools.partial(forms.RegistrationForm,
|
|
193 |
request=request))
|
|
194 | 194 |
authentic2/profile_urls.py | ||
---|---|---|
1 | 1 |
from django.conf.urls import patterns, url |
2 |
from django.contrib.auth import views as auth_views |
|
3 | ||
4 |
from authentic2.utils import get_form_class |
|
5 |
from . import app_settings |
|
6 | ||
7 |
SET_PASSWORD_FORM_CLASS = get_form_class( |
|
8 |
app_settings.A2_REGISTRATION_SET_PASSWORD_FORM_CLASS) |
|
9 |
CHANGE_PASSWORD_FORM_CLASS = get_form_class( |
|
10 |
app_settings.A2_REGISTRATION_CHANGE_PASSWORD_FORM_CLASS) |
|
2 | 11 | |
3 | 12 |
urlpatterns = patterns('authentic2.views', |
4 | 13 |
url(r'^logged-in/$', 'logged_in', name='logged-in'), |
... | ... | |
7 | 16 |
url(r'^change-email/verify/$', 'email_change_verify', |
8 | 17 |
name='email-change-verify'), |
9 | 18 |
url(r'^$', 'profile', name='account_management'), |
19 |
url(r'^password/change/$', |
|
20 |
auth_views.password_change, |
|
21 |
{'password_change_form': CHANGE_PASSWORD_FORM_CLASS}, |
|
22 |
name='auth_password_change'), |
|
23 |
url(r'^password/change/done/$', |
|
24 |
auth_views.password_change_done, |
|
25 |
name='auth_password_change_done'), |
|
26 |
url(r'^password/reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', |
|
27 |
auth_views.password_reset_confirm, |
|
28 |
{'set_password_form': SET_PASSWORD_FORM_CLASS}, |
|
29 |
name='auth_password_reset_confirm'), |
|
30 |
url(r'^password/reset/$', |
|
31 |
auth_views.password_reset, |
|
32 |
name='auth_password_reset'), |
|
33 |
url(r'^password/reset/complete/$', |
|
34 |
auth_views.password_reset_complete, |
|
35 |
name='auth_password_reset_complete'), |
|
36 |
url(r'^password/reset/done/$', |
|
37 |
auth_views.password_reset_done, |
|
38 |
name='auth_password_reset_done'), |
|
10 | 39 |
) |
authentic2/registration_backend/__init__.py | ||
---|---|---|
1 |
from django.conf import settings |
|
2 |
from django.template.loader import render_to_string |
|
3 | ||
4 |
from registration.models import RegistrationProfile |
|
5 | ||
6 |
def send_activation_email(self, site): |
|
7 |
""" |
|
8 |
Send an activation email to the user associated with this |
|
9 |
``RegistrationProfile``. |
|
10 |
|
|
11 |
The activation email will make use of two templates: |
|
12 | ||
13 |
``registration/activation_email_subject.txt`` |
|
14 |
This template will be used for the subject line of the |
|
15 |
email. Because it is used as the subject line of an email, |
|
16 |
this template's output **must** be only a single line of |
|
17 |
text; output longer than one line will be forcibly joined |
|
18 |
into only a single line. |
|
19 | ||
20 |
``registration/activation_email.txt`` |
|
21 |
This template will be used for the body of the email. |
|
22 | ||
23 |
These templates will each receive the following context |
|
24 |
variables: |
|
25 | ||
26 |
``user`` |
|
27 |
The new user account |
|
28 | ||
29 |
``activation_key`` |
|
30 |
The activation key for the new account. |
|
31 | ||
32 |
``expiration_days`` |
|
33 |
The number of days remaining during which the account may |
|
34 |
be activated. |
|
35 | ||
36 |
``site`` |
|
37 |
An object representing the site on which the user |
|
38 |
registered; depending on whether ``django.contrib.sites`` |
|
39 |
is installed, this may be an instance of either |
|
40 |
``django.contrib.sites.models.Site`` (if the sites |
|
41 |
application is installed) or |
|
42 |
``django.contrib.sites.models.RequestSite`` (if |
|
43 |
not). Consult the documentation for the Django sites |
|
44 |
framework for details regarding these objects' interfaces. |
|
45 | ||
46 |
""" |
|
47 |
ctx_dict = {'activation_key': self.activation_key, |
|
48 |
'user': self.user, |
|
49 |
'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS, |
|
50 |
'site': site} |
|
51 |
subject = render_to_string('registration/activation_email_subject.txt', |
|
52 |
ctx_dict) |
|
53 |
# Email subject *must not* contain newlines |
|
54 |
subject = ''.join(subject.splitlines()) |
|
55 |
|
|
56 |
message = render_to_string('registration/activation_email.txt', |
|
57 |
ctx_dict) |
|
58 |
|
|
59 |
self.user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) |
|
60 |
RegistrationProfile.send_activation_email = send_activation_email |
authentic2/registration_backend/forms.py | ||
---|---|---|
1 |
from uuid import uuid1 |
|
2 | ||
3 |
from django.conf import settings |
|
1 | 4 |
from django.core.exceptions import ValidationError |
2 | 5 |
from django.utils.translation import ugettext_lazy as _ |
3 | 6 |
from django.forms import Form, CharField, PasswordInput, EmailField |
4 | 7 |
from django.utils.datastructures import SortedDict |
5 | 8 |
from django.db.models import FieldDoesNotExist |
6 | 9 | |
10 |
from django.contrib.auth.models import BaseUserManager, Group |
|
7 | 11 |
from django.contrib.auth import forms as auth_forms |
12 |
from django.core.mail import send_mail |
|
13 |
from django.core import signing |
|
14 |
from django import get_version |
|
15 |
from django.template.loader import render_to_string |
|
16 |
from django.core.urlresolvers import reverse |
|
17 | ||
18 |
from .. import app_settings, compat, forms, utils,\ |
|
19 |
validators, widgets, fields, models |
|
8 | 20 | |
9 |
from .. import app_settings, compat, forms, utils, validators, widgets, fields |
|
21 |
User = compat.get_user_model() |
|
22 |
is_django_17 = get_version().startswith('1.7') |
|
23 |
EXPIRATION = settings.ACCOUNT_ACTIVATION_DAYS |
|
10 | 24 | |
25 |
registration_id_name = app_settings.A2_REGISTRATION_ID_NAME |
|
11 | 26 | |
12 |
class RegistrationForm(forms.UserAttributeFormMixin, Form): |
|
27 |
class RegistrationForm(Form): |
|
28 |
error_css_class = 'form-field-error' |
|
29 |
required_css_class = 'form-field-required' |
|
30 | ||
31 |
email = EmailField() |
|
32 | ||
33 |
def clean_email(self): |
|
34 |
""" |
|
35 |
Verify if email is unique |
|
36 |
""" |
|
37 |
User = compat.get_user_model() |
|
38 |
if app_settings.A2_REGISTRATION_EMAIL_IS_UNIQUE and \ |
|
39 |
User.objects.filter(email__iexact=self.cleaned_data['email']).exists(): |
|
40 |
raise ValidationError(_('This email address is already in ' |
|
41 |
'use. Please supply a different email address.')) |
|
42 |
return self.cleaned_data['email'] |
|
43 | ||
44 |
def save(self, request): |
|
45 |
data = self.cleaned_data |
|
46 |
registration_id = uuid1().hex |
|
47 |
data.update({'next_url': request.GET.get('next_url'), |
|
48 |
registration_id_name: registration_id}) |
|
49 |
registration_token = signing.dumps(data) |
|
50 |
ctx_dict = {'registration_url': request.build_absolute_uri( |
|
51 |
reverse('registration_activate', |
|
52 |
kwargs={'registration_token': registration_token})), |
|
53 |
'expiration_days': EXPIRATION, |
|
54 |
'email': data['email']} |
|
55 |
ctx_dict.update(self.cleaned_data) |
|
56 | ||
57 |
subject = render_to_string('registration/activation_email_subject.txt', |
|
58 |
ctx_dict) |
|
59 | ||
60 |
subject = ''.join(subject.splitlines()) |
|
61 |
message = render_to_string('registration/activation_email.txt', |
|
62 |
ctx_dict) |
|
63 |
if is_django_17: |
|
64 |
html_message = render_to_string('registration/activation_email.html', |
|
65 |
ctx_dict) |
|
66 |
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, |
|
67 |
[data['email']], fail_silently=True, |
|
68 |
html_message=message) |
|
69 |
else: |
|
70 |
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, |
|
71 |
[data['email']], fail_silently=True) |
|
72 | ||
73 |
class RegistrationCompletionForm(forms.UserAttributeFormMixin, Form): |
|
13 | 74 |
error_css_class = 'form-field-error' |
14 | 75 |
required_css_class = 'form-field-required' |
15 | 76 | |
... | ... | |
21 | 82 |
""" |
22 | 83 |
Inject required fields in registration form |
23 | 84 |
""" |
24 |
super(RegistrationForm, self).__init__(*args, **kwargs) |
|
85 |
super(RegistrationCompletionForm, self).__init__(*args, **kwargs)
|
|
25 | 86 |
User = compat.get_user_model() |
26 | 87 |
insert_idx = 0 |
27 | 88 |
field_names = compat.get_registration_fields() |
... | ... | |
38 | 99 |
kwargs['validators'] = model_field.validators |
39 | 100 |
field = model_field.formfield(**kwargs) |
40 | 101 |
if isinstance(field, EmailField): |
41 |
field = fields.EmailFieldWithValidation(**kwargs)
|
|
102 |
continue
|
|
42 | 103 |
self.fields.insert(insert_idx, field_name, field) |
43 | 104 |
insert_idx += 1 |
44 | 105 |
for field_name in self.fields: |
... | ... | |
64 | 125 |
self.fields['username'].help_text = app_settings.A2_REGISTRATION_FORM_USERNAME_HELP_TEXT |
65 | 126 |
self.fields['username'].label = app_settings.A2_REGISTRATION_FORM_USERNAME_LABEL |
66 | 127 | |
128 | ||
67 | 129 |
def clean_username(self): |
68 | 130 |
""" |
69 | 131 |
Validate that the username is alphanumeric and is not already |
... | ... | |
82 | 144 |
else: |
83 | 145 |
return self.cleaned_data['username'] |
84 | 146 | |
85 |
def clean_email(self): |
|
86 |
""" |
|
87 |
Verify if email is unique |
|
88 |
""" |
|
89 |
User = compat.get_user_model() |
|
90 |
if app_settings.A2_REGISTRATION_EMAIL_IS_UNIQUE: |
|
91 |
if User.objects.filter(email__iexact=self.cleaned_data['email']): |
|
92 |
raise ValidationError(_('This email address is already in ' |
|
93 |
'use. Please supply a different email address.')) |
|
94 |
return self.cleaned_data['email'] |
|
95 | ||
96 | 147 |
def clean(self): |
97 | 148 |
""" |
98 | 149 |
Verifiy that the values entered into the two password fields |
... | ... | |
105 | 156 |
raise ValidationError(_("The two password fields didn't match.")) |
106 | 157 |
return self.cleaned_data |
107 | 158 | |
159 |
def save(self, *args, **kwargs): |
|
160 |
user_fields = {} |
|
161 |
for field in compat.get_registration_fields(): |
|
162 |
# save User model fields |
|
163 |
try: |
|
164 |
User._meta.get_field(field) |
|
165 |
except FieldDoesNotExist: |
|
166 |
continue |
|
167 |
if field.startswith('password'): |
|
168 |
continue |
|
169 |
user_fields[field] = kwargs[field] |
|
170 |
if field == 'email': |
|
171 |
user_fields[field] = BaseUserManager.normalize_email(kwargs[field]) |
|
172 | ||
173 |
new_user = User(is_active=True, **user_fields) |
|
174 |
new_user.clean() |
|
175 |
new_user.set_password(kwargs['password1']) |
|
176 |
new_user.save() |
|
177 | ||
178 |
registration_id, created = models.Attribute.objects.get_or_create(label=registration_id_name, |
|
179 |
name=registration_id_name, |
|
180 |
kind='string', |
|
181 |
asked_on_registration=False, |
|
182 |
user_visible=False) |
|
183 |
registration_id.set_value(new_user, kwargs[registration_id_name]) |
|
184 | ||
185 |
attributes = models.Attribute.objects.filter( |
|
186 |
asked_on_registration=True) |
|
187 |
if attributes: |
|
188 |
for attribute in attributes: |
|
189 |
attribute.set_value(new_user, kwargs[attribute.name]) |
|
190 |
if app_settings.A2_REGISTRATION_GROUPS: |
|
191 |
groups = [] |
|
192 |
for name in app_settings.A2_REGISTRATION_GROUPS: |
|
193 |
group, created = Group.objects.get_or_create(name=name) |
|
194 |
groups.append(group) |
|
195 |
new_user.groups = groups |
|
196 |
return new_user, kwargs['next_url'] |
|
197 | ||
108 | 198 |
class SetPasswordForm(auth_forms.SetPasswordForm): |
109 | 199 |
new_password1 = CharField(label=_("New password"), |
110 | 200 |
widget=PasswordInput, |
authentic2/registration_backend/urls.py | ||
---|---|---|
1 | 1 |
from django.conf.urls import patterns |
2 | 2 |
from django.conf.urls import url |
3 | 3 |
from django.utils.importlib import import_module |
4 |
from django.contrib.auth import views as auth_views |
|
5 | 4 |
from django.views.generic.base import TemplateView |
6 | ||
7 | ||
8 |
from .. import app_settings |
|
9 | ||
10 |
from registration.backends.default.views import ActivationView |
|
11 |
from .. import decorators |
|
12 | ||
13 |
def get_form_class(form_class): |
|
14 |
module, form_class = form_class.rsplit('.', 1) |
|
15 |
module = import_module(module) |
|
16 |
return getattr(module, form_class) |
|
17 | ||
18 | ||
19 |
SET_PASSWORD_FORM_CLASS = get_form_class( |
|
20 |
app_settings.A2_REGISTRATION_SET_PASSWORD_FORM_CLASS) |
|
21 |
CHANGE_PASSWORD_FORM_CLASS = get_form_class( |
|
22 |
app_settings.A2_REGISTRATION_CHANGE_PASSWORD_FORM_CLASS) |
|
23 | ||
24 | ||
25 |
password_change_view = decorators.setting_enabled( |
|
26 |
'A2_REGISTRATION_CAN_CHANGE_PASSWORD')(auth_views.password_change) |
|
27 | ||
28 | ||
29 |
urlpatterns = patterns('authentic2.registration_backend.views', |
|
30 |
url(r'^activate/complete/$', |
|
31 |
TemplateView.as_view(template_name='registration/activation_complete.html'), |
|
32 |
name='registration_activation_complete'), |
|
33 |
# Activation keys get matched by \w+ instead of the more specific |
|
34 |
# [a-fA-F0-9]{40} because a bad activation key should still get to the view; |
|
35 |
# that way it can return a sensible "invalid key" message instead of a |
|
36 |
# confusing 404. |
|
37 |
url(r'^activate/(?P<activation_key>\w+)/$', |
|
38 |
ActivationView.as_view(), |
|
39 |
name='registration_activate'), |
|
40 |
url(r'^register/$', |
|
41 |
'register', |
|
42 |
name='registration_register'), |
|
43 |
url(r'^register/complete/$', |
|
44 |
TemplateView.as_view(template_name='registration/registration_complete.html'), |
|
45 |
name='registration_complete'), |
|
46 |
url(r'^register/closed/$', |
|
47 |
TemplateView.as_view(template_name='registration/registration_closed.html'), |
|
48 |
name='registration_disallowed'), |
|
49 |
url(r'^password/change/$', password_change_view, |
|
50 |
{'password_change_form': CHANGE_PASSWORD_FORM_CLASS}, |
|
51 |
name='auth_password_change'), |
|
52 |
url(r'^password/change/done/$', |
|
53 |
auth_views.password_change_done, |
|
54 |
name='auth_password_change_done'), |
|
55 |
url(r'^password/reset/$', |
|
56 |
auth_views.password_reset, |
|
57 |
name='auth_password_reset'), |
|
58 |
url(r'^password/reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', |
|
59 |
auth_views.password_reset_confirm, |
|
60 |
{'set_password_form': SET_PASSWORD_FORM_CLASS}, |
|
61 |
name='auth_password_reset_confirm'), |
|
62 |
url(r'^password/reset/complete/$', |
|
63 |
auth_views.password_reset_complete, |
|
64 |
name='auth_password_reset_complete'), |
|
65 |
url(r'^password/reset/done/$', |
|
66 |
auth_views.password_reset_done, |
|
67 |
name='auth_password_reset_done'), |
|
68 |
url(r'^delete/$', |
|
69 |
'delete', |
|
70 |
name='delete_account'), |
|
71 |
) |
|
5 |
from django.contrib.auth.decorators import login_required |
|
6 | ||
7 |
from .views import RegistrationView, RegistrationCompletionView, DeleteView,\ |
|
8 |
LoginView |
|
9 | ||
10 |
urlpatterns = patterns('', |
|
11 |
url(r'^activate/expired/$', |
|
12 |
TemplateView.as_view(template_name='registration/activation_expired.html'), |
|
13 |
name='registration_activation_expired'), |
|
14 |
url(r'^activate/(?P<registration_token>[\w:-]+)/$', |
|
15 |
RegistrationCompletionView.as_view(), |
|
16 |
name='registration_activate'), |
|
17 |
url(r'^activate/(?P<registration_token>[\w:-]+)/(?P<username>\w+)$', |
|
18 |
LoginView.as_view(), |
|
19 |
name='registration_login'), |
|
20 |
url(r'^register/$', |
|
21 |
RegistrationView.as_view(), |
|
22 |
name='registration_register'), |
|
23 |
url(r'^register/complete/$', |
|
24 |
TemplateView.as_view(template_name='registration/registration_complete.html'), |
|
25 |
name='registration_complete'), |
|
26 |
url(r'^register/closed/$', |
|
27 |
TemplateView.as_view(template_name='registration/registration_closed.html'), |
|
28 |
name='registration_disallowed'), |
|
29 |
url(r'^delete/$', |
|
30 |
login_required(DeleteView.as_view()), |
|
31 |
name='delete_account'), |
|
32 |
) |
authentic2/registration_backend/views.py | ||
---|---|---|
1 | 1 |
import logging |
2 |
from datetime import datetime |
|
2 | 3 | |
3 | ||
4 |
from django.conf import settings |
|
4 | 5 |
from django.shortcuts import redirect, render |
5 | 6 |
from django.utils.translation import ugettext as _ |
6 | 7 |
from django.contrib import messages |
7 |
from django.contrib.auth.decorators import login_required |
|
8 |
from django.contrib.sites.models import RequestSite |
|
9 |
from django.contrib.sites.models import Site |
|
10 |
from django.contrib.auth.models import BaseUserManager, Group |
|
11 |
from django.conf import settings |
|
8 |
from django.contrib.auth import authenticate, login as django_login, logout |
|
12 | 9 |
from django.db.models import FieldDoesNotExist |
10 |
from django.db import IntegrityError |
|
11 |
from django.template.loader import render_to_string |
|
12 |
from django.core import signing |
|
13 |
from django.views.generic.edit import FormView |
|
14 |
from django.views.generic.base import TemplateView, View |
|
13 | 15 | |
14 | ||
15 |
from registration.views import RegistrationView as BaseRegistrationView |
|
16 |
from registration.models import RegistrationProfile |
|
17 |
from registration import signals |
|
18 | ||
16 |
from authentic2.utils import get_form_class |
|
19 | 17 |
from .. import models, app_settings, compat |
20 |
from . import urls |
|
21 | ||
18 |
from .forms import EXPIRATION, registration_id_name |
|
22 | 19 | |
23 | 20 |
logger = logging.getLogger(__name__) |
24 | 21 | |
25 | ||
26 |
class RegistrationView(BaseRegistrationView): |
|
27 |
form_class = urls.get_form_class(app_settings.A2_REGISTRATION_FORM_CLASS) |
|
28 | ||
29 |
def register(self, request, **cleaned_data): |
|
30 |
User = compat.get_user_model() |
|
31 |
if Site._meta.installed: |
|
32 |
site = Site.objects.get_current() |
|
33 |
else: |
|
34 |
site = RequestSite(request) |
|
35 |
user_fields = {} |
|
36 |
for field in compat.get_registration_fields(): |
|
37 |
# save User model fields |
|
22 |
User = compat.get_user_model() |
|
23 | ||
24 |
def valid_token(method): |
|
25 |
def f(obj, *args, **kwargs): |
|
26 |
try: |
|
27 |
registration_kwargs = signing.loads(kwargs['registration_token'], |
|
28 |
max_age=EXPIRATION*3600*24) |
|
29 |
params = kwargs.copy() |
|
30 |
params.update(registration_kwargs) |
|
31 |
except signing.SignatureExpired: |
|
32 |
return redirect('registration_activation_expired') |
|
33 |
return method(obj, *args, **params) |
|
34 |
return f |
|
35 | ||
36 |
def login(request, user, redirect_url='auth_homepage'): |
|
37 |
user.backend = settings.AUTHENTICATION_BACKENDS[1] |
|
38 |
django_login(request, user) |
|
39 |
return redirect(redirect_url) |
|
40 | ||
41 |
class LoginView(View): |
|
42 |
redirect_url = 'auth_homepage' |
|
43 | ||
44 |
@valid_token |
|
45 |
def get(self, request, *args, **kwargs): |
|
46 |
try: |
|
47 |
user = User.objects.get(email=kwargs['email'], username=kwargs['username']) |
|
48 |
return login(request, user) |
|
49 |
except User.DoesNotExist: |
|
50 |
return redirect(self.redirect_url) |
|
51 | ||
52 |
class RegistrationView(FormView): |
|
53 |
form_class = get_form_class(app_settings.A2_REGISTRATION_FORM_CLASS) |
|
54 |
template_name = 'registration/registration_form.html' |
|
55 | ||
56 |
def form_valid(self, form): |
|
57 |
form.save(self.request) |
|
58 |
return redirect('registration_complete') |
|
59 | ||
60 |
class RegistrationCompletionView(FormView): |
|
61 |
form_class = get_form_class(app_settings.A2_REGISTRATION_COMPLETION_FORM_CLASS) |
|
62 |
http_method_names = ['get', 'post'] |
|
63 |
template_name = 'registration/registration_completion_form.html' |
|
64 | ||
65 |
@valid_token |
|
66 |
def get(self, request, *args, **kwargs): |
|
67 |
if app_settings.A2_REGISTRATION_EMAIL_IS_UNIQUE: |
|
68 |
print "UNIQUE???" |
|
38 | 69 |
try: |
39 |
User._meta.get_field(field) |
|
40 |
except FieldDoesNotExist: |
|
41 |
continue |
|
42 |
if field.startswith('password'): |
|
43 |
continue |
|
44 |
user_fields[field] = cleaned_data[field] |
|
45 |
if field == 'email': |
|
46 |
user_fields[field] = BaseUserManager.normalize_email(user_fields[field]) |
|
47 |
new_user = User(is_active=False, **user_fields) |
|
48 |
new_user.clean() |
|
49 |
new_user.set_password(cleaned_data['password1']) |
|
50 |
new_user.save() |
|
51 |
attributes = models.Attribute.objects.filter( |
|
52 |
asked_on_registration=True) |
|
53 |
if attributes: |
|
54 |
for attribute in attributes: |
|
55 |
attribute.set_value(new_user, cleaned_data[attribute.name]) |
|
56 |
if app_settings.A2_REGISTRATION_GROUPS: |
|
57 |
groups = [] |
|
58 |
for name in app_settings.A2_REGISTRATION_GROUPS: |
|
59 |
group, created = Group.objects.get_or_create(name=name) |
|
60 |
groups.append(group) |
|
61 |
new_user.groups = groups |
|
62 |
registration_profile = RegistrationProfile.objects.create_profile(new_user) |
|
63 |
registration_profile.send_activation_email(site) |
|
64 | ||
65 |
signals.user_registered.send(sender=self.__class__, |
|
66 |
user=new_user, |
|
67 |
request=request) |
|
68 |
return new_user |
|
69 | ||
70 |
def registration_allowed(self, request): |
|
71 |
""" |
|
72 |
Indicate whether account registration is currently permitted, |
|
73 |
based on the value of the setting ``REGISTRATION_OPEN``. This |
|
74 |
is determined as follows: |
|
75 | ||
76 |
* If ``REGISTRATION_OPEN`` is not specified in settings, or is |
|
77 |
set to ``True``, registration is permitted. |
|
78 | ||
79 |
* If ``REGISTRATION_OPEN`` is both specified and set to |
|
80 |
``False``, registration is not permitted. |
|
81 |
|
|
82 |
""" |
|
83 |
return getattr(settings, 'REGISTRATION_OPEN', True) |
|
84 | ||
85 |
def get_success_url(self, request, user): |
|
86 |
""" |
|
87 |
Return the name of the URL to redirect to after successful |
|
88 |
user registration. |
|
89 |
|
|
90 |
""" |
|
91 |
return ('registration_complete', (), {}) |
|
92 | ||
93 |
register = RegistrationView.as_view() |
|
70 |
user = User.objects.get(email__iexact=kwargs['email']) |
|
71 |
except User.DoesNotExist: |
|
72 |
return super(RegistrationCompletionView, self).get(request, *args, **kwargs) |
|
73 |
return login(request, user) |
|
74 |
else: |
|
75 |
user_accounts = User.objects.filter(email__iexact=kwargs['email']) |
|
76 |
if user_accounts: |
|
77 |
has_active_account = False |
|
78 |
for user in user_accounts: |
|
79 |
registration_id = kwargs[registration_id_name] |
|
80 |
if models.AttributeValue.objects.with_owner(user).filter(attribute__label=registration_id_name, |
|
81 |
content='"%s"' % registration_id).exists(): |
|
82 |
has_active_account = True |
|
83 |
if has_active_account: |
|
84 |
logout(request) |
|
85 |
context = kwargs.copy() |
|
86 |
context.update({'accounts': user_accounts}) |
|
87 |
self.template_name = 'registration/login_choices.html' |
|
88 |
return self.render_to_response(context) |
|
89 |
else: |
|
90 |
return super(RegistrationCompletionView, self).get(request, *args, **kwargs) |
|
91 |
else: |
|
92 |
return super(RegistrationCompletionView, self).get(request, *args, **kwargs) |
|
93 | ||
94 |
@valid_token |
|
95 |
def post(self, request, *args, **kwargs): |
|
96 |
form = self.get_form(self.form_class) |
|
97 |
if form.is_valid(): |
|
98 |
params = form.cleaned_data.copy() |
|
99 |
params.update(kwargs) |
|
100 |
user, next_url = form.save(**params) |
|
101 |
if next_url: |
|
102 |
return login(request, user, next_url) |
|
103 |
return login(request, user) |
|
104 |
else: |
|
105 |
return self.form_invalid(form) |
|
94 | 106 | |
107 |
class DeleteView(TemplateView): |
|
108 |
def get(self, request, *args, **kwargs): |
|
109 |
next_url = request.build_absolute_uri(request.META.get('HTTP_REFERER')\ |
|
110 |
or request.GET.get('next_url')) |
|
111 |
if not app_settings.A2_REGISTRATION_CAN_DELETE_ACCOUNT: |
|
112 |
return redirect(next_url) |
|
113 |
return render(request, 'registration/delete_account.html') |
|
95 | 114 | |
96 |
@login_required |
|
97 |
def delete(request, next_url='/'): |
|
98 |
next_url = request.build_absolute_uri(request.META.get('HTTP_REFERER') or next_url) |
|
99 |
if not app_settings.A2_REGISTRATION_CAN_DELETE_ACCOUNT: |
|
100 |
return redirect(next_url) |
|
101 |
if request.method == 'POST': |
|
115 |
def post(self, request, *args, **kwargs): |
|
116 |
next_url = request.build_absolute_uri(request.META.get('HTTP_REFERER')\ |
|
117 |
or request.GET.get('next_url')) |
|
102 | 118 |
if 'submit' in request.POST: |
103 | 119 |
models.DeletedUser.objects.delete_user(request.user) |
104 | 120 |
logger.info(u'deletion of account %s requested' % request.user) |
... | ... | |
106 | 122 |
return redirect('auth_logout') |
107 | 123 |
else: |
108 | 124 |
return redirect(next_url) |
109 |
return render(request, 'registration/delete_account.html') |
authentic2/settings.py | ||
---|---|---|
180 | 180 |
'admin_tools.menu', |
181 | 181 |
'admin_tools.dashboard', |
182 | 182 |
'django.contrib.admin', |
183 |
'registration', |
|
184 | 183 |
'django_select2', |
185 | 184 |
'django_tables2', |
186 | 185 |
'authentic2.nonce', |
authentic2/templates/auth/login_form.html | ||
---|---|---|
15 | 15 |
<p>→ {% trans "Forgot password?" %} <a href="{% url 'auth_password_reset' %}">{% trans "Reset it!" %}</a></p> |
16 | 16 |
{% endif %} |
17 | 17 |
{% if registration_authorized %} |
18 |
<p>→ {% trans "Not a member?" %} <a href="{% url 'registration_register' %}">{% trans "Register!" %}</a></p> |
|
18 |
<p>→ {% trans "Not a member?" %} <a href="{% url 'registration_register' %}?{{ request.GET.urlencode }}">{% trans "Register!" %}</a></p>
|
|
19 | 19 |
{% endif %} |
20 | 20 |
</div> |
authentic2/templates/registration/activate.html | ||
---|---|---|
1 |
{% extends "base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block title %} |
|
5 |
{% trans "Account activation" %} |
|
6 |
{% endblock %} |
|
7 | ||
8 |
{% block content %} |
|
9 | ||
10 |
{% if account %} |
|
11 | ||
12 |
<p>{% trans "Account successfully activated" %}</p> |
|
13 | ||
14 |
<p><a href="{% url 'auth_login' %}">{% trans "Log in" %}</a></p> |
|
15 | ||
16 |
{% else %} |
|
17 | ||
18 |
<p>{% trans "Account activation failed" %}</p> |
|
19 | ||
20 |
{% endif %} |
|
21 | ||
22 |
{% endblock %} |
authentic2/templates/registration/activation_complete.html | ||
---|---|---|
1 |
{% extends "base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block content %} |
|
5 |
{% trans "Your account is now activated" %} |
|
6 |
{% endblock %} |
authentic2/templates/registration/activation_email.html | ||
---|---|---|
1 |
{% load i18n %} |
|
2 |
<h3>{% trans "Account creation" %}</h3> |
|
3 |
<p> |
|
4 |
{% blocktrans %} |
|
5 |
To continue your account creation on {{ site }} please <a href="{{ site }}{{ registration_token }}">click here</a> |
|
6 |
{% endblocktrans %} |
|
7 |
</p> |
|
8 |
<p>{% blocktrans %}This link is valid for {{ expiration_days }} days.{% endblocktrans %}</p> |
authentic2/templates/registration/activation_email.txt | ||
---|---|---|
1 | 1 |
{% load i18n %} |
2 |
{% trans "Activate account at" %} {{ site.name }}:
|
|
2 |
{% trans "Activate account" %}:
|
|
3 | 3 | |
4 |
http://{{ site.domain }}{% url 'registration_activate' activation_key %}
|
|
4 |
{{ registration_url }}
|
|
5 | 5 | |
6 | 6 |
{% blocktrans %}Link is valid for {{ expiration_days }} days.{% endblocktrans %} |
authentic2/templates/registration/activation_email_subject.txt | ||
---|---|---|
1 |
{% load i18n %}{% trans "Account activation on" %} {{ site.name }} |
|
1 |
{% load i18n %}{% trans "Account activation on" %} {{ site }} |
authentic2/templates/registration/activation_expired.html | ||
---|---|---|
1 |
{% extends "base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block title %} |
|
5 |
{% trans "Account activation expired" %} |
|
6 |
{% endblock %} |
|
7 | ||
8 |
{% block content %} |
|
9 |
<h2>{% trans "Account activation" %}</h2> |
|
10 |
<p>{% trans "Your activation key is expired" %}</p> |
|
11 |
{% endblock %} |
authentic2/templates/registration/login_choices.html | ||
---|---|---|
1 |
{% extends "base.html" %} |
|
2 |
{% load breadcrumbs i18n %} |
|
3 | ||
4 |
{% block title %} |
|
5 |
{% trans "Login" %} |
|
6 |
{% endblock %} |
|
7 | ||
8 |
{% block breadcrumbs %} |
|
9 |
{{ block.super }} |
|
10 |
{% breadcrumb_url 'Register' %} |
|
11 |
{% endblock %} |
|
12 | ||
13 | ||
14 |
{% block content %} |
|
15 | ||
16 |
<h2>{% trans "Login" %}</h2> |
|
17 |
<p>{% trans "Multiple accounts are associated to this email." %} |
|
18 |
{% trans "Please choose the account you want to log in with:" %} |
|
19 |
</p> |
|
20 | ||
21 | ||
22 |
<ul> |
|
23 |
{% for account in accounts %} |
|
24 |
<li><a href="{% url "registration_login" registration_token account.username %}">{{ account }}</a></li> |
|
25 |
{% endfor %} |
|
26 |
</ul> |
|
27 | ||
28 |
{% endblock %} |
authentic2/templates/registration/registration_complete.html | ||
---|---|---|
6 | 6 |
{% endblock %} |
7 | 7 | |
8 | 8 |
{% block content %} |
9 |
<p>{% trans "You are now registered. Activation email sent." %}</p>
|
|
9 |
<p>{% trans "Thank you for registering. Activation email sent." %}</p>
|
|
10 | 10 |
<p><a href="/">{% trans "Back" %}</a></p> |
11 | 11 |
{% endblock %} |
authentic2/templates/registration/registration_completion_form.html | ||
---|---|---|
1 |
{% extends "base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block title %} |
|
5 |
{% trans "Registration" %} |
|
6 |
{% endblock %} |
|
7 | ||
8 |
{% load breadcrumbs %} |
|
9 |
{% block breadcrumbs %} |
|
10 |
{{ block.super }} |
|
11 |
{% breadcrumb_url 'Register' %} |
|
12 |
{% endblock %} |
|
13 | ||
14 |
{% block content %} |
|
15 | ||
16 |
<h2>{% trans "Registration" %}</h2> |
|
17 |
<p>{% trans "Please fill the formm to complete your registration" %}</p> |
|
18 |
<form method="post"> |
|
19 |
{% csrf_token %} |
|
20 |
{{ form.as_p }} |
|
21 |
<input type="submit" value="{% trans 'Submit' %}" /> |
|
22 |
</form> |
|
23 |
{% endblock %} |
authentic2/templates/registration/registration_form.html | ||
---|---|---|
15 | 15 | |
16 | 16 |
<h2>{% trans "Registration" %}</h2> |
17 | 17 | |
18 |
<form method="post" action=".">
|
|
18 |
<form method="post"> |
|
19 | 19 |
{% csrf_token %} |
20 | 20 |
{{ form.as_p }} |
21 | 21 |
authentic2/tests.py | ||
---|---|---|
1 |
from django.test import TestCase
|
|
1 |
import re
|
|
2 | 2 | |
3 |
from django.core import mail |
|
4 |
from django.core.urlresolvers import reverse |
|
5 |
from django.test import TestCase |
|
6 |
from django.test.client import Client |
|
7 |
from django.test.utils import override_settings |
|
3 | 8 |
from django.contrib.auth.hashers import check_password |
9 |
from django.conf import settings |
|
4 | 10 | |
5 | 11 |
from . import hashers |
6 | 12 | |
... | ... | |
88 | 94 |
self.assertEqual(User.objects.count(), 1) |
89 | 95 |
self.assertEqual(Attribute.objects.count(), 1) |
90 | 96 |
self.assertEqual(AttributeValue.objects.count(), 1) |
97 | ||
98 |
class RegistrationTests(TestCase): |
|
99 |
def setUp(self): |
|
100 |
self.client = Client() |
|
101 | ||
102 |
def test_registration(self): |
|
103 |
response = self.client.post(reverse('registration_register'), |
|
104 |
{'email': 'testbot@entrouvert.com'}) |
|
105 |
self.assertRedirects(response, reverse('registration_complete')) |
|
106 |
self.assertEqual(len(mail.outbox), 1) |
|
107 |
links = re.findall('http[s]://.*/', mail.outbox[0].body) |
|
108 |
self.assertIsInstance(links, list) and self.assertIsNot(links, []) |
|
109 |
link = links[0] |
|
110 |
completion = self.client.get(link) |
|
111 |
self.assertEqual(completion.status_code, 200) |
|
112 |
self.bad_password_test(link) |
|
113 |
self.good_password_test(link) |
|
114 |
mail.outbox = [] |
|
115 | ||
116 |
def bad_password_test(self, url): |
|
117 |
""" |
|
118 |
test short filled password |
|
119 |
""" |
|
120 |
completion = self.client.post(url, {'username': 'toto', |
|
121 |
'password1': 'toto', |
|
122 |
'password2': 'toto'}) |
|
123 |
self.assertEqual(completion.status_code, 200) |
|
124 | ||
125 |
def good_password_test(self, url): |
|
126 |
completion = self.client.post(url, {'username': 'toto', |
|
127 |
'password1': 'T0toto', |
|
128 |
'password2': 'T0toto'}) |
|
129 |
self.assertEqual(completion.status_code, 302) |
authentic2/utils.py | ||
---|---|---|
206 | 206 |
yield t |
207 | 207 |
else: |
208 | 208 |
yield t[0] |
209 | ||
210 |
def get_form_class(form_class): |
|
211 |
module, form_class = form_class.rsplit('.', 1) |
|
212 |
module = import_module(module) |
|
213 |
return getattr(module, form_class) |
diagnose.py | ||
---|---|---|
19 | 19 |
raise |
20 | 20 |
print 'django_authopenid is missing: easy_install django-authopenid' |
21 | 21 | |
22 |
try: |
|
23 |
import registration |
|
24 |
except ImportError: |
|
25 |
print 'registration is missing: easy_install django-registration' |
requirements.txt | ||
---|---|---|
2 | 2 |
south>=0.8.4 |
3 | 3 |
requests |
4 | 4 |
django-model-utils |
5 |
django-registration>=1 |
|
6 | 5 |
django-debug-toolbar>=1.2,<1.3 |
7 | 6 |
--allow-external django-admin-tools |
8 | 7 |
--allow-unverified django-admin-tools |
setup.py | ||
---|---|---|
117 | 117 |
'south>=0.8.4', |
118 | 118 |
'requests', |
119 | 119 |
'django-model-utils', |
120 |
'django-registration>=1', |
|
121 | 120 |
'django-admin-tools>=0.5.1', |
122 | 121 |
'dnspython', |
123 | 122 |
'django-select2', |
124 |
- |