Merge lp:~mfoord/canonical-identity-provider/keep-passwords into lp:canonical-identity-provider/release
- keep-passwords
- Merge into trunk
Proposed by
Michael Foord
Status: | Rejected |
---|---|
Rejected by: | Natalia Bidart |
Proposed branch: | lp:~mfoord/canonical-identity-provider/keep-passwords |
Merge into: | lp:canonical-identity-provider/release |
Diff against target: |
620 lines (+326/-54) 11 files modified
src/identityprovider/admin.py (+5/-0) src/identityprovider/management/commands/populate.py (+3/-2) src/identityprovider/migrations/0017_keep_passwords.py (+236/-0) src/identityprovider/models/account.py (+28/-10) src/identityprovider/schema.py (+1/-0) src/identityprovider/signals.py (+1/-13) src/identityprovider/templates/nexus/admin/password_change_form.html (+0/-1) src/identityprovider/tests/test_admin.py (+7/-7) src/identityprovider/tests/test_models_account.py (+7/-0) src/identityprovider/tests/test_signals.py (+2/-20) src/identityprovider/validators.py (+36/-1) |
To merge this branch: | bzr merge lp:~mfoord/canonical-identity-provider/keep-passwords |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Canonical ISD hackers | Pending | ||
Review via email: mp+196209@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
- 1098. By Michael Foord
-
Remove the invalidate tokens signal
- 1099. By Michael Foord
-
Merge
- 1100. By Michael Foord
-
Clear oauth tokens when password changes
- 1101. By Michael Foord
-
Remove and move old tests
- 1102. By Michael Foord
-
Merge
- 1103. By Michael Foord
-
PCI compliance validator
- 1104. By Michael Foord
-
Fixes to the PCI validator
- 1105. By Michael Foord
-
Fix circular import
- 1106. By Michael Foord
-
Add PCI group setting
- 1107. By Michael Foord
-
Merge
- 1108. By Michael Foord
-
Merge
Revision history for this message
Natalia Bidart (nataliabidart) wrote : | # |
Unmerged revisions
- 1108. By Michael Foord
-
Merge
- 1107. By Michael Foord
-
Merge
- 1106. By Michael Foord
-
Add PCI group setting
- 1105. By Michael Foord
-
Fix circular import
- 1104. By Michael Foord
-
Fixes to the PCI validator
- 1103. By Michael Foord
-
PCI compliance validator
- 1102. By Michael Foord
-
Merge
- 1101. By Michael Foord
-
Remove and move old tests
- 1100. By Michael Foord
-
Clear oauth tokens when password changes
- 1099. By Michael Foord
-
Merge
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/identityprovider/admin.py' | |||
2 | --- src/identityprovider/admin.py 2013-11-26 15:40:55 +0000 | |||
3 | +++ src/identityprovider/admin.py 2013-11-27 18:08:05 +0000 | |||
4 | @@ -1,6 +1,8 @@ | |||
5 | 1 | # Copyright 2010,2013 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2010,2013 Canonical Ltd. This software is licensed under the |
6 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
7 | 3 | 3 | ||
8 | 4 | from datetime import datetime | ||
9 | 5 | |||
10 | 4 | from django import forms | 6 | from django import forms |
11 | 5 | from django.contrib import admin | 7 | from django.contrib import admin |
12 | 6 | from django.contrib.auth.admin import UserAdmin | 8 | from django.contrib.auth.admin import UserAdmin |
13 | @@ -134,6 +136,7 @@ | |||
14 | 134 | 136 | ||
15 | 135 | class Meta: | 137 | class Meta: |
16 | 136 | model = AccountPassword | 138 | model = AccountPassword |
17 | 139 | exclude = ['when_created'] | ||
18 | 137 | 140 | ||
19 | 138 | def __init__(self, *args, **kwargs): | 141 | def __init__(self, *args, **kwargs): |
20 | 139 | initial = kwargs.get('initial', {}) | 142 | initial = kwargs.get('initial', {}) |
21 | @@ -143,6 +146,7 @@ | |||
22 | 143 | 146 | ||
23 | 144 | 147 | ||
24 | 145 | class AccountPasswordFormset(forms.models.BaseInlineFormSet): | 148 | class AccountPasswordFormset(forms.models.BaseInlineFormSet): |
25 | 149 | |||
26 | 146 | def __init__(self, *args, **kwargs): | 150 | def __init__(self, *args, **kwargs): |
27 | 147 | super(AccountPasswordFormset, self).__init__(*args, **kwargs) | 151 | super(AccountPasswordFormset, self).__init__(*args, **kwargs) |
28 | 148 | self.can_delete = False | 152 | self.can_delete = False |
29 | @@ -260,6 +264,7 @@ | |||
30 | 260 | instance = instances[0] | 264 | instance = instances[0] |
31 | 261 | encrypted_password = make_password(password) | 265 | encrypted_password = make_password(password) |
32 | 262 | instance.password = encrypted_password | 266 | instance.password = encrypted_password |
33 | 267 | instance.when_created = datetime.utcnow() | ||
34 | 263 | instance.save() | 268 | instance.save() |
35 | 264 | else: | 269 | else: |
36 | 265 | super(AccountAdmin, self).save_formset( | 270 | super(AccountAdmin, self).save_formset( |
37 | 266 | 271 | ||
38 | === modified file 'src/identityprovider/management/commands/populate.py' | |||
39 | --- src/identityprovider/management/commands/populate.py 2013-11-19 18:45:46 +0000 | |||
40 | +++ src/identityprovider/management/commands/populate.py 2013-11-27 18:08:05 +0000 | |||
41 | @@ -167,14 +167,15 @@ | |||
42 | 167 | def gen_accountpassword(self, id): | 167 | def gen_accountpassword(self, id): |
43 | 168 | """Return a sequence representing an AccountPassword. | 168 | """Return a sequence representing an AccountPassword. |
44 | 169 | 169 | ||
46 | 170 | Currently (id, account, password) | 170 | Currently (id, account, password, when_created) |
47 | 171 | """ | 171 | """ |
48 | 172 | account_id = (id - self.ranges['accountpassword'][0] + | 172 | account_id = (id - self.ranges['accountpassword'][0] + |
49 | 173 | self.ranges['account'][0]) | 173 | self.ranges['account'][0]) |
50 | 174 | plaintext = self.random_string(size=8) | 174 | plaintext = self.random_string(size=8) |
51 | 175 | self.passwords.append(plaintext) | 175 | self.passwords.append(plaintext) |
52 | 176 | password = make_password(plaintext) | 176 | password = make_password(plaintext) |
54 | 177 | return [str(id), str(account_id), password] | 177 | when_created = self.random_date() |
55 | 178 | return [str(id), str(account_id), password, when_created] | ||
56 | 178 | 179 | ||
57 | 179 | def gen_lp_openididentifier(self, id): | 180 | def gen_lp_openididentifier(self, id): |
58 | 180 | """ Returns a sequence representing an LPOpenIdIdentfier. | 181 | """ Returns a sequence representing an LPOpenIdIdentfier. |
59 | 181 | 182 | ||
60 | === added file 'src/identityprovider/migrations/0017_keep_passwords.py' | |||
61 | --- src/identityprovider/migrations/0017_keep_passwords.py 1970-01-01 00:00:00 +0000 | |||
62 | +++ src/identityprovider/migrations/0017_keep_passwords.py 2013-11-27 18:08:05 +0000 | |||
63 | @@ -0,0 +1,236 @@ | |||
64 | 1 | # -*- coding: utf-8 -*- | ||
65 | 2 | import datetime | ||
66 | 3 | from south.db import db | ||
67 | 4 | from south.v2 import SchemaMigration | ||
68 | 5 | from django.db import models | ||
69 | 6 | |||
70 | 7 | |||
71 | 8 | class Migration(SchemaMigration): | ||
72 | 9 | |||
73 | 10 | def forwards(self, orm): | ||
74 | 11 | # Removing unique constraint on 'AccountPassword', fields ['account'] | ||
75 | 12 | db.delete_unique(u'accountpassword', ['account']) | ||
76 | 13 | |||
77 | 14 | # Adding field 'AccountPassword.when_created' | ||
78 | 15 | db.add_column(u'accountpassword', 'when_created', | ||
79 | 16 | self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now), | ||
80 | 17 | keep_default=False) | ||
81 | 18 | |||
82 | 19 | |||
83 | 20 | # Changing field 'AccountPassword.account' | ||
84 | 21 | db.alter_column(u'accountpassword', 'account', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['identityprovider.Account'], db_column='account')) | ||
85 | 22 | |||
86 | 23 | def backwards(self, orm): | ||
87 | 24 | # Deleting field 'AccountPassword.when_created' | ||
88 | 25 | db.delete_column(u'accountpassword', 'when_created') | ||
89 | 26 | |||
90 | 27 | |||
91 | 28 | # Changing field 'AccountPassword.account' | ||
92 | 29 | db.alter_column(u'accountpassword', 'account', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['identityprovider.Account'], unique=True, db_column='account')) | ||
93 | 30 | # Adding unique constraint on 'AccountPassword', fields ['account'] | ||
94 | 31 | db.create_unique(u'accountpassword', ['account']) | ||
95 | 32 | |||
96 | 33 | |||
97 | 34 | models = { | ||
98 | 35 | 'identityprovider.account': { | ||
99 | 36 | 'Meta': {'object_name': 'Account', 'db_table': "u'account'"}, | ||
100 | 37 | 'creation_rationale': ('django.db.models.fields.IntegerField', [], {}), | ||
101 | 38 | 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}), | ||
102 | 39 | 'date_status_set': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}), | ||
103 | 40 | 'displayname': ('identityprovider.models.account.DisplaynameField', [], {}), | ||
104 | 41 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
105 | 42 | 'old_openid_identifier': ('django.db.models.fields.TextField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), | ||
106 | 43 | 'openid_identifier': ('django.db.models.fields.TextField', [], {'default': "u'XrCnJdc'", 'unique': 'True'}), | ||
107 | 44 | 'preferredlanguage': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
108 | 45 | 'status': ('django.db.models.fields.IntegerField', [], {}), | ||
109 | 46 | 'status_comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
110 | 47 | 'twofactor_attempts': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), | ||
111 | 48 | 'twofactor_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
112 | 49 | 'warn_about_backup_device': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) | ||
113 | 50 | }, | ||
114 | 51 | 'identityprovider.accountpassword': { | ||
115 | 52 | 'Meta': {'ordering': "['-when_created']", 'object_name': 'AccountPassword', 'db_table': "u'accountpassword'"}, | ||
116 | 53 | 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identityprovider.Account']", 'db_column': "'account'"}), | ||
117 | 54 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
118 | 55 | 'password': ('identityprovider.models.account.PasswordField', [], {}), | ||
119 | 56 | 'when_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}) | ||
120 | 57 | }, | ||
121 | 58 | 'identityprovider.apiuser': { | ||
122 | 59 | 'Meta': {'object_name': 'APIUser', 'db_table': "'api_user'"}, | ||
123 | 60 | 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), | ||
124 | 61 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
125 | 62 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '256'}), | ||
126 | 63 | 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), | ||
127 | 64 | 'username': ('django.db.models.fields.CharField', [], {'max_length': '256'}) | ||
128 | 65 | }, | ||
129 | 66 | 'identityprovider.authenticationdevice': { | ||
130 | 67 | 'Meta': {'ordering': "('id',)", 'object_name': 'AuthenticationDevice'}, | ||
131 | 68 | 'account': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'devices'", 'to': "orm['identityprovider.Account']"}), | ||
132 | 69 | 'counter': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
133 | 70 | 'device_type': ('django.db.models.fields.TextField', [], {'null': 'True'}), | ||
134 | 71 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
135 | 72 | 'key': ('django.db.models.fields.TextField', [], {}), | ||
136 | 73 | 'name': ('django.db.models.fields.TextField', [], {}) | ||
137 | 74 | }, | ||
138 | 75 | 'identityprovider.authtoken': { | ||
139 | 76 | 'Meta': {'object_name': 'AuthToken', 'db_table': "u'authtoken'"}, | ||
140 | 77 | 'date_consumed': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), | ||
141 | 78 | 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow', 'db_index': 'True', 'blank': 'True'}), | ||
142 | 79 | 'displayname': ('identityprovider.models.account.DisplaynameField', [], {'null': 'True', 'blank': 'True'}), | ||
143 | 80 | 'email': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), | ||
144 | 81 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
145 | 82 | 'password': ('identityprovider.models.account.PasswordField', [], {'null': 'True', 'blank': 'True'}), | ||
146 | 83 | 'redirection_url': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
147 | 84 | 'requester': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identityprovider.Account']", 'null': 'True', 'db_column': "'requester'", 'blank': 'True'}), | ||
148 | 85 | 'requester_email': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
149 | 86 | 'token': ('django.db.models.fields.TextField', [], {'unique': 'True'}), | ||
150 | 87 | 'token_type': ('django.db.models.fields.IntegerField', [], {}) | ||
151 | 88 | }, | ||
152 | 89 | 'identityprovider.emailaddress': { | ||
153 | 90 | 'Meta': {'object_name': 'EmailAddress', 'db_table': "u'emailaddress'"}, | ||
154 | 91 | 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identityprovider.Account']", 'null': 'True', 'db_column': "'account'", 'blank': 'True'}), | ||
155 | 92 | 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow', 'blank': 'True'}), | ||
156 | 93 | 'email': ('django.db.models.fields.TextField', [], {}), | ||
157 | 94 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
158 | 95 | 'lp_person': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_column': "'person'", 'blank': 'True'}), | ||
159 | 96 | 'status': ('django.db.models.fields.IntegerField', [], {}) | ||
160 | 97 | }, | ||
161 | 98 | 'identityprovider.invalidatedemailaddress': { | ||
162 | 99 | 'Meta': {'object_name': 'InvalidatedEmailAddress', 'db_table': "u'invalidated_emailaddress'"}, | ||
163 | 100 | 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identityprovider.Account']", 'null': 'True', 'db_column': "'account'", 'blank': 'True'}), | ||
164 | 101 | 'account_notified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
165 | 102 | 'date_created': ('django.db.models.fields.DateTimeField', [], {'blank': 'True'}), | ||
166 | 103 | 'date_invalidated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow', 'null': 'True', 'blank': 'True'}), | ||
167 | 104 | 'email': ('django.db.models.fields.TextField', [], {}), | ||
168 | 105 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) | ||
169 | 106 | }, | ||
170 | 107 | 'identityprovider.leakedcredential': { | ||
171 | 108 | 'Meta': {'unique_together': "(('email', 'source'),)", 'object_name': 'LeakedCredential'}, | ||
172 | 109 | 'date_leaked': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}), | ||
173 | 110 | 'email': ('django.db.models.fields.EmailField', [], {'default': 'None', 'max_length': '254'}), | ||
174 | 111 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
175 | 112 | 'password': ('django.db.models.fields.TextField', [], {'default': 'None'}), | ||
176 | 113 | 'source': ('django.db.models.fields.TextField', [], {'default': 'None'}) | ||
177 | 114 | }, | ||
178 | 115 | 'identityprovider.lpopenididentifier': { | ||
179 | 116 | 'Meta': {'object_name': 'LPOpenIdIdentifier', 'db_table': "u'lp_openididentifier'"}, | ||
180 | 117 | 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.date.today'}), | ||
181 | 118 | 'identifier': ('django.db.models.fields.TextField', [], {'unique': 'True', 'primary_key': 'True'}), | ||
182 | 119 | 'lp_account': ('django.db.models.fields.IntegerField', [], {'db_column': "'account'", 'db_index': 'True'}) | ||
183 | 120 | }, | ||
184 | 121 | 'identityprovider.openidassociation': { | ||
185 | 122 | 'Meta': {'unique_together': "(('server_url', 'handle'),)", 'object_name': 'OpenIDAssociation', 'db_table': "u'openidassociation'"}, | ||
186 | 123 | 'assoc_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), | ||
187 | 124 | 'handle': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}), | ||
188 | 125 | 'issued': ('django.db.models.fields.IntegerField', [], {}), | ||
189 | 126 | 'lifetime': ('django.db.models.fields.IntegerField', [], {}), | ||
190 | 127 | 'secret': ('django.db.models.fields.TextField', [], {}), | ||
191 | 128 | 'server_url': ('django.db.models.fields.CharField', [], {'max_length': '2047'}) | ||
192 | 129 | }, | ||
193 | 130 | 'identityprovider.openidauthorization': { | ||
194 | 131 | 'Meta': {'object_name': 'OpenIDAuthorization', 'db_table': "u'openidauthorization'"}, | ||
195 | 132 | 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identityprovider.Account']", 'db_column': "'account'"}), | ||
196 | 133 | 'client_id': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
197 | 134 | 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow', 'blank': 'True'}), | ||
198 | 135 | 'date_expires': ('django.db.models.fields.DateTimeField', [], {}), | ||
199 | 136 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
200 | 137 | 'trust_root': ('django.db.models.fields.TextField', [], {}) | ||
201 | 138 | }, | ||
202 | 139 | 'identityprovider.openidnonce': { | ||
203 | 140 | 'Meta': {'unique_together': "(('server_url', 'timestamp', 'salt'),)", 'object_name': 'OpenIDNonce', 'db_table': "'openidnonce'"}, | ||
204 | 141 | 'salt': ('django.db.models.fields.CharField', [], {'max_length': '40'}), | ||
205 | 142 | 'server_url': ('django.db.models.fields.CharField', [], {'max_length': '2047', 'primary_key': 'True'}), | ||
206 | 143 | 'timestamp': ('django.db.models.fields.IntegerField', [], {}) | ||
207 | 144 | }, | ||
208 | 145 | 'identityprovider.openidrpconfig': { | ||
209 | 146 | 'Meta': {'object_name': 'OpenIDRPConfig', 'db_table': "'ssoopenidrpconfig'"}, | ||
210 | 147 | 'allow_unverified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
211 | 148 | 'allowed_ax': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
212 | 149 | 'allowed_sreg': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
213 | 150 | 'allowed_user_attribs': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
214 | 151 | 'auto_authorize': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
215 | 152 | 'can_query_any_team': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
216 | 153 | 'creation_rationale': ('django.db.models.fields.IntegerField', [], {'default': '13'}), | ||
217 | 154 | 'description': ('django.db.models.fields.TextField', [], {}), | ||
218 | 155 | 'displayname': ('django.db.models.fields.TextField', [], {}), | ||
219 | 156 | 'flag_twofactor': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}), | ||
220 | 157 | 'ga_snippet': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
221 | 158 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
222 | 159 | 'logo': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
223 | 160 | 'prefer_canonical_email': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
224 | 161 | 'require_two_factor': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
225 | 162 | 'trust_root': ('django.db.models.fields.TextField', [], {'unique': 'True'}) | ||
226 | 163 | }, | ||
227 | 164 | 'identityprovider.openidrpsummary': { | ||
228 | 165 | 'Meta': {'unique_together': "(('account', 'trust_root', 'openid_identifier'),)", 'object_name': 'OpenIDRPSummary', 'db_table': "u'openidrpsummary'"}, | ||
229 | 166 | 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identityprovider.Account']", 'db_column': "'account'"}), | ||
230 | 167 | 'approved_data': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), | ||
231 | 168 | 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow', 'blank': 'True'}), | ||
232 | 169 | 'date_last_used': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow', 'blank': 'True'}), | ||
233 | 170 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
234 | 171 | 'openid_identifier': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), | ||
235 | 172 | 'total_logins': ('django.db.models.fields.IntegerField', [], {'default': '1'}), | ||
236 | 173 | 'trust_root': ('django.db.models.fields.TextField', [], {'db_index': 'True'}) | ||
237 | 174 | }, | ||
238 | 175 | 'identityprovider.person': { | ||
239 | 176 | 'Meta': {'object_name': 'Person', 'db_table': "u'lp_person'"}, | ||
240 | 177 | 'addressline1': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
241 | 178 | 'addressline2': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
242 | 179 | 'city': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
243 | 180 | 'country': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_column': "'country'", 'blank': 'True'}), | ||
244 | 181 | 'creation_comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
245 | 182 | 'creation_rationale': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), | ||
246 | 183 | 'datecreated': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}), | ||
247 | 184 | 'defaultmembershipperiod': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), | ||
248 | 185 | 'defaultrenewalperiod': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), | ||
249 | 186 | 'displayname': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
250 | 187 | 'fti': ('django.db.models.fields.TextField', [], {'null': 'True'}), | ||
251 | 188 | 'hide_email_addresses': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}), | ||
252 | 189 | 'homepage_content': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
253 | 190 | 'icon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_column': "'icon'", 'blank': 'True'}), | ||
254 | 191 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
255 | 192 | 'language': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_column': "'language'", 'blank': 'True'}), | ||
256 | 193 | 'logo': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_column': "'logo'", 'blank': 'True'}), | ||
257 | 194 | 'lp_account': ('django.db.models.fields.IntegerField', [], {'unique': 'True', 'null': 'True', 'db_column': "'account'"}), | ||
258 | 195 | 'mail_resumption_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), | ||
259 | 196 | 'mailing_list_auto_subscribe_policy': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True'}), | ||
260 | 197 | 'mailing_list_receive_duplicates': ('django.db.models.fields.NullBooleanField', [], {'default': 'True', 'null': 'True', 'blank': 'True'}), | ||
261 | 198 | 'merged': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_column': "'merged'", 'blank': 'True'}), | ||
262 | 199 | 'mugshot': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_column': "'mugshot'", 'blank': 'True'}), | ||
263 | 200 | 'name': ('django.db.models.fields.TextField', [], {'unique': 'True', 'null': 'True'}), | ||
264 | 201 | 'organization': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
265 | 202 | 'personal_standing': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True'}), | ||
266 | 203 | 'personal_standing_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
267 | 204 | 'phone': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
268 | 205 | 'postcode': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
269 | 206 | 'province': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
270 | 207 | 'registrant': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_column': "'registrant'", 'blank': 'True'}), | ||
271 | 208 | 'renewal_policy': ('django.db.models.fields.IntegerField', [], {'default': '10', 'null': 'True'}), | ||
272 | 209 | 'subscriptionpolicy': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True'}), | ||
273 | 210 | 'teamdescription': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
274 | 211 | 'teamowner': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_column': "'teamowner'", 'blank': 'True'}), | ||
275 | 212 | 'verbose_bugnotifications': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}), | ||
276 | 213 | 'visibility': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True'}) | ||
277 | 214 | }, | ||
278 | 215 | 'identityprovider.personlocation': { | ||
279 | 216 | 'Meta': {'object_name': 'PersonLocation', 'db_table': "u'lp_personlocation'"}, | ||
280 | 217 | 'date_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), | ||
281 | 218 | 'date_last_modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), | ||
282 | 219 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
283 | 220 | 'last_modified_by': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_column': "'last_modified_by'"}), | ||
284 | 221 | 'latitude': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), | ||
285 | 222 | 'locked': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}), | ||
286 | 223 | 'longitude': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), | ||
287 | 224 | 'person': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['identityprovider.Person']", 'unique': 'True', 'null': 'True', 'db_column': "'person'"}), | ||
288 | 225 | 'time_zone': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
289 | 226 | 'visible': ('django.db.models.fields.NullBooleanField', [], {'default': 'True', 'null': 'True', 'blank': 'True'}) | ||
290 | 227 | }, | ||
291 | 228 | 'identityprovider.teamparticipation': { | ||
292 | 229 | 'Meta': {'unique_together': "(('team', 'person'),)", 'object_name': 'TeamParticipation', 'db_table': "u'lp_teamparticipation'"}, | ||
293 | 230 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
294 | 231 | 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identityprovider.Person']", 'null': 'True', 'db_column': "'person'"}), | ||
295 | 232 | 'team': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'team_participations'", 'null': 'True', 'db_column': "'team'", 'to': "orm['identityprovider.Person']"}) | ||
296 | 233 | } | ||
297 | 234 | } | ||
298 | 235 | |||
299 | 236 | complete_apps = ['identityprovider'] | ||
300 | 0 | \ No newline at end of file | 237 | \ No newline at end of file |
301 | 1 | 238 | ||
302 | === modified file 'src/identityprovider/models/account.py' | |||
303 | --- src/identityprovider/models/account.py 2013-11-21 11:56:12 +0000 | |||
304 | +++ src/identityprovider/models/account.py 2013-11-27 18:08:05 +0000 | |||
305 | @@ -120,7 +120,8 @@ | |||
306 | 120 | email.save() | 120 | email.save() |
307 | 121 | password = AccountPassword.objects.create( | 121 | password = AccountPassword.objects.create( |
308 | 122 | account=account, | 122 | account=account, |
310 | 123 | password=password) | 123 | password=password, |
311 | 124 | when_created=datetime.utcnow()) | ||
312 | 124 | return account | 125 | return account |
313 | 125 | 126 | ||
314 | 126 | def get_by_email(self, email): | 127 | def get_by_email(self, email): |
315 | @@ -166,6 +167,10 @@ | |||
316 | 166 | 167 | ||
317 | 167 | objects = AccountManager.for_queryset_class(AccountQuerySet)() | 168 | objects = AccountManager.for_queryset_class(AccountQuerySet)() |
318 | 168 | 169 | ||
319 | 170 | @property | ||
320 | 171 | def accountpassword(self): | ||
321 | 172 | return self.accountpassword_set.latest() | ||
322 | 173 | |||
323 | 169 | class Meta: | 174 | class Meta: |
324 | 170 | app_label = 'identityprovider' | 175 | app_label = 'identityprovider' |
325 | 171 | db_table = u'account' | 176 | db_table = u'account' |
326 | @@ -235,10 +240,14 @@ | |||
327 | 235 | def set_deleted(self): | 240 | def set_deleted(self): |
328 | 236 | self.status = AccountStatus.DELETED | 241 | self.status = AccountStatus.DELETED |
329 | 237 | self.emailaddress_set.delete() | 242 | self.emailaddress_set.delete() |
330 | 238 | self.accountpassword.password = 'invalid' | ||
331 | 239 | self.accountpassword.save() | ||
332 | 240 | self.save() | 243 | self.save() |
333 | 241 | 244 | ||
334 | 245 | for old_password in self.accountpassword_set.all()[1:]: | ||
335 | 246 | old_password.delete() | ||
336 | 247 | latest_password = self.accountpassword | ||
337 | 248 | latest_password.password = 'invalid' | ||
338 | 249 | latest_password.save() | ||
339 | 250 | |||
340 | 242 | user, _ = User.objects.get_or_create(username=self.openid_identifier) | 251 | user, _ = User.objects.get_or_create(username=self.openid_identifier) |
341 | 243 | user.is_active = False | 252 | user.is_active = False |
342 | 244 | user.save() | 253 | user.save() |
343 | @@ -407,16 +416,21 @@ | |||
344 | 407 | 416 | ||
345 | 408 | def set_password(self, password, salt=None): | 417 | def set_password(self, password, salt=None): |
346 | 409 | validate_password_policy(password, self) | 418 | validate_password_policy(password, self) |
351 | 410 | try: | 419 | accountpassword = AccountPassword( |
352 | 411 | accountpassword = self.accountpassword | 420 | account=self, password='invalid', when_created=datetime.utcnow()) |
349 | 412 | except AccountPassword.DoesNotExist: | ||
350 | 413 | accountpassword = AccountPassword(account=self, password='invalid') | ||
353 | 414 | accountpassword.password = make_password(password, salt=salt) | 421 | accountpassword.password = make_password(password, salt=salt) |
354 | 415 | accountpassword.save() | 422 | accountpassword.save() |
355 | 416 | # clear the password reset flag | 423 | # clear the password reset flag |
356 | 417 | self.need_password_reset = False | 424 | self.need_password_reset = False |
357 | 418 | self.save() | 425 | self.save() |
358 | 419 | 426 | ||
359 | 427 | # clear all oauth tokens as password has changed | ||
360 | 428 | self.invalidate_oauth_tokens() | ||
361 | 429 | |||
362 | 430 | # delete all but the current and four previous | ||
363 | 431 | for old_password in self.accountpassword_set.all()[5:]: | ||
364 | 432 | old_password.delete() | ||
365 | 433 | |||
366 | 420 | def get_and_delete_messages(self): | 434 | def get_and_delete_messages(self): |
367 | 421 | return [] | 435 | return [] |
368 | 422 | 436 | ||
369 | @@ -523,8 +537,9 @@ | |||
370 | 523 | try: | 537 | try: |
371 | 524 | # by setting a plain text value we make sure its not going to | 538 | # by setting a plain text value we make sure its not going to |
372 | 525 | # match against anything | 539 | # match against anything |
375 | 526 | self.accountpassword.password = 'invalid' | 540 | accountpassword = self.accountpassword |
376 | 527 | self.accountpassword.save() | 541 | accountpassword.password = 'invalid' |
377 | 542 | accountpassword.save() | ||
378 | 528 | except AccountPassword.DoesNotExist: | 543 | except AccountPassword.DoesNotExist: |
379 | 529 | # no password, so nothing to reset | 544 | # no password, so nothing to reset |
380 | 530 | pass | 545 | pass |
381 | @@ -557,14 +572,17 @@ | |||
382 | 557 | 572 | ||
383 | 558 | 573 | ||
384 | 559 | class AccountPassword(models.Model): | 574 | class AccountPassword(models.Model): |
386 | 560 | account = models.OneToOneField(Account, db_column='account') | 575 | account = models.ForeignKey(Account, db_column='account') |
387 | 561 | password = PasswordField() | 576 | password = PasswordField() |
388 | 577 | when_created = models.DateTimeField(default=datetime.now) | ||
389 | 562 | 578 | ||
390 | 563 | class Meta: | 579 | class Meta: |
391 | 564 | app_label = 'identityprovider' | 580 | app_label = 'identityprovider' |
392 | 565 | db_table = u'accountpassword' | 581 | db_table = u'accountpassword' |
393 | 566 | verbose_name = _('account password') | 582 | verbose_name = _('account password') |
394 | 567 | verbose_name_plural = _('account passwords') | 583 | verbose_name_plural = _('account passwords') |
395 | 584 | ordering = ['-when_created'] | ||
396 | 585 | get_latest_by = 'when_created' | ||
397 | 568 | 586 | ||
398 | 569 | def __unicode__(self): | 587 | def __unicode__(self): |
399 | 570 | return _("Password for %s") % unicode(self.account) | 588 | return _("Password for %s") % unicode(self.account) |
400 | 571 | 589 | ||
401 | === modified file 'src/identityprovider/schema.py' | |||
402 | --- src/identityprovider/schema.py 2013-11-12 13:44:06 +0000 | |||
403 | +++ src/identityprovider/schema.py 2013-11-27 18:08:05 +0000 | |||
404 | @@ -172,6 +172,7 @@ | |||
405 | 172 | max_password_reset_tokens = IntOption(default=5) | 172 | max_password_reset_tokens = IntOption(default=5) |
406 | 173 | combo_url = StringOption(default="/combo/") | 173 | combo_url = StringOption(default="/combo/") |
407 | 174 | combine = BoolOption(default=False) | 174 | combine = BoolOption(default=False) |
408 | 175 | pci_compliance_group = StringOption(default="pci-compliance") | ||
409 | 175 | 176 | ||
410 | 176 | class static_urls(Section): | 177 | class static_urls(Section): |
411 | 177 | support_form_url = StringOption() | 178 | support_form_url = StringOption() |
412 | 178 | 179 | ||
413 | === modified file 'src/identityprovider/signals.py' | |||
414 | --- src/identityprovider/signals.py 2013-05-16 13:24:00 +0000 | |||
415 | +++ src/identityprovider/signals.py 2013-11-27 18:08:05 +0000 | |||
416 | @@ -4,13 +4,12 @@ | |||
417 | 4 | from django.dispatch import Signal | 4 | from django.dispatch import Signal |
418 | 5 | from django.conf import settings | 5 | from django.conf import settings |
419 | 6 | from django.contrib.auth.signals import user_logged_in | 6 | from django.contrib.auth.signals import user_logged_in |
420 | 7 | from django.db.models.signals import post_save | ||
421 | 8 | 7 | ||
422 | 9 | from oauth.oauth import OAuthRequest | 8 | from oauth.oauth import OAuthRequest |
423 | 10 | from oauth_backend.models import Token | 9 | from oauth_backend.models import Token |
424 | 11 | 10 | ||
425 | 12 | from identityprovider.const import SESSION_TOKEN_KEY, SESSION_TOKEN_NAME | 11 | from identityprovider.const import SESSION_TOKEN_KEY, SESSION_TOKEN_NAME |
427 | 13 | from identityprovider.models import Account, AccountPassword | 12 | from identityprovider.models import Account |
428 | 14 | from identityprovider.utils import http_request_with_timeout | 13 | from identityprovider.utils import http_request_with_timeout |
429 | 15 | 14 | ||
430 | 16 | 15 | ||
431 | @@ -39,17 +38,6 @@ | |||
432 | 39 | application_token_invalidated.connect(account_change_notify) | 38 | application_token_invalidated.connect(account_change_notify) |
433 | 40 | 39 | ||
434 | 41 | 40 | ||
435 | 42 | def invalidate_account_oauth_tokens(sender, instance, created, **kwargs): | ||
436 | 43 | if not created: | ||
437 | 44 | # invalidate oauth tokens on password change | ||
438 | 45 | instance.account.invalidate_oauth_tokens() | ||
439 | 46 | |||
440 | 47 | |||
441 | 48 | post_save.connect( | ||
442 | 49 | invalidate_account_oauth_tokens, sender=AccountPassword, | ||
443 | 50 | dispatch_uid='identityprovider.AccountPassword.post_save') | ||
444 | 51 | |||
445 | 52 | |||
446 | 53 | def set_session_oauth_token(sender, user, request, **kwargs): | 41 | def set_session_oauth_token(sender, user, request, **kwargs): |
447 | 54 | # user is an Account instance here | 42 | # user is an Account instance here |
448 | 55 | 43 | ||
449 | 56 | 44 | ||
450 | === modified file 'src/identityprovider/templates/nexus/admin/password_change_form.html' | |||
451 | --- src/identityprovider/templates/nexus/admin/password_change_form.html 2013-05-15 13:39:40 +0000 | |||
452 | +++ src/identityprovider/templates/nexus/admin/password_change_form.html 2013-11-27 18:08:05 +0000 | |||
453 | @@ -45,7 +45,6 @@ | |||
454 | 45 | <div class="submit-row"> | 45 | <div class="submit-row"> |
455 | 46 | <input type="submit" value="{% trans 'Change my password' %}" class="default" /> | 46 | <input type="submit" value="{% trans 'Change my password' %}" class="default" /> |
456 | 47 | </div> | 47 | </div> |
457 | 48 | |||
458 | 49 | <script type="text/javascript">document.getElementById("id_old_password").focus();</script> | 48 | <script type="text/javascript">document.getElementById("id_old_password").focus();</script> |
459 | 50 | </div> | 49 | </div> |
460 | 51 | </form> | 50 | </form> |
461 | 52 | 51 | ||
462 | === modified file 'src/identityprovider/tests/test_admin.py' | |||
463 | --- src/identityprovider/tests/test_admin.py 2013-10-22 19:23:13 +0000 | |||
464 | +++ src/identityprovider/tests/test_admin.py 2013-11-27 18:08:05 +0000 | |||
465 | @@ -123,10 +123,10 @@ | |||
466 | 123 | 'status': str(self.account.status), | 123 | 'status': str(self.account.status), |
467 | 124 | 'displayname': self.account.displayname, | 124 | 'displayname': self.account.displayname, |
468 | 125 | 'openid_identifier': self.account.openid_identifier, | 125 | 'openid_identifier': self.account.openid_identifier, |
473 | 126 | 'accountpassword-TOTAL_FORMS': '1', | 126 | 'accountpassword_set-TOTAL_FORMS': '1', |
474 | 127 | 'accountpassword-INITIAL_FORMS': '1', | 127 | 'accountpassword_set-INITIAL_FORMS': '1', |
475 | 128 | 'accountpassword-0-id': str(account_id), | 128 | 'accountpassword_set-0-id': str(account_id), |
476 | 129 | 'accountpassword-0-account': str(account_id), | 129 | 'accountpassword_set-0-account': str(account_id), |
477 | 130 | 'emailaddress_set-TOTAL_FORMS': '1', | 130 | 'emailaddress_set-TOTAL_FORMS': '1', |
478 | 131 | 'emailaddress_set-INITIAL_FORMS': '1', | 131 | 'emailaddress_set-INITIAL_FORMS': '1', |
479 | 132 | 'emailaddress_set-0-id': str(email.id), | 132 | 'emailaddress_set-0-id': str(email.id), |
480 | @@ -150,7 +150,7 @@ | |||
481 | 150 | new_password = 'blah' | 150 | new_password = 'blah' |
482 | 151 | parameters = { | 151 | parameters = { |
483 | 152 | 'emailaddress_set-0-email': new_email, | 152 | 'emailaddress_set-0-email': new_email, |
485 | 153 | 'accountpassword-0-password': new_password, | 153 | 'accountpassword_set-0-password': new_password, |
486 | 154 | } | 154 | } |
487 | 155 | self.post_account_change(**parameters) | 155 | self.post_account_change(**parameters) |
488 | 156 | 156 | ||
489 | @@ -244,8 +244,8 @@ | |||
490 | 244 | self.assertEqual(r.status_code, 200) | 244 | self.assertEqual(r.status_code, 200) |
491 | 245 | self.assertContains(r, | 245 | self.assertContains(r, |
492 | 246 | '<input type="password" ' | 246 | '<input type="password" ' |
495 | 247 | 'name="accountpassword-0-password" ' | 247 | 'name="accountpassword_set-0-password" ' |
496 | 248 | 'id="id_accountpassword-0-password">', | 248 | 'id="id_accountpassword_set-0-password">', |
497 | 249 | html=True) | 249 | html=True) |
498 | 250 | 250 | ||
499 | 251 | def test_add_api_user(self): | 251 | def test_add_api_user(self): |
500 | 252 | 252 | ||
501 | === modified file 'src/identityprovider/tests/test_models_account.py' | |||
502 | --- src/identityprovider/tests/test_models_account.py 2013-11-21 13:15:42 +0000 | |||
503 | +++ src/identityprovider/tests/test_models_account.py 2013-11-27 18:08:05 +0000 | |||
504 | @@ -425,6 +425,13 @@ | |||
505 | 425 | self.assertNotEqual(account.accountpassword.password, | 425 | self.assertNotEqual(account.accountpassword.password, |
506 | 426 | original_password) | 426 | original_password) |
507 | 427 | 427 | ||
508 | 428 | def test_password_change_deletes_tokns(self): | ||
509 | 429 | account = self.factory.make_account() | ||
510 | 430 | self.factory.make_oauth_token(account=account) | ||
511 | 431 | |||
512 | 432 | account.set_password('fooBar123') | ||
513 | 433 | self.assertEqual(account.oauth_tokens().count(), 0) | ||
514 | 434 | |||
515 | 428 | def test_sets_password_with_django_cypher(self): | 435 | def test_sets_password_with_django_cypher(self): |
516 | 429 | password = 'Some password from 2013' | 436 | password = 'Some password from 2013' |
517 | 430 | account = self.factory.make_account() | 437 | account = self.factory.make_account() |
518 | 431 | 438 | ||
519 | === modified file 'src/identityprovider/tests/test_signals.py' | |||
520 | --- src/identityprovider/tests/test_signals.py 2013-08-16 15:12:35 +0000 | |||
521 | +++ src/identityprovider/tests/test_signals.py 2013-11-27 18:08:05 +0000 | |||
522 | @@ -5,15 +5,12 @@ | |||
523 | 5 | 5 | ||
524 | 6 | from django.contrib.auth.signals import user_logged_in | 6 | from django.contrib.auth.signals import user_logged_in |
525 | 7 | from django.core.urlresolvers import reverse | 7 | from django.core.urlresolvers import reverse |
526 | 8 | from django.db.models.signals import post_save | ||
527 | 9 | 8 | ||
528 | 10 | from oauth_backend.models import Token | 9 | from oauth_backend.models import Token |
529 | 11 | 10 | ||
530 | 12 | from identityprovider.const import SESSION_TOKEN_KEY, SESSION_TOKEN_NAME | 11 | from identityprovider.const import SESSION_TOKEN_KEY, SESSION_TOKEN_NAME |
535 | 13 | from identityprovider.signals import ( | 12 | from identityprovider.signals import set_session_oauth_token |
536 | 14 | invalidate_account_oauth_tokens, | 13 | |
533 | 15 | set_session_oauth_token, | ||
534 | 16 | ) | ||
537 | 17 | from identityprovider.readonly import ReadOnlyManager | 14 | from identityprovider.readonly import ReadOnlyManager |
538 | 18 | from identityprovider.tests import DEFAULT_USER_PASSWORD | 15 | from identityprovider.tests import DEFAULT_USER_PASSWORD |
539 | 19 | from identityprovider.tests.utils import ( | 16 | from identityprovider.tests.utils import ( |
540 | @@ -77,18 +74,3 @@ | |||
541 | 77 | self.client.login(username=self.email, password=DEFAULT_USER_PASSWORD) | 74 | self.client.login(username=self.email, password=DEFAULT_USER_PASSWORD) |
542 | 78 | self.assertEqual( | 75 | self.assertEqual( |
543 | 79 | self.client.session.get(SESSION_TOKEN_KEY), token.token) | 76 | self.client.session.get(SESSION_TOKEN_KEY), token.token) |
544 | 80 | |||
545 | 81 | |||
546 | 82 | class InvalidateOauthTokenOnPasswordChangeTestCase(SSOBaseTestCase): | ||
547 | 83 | |||
548 | 84 | def test_signal_connected(self): | ||
549 | 85 | # r[1] is a weak ref | ||
550 | 86 | registered_functions = [r[1]() for r in post_save.receivers] | ||
551 | 87 | self.assertIn(invalidate_account_oauth_tokens, registered_functions) | ||
552 | 88 | |||
553 | 89 | def test_listener_on_password_change(self): | ||
554 | 90 | account = self.factory.make_account() | ||
555 | 91 | self.factory.make_oauth_token(account=account) | ||
556 | 92 | |||
557 | 93 | account.set_password('fooBar123') | ||
558 | 94 | self.assertEqual(account.oauth_tokens().count(), 0) | ||
559 | 95 | 77 | ||
560 | === modified file 'src/identityprovider/validators.py' | |||
561 | --- src/identityprovider/validators.py 2013-09-12 14:45:21 +0000 | |||
562 | +++ src/identityprovider/validators.py 2013-11-27 18:08:05 +0000 | |||
563 | @@ -1,6 +1,7 @@ | |||
564 | 1 | from contextlib import contextmanager | 1 | from contextlib import contextmanager |
565 | 2 | import re | 2 | import re |
566 | 3 | 3 | ||
567 | 4 | from django.conf import settings | ||
568 | 4 | from django.core.exceptions import ValidationError | 5 | from django.core.exceptions import ValidationError |
569 | 5 | from django.utils.translation import ugettext_lazy as _ | 6 | from django.utils.translation import ugettext_lazy as _ |
570 | 6 | 7 | ||
571 | @@ -11,6 +12,10 @@ | |||
572 | 11 | "Password must consist of lower- and upper-case characters, and at " | 12 | "Password must consist of lower- and upper-case characters, and at " |
573 | 12 | "least one digit." | 13 | "least one digit." |
574 | 13 | ) | 14 | ) |
575 | 15 | PCI_PASSWORD_POLICY_ERROR = _( | ||
576 | 16 | "Your password matches a previous passowrd. PCI compliance rules " | ||
577 | 17 | "forbid the reuse of recent passwords." | ||
578 | 18 | ) | ||
579 | 14 | 19 | ||
580 | 15 | 20 | ||
581 | 16 | class BasicValidator(object): | 21 | class BasicValidator(object): |
582 | @@ -88,7 +93,37 @@ | |||
583 | 88 | raise ValidationError(CANONICAL_PASSWORD_POLICY_ERROR) | 93 | raise ValidationError(CANONICAL_PASSWORD_POLICY_ERROR) |
584 | 89 | 94 | ||
585 | 90 | 95 | ||
587 | 91 | _validators = [CanonicalValidator(), BasicValidator()] | 96 | class PCIValidator(BasicValidator): |
588 | 97 | """Apply PCI compliance password rules""" | ||
589 | 98 | |||
590 | 99 | def match(self, account): | ||
591 | 100 | """Return True if the account is in the 'PCI' team.""" | ||
592 | 101 | # store the account | ||
593 | 102 | self.account = account | ||
594 | 103 | return (account is not None and | ||
595 | 104 | account.person_in_team(settings.PCI_COMLIANCE_GROUP)) | ||
596 | 105 | |||
597 | 106 | def validate(self, password): | ||
598 | 107 | """Check the new password doesn't match any of the last four | ||
599 | 108 | passwords.""" | ||
600 | 109 | super(CanonicalValidator, self).validate(password) | ||
601 | 110 | |||
602 | 111 | # import here to avoid circular imports | ||
603 | 112 | from identityprovider.auth import LaunchpadBackend | ||
604 | 113 | |||
605 | 114 | account = self.account | ||
606 | 115 | backend = LaunchpadBackend() | ||
607 | 116 | for accountpassword in account.accountpassword_set.all()[1:]: | ||
608 | 117 | try: | ||
609 | 118 | backend._validate_raw_password( | ||
610 | 119 | account.accountpassword, password, update=False) | ||
611 | 120 | except ValidationError: | ||
612 | 121 | pass | ||
613 | 122 | else: | ||
614 | 123 | raise ValidationError(PCI_PASSWORD_POLICY_ERROR) | ||
615 | 124 | |||
616 | 125 | |||
617 | 126 | _validators = [CanonicalValidator(), BasicValidator(), PCIValidator()] | ||
618 | 92 | 127 | ||
619 | 93 | 128 | ||
620 | 94 | def validate_password_policy(password, account=None): | 129 | def validate_password_policy(password, account=None): |
Hi! I'm doing some cleanup on SSO MPs, I assume this is no longer valid, will set as Rejected.
Please shange the global status if this is still current. Thanks!