Skip to content

Commit 15628c6

Browse files
committed
Tweaked the IPR Details page to show the possible a), b), and c) choices under section 'V' when licensing declaration to be provided later has been chosen.
- Legacy-Id: 12791
1 parent ffb0293 commit 15628c6

14 files changed

Lines changed: 342 additions & 81 deletions

File tree

ietf/bower.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"respond": "~1",
1616
"select2": "~3",
1717
"select2-bootstrap-css": "~1",
18-
"spin.js": "~2"
18+
"spin.js": "~2",
19+
"zxcvbn": "~4"
1920
},
2021
"devDependencies": {},
2122
"overrides": {

ietf/ietfauth/forms.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import re
2+
from unidecode import unidecode
23

34
from django import forms
45
from django.conf import settings
@@ -8,7 +9,7 @@
89
from django.utils.html import mark_safe
910
from django.core.urlresolvers import reverse as urlreverse
1011

11-
from unidecode import unidecode
12+
from django_password_strength.widgets import PasswordStrengthInput, PasswordConfirmationInput
1213

1314
import debug # pyflakes:ignore
1415

@@ -31,8 +32,8 @@ def clean_email(self):
3132

3233

3334
class PasswordForm(forms.Form):
34-
password = forms.CharField(widget=forms.PasswordInput)
35-
password_confirmation = forms.CharField(widget=forms.PasswordInput,
35+
password = forms.CharField(widget=PasswordStrengthInput)
36+
password_confirmation = forms.CharField(widget=PasswordConfirmationInput,
3637
help_text="Enter the same password as above, for verification.")
3738

3839
def clean_password_confirmation(self):
@@ -166,3 +167,28 @@ class Meta:
166167
exclude = ['by', 'time' ]
167168

168169

170+
from django import forms
171+
172+
173+
class ChangePasswordForm(forms.Form):
174+
current_password = forms.CharField(widget=forms.PasswordInput)
175+
176+
177+
new_password = forms.CharField(widget=PasswordStrengthInput)
178+
new_password_confirmation = forms.CharField(widget=PasswordConfirmationInput)
179+
180+
def __init__(self, user, data=None):
181+
self.user = user
182+
super(ChangePasswordForm, self).__init__(data)
183+
184+
def clean_current_password(self):
185+
password = self.cleaned_data.get('current_password', None)
186+
if not self.user.check_password(password):
187+
raise ValidationError('Invalid password')
188+
189+
def clean(self):
190+
new_password = self.cleaned_data.get('new_password', None)
191+
conf_password = self.cleaned_data.get('new_password_confirmation', None)
192+
if not new_password == conf_password:
193+
raise ValidationError("The password confirmation is different than the new password")
194+

ietf/ietfauth/urls.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,20 @@
33
from django.conf.urls import url
44
from django.contrib.auth.views import login, logout
55

6-
from ietf.ietfauth.views import add_account_whitelist
6+
from ietf.ietfauth import views
77

88
urlpatterns = [
9-
url(r'^$', 'ietf.ietfauth.views.index'),
10-
# url(r'^login/$', 'ietf.ietfauth.views.ietf_login'),
9+
url(r'^$', views.index),
10+
url(r'^confirmnewemail/(?P<auth>[^/]+)/$', views.confirm_new_email),
11+
url(r'^create/$', views.create_account),
12+
url(r'^create/confirm/(?P<auth>[^/]+)/$', views.confirm_account),
1113
url(r'^login/$', login),
1214
url(r'^logout/$', logout),
13-
# url(r'^loggedin/$', 'ietf.ietfauth.views.ietf_loggedin'),
14-
# url(r'^loggedout/$', 'ietf.ietfauth.views.logged_out'),
15-
url(r'^profile/$', 'ietf.ietfauth.views.profile'),
16-
# (r'^login/(?P<user>[a-z0-9.@]+)/(?P<passwd>.+)$', 'ietf.ietfauth.views.url_login'),
17-
url(r'^testemail/$', 'ietf.ietfauth.views.test_email'),
18-
url(r'^create/$', 'ietf.ietfauth.views.create_account'),
19-
url(r'^create/confirm/(?P<auth>[^/]+)/$', 'ietf.ietfauth.views.confirm_account'),
20-
url(r'^reset/$', 'ietf.ietfauth.views.password_reset'),
21-
url(r'^reset/confirm/(?P<auth>[^/]+)/$', 'ietf.ietfauth.views.confirm_password_reset'),
22-
url(r'^confirmnewemail/(?P<auth>[^/]+)/$', 'ietf.ietfauth.views.confirm_new_email'),
23-
url(r'whitelist/add/?$', add_account_whitelist),
24-
url(r'^review/$', 'ietf.ietfauth.views.review_overview'),
15+
url(r'^password/$', views.change_password),
16+
url(r'^profile/$', views.profile),
17+
url(r'^reset/$', views.password_reset),
18+
url(r'^reset/confirm/(?P<auth>[^/]+)/$', views.confirm_password_reset),
19+
url(r'^review/$', views.review_overview),
20+
url(r'^testemail/$', views.test_email),
21+
url(r'whitelist/add/?$', views.add_account_whitelist),
2522
]

ietf/ietfauth/views.py

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,27 @@
3232

3333
# Copyright The IETF Trust 2007, All Rights Reserved
3434

35+
import importlib
36+
3537
from datetime import datetime as DateTime, timedelta as TimeDelta, date as Date
3638
from collections import defaultdict
3739

40+
import django.core.signing
41+
from django import forms
42+
from django.contrib import messages
3843
from django.conf import settings
39-
from django.http import Http404 #, HttpResponse, HttpResponseRedirect
40-
from django.shortcuts import render, redirect, get_object_or_404
41-
#from django.contrib.auth import REDIRECT_FIELD_NAME, authenticate, login
44+
from django.contrib.auth import update_session_auth_hash
4245
from django.contrib.auth.decorators import login_required
43-
#from django.utils.http import urlquote
44-
import django.core.signing
45-
from django.contrib.sites.models import Site
4646
from django.contrib.auth.models import User
47-
from django import forms
47+
from django.contrib.sites.models import Site
48+
from django.core.urlresolvers import reverse as urlreverse
49+
from django.http import Http404, HttpResponseRedirect #, HttpResponse,
50+
from django.shortcuts import render, redirect, get_object_or_404
4851

4952
import debug # pyflakes:ignore
5053

5154
from ietf.group.models import Role, Group
52-
from ietf.ietfauth.forms import RegistrationForm, PasswordForm, ResetPasswordForm, TestEmailForm, WhitelistForm
55+
from ietf.ietfauth.forms import RegistrationForm, PasswordForm, ResetPasswordForm, TestEmailForm, WhitelistForm, ChangePasswordForm
5356
from ietf.ietfauth.forms import get_person_form, RoleEmailForm, NewEmailForm
5457
from ietf.ietfauth.htpasswd import update_htpasswd_file
5558
from ietf.ietfauth.utils import role_required
@@ -465,3 +468,46 @@ def review_overview(request):
465468
'review_wishes': review_wishes,
466469
'review_wish_form': review_wish_form,
467470
})
471+
472+
@login_required
473+
def change_password(request):
474+
success = False
475+
person = None
476+
477+
try:
478+
person = request.user.person
479+
except Person.DoesNotExist:
480+
return render(request, 'registration/missing_person.html')
481+
482+
emails = Email.objects.filter(person=person, active=True).order_by('-primary','-time').first
483+
484+
if request.method == 'POST':
485+
user = request.user
486+
form = ChangePasswordForm(user, request.POST)
487+
if form.is_valid():
488+
new_password = form.cleaned_data["new_password"]
489+
490+
user.set_password(new_password)
491+
user.save()
492+
# password is also stored in htpasswd file
493+
update_htpasswd_file(user.username, new_password)
494+
# keep the session
495+
update_session_auth_hash(request, user)
496+
497+
messages.success(request, "Your password was successfully changed")
498+
return HttpResponseRedirect(urlreverse('ietf.ietfauth.views.profile'))
499+
500+
else:
501+
form = ChangePasswordForm(request.user)
502+
503+
hlibname, hashername = settings.PASSWORD_HASHERS[0].rsplit('.',1)
504+
505+
hlib = importlib.import_module(hlibname)
506+
hasher = getattr(hlib, hashername)
507+
return render(request, 'registration/change_password.html', {
508+
'form': form,
509+
'success': success,
510+
'hasher': hasher,
511+
})
512+
513+

ietf/ipr/views.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
iprs_from_docs, related_docs)
3333
from ietf.message.models import Message
3434
from ietf.message.utils import infer_message
35+
from ietf.name.models import IprLicenseTypeName
3536
from ietf.person.models import Person
3637
from ietf.secr.utils.document import get_rfc_num, is_draft
3738
from ietf.utils.draft_search import normalize_draftname
@@ -703,6 +704,7 @@ def get_details_tabs(ipr, selected):
703704
('History', urlreverse('ipr_history', kwargs={ 'id': ipr.pk }))
704705
]]
705706

707+
@debug.trace
706708
def show(request, id):
707709
"""View of individual declaration"""
708710
ipr = get_object_or_404(IprDisclosureBase, id=id).get_child()
@@ -717,6 +719,7 @@ def show(request, id):
717719
return render(request, "ipr/details_view.html", {
718720
'ipr': ipr,
719721
'tabs': get_details_tabs(ipr, 'Disclosure'),
722+
'choices_abc': [ i.desc for i in IprLicenseTypeName.objects.filter(slug__in=['no-license', 'royalty-free', 'reasonable', ]) ],
720723
'updates_iprs': ipr.relatedipr_source_set.all(),
721724
'updated_by_iprs': ipr.relatedipr_target_set.filter(source__state="posted")
722725
})

ietf/settings.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@
5454
('Ryan Cross', 'rcross@amsl.com'),
5555
)
5656

57+
PASSWORD_HASHERS = [
58+
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
59+
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
60+
'django.contrib.auth.hashers.SHA1PasswordHasher',
61+
'django.contrib.auth.hashers.CryptPasswordHasher',
62+
]
63+
5764
ALLOWED_HOSTS = [".ietf.org", ".ietf.org.", "209.208.19.216", "4.31.198.44", ]
5865

5966

@@ -296,11 +303,12 @@ def skip_unreadable_post(record):
296303
'django.contrib.staticfiles',
297304
# External apps
298305
'bootstrap3',
306+
'django_markup',
307+
'django_password_strength',
299308
'djangobwr',
300309
'form_utils',
301310
'tastypie',
302311
'widget_tweaks',
303-
'django_markup',
304312
# IETF apps
305313
'ietf.api',
306314
'ietf.community',
@@ -782,7 +790,6 @@ def skip_unreadable_post(record):
782790
"fields.W342", # Setting unique=True on a ForeignKey has the same effect as using a OneToOneField.
783791
]
784792

785-
786793
# Put the production SECRET_KEY in settings_local.py, and also any other
787794
# sensitive or site-specific changes. DO NOT commit settings_local.py to svn.
788795
from settings_local import * # pyflakes:ignore pylint: disable=wildcard-import
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Taken from django-password-strength, with changes to use the bower-managed zxcvbn.js The
2+
// bower-managed zxcvbn.js is kept up-to-date to a larger extent than the copy packaged with
3+
// the django-password-strength component.
4+
(function($, window, document, undefined){
5+
window.djangoPasswordStrength = {
6+
config: {
7+
passwordClass: 'password_strength',
8+
confirmationClass: 'password_confirmation'
9+
},
10+
11+
init: function (config) {
12+
var self = this;
13+
// Setup configuration
14+
if ($.isPlainObject(config)) {
15+
$.extend(self.config, config);
16+
}
17+
18+
self.initListeners();
19+
},
20+
21+
initListeners: function() {
22+
var self = this;
23+
var body = $('body');
24+
25+
$('.' + self.config.passwordClass).on('keyup', function() {
26+
var password_strength_bar = $(this).parent().find('.password_strength_bar');
27+
var password_strength_info = $(this).parent().find('.password_strength_info');
28+
29+
if( $(this).val() ) {
30+
var result = zxcvbn( $(this).val() );
31+
32+
if( result.score < 3 ) {
33+
password_strength_bar.removeClass('progress-bar-success').addClass('progress-bar-warning');
34+
password_strength_info.find('.label').removeClass('hidden');
35+
} else {
36+
password_strength_bar.removeClass('progress-bar-warning').addClass('progress-bar-success');
37+
password_strength_info.find('.label').addClass('hidden');
38+
}
39+
40+
password_strength_bar.width( ((result.score+1)/5)*100 + '%' ).attr('aria-valuenow', result.score + 1);
41+
// henrik@levkowetz.com -- this is the only changed line:
42+
password_strength_info.find('.password_strength_time').html(result.crack_times_display.online_no_throttling_10_per_second);
43+
password_strength_info.removeClass('hidden');
44+
} else {
45+
password_strength_bar.removeClass('progress-bar-success').addClass('progress-bar-warning');
46+
password_strength_bar.width( '0%' ).attr('aria-valuenow', 0);
47+
password_strength_info.addClass('hidden');
48+
}
49+
self.match_passwords($(this));
50+
});
51+
52+
var timer = null;
53+
$('.' + self.config.confirmationClass).on('keyup', function() {
54+
var password_field;
55+
var confirm_with = $(this).data('confirm-with');
56+
57+
if( confirm_with ) {
58+
password_field = $('#' + confirm_with);
59+
} else {
60+
password_field = $('.' + self.config.passwordClass);
61+
}
62+
63+
if (timer !== null) clearTimeout(timer);
64+
65+
timer = setTimeout(function(){
66+
self.match_passwords(password_field);
67+
}, 400);
68+
});
69+
},
70+
71+
display_time: function(seconds) {
72+
var minute = 60;
73+
var hour = minute * 60;
74+
var day = hour * 24;
75+
var month = day * 31;
76+
var year = month * 12;
77+
var century = year * 100;
78+
79+
// Provide fake gettext for when it is not available
80+
if( typeof gettext !== 'function' ) { gettext = function(text) { return text; }; };
81+
82+
if( seconds < minute ) return gettext('only an instant');
83+
if( seconds < hour) return (1 + Math.ceil(seconds / minute)) + ' ' + gettext('minutes');
84+
if( seconds < day) return (1 + Math.ceil(seconds / hour)) + ' ' + gettext('hours');
85+
if( seconds < month) return (1 + Math.ceil(seconds / day)) + ' ' + gettext('days');
86+
if( seconds < year) return (1 + Math.ceil(seconds / month)) + ' ' + gettext('months');
87+
if( seconds < century) return (1 + Math.ceil(seconds / year)) + ' ' + gettext('years');
88+
89+
return gettext('centuries');
90+
},
91+
92+
match_passwords: function(password_field, confirmation_fields) {
93+
var self = this;
94+
// Optional parameter: if no specific confirmation field is given, check all
95+
if( confirmation_fields === undefined ) { confirmation_fields = $('.' + self.config.confirmationClass) }
96+
if( confirmation_fields === undefined ) { return; }
97+
98+
var password = password_field.val();
99+
100+
confirmation_fields.each(function(index, confirm_field) {
101+
var confirm_value = $(confirm_field).val();
102+
var confirm_with = $(confirm_field).data('confirm-with');
103+
104+
if( confirm_with && confirm_with == password_field.attr('id')) {
105+
if( confirm_value && password ) {
106+
if (confirm_value === password) {
107+
$(confirm_field).parent().find('.password_strength_info').addClass('hidden');
108+
} else {
109+
$(confirm_field).parent().find('.password_strength_info').removeClass('hidden');
110+
}
111+
} else {
112+
$(confirm_field).parent().find('.password_strength_info').addClass('hidden');
113+
}
114+
}
115+
});
116+
117+
// If a password field other than our own has been used, add the listener here
118+
if( !password_field.hasClass(self.config.passwordClass) && !password_field.data('password-listener') ) {
119+
password_field.on('keyup', function() {
120+
self.match_passwords($(this));
121+
});
122+
password_field.data('password-listener', true);
123+
}
124+
}
125+
};
126+
127+
// Call the init for backwards compatibility
128+
djangoPasswordStrength.init();
129+
130+
})(jQuery, window, document);

ietf/templates/base/menu_user.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@
1616
{% else %}
1717
{% if user.is_authenticated %}
1818
<li><a rel="nofollow" href="/accounts/logout/" >Sign out</a></li>
19-
<li><a rel="nofollow" href="/accounts/profile/">Edit profile</a></li>
19+
<li><a rel="nofollow" href="/accounts/profile/">Account info</a></li>
2020
{% else %}
2121
<li><a rel="nofollow" href="/accounts/login/?next={{request.get_full_path|urlencode}}">Sign in</a></li>
2222
<li><a rel="nofollow" href="/accounts/reset/">Password reset</a></li>
2323
{% endif %}
2424
{% endif %}
2525

26-
<li><a href="{% url "ietf.ietfauth.views.create_account" %}">{% if request.user.is_authenticated %}Manage account{% else %}New account{% endif %}</a></li>
26+
{% if not request.user.is_authenticated %}
27+
<li><a href="{% url "ietf.ietfauth.views.create_account" %}">New account</a></li>
28+
{% endif %}
2729
<li><a href="{%url "ietf.cookies.views.preferences" %}" rel="nofollow">Preferences</a></li>
2830

2931
{% if user|has_role:"Reviewer" %}

0 commit comments

Comments
 (0)