Projet

Général

Profil

0001-create-assisted-password-input-widgets-24438.patch

Anonyme, 06 juillet 2018 18:25

Télécharger (29,1 ko)

Voir les différences:

Subject: [PATCH 1/2] create assisted password input widgets (#24438)

 src/authentic2/app_settings.py                |   1 +
 src/authentic2/passwords.py                   |   8 +-
 src/authentic2/registration_backend/forms.py  |  12 +-
 .../registration_backend/widgets.py           |  92 +++++++++
 .../static/authentic2/css/password.css        | 140 ++++++++++++++
 .../static/authentic2/css/style.css           |  19 ++
 .../static/authentic2/js/password.js          | 181 ++++++++++++++++++
 .../authentic2/widgets/assisted_password.html |  34 ++++
 .../templates/authentic2/widgets/attrs.html   |   2 +
 .../registration_completion_form.html         |   2 +-
 tests/test_api.py                             |  26 +--
 tests/test_registration.py                    |  49 ++++-
 12 files changed, 542 insertions(+), 24 deletions(-)
 create mode 100644 src/authentic2/registration_backend/widgets.py
 create mode 100644 src/authentic2/static/authentic2/css/password.css
 create mode 100644 src/authentic2/static/authentic2/js/password.js
 create mode 100644 src/authentic2/templates/authentic2/widgets/assisted_password.html
 create mode 100644 src/authentic2/templates/authentic2/widgets/attrs.html
src/authentic2/app_settings.py
143 143
    A2_PASSWORD_POLICY_MIN_LENGTH=Setting(default=8, definition='Minimum number of characters in a password'),
144 144
    A2_PASSWORD_POLICY_REGEX=Setting(default=None, definition='Regular expression for validating passwords'),
145 145
    A2_PASSWORD_POLICY_REGEX_ERROR_MSG=Setting(default=None, definition='Error message to show when the password do not validate the regular expression'),
146
    A2_PASSWORD_WIDGET_SHOW_ALL_BUTTON=Setting(default=False, definition='Show a button on BasePasswordInput for the user to see password input text'),
146 147
    A2_PASSWORD_POLICY_CLASS=Setting(
147 148
        default='authentic2.passwords.DefaultPasswordChecker',
148 149
        definition='path of a class to validate passwords'),
src/authentic2/passwords.py
76 76
        if self.min_length:
77 77
            yield self.Check(
78 78
                result=len(password) >= self.min_length,
79
                label=_('at least %s characters') % self.min_length)
79
                label=_('%s characters') % self.min_length)
80 80

  
81 81
        if self.at_least_one_lowercase:
82 82
            yield self.Check(
83 83
                result=any(c.islower() for c in password),
84
                label=_('at least 1 lowercase letter'))
84
                label=_('1 lowercase letter'))
85 85

  
86 86
        if self.at_least_one_digit:
87 87
            yield self.Check(
88 88
                result=any(c.isdigit() for c in password),
89
                label=_('at least 1 digit'))
89
                label=_('1 digit'))
90 90

  
91 91
        if self.at_least_one_uppercase:
92 92
            yield self.Check(
93 93
                result=any(c.isupper() for c in password),
94
                label=_('at least 1 uppercase letter'))
94
                label=_('1 uppercase letter'))
95 95

  
96 96
        if self.regexp and self.regexp_label:
97 97
            yield self.Check(
src/authentic2/registration_backend/forms.py
1 1
import re
2 2
import copy
3 3
from collections import OrderedDict
4
import json
4 5

  
5 6
from django.conf import settings
6 7
from django.core.exceptions import ValidationError
......
15 16
from django.core.mail import send_mail
16 17
from django.core import signing
17 18
from django.template import RequestContext
18
from django.template.loader import render_to_string
19 19
from django.core.urlresolvers import reverse
20 20
from django.core.validators import RegexValidator
21 21

  
22
from .widgets import CheckPasswordInput, NewPasswordInput
22 23
from .. import app_settings, compat, forms, utils, validators, models, middleware, hooks
23 24
from authentic2.a2_rbac.models import OrganizationalUnit
24 25

  
......
115 116

  
116 117

  
117 118
class RegistrationCompletionForm(RegistrationCompletionFormNoPassword):
118
    password1 = CharField(widget=PasswordInput, label=_("Password"),
119
            validators=[validators.validate_password],
120
            help_text=validators.password_help_text())
121
    password2 = CharField(widget=PasswordInput, label=_("Password (again)"))
119

  
120
    password1 = CharField(widget=NewPasswordInput(), label=_("Password"),
121
        validators=[validators.validate_password],
122
        help_text=validators.password_help_text())
123
    password2 = CharField(widget=CheckPasswordInput(), label=_("Password (again)"))
122 124

  
123 125
    def clean(self):
124 126
        """
src/authentic2/registration_backend/widgets.py
1
from django.forms import PasswordInput
2
from django.template.loader import render_to_string
3
from django.utils.encoding import force_text
4
from django.utils.safestring import mark_safe
5

  
6
from .. import app_settings
7

  
8

  
9
class BasePasswordInput(PasswordInput):
10
    """
11
    a password Input with some features to help the user choosing a new password
12
    Inspired by Django >= 1.11 new-style rendering
13
    (cf. https://docs.djangoproject.com/fr/1.11/ref/forms/renderers)
14
    """
15
    template_name = 'authentic2/widgets/assisted_password.html'
16
    features = {}
17

  
18
    class Media:
19
        js = ('authentic2/js/password.js',)
20
        css = {
21
            'all': ('authentic2/css/password.css',)
22
        }
23

  
24
    def get_context(self, name, value, attrs):
25
        """
26
        Base get_context
27
        """
28
        context = {
29
            'app_settings': {
30
                'A2_PASSWORD_POLICY_MIN_LENGTH': app_settings.A2_PASSWORD_POLICY_MIN_LENGTH,
31
                'A2_PASSWORD_POLICY_MIN_CLASSES': app_settings.A2_PASSWORD_POLICY_MIN_CLASSES,
32
                'A2_PASSWORD_POLICY_REGEX': app_settings.A2_PASSWORD_POLICY_REGEX,
33
            },
34
            'features': self.features
35
        }
36
        # attach data-* attributes for password.js to activate events
37
        attrs.update(dict([('data-%s' % feat.replace('_', '-'), is_active) for feat, is_active in self.features.items()]))
38

  
39
        context['widget'] = {
40
            'name': name,
41
            'is_hidden': self.is_hidden,
42
            'required': self.is_required,
43
            'template_name': self.template_name,
44
            'attrs': self.build_attrs(extra_attrs=attrs, name=name, type=self.input_type)
45
        }
46
        # Only add the 'value' attribute if a value is non-empty.
47
        if value is None:
48
            value = ''
49
        if value != '':
50
            context['widget']['value'] = force_text(self._format_value(value))
51

  
52
        return context
53

  
54
    def render(self, name, value, attrs=None, **kwargs):
55
        """
56
        Override render with a template-based system
57
        Remove this line when dropping Django 1.8, 1.9, 1.10 compatibility
58
        """
59
        return mark_safe(render_to_string(self.template_name,
60
            self.get_context(name, value, attrs)))
61

  
62

  
63
class CheckPasswordInput(BasePasswordInput):
64
    """
65
    Password typing assistance widget (eg. password2)
66
    """
67
    features = {
68
        'check_equality': True,
69
        'show_all': app_settings.A2_PASSWORD_WIDGET_SHOW_ALL_BUTTON,
70
        'show_last': True,
71
    }
72

  
73
    def get_context(self, name, value, attrs):
74
        context = super(CheckPasswordInput, self).get_context(
75
            name, value, attrs)
76
        return context
77

  
78

  
79
class NewPasswordInput(CheckPasswordInput):
80
    """
81
    Password creation assistance widget with policy (eg. password1)
82
    """
83
    features = {
84
        'check_equality': False,
85
        'show_all': app_settings.A2_PASSWORD_WIDGET_SHOW_ALL_BUTTON,
86
        'show_last': True,
87
        'check_policy': True,
88
    }
89

  
90
    def get_context(self, name, value, attrs):
91
        context = super(NewPasswordInput, self).get_context(name, value, attrs)
92
        return context
src/authentic2/static/authentic2/css/password.css
1
/* required in order to position a2-password-show-all and a2-password-show-last */
2
input[type=password].a2-password-assisted {
3
  padding-right: 60px;
4
  width: 100%;
5
}
6

  
7
.a2-password-icon {
8
	display: inline-block;
9
	width: calc(18em / 14);
10
	text-align: center;
11
	font-style: normal;
12
	padding-right: 1em;
13
}
14

  
15
/* default circle icon */
16
.a2-password-icon:before {
17
	font-family: FontAwesome;
18
  content: "\f111"; /* right hand icon */
19
  font-size: 50%;
20
}
21

  
22
.a2-password-policy-helper {
23
  display: flex;
24
  height: auto;
25
  flex-direction: row;
26
  flex-wrap: wrap;
27
  position: relative;
28
  padding: 0.5rem 1rem;
29
  width: 90%;
30
}
31

  
32
/* we don't want helptext when a2-password-policy-helper is here */
33
.a2-password-policy-helper ~ .helptext {
34
  display: none;
35
}
36

  
37
.a2-password-policy-rule {
38
  flex: 1 1 50%;
39
  list-style: none;
40
}
41

  
42
.password-error {
43
  color: black;
44
}
45

  
46
.password-ok {
47
  color: green;
48
}
49

  
50
.password-error .a2-password-icon:before {
51
  content: "\f00d"; /* cross icon */
52
  color: red;
53
}
54

  
55
.password-ok .a2-password-icon::before {
56
  content: "\f00c"; /* ok icon */
57
  color: green;
58
}
59

  
60
.a2-password-show-last {
61
  position: relative;
62
  display: inline-block;
63
  float: right;
64
  opacity: 0;
65
  text-align: center;
66
  right: 10px;
67
  top: -4.5ex;
68
  width: 20px;
69
}
70

  
71
.a2-password-show-button {
72
  position: relative;
73
  display: inline-block;
74
  float: right;
75
  padding: 0;
76
  right: 10px;
77
  top: -4.4ex;
78
  cursor: pointer;
79
  width: 20px;
80
}
81

  
82
.a2-password-show-button:after {
83
  content: "\f06e"; /* eye */
84
  font-family: FontAwesome;
85
  font-size: 125%;
86
}
87

  
88
.hide-password-button:after {
89
  content: "\f070"; /* crossed eye */
90
  font-family: FontAwesome;
91
  font-size: 125%;
92
}
93

  
94
.a2-passwords-messages {
95
  display: block;
96
  padding: 0.5rem 1rem;
97
}
98

  
99
.a2-passwords-default {
100
	list-style: none;
101
  opacity: 0;
102
}
103

  
104
.password-error .a2-passwords-default,
105
.password-ok .a2-passwords-default {
106
	display: none;
107
}
108

  
109
.a2-passwords-matched,
110
.a2-passwords-unmatched {
111
	display: none;
112
	list-style: none;
113
	opacity: 0;
114
	transition: all 0.3s ease;
115
}
116

  
117
.password-error.a2-passwords-messages:before,
118
.password-ok.a2-passwords-messages:before {
119
  display: none;
120
}
121

  
122
.password-error .a2-passwords-unmatched,
123
.password-ok .a2-passwords-matched {
124
	display: block;
125
	opacity: 1;
126
}
127

  
128
.password-error .a2-passwords-unmatched .a2-password-icon:before {
129
  content: "\f00d"; /* cross icon */
130
  color: red;
131
}
132

  
133
.password-ok .a2-passwords-matched .a2-password-icon:before {
134
  content: "\f00c"; /* ok icon */
135
  color: green;
136
}
137

  
138
.a2-password-policy-intro {
139
  margin: 0;
140
}
src/authentic2/static/authentic2/css/style.css
76 76
.a2-log-message {
77 77
  white-space: pre-wrap;
78 78
}
79

  
80
.a2-registration-completion {
81
  padding: 1rem;
82
  min-width: 320px;
83
  width: 50%;
84
}
85

  
86
@media screen and (max-width: 800px) {
87
  .a2-registration-completion {
88
    width: 100%;
89
  }
90
}
91

  
92
.a2-registration-completion input,
93
.a2-registration-completion select,
94
.a2-registration-completion textarea
95
{
96
  width: 100%;
97
}
src/authentic2/static/authentic2/js/password.js
1
"use strict";
2
/* globals $, window, console */
3

  
4
$(function () {
5
	var debounce = function (func, milliseconds) {
6
		var timer;
7
		return function() {
8
			window.clearTimeout(timer);
9
			timer = window.setTimeout(function() {
10
				func();
11
			}, milliseconds);
12
		};
13
	}
14
	var toggleError = function($elt) {
15
		$elt.removeClass('password-ok');
16
		$elt.addClass('password-error');
17
	}
18
	var toggleOk = function($elt) {
19
		$elt.removeClass('password-error');
20
		$elt.addClass('password-ok');
21
	}
22
	/*
23
	* toggle error/ok on element with class names same as the validation code names
24
	* (cf. error_codes in authentic2.validators.validate_password)
25
	*/
26
	var validatePassword = function(event) {
27
		var $this = $(event.target);
28
		if (!$this.val()) return;
29
		var password = $this.val();
30
		var inputName = $this.attr('name');
31
		getValidation(password, inputName);
32
	}
33
	var getValidation = function(password, inputName) {
34
		var policyContainer = $('#a2-password-policy-helper-' + inputName);
35
		$.ajax({
36
			method: 'POST',
37
			url: '/api/validate-password/',
38
			data: JSON.stringify({'password': password}),
39
			dataType: 'json',
40
			contentType: 'application/json; charset=utf-8',
41
			success: function(data) {
42
				if (data.result) {
43
					policyContainer
44
					.empty()
45
					.removeClass('password-error password-ok');
46
					data.checks.forEach(function (error) {
47
						var $li = $('<li class="a2-password-policy-rule"></li>')
48
							.html('<i class="a2-password-icon"></i>' + error.label)
49
							.appendTo(policyContainer);
50
						if (!error.result) {
51
							toggleError($li);
52
						} else {
53
							toggleOk($li);
54
						}
55
					});
56
				}
57
			}
58
		});
59
	}
60
	/*
61
	* Check password equality
62
	*/
63
	var displayPasswordEquality = function($input, $inputTarget) {
64
		var messages = $('#a2-password-equality-helper-' + $input.attr('name'));
65
		var form = $input.parents('form');
66
		if ($inputTarget === undefined) {
67
			$inputTarget = form.find('input[type=password]:not(input[name='+$input.attr('name')+'])');
68
		}
69
		if (!$input.val() || !$inputTarget.val()) return;
70
		if ($inputTarget.val() !== $input.val()) {
71
			toggleError(messages);
72
		} else {
73
			toggleOk(messages);
74
		}
75
	}
76
	var passwordEquality = function () {
77
		var $this = $(this);
78
		displayPasswordEquality($this);
79
	}
80
	/*
81
	* Hide and show password handlers
82
	*/
83
	var showPassword = function (event) {
84
		var $this = $(event.target);
85
		$this.addClass('hide-password-button');
86
		var name = $this.attr('id').split('a2-password-show-button-')[1];
87
		$('[name='+name+']').attr('type', 'text');
88
		event.preventDefault();
89
	}
90
	var hidePassword = function (event) {
91
		var $this = $(event.target);
92
		window.setTimeout(function () {
93
			$this.removeClass('hide-password-button');
94
			var name = $this.attr('id').split('a2-password-show-button-')[1];
95
			$('[name='+name+']').attr('type', 'password');
96
		}, 3000);
97
	}
98
	/*
99
	* Show the last character
100
	*/
101
	var showLastChar = function(event) {
102
		if (event.keyCode == 32 || event.key === undefined || event.key == ""
103
			|| event.key == "Unidentified" || event.key.length > 1) {
104
			return;
105
		}
106
		var duration = 1000;
107
		$('#a2-password-show-last-'+$(event.target).attr('name'))
108
			.text(event.key)
109
			.animate({'opacity': 1}, {
110
				duration: 50,
111
				queue: false,
112
				complete: function () {
113
					var $this = $(this);
114
					window.setTimeout(
115
						debounce(function () {
116
							$this.animate({'opacity': 0}, {
117
								duration: 50
118
							});
119
						}, duration), duration);
120
				}
121
			});
122
	}
123
	/*
124
	* Init events
125
	*/
126
	/* add password validation and equality check event handlers */
127
	$('form input[type=password]:not(input[data-check-policy])').each(function () {
128
		$('#a2-password-policy-helper-' + $(this).attr('name')).hide();
129
	});
130
	$('body').on('keyup', 'form input[data-check-policy]', validatePassword);
131
	$('body').on('keyup', 'form input[data-check-equality]', passwordEquality);
132
	/*
133
	* Add event to handle displaying error/OK
134
	* while editing the first password
135
	* only if the second one is not empty
136
	*/
137
	$('input[data-check-equality]')
138
		.each(function () {
139
			var $input2 = $(this);
140
			$('body')
141
				.on('keyup', 'form input[type=password]:not([name=' + $input2.attr('name') + '])',
142
					function (event) {
143
						var $input1 = $(event.target);
144
						if ($input2.val().length) {
145
							displayPasswordEquality($input2, $input1);
146
						}
147
					});
148
		});
149
	/* add the a2-password-show-button after the first input */
150
	$('input[data-show-all]')
151
		.each(function () {
152
			var $this = $(this);
153
			if (!$('#a2-password-show-button-' + $this.attr('name')).length) {
154
				$(this).after($('<i class="a2-password-show-button" id="a2-password-show-button-'
155
					+ $this.attr('name') + '"></i>')
156
						.on('mousedown', showPassword)
157
						.on('mouseup mouseleave', hidePassword)
158
				);
159
			}
160
		});
161
	/* show the last character on keypress */
162
	$('input[data-show-last]')
163
		.each(function () {
164
			var $this = $(this);
165
			if (!$('#a2-password-show-last-' + $this.attr('name')).length) {
166
				// on crée un div placé dans le padding-right de l'input
167
				var $span = $('<span class="a2-password-show-last" id="a2-password-show-last-'
168
					+ $this.attr('name') + '"></span>)')
169
				$span.css({
170
					'font-size': $this.css('font-size'),
171
					'font-family': $this.css('font-family'),
172
					'line-height': parseInt($this.css('line-height').replace('px', '')) - parseInt($this.css('padding-bottom').replace('px', '')) + 'px',
173
					'vertical-align': $this.css('vertical-align'),
174
					'padding-top': $this.css('padding-top'),
175
					'padding-bottom': $this.css('padding-bottom')
176
				});
177
				$this.after($span);
178
			}
179
		});
180
	$('body').on('keyup', 'form input[data-show-last]', showLastChar);
181
});
src/authentic2/templates/authentic2/widgets/assisted_password.html
1
{% load i18n %}
2
<input class="a2-password-assisted" {% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "authentic2/widgets/attrs.html" %}>
3
{% if features.check_policy %}
4
<p class="a2-password-policy-intro">{% blocktrans %}In order to create a secure password, please use <i>at least</i> : {% endblocktrans %}</p>
5
<ul class="a2-password-policy-helper" id="a2-password-policy-helper-{{ widget.attrs.name }}">
6
	{% comment %}Required to display the initial rules on page load{% endcomment %}
7
	{% if app_settings.A2_PASSWORD_POLICY_MIN_LENGTH %}
8
	<li class="a2-password-policy-rule"><i class="a2-password-icon"></i>{% blocktrans with A2_PASSWORD_POLICY_MIN_LENGTH=app_settings.A2_PASSWORD_POLICY_MIN_LENGTH %}{{ A2_PASSWORD_POLICY_MIN_LENGTH }} characters{% endblocktrans %}</li>
9
	{% endif %}
10
	{% if app_settings.A2_PASSWORD_POLICY_MIN_CLASSES > 0 %}
11
	<li class="a2-password-policy-rule"><i class="a2-password-icon"></i>{% trans "1 lowercase letter" %}</li>
12
	{% endif %}
13
	{% if app_settings.A2_PASSWORD_POLICY_MIN_CLASSES > 1 %}
14
	<li class="a2-password-policy-rule"><i class="a2-password-icon"></i>{% trans "1 digit" %}</li>
15
	{% endif %}
16
	{% if app_settings.A2_PASSWORD_POLICY_MIN_CLASSES > 2 %}
17
	<li class="a2-password-policy-rule"><i class="a2-password-icon"></i>{% trans "1 uppercase letter" %}</li>
18
	{% endif %}
19
	{% if app_settings.A2_PASSWORD_POLICY_REGEX %}
20
		{% if app_settings.A2_PASSWORD_POLICY_REGEX_ERROR_MSG %}
21
			<li class="a2-password-policy-rule"><i class="a2-password-icon"></i>{% blocktrans with A2_PASSWORD_POLICY_REGEX_ERROR_MSG=app_settings.A2_PASSWORD_POLICY_REGEX_ERROR_MSG %}{{ A2_PASSWORD_POLICY_REGEX_ERROR_MSG }}{% endblocktrans %}</li>
22
		{% else %}
23
			<li class="a2-password-policy-rule"><i class="a2-password-icon"></i>{% blocktrans with A2_PASSWORD_POLICY_REGEX=app_settings.A2_PASSWORD_POLICY_REGEX %}Match the regular expression: {{ A2_PASSWORD_POLICY_REGEX }}, please change this message using the A2_PASSWORD_POLICY_REGEX_ERROR_MSG setting.'{% endblocktrans %}</li>
24
		{% endif %}
25
	{% endif %}
26
</ul>
27
{% endif %}
28
{% if features.check_equality %}
29
<ul class="a2-passwords-messages" id="a2-password-equality-helper-{{ widget.attrs.name }}">
30
	<li class="a2-passwords-default"><i class="a2-password-icon"></i>{% trans 'Both passwords must match.' %}</li>
31
	<li class="a2-passwords-matched"><i class="a2-password-icon"></i>{% trans 'Passwords match.' %}</li>
32
	<li class="a2-passwords-unmatched"><i class="a2-password-icon"></i>{% trans 'Passwords do not match.' %}</li>
33
</ul>
34
{% endif %}
src/authentic2/templates/authentic2/widgets/attrs.html
1
{% comment %}Will be deprecated in Django 1.11 : replace with django/forms/widgets/attrs.html{% endcomment %}
2
{% for name, value in widget.attrs.items %}{% if value != False %} {{ name }}{% if value != True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}{% endfor %}
src/authentic2/templates/registration/registration_completion_form.html
25 25
{% block content %}
26 26
      <h2>{% trans "Registration" %}</h2>
27 27
      <p>{% trans "Please fill the form to complete your registration" %}</p>
28
      <form method="post">
28
      <form method="post" class="a2-registration-completion">
29 29
        {% csrf_token %}
30 30
        {{ form.as_p }}
31 31
        <button class="submit-button">{% trans 'Submit' %}</button>
tests/test_api.py
834 834
            ('x' * 8,        False,  True,  True, False, False),
835 835
            ('x' * 8 + '1',  False,  True,  True,  True, False),
836 836
            ('x' * 8 + '1X',  True,  True,  True,  True,  True)):
837
        response = app.post_json('/api/validate-password/', params={'password': password}) 
837
        response = app.post_json('/api/validate-password/', params={'password': password})
838 838
        assert response.json['result'] == 1
839 839
        assert response.json['ok'] is ok
840 840
        assert len(response.json['checks']) == 4
841
        assert response.json['checks'][0]['label'] == 'at least 8 characters'
841
        assert response.json['checks'][0]['label'] == '8 characters'
842 842
        assert response.json['checks'][0]['result'] is length
843
        assert response.json['checks'][1]['label'] == 'at least 1 lowercase letter'
843
        assert response.json['checks'][1]['label'] == '1 lowercase letter'
844 844
        assert response.json['checks'][1]['result'] is lower
845
        assert response.json['checks'][2]['label'] == 'at least 1 digit'
845
        assert response.json['checks'][2]['label'] == '1 digit'
846 846
        assert response.json['checks'][2]['result'] is digit
847
        assert response.json['checks'][3]['label'] == 'at least 1 uppercase letter'
847
        assert response.json['checks'][3]['label'] == '1 uppercase letter'
848 848
        assert response.json['checks'][3]['result'] is upper
849 849

  
850 850

  
......
856 856
    assert response.json['result'] == 1
857 857
    assert response.json['ok'] is False
858 858
    assert len(response.json['checks']) == 5
859
    assert response.json['checks'][0]['label'] == 'at least 8 characters'
859
    assert response.json['checks'][0]['label'] == '8 characters'
860 860
    assert response.json['checks'][0]['result'] is True
861
    assert response.json['checks'][1]['label'] == 'at least 1 lowercase letter'
861
    assert response.json['checks'][1]['label'] == '1 lowercase letter'
862 862
    assert response.json['checks'][1]['result'] is True
863
    assert response.json['checks'][2]['label'] == 'at least 1 digit'
863
    assert response.json['checks'][2]['label'] == '1 digit'
864 864
    assert response.json['checks'][2]['result'] is True
865
    assert response.json['checks'][3]['label'] == 'at least 1 uppercase letter'
865
    assert response.json['checks'][3]['label'] == '1 uppercase letter'
866 866
    assert response.json['checks'][3]['result'] is True
867 867
    assert response.json['checks'][4]['label'] == 'must contain "ok"'
868 868
    assert response.json['checks'][4]['result'] is False
......
871 871
    assert response.json['result'] == 1
872 872
    assert response.json['ok'] is True
873 873
    assert len(response.json['checks']) == 5
874
    assert response.json['checks'][0]['label'] == 'at least 8 characters'
874
    assert response.json['checks'][0]['label'] == '8 characters'
875 875
    assert response.json['checks'][0]['result'] is True
876
    assert response.json['checks'][1]['label'] == 'at least 1 lowercase letter'
876
    assert response.json['checks'][1]['label'] == '1 lowercase letter'
877 877
    assert response.json['checks'][1]['result'] is True
878
    assert response.json['checks'][2]['label'] == 'at least 1 digit'
878
    assert response.json['checks'][2]['label'] == '1 digit'
879 879
    assert response.json['checks'][2]['result'] is True
880
    assert response.json['checks'][3]['label'] == 'at least 1 uppercase letter'
880
    assert response.json['checks'][3]['label'] == '1 uppercase letter'
881 881
    assert response.json['checks'][3]['result'] is True
882 882
    assert response.json['checks'][4]['label'] == 'must contain "ok"'
883 883
    assert response.json['checks'][4]['result'] is True
tests/test_registration.py
1 1
# -*- coding: utf-8 -*-
2 2

  
3
import re
3 4
from urlparse import urlparse
4 5

  
5 6
from django.core.urlresolvers import reverse
......
40 41
    response.form.set('password1', 'toto')
41 42
    response.form.set('password2', 'toto')
42 43
    response = response.form.submit()
43
    assert 'at least 8 characters' in response.content
44
    assert '8 characters' in response.content
44 45

  
45 46
    # set valid password
46 47
    response.form.set('password1', 'T0==toto')
......
585 586
    response = response.form.submit()
586 587
    assert new_next_url in response.content
587 588

  
589

  
590
def test_registration_activate_passwords_not_equal(app, db, settings, mailoutbox):
591
    settings.LANGUAGE_CODE = 'en-us'
592
    settings.A2_VALIDATE_EMAIL_DOMAIN = can_resolve_dns()
593
    settings.A2_EMAIL_IS_UNIQUE = True
594

  
595
    response = app.get(reverse('registration_register'))
596
    response.form.set('email', 'testbot@entrouvert.com')
597
    response = response.form.submit()
598
    response = response.follow()
599
    link = get_link_from_mail(mailoutbox[0])
600
    response = app.get(link)
601
    response.form.set('password1', 'azerty12AZ')
602
    response.form.set('password2', 'AAAazerty12AZ')
603
    response = response.form.submit()
604
    assert "The two password fields didn&#39;t match." in response.content
605

  
606

  
607
def test_registration_activate_assisted_password(app, db, settings, mailoutbox):
608
    response = app.get(reverse('registration_register'))
609
    response.form.set('email', 'testbot@entrouvert.com')
610
    response = response.form.submit()
611
    response = response.follow()
612
    link = get_link_from_mail(mailoutbox[0])
613
    response = app.get(link)
614
    # check presence of the script and css for RegistrationCompletionForm to work
615
    assert "password.js" in response.content
616
    assert "password.css" in response.content
617
    # check default attributes for password.js and css to work
618
    assert re.search('<input class="a2-password-assisted".*data-show-last.*>', response.content, re.I | re.M | re.S)
619
    assert re.search('<input class="a2-password-assisted".*data-check-equality.*>', response.content, re.I | re.M | re.S)
620
    assert re.search('<input class="a2-password-assisted".*data-check-policy.*>', response.content, re.I | re.M | re.S)
621
    # check template containers for password.js to display its results
622
    assert re.search('class="a2-passwords-messages" id="a2-password-equality-helper-', response.content, re.I | re.M | re.S)
623
    assert re.search('class="a2-password-policy-helper" id="a2-password-policy-helper-', response.content, re.I | re.M | re.S)
624
    assert re.search('class="a2-password-policy-rule"', response.content, re.I | re.M | re.S)
625

  
626

  
627
def test_registration_activate_password_no_show_all_button(app, db, settings, mailoutbox):
628
    response = app.get(reverse('registration_register'))
629
    response.form.set('email', 'testbot@entrouvert.com')
630
    response = response.form.submit()
631
    response = response.follow()
632
    link = get_link_from_mail(mailoutbox[0])
633
    response = app.get(link)
634
    assert not re.search('<input class="a2-password-assisted".*data-show-all.*>', response.content, re.I | re.M | re.S)
588
-