Merge lp:~wgrant/launchpad/no-passwords into lp:launchpad
- no-passwords
- Merge into devel
Proposed by
William Grant
on 2012-01-18
| Status: | Merged |
|---|---|
| Merged at revision: | 14694 |
| Proposed branch: | lp:~wgrant/launchpad/no-passwords |
| Merge into: | lp:launchpad |
| Prerequisite: | lp:~wgrant/launchpad/hardcoded-password |
| Diff against target: |
694 lines (+33/-309) 14 files modified
lib/lp/registry/doc/person-account.txt (+3/-5) lib/lp/registry/doc/person.txt (+1/-1) lib/lp/registry/interfaces/person.py (+3/-9) lib/lp/registry/model/person.py (+5/-10) lib/lp/services/database/doc/storm.txt (+0/-4) lib/lp/services/identity/configure.zcml (+1/-1) lib/lp/services/identity/doc/account.txt (+5/-45) lib/lp/services/identity/interfaces/account.py (+4/-17) lib/lp/services/identity/model/account.py (+3/-64) lib/lp/services/webapp/authentication.py (+0/-47) lib/lp/services/webapp/configure.zcml (+0/-6) lib/lp/services/webapp/interfaces.py (+0/-14) lib/lp/services/webapp/tests/test_encryptor.py (+0/-63) lib/lp/testing/factory.py (+8/-23) |
| To merge this branch: | bzr merge lp:~wgrant/launchpad/no-passwords |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Steve Kowalik (community) | code | 2012-01-18 | Approve on 2012-01-18 |
|
Review via email:
|
|||
Commit Message
[r=stevenk][no-qa] Rip out the password infrastructure.
Description of the Change
The password infrastructure (AccountPassword and friends) is no longer used. Good riddance.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
| 1 | === modified file 'lib/lp/registry/doc/person-account.txt' |
| 2 | --- lib/lp/registry/doc/person-account.txt 2012-01-15 11:50:42 +0000 |
| 3 | +++ lib/lp/registry/doc/person-account.txt 2012-01-18 07:40:38 +0000 |
| 4 | @@ -31,17 +31,16 @@ |
| 5 | the profile. Sample Person cannot claim it. |
| 6 | |
| 7 | >>> login('test@canonical.com') |
| 8 | - >>> matsubara.account.reactivate(comment="test", password='ok') |
| 9 | + >>> matsubara.account.reactivate(comment="test") |
| 10 | Traceback (most recent call last): |
| 11 | ... |
| 12 | Unauthorized: ...'launchpad.Special') |
| 13 | |
| 14 | -Matsubara can. A password and a preferred email address must be passed |
| 15 | -as arguments. |
| 16 | +Matsubara can. |
| 17 | |
| 18 | >>> from zope.security.proxy import removeSecurityProxy |
| 19 | >>> login('matsubara@async.com.br') |
| 20 | - >>> matsubara.account.reactivate(comment="test", password='ok') |
| 21 | + >>> matsubara.account.reactivate(comment="test") |
| 22 | >>> matsubara.setPreferredEmail(emailaddress) |
| 23 | >>> import transaction |
| 24 | >>> transaction.commit() |
| 25 | @@ -215,7 +214,6 @@ |
| 26 | |
| 27 | >>> foobar.reactivate( |
| 28 | ... 'User reactivated the account using reset password.', |
| 29 | - ... password="ok", |
| 30 | ... preferred_email=foobar_preferredemail) |
| 31 | >>> transaction.commit() # To see the changes on other stores. |
| 32 | >>> foobar.account.status |
| 33 | |
| 34 | === modified file 'lib/lp/registry/doc/person.txt' |
| 35 | --- lib/lp/registry/doc/person.txt 2012-01-17 00:39:56 +0000 |
| 36 | +++ lib/lp/registry/doc/person.txt 2012-01-18 07:40:38 +0000 |
| 37 | @@ -142,7 +142,7 @@ |
| 38 | >>> from lp.services.identity.model.account import Account |
| 39 | >>> from lp.services.database.lpstorm import IMasterStore |
| 40 | >>> account = IMasterStore(Account).get(Account, p.accountID) |
| 41 | - >>> account.reactivate("Activated by doc test.", account.password) |
| 42 | + >>> account.reactivate("Activated by doc test.") |
| 43 | >>> p.account_status |
| 44 | <DBItem AccountStatus.ACTIVE... |
| 45 | |
| 46 | |
| 47 | === modified file 'lib/lp/registry/interfaces/person.py' |
| 48 | --- lib/lp/registry/interfaces/person.py 2012-01-15 11:50:42 +0000 |
| 49 | +++ lib/lp/registry/interfaces/person.py 2012-01-18 07:40:38 +0000 |
| 50 | @@ -1817,7 +1817,6 @@ |
| 51 | """Deactivate this person's Launchpad account. |
| 52 | |
| 53 | Deactivating an account means: |
| 54 | - - Setting its password to NULL; |
| 55 | - Removing the user from all teams he's a member of; |
| 56 | - Changing all his email addresses' status to NEW; |
| 57 | - Revoking Code of Conduct signatures of that user; |
| 58 | @@ -1827,17 +1826,16 @@ |
| 59 | :param comment: An explanation of why the account status changed. |
| 60 | """ |
| 61 | |
| 62 | - def reactivate(comment, password, preferred_email): |
| 63 | + def reactivate(comment, preferred_email): |
| 64 | """Reactivate this person and its account. |
| 65 | |
| 66 | - Set the account status to ACTIVE, the account's password to the given |
| 67 | - one and its preferred email address. |
| 68 | + Set the account status to ACTIVE, and update the preferred email |
| 69 | + address. |
| 70 | |
| 71 | If the person's name contains a -deactivatedaccount suffix (usually |
| 72 | added by `IPerson`.deactivateAccount(), it is removed. |
| 73 | |
| 74 | :param comment: An explanation of why the account status changed. |
| 75 | - :param password: The user's password. |
| 76 | :param preferred_email: The `EmailAddress` to set as the account's |
| 77 | preferred email address. It cannot be None. |
| 78 | """ |
| 79 | @@ -2070,7 +2068,6 @@ |
| 80 | |
| 81 | def createPersonAndEmail( |
| 82 | email, rationale, comment=None, name=None, displayname=None, |
| 83 | - password=None, passwordEncrypted=False, |
| 84 | hide_email_addresses=False, registrant=None): |
| 85 | """Create and return an `IPerson` and `IEmailAddress`. |
| 86 | |
| 87 | @@ -2091,9 +2088,6 @@ |
| 88 | (e.g. "when the foo package was imported into Ubuntu Breezy"). |
| 89 | :param name: The person's name. |
| 90 | :param displayname: The person's displayname. |
| 91 | - :param password: The person's password. |
| 92 | - :param passwordEncrypted: Whether or not the given password is |
| 93 | - encrypted. |
| 94 | :param registrant: The user who created this person, if any. |
| 95 | :param hide_email_addresses: Whether or not Launchpad should hide the |
| 96 | person's email addresses from other users. |
| 97 | |
| 98 | === modified file 'lib/lp/registry/model/person.py' |
| 99 | --- lib/lp/registry/model/person.py 2012-01-16 13:11:00 +0000 |
| 100 | +++ lib/lp/registry/model/person.py 2012-01-18 07:40:38 +0000 |
| 101 | @@ -2516,10 +2516,10 @@ |
| 102 | else: |
| 103 | return None |
| 104 | |
| 105 | - def reactivate(self, comment, password, preferred_email): |
| 106 | + def reactivate(self, comment, preferred_email): |
| 107 | """See `IPersonSpecialRestricted`.""" |
| 108 | account = IMasterObject(self.account) |
| 109 | - account.reactivate(comment, password) |
| 110 | + account.reactivate(comment) |
| 111 | self.setPreferredEmail(preferred_email) |
| 112 | if '-deactivatedaccount' in self.name: |
| 113 | # The name was changed by deactivateAccount(). Restore the |
| 114 | @@ -3153,9 +3153,7 @@ |
| 115 | |
| 116 | elif person.account.status in [AccountStatus.DEACTIVATED, |
| 117 | AccountStatus.NOACCOUNT]: |
| 118 | - password = '' |
| 119 | - removeSecurityProxy(person.account).reactivate( |
| 120 | - comment, password) |
| 121 | + removeSecurityProxy(person.account).reactivate(comment) |
| 122 | removeSecurityProxy(person).setPreferredEmail(email) |
| 123 | db_updated = True |
| 124 | else: |
| 125 | @@ -3186,8 +3184,7 @@ |
| 126 | return team |
| 127 | |
| 128 | def createPersonAndEmail( |
| 129 | - self, email, rationale, comment=None, name=None, |
| 130 | - displayname=None, password=None, passwordEncrypted=False, |
| 131 | + self, email, rationale, comment=None, name=None, displayname=None, |
| 132 | hide_email_addresses=False, registrant=None): |
| 133 | """See `IPersonSet`.""" |
| 134 | |
| 135 | @@ -3208,9 +3205,7 @@ |
| 136 | # Convert the PersonCreationRationale to an AccountCreationRationale |
| 137 | account_rationale = getattr(AccountCreationRationale, rationale.name) |
| 138 | |
| 139 | - account = getUtility(IAccountSet).new( |
| 140 | - account_rationale, displayname, password=password, |
| 141 | - password_is_encrypted=passwordEncrypted) |
| 142 | + account = getUtility(IAccountSet).new(account_rationale, displayname) |
| 143 | |
| 144 | person = self._newPerson( |
| 145 | name, displayname, hide_email_addresses, rationale=rationale, |
| 146 | |
| 147 | === modified file 'lib/lp/services/database/doc/storm.txt' |
| 148 | --- lib/lp/services/database/doc/storm.txt 2012-01-06 11:08:30 +0000 |
| 149 | +++ lib/lp/services/database/doc/storm.txt 2012-01-18 07:40:38 +0000 |
| 150 | @@ -16,10 +16,6 @@ |
| 151 | ... ISlaveStore, |
| 152 | ... IStore, |
| 153 | ... ) |
| 154 | - >>> from lp.services.identity.model.account import ( |
| 155 | - ... Account, |
| 156 | - ... AccountPassword, |
| 157 | - ... ) |
| 158 | >>> from lp.services.identity.model.emailaddress import EmailAddress |
| 159 | >>> from zope.security.proxy import ProxyFactory |
| 160 | >>> from lp.registry.interfaces.person import IPersonSet |
| 161 | |
| 162 | === modified file 'lib/lp/services/identity/configure.zcml' |
| 163 | --- lib/lp/services/identity/configure.zcml 2011-12-24 17:49:30 +0000 |
| 164 | +++ lib/lp/services/identity/configure.zcml 2012-01-18 07:40:38 +0000 |
| 165 | @@ -59,7 +59,7 @@ |
| 166 | set_attributes="status date_status_set status_comment"/> |
| 167 | <require |
| 168 | permission="launchpad.Edit" |
| 169 | - set_attributes="displayname password"/> |
| 170 | + set_attributes="displayname"/> |
| 171 | </class> |
| 172 | |
| 173 | <securedutility |
| 174 | |
| 175 | === modified file 'lib/lp/services/identity/doc/account.txt' |
| 176 | --- lib/lp/services/identity/doc/account.txt 2012-01-03 11:08:31 +0000 |
| 177 | +++ lib/lp/services/identity/doc/account.txt 2012-01-18 07:40:38 +0000 |
| 178 | @@ -83,22 +83,6 @@ |
| 179 | >>> account |
| 180 | <Account 'No Privileges Person' (Active account)> |
| 181 | |
| 182 | -It also has an encrypted password. |
| 183 | - |
| 184 | - >>> print account.password |
| 185 | - K7Qmeansl6RbuPfulfcmyDQOzp70OxVh5Fcf |
| 186 | - |
| 187 | -Ensure the password changes are sticky, as this is a property hiding the |
| 188 | -AccountPassword table. |
| 189 | - |
| 190 | - >>> account.password = None |
| 191 | - >>> print account.password |
| 192 | - None |
| 193 | - |
| 194 | - >>> account.password = u'K7Qmeansl6RbuPfulfcmyDQOzp70OxVh5Fcf' |
| 195 | - >>> print account.password |
| 196 | - K7Qmeansl6RbuPfulfcmyDQOzp70OxVh5Fcf |
| 197 | - |
| 198 | The account has other metadata. |
| 199 | |
| 200 | >>> account.date_created |
| 201 | @@ -152,36 +136,12 @@ |
| 202 | |
| 203 | >>> from lp.services.identity.interfaces.account import ( |
| 204 | ... AccountCreationRationale) |
| 205 | - >>> from storm.store import Store |
| 206 | |
| 207 | >>> login('admin@canonical.com') |
| 208 | - >>> passwordless_account = account_set.new( |
| 209 | - ... AccountCreationRationale.USER_CREATED, 'Passwordless') |
| 210 | + >>> new_account = account_set.new( |
| 211 | + ... AccountCreationRationale.USER_CREATED, 'New Account') |
| 212 | >>> transaction.commit() |
| 213 | - >>> print passwordless_account.creation_rationale.name |
| 214 | + >>> print new_account.creation_rationale.name |
| 215 | USER_CREATED |
| 216 | - >>> print passwordless_account.displayname |
| 217 | - Passwordless |
| 218 | - >>> print passwordless_account.password |
| 219 | - None |
| 220 | - |
| 221 | -The new() method accepts the optional parameters of password and |
| 222 | -password_is_encrypted. If password_is_encrypted is False, the default, |
| 223 | -then the method encrypts it for us. |
| 224 | - |
| 225 | - >>> passworded_account = account_set.new( |
| 226 | - ... AccountCreationRationale.OWNER_CREATED_LAUNCHPAD , 'Passworded', |
| 227 | - ... password=u'clear_password') |
| 228 | - >>> Store.of(passworded_account).flush() |
| 229 | - >>> passworded_account.password == u'clear_password' |
| 230 | - False |
| 231 | - |
| 232 | -The method does not encrypt the password if told that it is already |
| 233 | -encrypted, by setting password_is_encrypted to True. |
| 234 | - |
| 235 | - >>> clear_account = account_set.new( |
| 236 | - ... AccountCreationRationale.OWNER_CREATED_LAUNCHPAD , 'Clear', |
| 237 | - ... password=u'clear_password', password_is_encrypted=True) |
| 238 | - >>> Store.of(clear_account).flush() |
| 239 | - >>> print clear_account.password |
| 240 | - clear_password |
| 241 | + >>> print new_account.displayname |
| 242 | + New Account |
| 243 | |
| 244 | === modified file 'lib/lp/services/identity/interfaces/account.py' |
| 245 | --- lib/lp/services/identity/interfaces/account.py 2012-01-03 11:42:33 +0000 |
| 246 | +++ lib/lp/services/identity/interfaces/account.py 2012-01-18 07:40:38 +0000 |
| 247 | @@ -35,10 +35,7 @@ |
| 248 | ) |
| 249 | |
| 250 | from lp import _ |
| 251 | -from lp.services.fields import ( |
| 252 | - PasswordField, |
| 253 | - StrippedTextLine, |
| 254 | - ) |
| 255 | +from lp.services.fields import StrippedTextLine |
| 256 | |
| 257 | |
| 258 | class AccountSuspendedError(Exception): |
| 259 | @@ -229,9 +226,6 @@ |
| 260 | |
| 261 | openid_identifiers = Attribute(_("Linked OpenId Identifiers")) |
| 262 | |
| 263 | - password = PasswordField( |
| 264 | - title=_("Password."), readonly=False, required=True) |
| 265 | - |
| 266 | |
| 267 | class IAccountSpecialRestricted(Interface): |
| 268 | """Attributes of `IAccount` protected with launchpad.Special.""" |
| 269 | @@ -244,14 +238,12 @@ |
| 270 | title=_("Why are you deactivating your account?"), |
| 271 | required=False, readonly=False) |
| 272 | |
| 273 | - def reactivate(comment, password): |
| 274 | + def reactivate(comment): |
| 275 | """Activate this account. |
| 276 | |
| 277 | - Set the account status to ACTIVE, the account's password to the given |
| 278 | - one and its preferred email address. |
| 279 | + Set the account status to ACTIVE. |
| 280 | |
| 281 | :param comment: An explanation of why the account status changed. |
| 282 | - :param password: The user's password. |
| 283 | """ |
| 284 | |
| 285 | |
| 286 | @@ -262,16 +254,11 @@ |
| 287 | class IAccountSet(Interface): |
| 288 | """Creation of and access to `IAccount` providers.""" |
| 289 | |
| 290 | - def new(rationale, displayname, password=None, |
| 291 | - password_is_encrypted=False): |
| 292 | + def new(rationale, displayname): |
| 293 | """Create a new `IAccount`. |
| 294 | |
| 295 | :param rationale: An `AccountCreationRationale` value. |
| 296 | :param displayname: The user's display name. |
| 297 | - :param password: A password. |
| 298 | - :param password_is_encrypted: If True, the password parameter has |
| 299 | - already been encrypted using the `IPasswordEncryptor` utility. |
| 300 | - If False, the password will be encrypted automatically. |
| 301 | |
| 302 | :return: The newly created `IAccount` provider. |
| 303 | """ |
| 304 | |
| 305 | === modified file 'lib/lp/services/identity/model/account.py' |
| 306 | --- lib/lp/services/identity/model/account.py 2012-01-03 11:08:31 +0000 |
| 307 | +++ lib/lp/services/identity/model/account.py 2012-01-18 07:40:38 +0000 |
| 308 | @@ -6,16 +6,11 @@ |
| 309 | __metaclass__ = type |
| 310 | __all__ = [ |
| 311 | 'Account', |
| 312 | - 'AccountPassword', |
| 313 | 'AccountSet', |
| 314 | ] |
| 315 | |
| 316 | -from sqlobject import ( |
| 317 | - ForeignKey, |
| 318 | - StringCol, |
| 319 | - ) |
| 320 | +from sqlobject import StringCol |
| 321 | from storm.locals import ReferenceSet |
| 322 | -from zope.component import getUtility |
| 323 | from zope.interface import implements |
| 324 | |
| 325 | from lp.services.database.constants import UTC_NOW |
| 326 | @@ -33,7 +28,6 @@ |
| 327 | IAccountSet, |
| 328 | ) |
| 329 | from lp.services.openid.model.openididentifier import OpenIdIdentifier |
| 330 | -from lp.services.webapp.interfaces import IPasswordEncryptor |
| 331 | |
| 332 | |
| 333 | class Account(SQLBase): |
| 334 | @@ -61,55 +55,17 @@ |
| 335 | return "<%s '%s' (%s)>" % ( |
| 336 | self.__class__.__name__, displayname, self.status) |
| 337 | |
| 338 | - def reactivate(self, comment, password): |
| 339 | + def reactivate(self, comment): |
| 340 | """See `IAccountSpecialRestricted`.""" |
| 341 | self.status = AccountStatus.ACTIVE |
| 342 | self.status_comment = comment |
| 343 | - self.password = password |
| 344 | - |
| 345 | - # The password is actually stored in a separate table for security |
| 346 | - # reasons, so use a property to hide this implementation detail. |
| 347 | - def _get_password(self): |
| 348 | - # We have to force the switch to the auth store, because the |
| 349 | - # AccountPassword table is not visible via the main store |
| 350 | - # for security reasons. |
| 351 | - password = IStore(AccountPassword).find( |
| 352 | - AccountPassword, accountID=self.id).one() |
| 353 | - if password is None: |
| 354 | - return None |
| 355 | - else: |
| 356 | - return password.password |
| 357 | - |
| 358 | - def _set_password(self, value): |
| 359 | - # Making a modification, so we explicitly use the auth store master. |
| 360 | - store = IMasterStore(AccountPassword) |
| 361 | - password = store.find( |
| 362 | - AccountPassword, accountID=self.id).one() |
| 363 | - |
| 364 | - if value is not None and password is None: |
| 365 | - # There is currently no AccountPassword record and we need one. |
| 366 | - AccountPassword(accountID=self.id, password=value) |
| 367 | - elif value is None and password is not None: |
| 368 | - # There is an AccountPassword record that needs removing. |
| 369 | - store.remove(password) |
| 370 | - elif value is not None: |
| 371 | - # There is an AccountPassword record that needs updating. |
| 372 | - password.password = value |
| 373 | - elif value is None and password is None: |
| 374 | - # Nothing to do |
| 375 | - pass |
| 376 | - else: |
| 377 | - assert False, "This should not be reachable." |
| 378 | - |
| 379 | - password = property(_get_password, _set_password) |
| 380 | |
| 381 | |
| 382 | class AccountSet: |
| 383 | """See `IAccountSet`.""" |
| 384 | implements(IAccountSet) |
| 385 | |
| 386 | - def new(self, rationale, displayname, password=None, |
| 387 | - password_is_encrypted=False, openid_identifier=None): |
| 388 | + def new(self, rationale, displayname, openid_identifier=None): |
| 389 | """See `IAccountSet`.""" |
| 390 | |
| 391 | account = Account( |
| 392 | @@ -123,12 +79,6 @@ |
| 393 | identifier.identifier = openid_identifier |
| 394 | IMasterStore(OpenIdIdentifier).add(identifier) |
| 395 | |
| 396 | - # Create the password record. |
| 397 | - if password is not None: |
| 398 | - if not password_is_encrypted: |
| 399 | - password = getUtility(IPasswordEncryptor).encrypt(password) |
| 400 | - AccountPassword(account=account, password=password) |
| 401 | - |
| 402 | return account |
| 403 | |
| 404 | def get(self, id): |
| 405 | @@ -148,14 +98,3 @@ |
| 406 | if account is None: |
| 407 | raise LookupError(openid_identifier) |
| 408 | return account |
| 409 | - |
| 410 | - |
| 411 | -class AccountPassword(SQLBase): |
| 412 | - """SQLObject wrapper to the AccountPassword table. |
| 413 | - |
| 414 | - Note that this class is not exported, as the existence of the |
| 415 | - AccountPassword table only needs to be known by this module. |
| 416 | - """ |
| 417 | - account = ForeignKey( |
| 418 | - dbName='account', foreignKey='Account', alternateID=True) |
| 419 | - password = StringCol(dbName='password', notNull=True) |
| 420 | |
| 421 | === modified file 'lib/lp/services/webapp/authentication.py' |
| 422 | --- lib/lp/services/webapp/authentication.py 2012-01-18 07:40:38 +0000 |
| 423 | +++ lib/lp/services/webapp/authentication.py 2012-01-18 07:40:38 +0000 |
| 424 | @@ -9,13 +9,10 @@ |
| 425 | 'LaunchpadLoginSource', |
| 426 | 'LaunchpadPrincipal', |
| 427 | 'PlacelessAuthUtility', |
| 428 | - 'SSHADigestEncryptor', |
| 429 | ] |
| 430 | |
| 431 | |
| 432 | import binascii |
| 433 | -import hashlib |
| 434 | -import random |
| 435 | from UserDict import UserDict |
| 436 | |
| 437 | from contrib.oauth import OAuthRequest |
| 438 | @@ -46,7 +43,6 @@ |
| 439 | BasicAuthLoggedInEvent, |
| 440 | CookieAuthPrincipalIdentifiedEvent, |
| 441 | ILaunchpadPrincipal, |
| 442 | - IPasswordEncryptor, |
| 443 | IPlacelessAuthUtility, |
| 444 | IPlacelessLoginSource, |
| 445 | ) |
| 446 | @@ -180,49 +176,6 @@ |
| 447 | return getUtility(IPlacelessLoginSource).getPrincipalByLogin(login) |
| 448 | |
| 449 | |
| 450 | -class SSHADigestEncryptor: |
| 451 | - """SSHA is a modification of the SHA digest scheme with a salt |
| 452 | - starting at byte 20 of the base64-encoded string. |
| 453 | - """ |
| 454 | - implements(IPasswordEncryptor) |
| 455 | - |
| 456 | - # Source: http://developer.netscape.com/docs/technote/ldap/pass_sha.html |
| 457 | - |
| 458 | - saltLength = 20 |
| 459 | - |
| 460 | - def generate_salt(self): |
| 461 | - # Salt can be any length, but not more than about 37 characters |
| 462 | - # because of limitations of the binascii module. |
| 463 | - # All 256 characters are available. |
| 464 | - salt = '' |
| 465 | - for n in range(self.saltLength): |
| 466 | - salt += chr(random.randrange(256)) |
| 467 | - return salt |
| 468 | - |
| 469 | - def encrypt(self, plaintext, salt=None): |
| 470 | - plaintext = str(plaintext) |
| 471 | - if salt is None: |
| 472 | - salt = self.generate_salt() |
| 473 | - v = binascii.b2a_base64( |
| 474 | - hashlib.sha1(plaintext + salt).digest() + salt) |
| 475 | - return v[:-1] |
| 476 | - |
| 477 | - def validate(self, plaintext, encrypted): |
| 478 | - encrypted = str(encrypted) |
| 479 | - plaintext = str(plaintext) |
| 480 | - try: |
| 481 | - ref = binascii.a2b_base64(encrypted) |
| 482 | - except binascii.Error: |
| 483 | - # Not valid base64. |
| 484 | - return False |
| 485 | - salt = ref[20:] |
| 486 | - v = binascii.b2a_base64( |
| 487 | - hashlib.sha1(plaintext + salt).digest() + salt)[:-1] |
| 488 | - pw1 = (v or '').strip() |
| 489 | - pw2 = (encrypted or '').strip() |
| 490 | - return pw1 == pw2 |
| 491 | - |
| 492 | - |
| 493 | class LaunchpadLoginSource: |
| 494 | """A login source that uses the launchpad SQL database to look up |
| 495 | principal information. |
| 496 | |
| 497 | === modified file 'lib/lp/services/webapp/configure.zcml' |
| 498 | --- lib/lp/services/webapp/configure.zcml 2011-12-30 07:32:58 +0000 |
| 499 | +++ lib/lp/services/webapp/configure.zcml 2012-01-18 07:40:38 +0000 |
| 500 | @@ -169,12 +169,6 @@ |
| 501 | /> |
| 502 | |
| 503 | <utility |
| 504 | - factory="lp.services.webapp.authentication.SSHADigestEncryptor" |
| 505 | - provides="lp.services.webapp.interfaces.IPasswordEncryptor" |
| 506 | - permission="zope.Public" |
| 507 | - /> |
| 508 | - |
| 509 | - <utility |
| 510 | component="lp.services.webapp.authentication.loginSource" |
| 511 | provides="lp.services.webapp.interfaces.IPlacelessLoginSource" |
| 512 | permission="zope.Public" |
| 513 | |
| 514 | === modified file 'lib/lp/services/webapp/interfaces.py' |
| 515 | --- lib/lp/services/webapp/interfaces.py 2012-01-18 07:40:38 +0000 |
| 516 | +++ lib/lp/services/webapp/interfaces.py 2012-01-18 07:40:38 +0000 |
| 517 | @@ -412,20 +412,6 @@ |
| 518 | schema=IBrowserFormNG) |
| 519 | |
| 520 | |
| 521 | -class IPasswordEncryptor(Interface): |
| 522 | - """An interface representing a password encryption scheme.""" |
| 523 | - |
| 524 | - def encrypt(plaintext): |
| 525 | - """Return the encrypted value of plaintext.""" |
| 526 | - |
| 527 | - def validate(plaintext, encrypted): |
| 528 | - """Return a true value if the encrypted value of 'plaintext' is |
| 529 | - equivalent to the value of 'encrypted'. In general, if this |
| 530 | - method returns true, it can also be assumed that the value of |
| 531 | - self.encrypt(plaintext) will compare equal to 'encrypted'. |
| 532 | - """ |
| 533 | - |
| 534 | - |
| 535 | class IPrincipalIdentifiedEvent(Interface): |
| 536 | """An event that is sent after a principal has been recovered from the |
| 537 | request's credentials. |
| 538 | |
| 539 | === removed file 'lib/lp/services/webapp/tests/test_encryptor.py' |
| 540 | --- lib/lp/services/webapp/tests/test_encryptor.py 2012-01-01 02:58:52 +0000 |
| 541 | +++ lib/lp/services/webapp/tests/test_encryptor.py 1970-01-01 00:00:00 +0000 |
| 542 | @@ -1,63 +0,0 @@ |
| 543 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
| 544 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
| 545 | - |
| 546 | -__metaclass__ = type |
| 547 | - |
| 548 | - |
| 549 | -import binascii |
| 550 | -import hashlib |
| 551 | -import unittest |
| 552 | - |
| 553 | -from zope.app.testing import ztapi |
| 554 | -from zope.app.testing.placelesssetup import PlacelessSetup |
| 555 | -from zope.component import getUtility |
| 556 | - |
| 557 | -from lp.services.webapp.authentication import SSHADigestEncryptor |
| 558 | -from lp.services.webapp.interfaces import IPasswordEncryptor |
| 559 | - |
| 560 | - |
| 561 | -class TestSSHADigestEncryptor(PlacelessSetup, unittest.TestCase): |
| 562 | - |
| 563 | - def setUp(self): |
| 564 | - PlacelessSetup.setUp(self) |
| 565 | - encryptor = SSHADigestEncryptor() |
| 566 | - ztapi.provideUtility(IPasswordEncryptor, encryptor) |
| 567 | - |
| 568 | - def test_encrypt(self): |
| 569 | - encryptor = getUtility(IPasswordEncryptor) |
| 570 | - encrypted1 = encryptor.encrypt('motorhead') |
| 571 | - encrypted2 = encryptor.encrypt('motorhead') |
| 572 | - self.failIfEqual(encrypted1, encrypted2) |
| 573 | - salt = encrypted1[20:] |
| 574 | - v = binascii.b2a_base64( |
| 575 | - hashlib.sha1('motorhead' + salt).digest() + salt)[:-1] |
| 576 | - return (v == encrypted1) |
| 577 | - |
| 578 | - def test_validate(self): |
| 579 | - encryptor = getUtility(IPasswordEncryptor) |
| 580 | - self.assertEqual(encryptor.validate( |
| 581 | - 'motorhead', '+uSsxIfQDRUxG1oDTu1SsQN0P0RTl4SL9XRd'), True) |
| 582 | - |
| 583 | - def test_unicode_encrypt(self): |
| 584 | - encryptor = getUtility(IPasswordEncryptor) |
| 585 | - encrypted1 = encryptor.encrypt(u'motorhead') |
| 586 | - encrypted2 = encryptor.encrypt(u'motorhead') |
| 587 | - self.failIfEqual(encrypted1, encrypted2) |
| 588 | - salt = encrypted1[20:] |
| 589 | - v = binascii.b2a_base64( |
| 590 | - hashlib.sha1('motorhead' + salt).digest() + salt)[:-1] |
| 591 | - return v == encrypted1 |
| 592 | - |
| 593 | - def test_unicode_validate(self): |
| 594 | - encryptor = getUtility(IPasswordEncryptor) |
| 595 | - self.assertEqual(encryptor.validate( |
| 596 | - u'motorhead', u'+uSsxIfQDRUxG1oDTu1SsQN0P0RTl4SL9XRd'), True) |
| 597 | - |
| 598 | - def test_nonunicode_password(self): |
| 599 | - encryptor = getUtility(IPasswordEncryptor) |
| 600 | - try: |
| 601 | - encryptor.encrypt(u'motorhead\xc3\xb3') |
| 602 | - except UnicodeEncodeError: |
| 603 | - pass |
| 604 | - else: |
| 605 | - self.fail("uncaught non-ascii text") |
| 606 | |
| 607 | === modified file 'lib/lp/testing/factory.py' |
| 608 | --- lib/lp/testing/factory.py 2012-01-17 14:14:49 +0000 |
| 609 | +++ lib/lp/testing/factory.py 2012-01-18 07:40:38 +0000 |
| 610 | @@ -529,20 +529,16 @@ |
| 611 | |
| 612 | @with_celebrity_logged_in('admin') |
| 613 | def makeAdministrator(self, name=None, email=None, password=None): |
| 614 | - user = self.makePerson(name=name, |
| 615 | - email=email, |
| 616 | - password=password) |
| 617 | + user = self.makePerson(name=name, email=email) |
| 618 | administrators = getUtility(ILaunchpadCelebrities).admin |
| 619 | administrators.addMember(user, administrators.teamowner) |
| 620 | return user |
| 621 | |
| 622 | def makeRegistryExpert(self, name=None, email='expert@example.com', |
| 623 | - password='test'): |
| 624 | + password=None): |
| 625 | from lp.testing.sampledata import ADMIN_EMAIL |
| 626 | login(ADMIN_EMAIL) |
| 627 | - user = self.makePerson(name=name, |
| 628 | - email=email, |
| 629 | - password=password) |
| 630 | + user = self.makePerson(name=name, email=email) |
| 631 | registry_team = getUtility(ILaunchpadCelebrities).registry_experts |
| 632 | registry_team.addMember(user, registry_team.teamowner) |
| 633 | return user |
| 634 | @@ -561,14 +557,12 @@ |
| 635 | pocket) |
| 636 | return ProxyFactory(location) |
| 637 | |
| 638 | - def makeAccount(self, displayname=None, password=None, |
| 639 | - status=AccountStatus.ACTIVE, |
| 640 | + def makeAccount(self, displayname=None, status=AccountStatus.ACTIVE, |
| 641 | rationale=AccountCreationRationale.UNKNOWN): |
| 642 | """Create and return a new Account.""" |
| 643 | if displayname is None: |
| 644 | displayname = self.getUniqueString('displayname') |
| 645 | - account = getUtility(IAccountSet).new( |
| 646 | - rationale, displayname, password=password) |
| 647 | + account = getUtility(IAccountSet).new(rationale, displayname) |
| 648 | removeSecurityProxy(account).status = status |
| 649 | self.makeOpenIdIdentifier(account) |
| 650 | return account |
| 651 | @@ -612,10 +606,7 @@ |
| 652 | |
| 653 | :param email: The email address for the new person. |
| 654 | :param name: The name for the new person. |
| 655 | - :param password: The password for the person. |
| 656 | - This password can be used in setupBrowser in combination |
| 657 | - with the email address to create a browser for this new |
| 658 | - person. |
| 659 | + :param password: Ignored. |
| 660 | :param email_address_status: If specified, the status of the email |
| 661 | address is set to the email_address_status. |
| 662 | :param displayname: The display name to use for the person. |
| 663 | @@ -630,20 +621,15 @@ |
| 664 | email = self.getUniqueEmailAddress() |
| 665 | if name is None: |
| 666 | name = self.getUniqueString('person-name') |
| 667 | - if password is None: |
| 668 | - password = self.getUniqueString('password') |
| 669 | # By default, make the email address preferred. |
| 670 | if (email_address_status is None |
| 671 | or email_address_status == EmailAddressStatus.VALIDATED): |
| 672 | email_address_status = EmailAddressStatus.PREFERRED |
| 673 | - # Set the password to test in order to allow people that have |
| 674 | - # been created this way can be logged in. |
| 675 | person, email = getUtility(IPersonSet).createPersonAndEmail( |
| 676 | email, rationale=PersonCreationRationale.UNKNOWN, name=name, |
| 677 | - password=password, displayname=displayname, |
| 678 | + displayname=displayname, |
| 679 | hide_email_addresses=hide_email_addresses) |
| 680 | naked_person = removeSecurityProxy(person) |
| 681 | - naked_person._password_cleartext_cached = password |
| 682 | if homepage_content is not None: |
| 683 | naked_person.homepage_content = homepage_content |
| 684 | |
| 685 | @@ -721,8 +707,7 @@ |
| 686 | # setPreferredEmail no longer activates the account |
| 687 | # automatically. |
| 688 | account = IMasterStore(Account).get(Account, person.accountID) |
| 689 | - account.reactivate( |
| 690 | - "Activated by factory.makePersonByName", password='foo') |
| 691 | + account.reactivate("Activated by factory.makePersonByName") |
| 692 | person.setPreferredEmail(email) |
| 693 | |
| 694 | if not use_default_autosubscribe_policy: |

As you say, good riddance.