[Checkins] SVN: z3c.password/trunk/ merge branch adamg-tooManyLoginFailures

Adam Groszer agroszer at gmail.com
Fri Jan 29 12:28:55 EST 2010


Log message for revision 108644:
  merge branch adamg-tooManyLoginFailures

Changed:
  U   z3c.password/trunk/CHANGES.txt
  U   z3c.password/trunk/setup.py
  U   z3c.password/trunk/src/z3c/password/README.txt
  U   z3c.password/trunk/src/z3c/password/interfaces.py
  U   z3c.password/trunk/src/z3c/password/locales/de/LC_MESSAGES/z3c.password.mo
  U   z3c.password/trunk/src/z3c/password/locales/de/LC_MESSAGES/z3c.password.po
  U   z3c.password/trunk/src/z3c/password/locales/ru/LC_MESSAGES/z3c.password.mo
  U   z3c.password/trunk/src/z3c/password/locales/ru/LC_MESSAGES/z3c.password.po
  U   z3c.password/trunk/src/z3c/password/locales/z3c.password.pot
  U   z3c.password/trunk/src/z3c/password/password.py
  U   z3c.password/trunk/src/z3c/password/principal.py
  U   z3c.password/trunk/src/z3c/password/principal.txt
  U   z3c.password/trunk/src/z3c/password/testing.py

-=-
Modified: z3c.password/trunk/CHANGES.txt
===================================================================
--- z3c.password/trunk/CHANGES.txt	2010-01-29 16:30:50 UTC (rev 108643)
+++ z3c.password/trunk/CHANGES.txt	2010-01-29 17:28:55 UTC (rev 108644)
@@ -5,8 +5,13 @@
 0.8.0 (unreleased)
 ------------------
 
-- ...
+- Feature: ``failedAttemptCheck``
+  * increment failedAttempts on all/any request (this is the default)
+  * increment failedAttempts only on non-resource requests
+  * increment failedAttempts only on POST requests
 
+- Feature: more specific exceptions on new password verification.
+
 0.7.4 (2009-12-22)
 ------------------
 

Modified: z3c.password/trunk/setup.py
===================================================================
--- z3c.password/trunk/setup.py	2010-01-29 16:30:50 UTC (rev 108643)
+++ z3c.password/trunk/setup.py	2010-01-29 17:28:55 UTC (rev 108644)
@@ -64,6 +64,7 @@
         'zope.i18nmessageid',
         'zope.interface',
         'zope.schema',
+        'zope.security',
         ],
     zip_safe = False,
 )
\ No newline at end of file

Modified: z3c.password/trunk/src/z3c/password/README.txt
===================================================================
--- z3c.password/trunk/src/z3c/password/README.txt	2010-01-29 16:30:50 UTC (rev 108643)
+++ z3c.password/trunk/src/z3c/password/README.txt	2010-01-29 17:28:55 UTC (rev 108644)
@@ -167,12 +167,12 @@
   >>> pwd.verify('FOOBAR123')
   Traceback (most recent call last):
   ...
-  TooFewGroupCharacters
+  TooFewGroupCharactersLowerLetter
 
   >>> pwd.verify('foobAR123')
   Traceback (most recent call last):
   ...
-  TooFewGroupCharacters
+  TooFewGroupCharactersLowerLetter
 
   >>> pwd.verify('foobaR123')
 
@@ -191,12 +191,12 @@
   >>> pwd.verify('foobar123')
   Traceback (most recent call last):
   ...
-  TooFewGroupCharacters
+  TooFewGroupCharactersUpperLetter
 
   >>> pwd.verify('FOOBar123')
   Traceback (most recent call last):
   ...
-  TooFewGroupCharacters
+  TooFewGroupCharactersUpperLetter
 
   >>> pwd.verify('fOOBAR123')
 
@@ -215,12 +215,12 @@
   >>> pwd.verify('foobar123')
   Traceback (most recent call last):
   ...
-  TooFewGroupCharacters
+  TooFewGroupCharactersDigits
 
   >>> pwd.verify('FOOBa1234')
   Traceback (most recent call last):
   ...
-  TooFewGroupCharacters
+  TooFewGroupCharactersDigits
 
   >>> pwd.verify('fOBA12345')
 
@@ -239,12 +239,12 @@
   >>> pwd.verify('foo(bar)')
   Traceback (most recent call last):
   ...
-  TooFewGroupCharacters
+  TooFewGroupCharactersSpecials
 
   >>> pwd.verify('FO.#(Ba1)')
   Traceback (most recent call last):
   ...
-  TooFewGroupCharacters
+  TooFewGroupCharactersSpecials
 
   >>> pwd.verify('fO.,;()5')
 
@@ -262,12 +262,12 @@
   >>> pwd.verify('foobar'+unichr(0x0c3)+unichr(0x0c4))
   Traceback (most recent call last):
   ...
-  TooFewGroupCharacters
+  TooFewGroupCharactersOthers
 
   >>> pwd.verify('foobar'+unichr(0x0c3)+unichr(0x0c4)+unichr(0x0e1))
   Traceback (most recent call last):
   ...
-  TooFewGroupCharacters
+  TooFewGroupCharactersOthers
 
   >>> pwd.verify('fOO'+unichr(0x0e1)*5)
 

Modified: z3c.password/trunk/src/z3c/password/interfaces.py
===================================================================
--- z3c.password/trunk/src/z3c/password/interfaces.py	2010-01-29 16:30:50 UTC (rev 108643)
+++ z3c.password/trunk/src/z3c/password/interfaces.py	2010-01-29 17:28:55 UTC (rev 108644)
@@ -43,6 +43,25 @@
 class TooFewGroupCharacters(InvalidPassword):
     __doc__ = _('''Password does not contain enough characters of one group.''')
 
+class TooFewGroupCharactersLowerLetter(TooFewGroupCharacters):
+    __doc__ = _(
+        '''Password does not contain enough characters of lowercase letters.''')
+
+class TooFewGroupCharactersUpperLetter(TooFewGroupCharacters):
+    __doc__ = _(
+        '''Password does not contain enough characters of uppercase letters.''')
+
+class TooFewGroupCharactersDigits(TooFewGroupCharacters):
+    __doc__ = _('''Password does not contain enough characters of digits.''')
+
+class TooFewGroupCharactersSpecials(TooFewGroupCharacters):
+    __doc__ = _(
+        '''Password does not contain enough characters of special characters.''')
+
+class TooFewGroupCharactersOthers(TooFewGroupCharacters):
+    __doc__ = _(
+        '''Password does not contain enough characters of other characters.''')
+
 class TooFewUniqueCharacters(InvalidPassword):
     __doc__ = _('''Password does not contain enough unique characters.''')
 
@@ -70,6 +89,10 @@
         self.principal = principal
         Exception.__init__(self, self.__doc__)
 
+TML_CHECK_ALL = None
+TML_CHECK_NONRESOURCE = 'nonres'
+TML_CHECK_POSTONLY = 'post'
+
 class AccountLocked(Exception):
     __doc__ = _('The account is locked, because the password was '
                 'entered incorrectly too often.')
@@ -302,6 +325,14 @@
         required=False,
         default=None)
 
+    failedAttemptCheck = zope.schema.Choice(
+        title=_(u'Failed password check method'),
+        description=_(u'Failed password check method. '
+                      'All requests, non-reqource requests, POST requests.'),
+        required=False,
+        values=[TML_CHECK_ALL, TML_CHECK_NONRESOURCE, TML_CHECK_POSTONLY],
+        default=TML_CHECK_ALL )
+
     disallowPasswordReuse = zope.schema.Bool(
         title=_(u'Disallow Password Reuse'),
         description=_(u'Do not allow to set a previously set password again.'),

Modified: z3c.password/trunk/src/z3c/password/locales/de/LC_MESSAGES/z3c.password.mo
===================================================================
(Binary files differ)

Modified: z3c.password/trunk/src/z3c/password/locales/de/LC_MESSAGES/z3c.password.po
===================================================================
--- z3c.password/trunk/src/z3c/password/locales/de/LC_MESSAGES/z3c.password.po	2010-01-29 16:30:50 UTC (rev 108643)
+++ z3c.password/trunk/src/z3c/password/locales/de/LC_MESSAGES/z3c.password.po	2010-01-29 17:28:55 UTC (rev 108644)
@@ -11,193 +11,242 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
+# #############################################################################
 # Stephan Richter <stephan.richter at tufts.edu>, 2006.
-# #############################################################################
+# Adam Groszer <agroszer at gmail.com>, 2010.
 msgid ""
 msgstr ""
 "Project-Id-Version: z3c.password\n"
-"POT-Creation-Date: Thu Aug  6 16:10:14 2009\n"
-"PO-Revision-Date: 2009-08-06 18:59+0100\n"
-"Last-Translator: Michael Laubacher <michael.laubacher at refline.ch>\n"
-"Language-Team: German <zope3-dev at zope.org>\n"
+"POT-Creation-Date: Fri Jan 29 17:24:33 2010\n"
+"PO-Revision-Date: 2010-01-29 17:26+0100\n"
+"Last-Translator: Adam Groszer <agroszer at gmail.com>\n"
+"Language-Team: American English <>\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: zope/app/locales/extract.py\n"
-"X-Generator: KBabel 1.11.4\n"
+"X-Generator: Lokalize 1.0\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#: src/z3c/password/interfaces.py:122
+#: z3c/password/interfaces.py:114
+msgid "Description"
+msgstr "Beschreibung"
+
+#: z3c/password/interfaces.py:115
+msgid "A description of the password utility."
+msgstr "Eine Beschreibung des Passwortdienstes."
+
+#: z3c/password/interfaces.py:145
 msgid "Minimum Length"
 msgstr "Minimale Länge"
 
-#: src/z3c/password/interfaces.py:123
+#: z3c/password/interfaces.py:146
 msgid "The minimum length of the password."
 msgstr "Die minimale Länge des Passwortes."
 
-#: src/z3c/password/interfaces.py:128
+#: z3c/password/interfaces.py:151
 msgid "Maximum Length"
 msgstr "Maximale Länge"
 
-#: src/z3c/password/interfaces.py:129
+#: z3c/password/interfaces.py:152
 msgid "The maximum length of the password."
 msgstr "Die maximale Länge des Passwortes."
 
-#: src/z3c/password/interfaces.py:141
+#: z3c/password/interfaces.py:164
 msgid "Maximum Characters of Group"
 msgstr "Maximale Zeichen einer Gruppe"
 
-#: src/z3c/password/interfaces.py:142
-msgid "The maximum amount of characters that a password can have from one group. The groups are: digits, letters, punctuation."
-msgstr "Die maximale Anzahl von Zeichen welche das Passwort von einer Gruppe enthalten darf. Die Gruppen sind: Zahlen, Buchstaben, Zeichen."
+#: z3c/password/interfaces.py:165
+msgid ""
+"The maximum amount of characters that a password can have from one group. "
+"The groups are: digits, letters, punctuation."
+msgstr ""
+"Die maximale Anzahl von Zeichen welche das Passwort von einer Gruppe "
+"enthalten darf. Die Gruppen sind: Zahlen, Buchstaben, Zeichen."
 
-#: src/z3c/password/interfaces.py:149
+#: z3c/password/interfaces.py:172
 msgid "Old/New Similarity"
 msgstr "Alt/Neu Ähnlichkeit"
 
-#: src/z3c/password/interfaces.py:155
+#: z3c/password/interfaces.py:178
 msgid "Minimum Number of Lowercase letters"
 msgstr "Die minimale Anzahl Kleinbuchstaben"
 
-#: src/z3c/password/interfaces.py:156
+#: z3c/password/interfaces.py:179
 msgid "The minimum amount of lowercase letters that a password must have."
 msgstr "Die minimale Anzahl Kleinbuchstaben"
 
-#: src/z3c/password/interfaces.py:162
+#: z3c/password/interfaces.py:185
 msgid "Minimum Number of Uppercase letters"
 msgstr "Die minimale Anzahl Grossbuchstaben"
 
-#: src/z3c/password/interfaces.py:163
+#: z3c/password/interfaces.py:186
 msgid "The minimum amount of uppercase letters that a password must have."
 msgstr "Die minimale Anzahl Grossbuchstaben"
 
-#: src/z3c/password/interfaces.py:169
+#: z3c/password/interfaces.py:192
 msgid "Minimum Number of Numeric digits"
 msgstr "Die minimale Anzahl unterschiedlicher Zahlen"
 
-#: src/z3c/password/interfaces.py:170
+#: z3c/password/interfaces.py:193
 msgid "The minimum amount of numeric digits that a password must have."
 msgstr "Die minimale Menge Nummern, die ein Passwort haben muss."
 
-#: src/z3c/password/interfaces.py:176
+#: z3c/password/interfaces.py:199
 msgid "Minimum Number of Special characters"
 msgstr "Die minimale Anzahl unterschiedlicher Sonderzeichen"
 
-#: src/z3c/password/interfaces.py:177
+#: z3c/password/interfaces.py:200
 msgid "The minimum amount of special characters that a password must have."
 msgstr "Die minimale Anzahl unterschiedlicher Sonderzeichen"
 
-#: src/z3c/password/interfaces.py:184
+#: z3c/password/interfaces.py:207
 msgid "Minimum Number of Other characters"
 msgstr "Die minimale Anzahl unterschiedlicher Zeichen"
 
-#: src/z3c/password/interfaces.py:185
+#: z3c/password/interfaces.py:208
 msgid "The minimum amount of other characters that a password must have."
-msgstr "Die minimale Menge unterschiedlicher Zeichen, die ein Passwort enthalten muss."
+msgstr ""
+"Die minimale Menge unterschiedlicher Zeichen, die ein Passwort enthalten "
+"muss."
 
-#: src/z3c/password/interfaces.py:236
+#: z3c/password/interfaces.py:259
 msgid "Minimum Number of Unique letters"
 msgstr "Die minimale Anzahl unterschiedlicher Buchstaben"
 
-#: src/z3c/password/interfaces.py:237
-msgid "The minimum amount of unique letters that a password must have. This is against passwords like `aAaA0000`. All characters taken lowercase."
+#: z3c/password/interfaces.py:260
+msgid ""
+"The minimum amount of unique letters that a password must have. This is "
+"against passwords like `aAaA0000`. All characters taken lowercase."
 msgstr "Die minimale Anzahl unterschiedlicher Buchstaben"
 
-#: src/z3c/password/interfaces.py:253
+#: z3c/password/interfaces.py:276
 msgid "Minimum Number of Unique characters"
 msgstr "Die minimale Anzahl unterschiedlicher Zeichen"
 
-#: src/z3c/password/interfaces.py:254
-msgid "The minimum amount of unique characters that a password must have. This is against passwords like `aAaA0000`. All characters taken lowercase."
+#: z3c/password/interfaces.py:277
+msgid ""
+"The minimum amount of unique characters that a password must have. This is "
+"against passwords like `aAaA0000`. All characters taken lowercase."
 msgstr "Die minimale Anzahl unterschiedlicher Zeichen"
 
-#: src/z3c/password/interfaces.py:279
-#: src/z3c/password/interfaces.py:280
+#: z3c/password/interfaces.py:29
+msgid "No new password specified."
+msgstr "Es wurde kein neues Passwort eingegeben."
+
+#: z3c/password/interfaces.py:302 z3c/password/interfaces.py:303
 msgid "Password must be changed on next login"
 msgstr "Das Passwort muss beim nächsten Login geändert werden."
 
-#: src/z3c/password/interfaces.py:285
-#: src/z3c/password/interfaces.py:286
+#: z3c/password/interfaces.py:308 z3c/password/interfaces.py:309
 msgid "Password expires after (days)"
 msgstr "Das Passwort läuft aus nach (Tage)"
 
-#: src/z3c/password/interfaces.py:29
-msgid "No new password specified."
-msgstr "Es wurde kein neues Passwort eingegeben."
-
-#: src/z3c/password/interfaces.py:291
+#: z3c/password/interfaces.py:314
 msgid "Lockout period (minutes)"
 msgstr "Dauer Passwortsperrung (Minuten)"
 
-#: src/z3c/password/interfaces.py:292
-msgid "Lockout the user after too many failed password entriesfor this many minutes. The user can try again after."
-msgstr "Der Benutzer wird nach zu vielen Fehlversuchen gesperrt und kann nach dieser Anzahl Minuten nochmals versuchen."
+#: z3c/password/interfaces.py:315
+msgid ""
+"Lockout the user after too many failed password entriesfor this many "
+"minutes. The user can try again after."
+msgstr ""
+"Der Benutzer wird nach zu vielen Fehlversuchen gesperrt und kann nach dieser "
+"Anzahl Minuten nochmals versuchen."
 
-#: src/z3c/password/interfaces.py:298
+#: z3c/password/interfaces.py:32
+msgid "Password is too short."
+msgstr "Passwort ist zu kurz."
+
+#: z3c/password/interfaces.py:321
 msgid "Max. number of failed password entries before account is locked"
 msgstr "Gibt die Anzahl Fehlversuche an, bevor das Passwort gesperrt wird."
 
-#: src/z3c/password/interfaces.py:299
-msgid "Specifies the amount of failed attempts allowed to check the password before the password is locked and no new password can be provided."
+#: z3c/password/interfaces.py:322
+msgid ""
+"Specifies the amount of failed attempts allowed to check the password before "
+"the password is locked and no new password can be provided."
 msgstr "Gibt die Anzahl Fehlversuche an, bevor das Passwort gesperrt wird."
 
-#: src/z3c/password/interfaces.py:306
+#: z3c/password/interfaces.py:329
+msgid "Failed password check method"
+msgstr ""
+
+#: z3c/password/interfaces.py:330
+msgid ""
+"Failed password check method. All requests, non-reqource requests, POST "
+"requests."
+msgstr ""
+
+#: z3c/password/interfaces.py:337
 msgid "Disallow Password Reuse"
 msgstr "Erlaubt nicht ein bisheriges Passwort wiederzuverwenden."
 
-#: src/z3c/password/interfaces.py:307
+#: z3c/password/interfaces.py:338
 msgid "Do not allow to set a previously set password again."
 msgstr "Erlaubt nicht ein bisheriges Passwort wiederzuverwenden."
 
-#: src/z3c/password/interfaces.py:32
-msgid "Password is too short."
-msgstr "Passwort ist zu kurz."
-
-#: src/z3c/password/interfaces.py:35
+#: z3c/password/interfaces.py:35
 msgid "Password is too long."
 msgstr "Passwort ist zu lang."
 
-#: src/z3c/password/interfaces.py:38
+#: z3c/password/interfaces.py:38
 msgid "Password is too similar to old one."
 msgstr "Das Passwort ist gleich oder zu ähnlich wie das bisherige."
 
-#: src/z3c/password/interfaces.py:41
+#: z3c/password/interfaces.py:41
 msgid "Password contains too many characters of one group."
 msgstr "Passwort enthält zu viele Zeichen einer Gruppe."
 
-#: src/z3c/password/interfaces.py:44
+#: z3c/password/interfaces.py:44
 msgid "Password does not contain enough characters of one group."
 msgstr "Das Passwort besteht nicht aus genügend Zeichen einer Zeichen-Gruppe."
 
-#: src/z3c/password/interfaces.py:47
+#: z3c/password/interfaces.py:47
+msgid "Password does not contain enough characters of lowercase letters."
+msgstr "Das Passwort besteht nicht aus genügend Kleinbuchstaben."
+
+#: z3c/password/interfaces.py:51
+msgid "Password does not contain enough characters of uppercase letters."
+msgstr "Das Passwort besteht nicht aus genügend Zeichen Grossbuchstaben."
+
+#: z3c/password/interfaces.py:55
+msgid "Password does not contain enough characters of digits."
+msgstr "Das Passwort besteht nicht aus genügend Zahlen."
+
+#: z3c/password/interfaces.py:58
+msgid "Password does not contain enough characters of special characters."
+msgstr "Das Passwort besteht nicht aus genügend Sonderzeichen."
+
+#: z3c/password/interfaces.py:62
+msgid "Password does not contain enough characters of other characters."
+msgstr "Das Passwort besteht nicht aus genügend andere Zeichen."
+
+#: z3c/password/interfaces.py:66
 msgid "Password does not contain enough unique characters."
 msgstr "Das Passwort besteht nicht aus genügend unterschiedlichen Zeichen."
 
-#: src/z3c/password/interfaces.py:50
+#: z3c/password/interfaces.py:69
 msgid "Password does not contain enough unique letters."
 msgstr "Das Passwort besteht nicht aus genügend unterschiedlichen Zeichen."
 
-#: src/z3c/password/interfaces.py:53
+#: z3c/password/interfaces.py:72
 msgid "The password has expired."
 msgstr "Das Passwort ist abgelaufen."
 
-#: src/z3c/password/interfaces.py:60
+#: z3c/password/interfaces.py:79
 msgid "The password set was already used before."
 msgstr "Dieses Passwort wurde bereits verwendet."
 
-#: src/z3c/password/interfaces.py:67
+#: z3c/password/interfaces.py:86
 msgid "The password was entered incorrectly too often."
 msgstr "Das Passwort wurde zu oft falsch eingegeben."
 
-#: src/z3c/password/interfaces.py:74
-msgid "The account is locked, because the password was entered incorrectly too often."
-msgstr "Das Passwort wurde zu häufig falsch eingegeben. Der Benutzer wurde gesperrt und kann vom Administrator wieder freigegeben werden."
+#: z3c/password/interfaces.py:97
+msgid ""
+"The account is locked, because the password was entered incorrectly too "
+"often."
+msgstr ""
+"Das Passwort wurde zu häufig falsch eingegeben. Der Benutzer wurde gesperrt "
+"und kann vom Administrator wieder freigegeben werden."
 
-#: src/z3c/password/interfaces.py:91
-msgid "Description"
-msgstr "Beschreibung"
-
-#: src/z3c/password/interfaces.py:92
-msgid "A description of the password utility."
-msgstr "Eine Beschreibung des Passwortdienstes."
-

Modified: z3c.password/trunk/src/z3c/password/locales/ru/LC_MESSAGES/z3c.password.mo
===================================================================
(Binary files differ)

Modified: z3c.password/trunk/src/z3c/password/locales/ru/LC_MESSAGES/z3c.password.po
===================================================================
--- z3c.password/trunk/src/z3c/password/locales/ru/LC_MESSAGES/z3c.password.po	2010-01-29 16:30:50 UTC (rev 108643)
+++ z3c.password/trunk/src/z3c/password/locales/ru/LC_MESSAGES/z3c.password.po	2010-01-29 17:28:55 UTC (rev 108644)
@@ -1,4 +1,4 @@
-##############################################################################
+# #############################################################################
 #
 # Copyright (c) 2003-2004 Zope Corporation and Contributors.
 # All Rights Reserved.
@@ -10,11 +10,11 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
-##############################################################################
+# #############################################################################
 msgid ""
 msgstr ""
 "Project-Id-Version: Development/Revision: 70761\n"
-"POT-Creation-Date: Tue Oct 17 23:25:44 2006\n"
+"POT-Creation-Date: Fri Jan 29 17:24:33 2010\n"
 "PO-Revision-Date: 2008-10-23 10:33+0300\n"
 "Last-Translator: Dan Korostelev <nadako at gmail.com>\n"
 "Language-Team: Zope 3 Developers <zope3-dev at zope.org>\n"
@@ -23,67 +23,229 @@
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: zope/app/locales/extract.py\n"
 
-#: src/z3c/password/interfaces.py:104
+#: z3c/password/interfaces.py:114
+msgid "Description"
+msgstr "Описание"
+
+#: z3c/password/interfaces.py:115
+msgid "A description of the password utility."
+msgstr "Описание утилиты паролей."
+
+#: z3c/password/interfaces.py:145
+msgid "Minimum Length"
+msgstr "Минимальная длина"
+
+#: z3c/password/interfaces.py:146
+msgid "The minimum length of the password."
+msgstr "Минимальная длина пароля."
+
+#: z3c/password/interfaces.py:151
 msgid "Maximum Length"
 msgstr "Максимальная длина"
 
-#: src/z3c/password/interfaces.py:105
+#: z3c/password/interfaces.py:152
 msgid "The maximum length of the password."
 msgstr "Максимальная длина пароля."
 
-#: src/z3c/password/interfaces.py:117
+#: z3c/password/interfaces.py:164
 msgid "Maximum Characters of Group"
 msgstr "Максимум символов группы"
 
-#: src/z3c/password/interfaces.py:118
-msgid "The maximum amount of characters that a password can have from one group. The groups are: digits, letters, punctuation."
-msgstr "Максимальное количество символов одной группы, которое может содержать пароль. Группы - это цифры, буквы, знаки пунктуации."
+#: z3c/password/interfaces.py:165
+msgid ""
+"The maximum amount of characters that a password can have from one group. "
+"The groups are: digits, letters, punctuation."
+msgstr ""
+"Максимальное количество символов одной группы, которое может содержать "
+"пароль. Группы - это цифры, буквы, знаки пунктуации."
 
-#: src/z3c/password/interfaces.py:125
+#: z3c/password/interfaces.py:172
 msgid "Old/New Similarity"
 msgstr "Схожесть нового и старого паролей"
 
-#: src/z3c/password/interfaces.py:29
+#: z3c/password/interfaces.py:178
+msgid "Minimum Number of Lowercase letters"
+msgstr ""
+
+#: z3c/password/interfaces.py:179
+msgid "The minimum amount of lowercase letters that a password must have."
+msgstr ""
+
+#: z3c/password/interfaces.py:185
+msgid "Minimum Number of Uppercase letters"
+msgstr ""
+
+#: z3c/password/interfaces.py:186
+msgid "The minimum amount of uppercase letters that a password must have."
+msgstr ""
+
+#: z3c/password/interfaces.py:192
+msgid "Minimum Number of Numeric digits"
+msgstr ""
+
+#: z3c/password/interfaces.py:193
+#, fuzzy
+msgid "The minimum amount of numeric digits that a password must have."
+msgstr "Минимальная длина пароля."
+
+#: z3c/password/interfaces.py:199
+msgid "Minimum Number of Special characters"
+msgstr ""
+
+#: z3c/password/interfaces.py:200
+msgid "The minimum amount of special characters that a password must have."
+msgstr ""
+
+#: z3c/password/interfaces.py:207
+msgid "Minimum Number of Other characters"
+msgstr ""
+
+#: z3c/password/interfaces.py:208
+#, fuzzy
+msgid "The minimum amount of other characters that a password must have."
+msgstr "Минимальная длина пароля."
+
+#: z3c/password/interfaces.py:259
+msgid "Minimum Number of Unique letters"
+msgstr ""
+
+#: z3c/password/interfaces.py:260
+msgid ""
+"The minimum amount of unique letters that a password must have. This is "
+"against passwords like `aAaA0000`. All characters taken lowercase."
+msgstr ""
+
+#: z3c/password/interfaces.py:276
+msgid "Minimum Number of Unique characters"
+msgstr ""
+
+#: z3c/password/interfaces.py:277
+msgid ""
+"The minimum amount of unique characters that a password must have. This is "
+"against passwords like `aAaA0000`. All characters taken lowercase."
+msgstr ""
+
+#: z3c/password/interfaces.py:29
 msgid "No new password specified."
 msgstr "Новый пароль не указан."
 
-#: src/z3c/password/interfaces.py:32
+#: z3c/password/interfaces.py:302 z3c/password/interfaces.py:303
+msgid "Password must be changed on next login"
+msgstr ""
+
+#: z3c/password/interfaces.py:308 z3c/password/interfaces.py:309
+msgid "Password expires after (days)"
+msgstr ""
+
+#: z3c/password/interfaces.py:314
+msgid "Lockout period (minutes)"
+msgstr ""
+
+#: z3c/password/interfaces.py:315
+msgid ""
+"Lockout the user after too many failed password entriesfor this many "
+"minutes. The user can try again after."
+msgstr ""
+
+#: z3c/password/interfaces.py:32
 msgid "Password is too short."
 msgstr "Пароль слишком короткий."
 
-#: src/z3c/password/interfaces.py:35
+#: z3c/password/interfaces.py:321
+msgid "Max. number of failed password entries before account is locked"
+msgstr ""
+
+#: z3c/password/interfaces.py:322
+msgid ""
+"Specifies the amount of failed attempts allowed to check the password before "
+"the password is locked and no new password can be provided."
+msgstr ""
+
+#: z3c/password/interfaces.py:329
+msgid "Failed password check method"
+msgstr ""
+
+#: z3c/password/interfaces.py:330
+msgid ""
+"Failed password check method. All requests, non-reqource requests, POST "
+"requests."
+msgstr ""
+
+#: z3c/password/interfaces.py:337
+msgid "Disallow Password Reuse"
+msgstr ""
+
+#: z3c/password/interfaces.py:338
+msgid "Do not allow to set a previously set password again."
+msgstr ""
+
+#: z3c/password/interfaces.py:35
 msgid "Password is too long."
 msgstr "Пароль слишком длинный."
 
-#: src/z3c/password/interfaces.py:38
+#: z3c/password/interfaces.py:38
 msgid "Password is too similar to old one."
 msgstr "Пароль слишком похож на старый."
 
-#: src/z3c/password/interfaces.py:41
+#: z3c/password/interfaces.py:41
 msgid "Password contains too many characters of one group."
 msgstr "Пароль содержит слишком много символов одной группы."
 
-#: src/z3c/password/interfaces.py:44
+#: z3c/password/interfaces.py:44
+#, fuzzy
+msgid "Password does not contain enough characters of one group."
+msgstr "Пароль содержит слишком много символов одной группы."
+
+#: z3c/password/interfaces.py:47
+#, fuzzy
+msgid "Password does not contain enough characters of lowercase letters."
+msgstr "Пароль содержит слишком много символов одной группы."
+
+#: z3c/password/interfaces.py:51
+#, fuzzy
+msgid "Password does not contain enough characters of uppercase letters."
+msgstr "Пароль содержит слишком много символов одной группы."
+
+#: z3c/password/interfaces.py:55
+#, fuzzy
+msgid "Password does not contain enough characters of digits."
+msgstr "Пароль содержит слишком много символов одной группы."
+
+#: z3c/password/interfaces.py:58
+#, fuzzy
+msgid "Password does not contain enough characters of special characters."
+msgstr "Пароль содержит слишком много символов одной группы."
+
+#: z3c/password/interfaces.py:62
+#, fuzzy
+msgid "Password does not contain enough characters of other characters."
+msgstr "Пароль содержит слишком много символов одной группы."
+
+#: z3c/password/interfaces.py:66
+#, fuzzy
+msgid "Password does not contain enough unique characters."
+msgstr "Пароль содержит слишком много символов одной группы."
+
+#: z3c/password/interfaces.py:69
+msgid "Password does not contain enough unique letters."
+msgstr ""
+
+#: z3c/password/interfaces.py:72
 msgid "The password has expired."
 msgstr "Время действия пароля истекло."
 
-#: src/z3c/password/interfaces.py:51
+#: z3c/password/interfaces.py:79
+#, fuzzy
+msgid "The password set was already used before."
+msgstr "Время действия пароля истекло."
+
+#: z3c/password/interfaces.py:86
 msgid "The password was entered incorrectly too often."
 msgstr "Неправильный пароль был введён слишком много раз."
 
-#: src/z3c/password/interfaces.py:67
-msgid "Description"
-msgstr "Описание"
-
-#: src/z3c/password/interfaces.py:68
-msgid "A description of the password utility."
-msgstr "Описание утилиты паролей."
-
-#: src/z3c/password/interfaces.py:98
-msgid "Minimum Length"
-msgstr "Минимальная длина"
-
-#: src/z3c/password/interfaces.py:99
-msgid "The minimum length of the password."
-msgstr "Минимальная длина пароля."
-
+#: z3c/password/interfaces.py:97
+#, fuzzy
+msgid ""
+"The account is locked, because the password was entered incorrectly too "
+"often."
+msgstr "Неправильный пароль был введён слишком много раз."

Modified: z3c.password/trunk/src/z3c/password/locales/z3c.password.pot
===================================================================
--- z3c.password/trunk/src/z3c/password/locales/z3c.password.pot	2010-01-29 16:30:50 UTC (rev 108643)
+++ z3c.password/trunk/src/z3c/password/locales/z3c.password.pot	2010-01-29 17:28:55 UTC (rev 108644)
@@ -14,7 +14,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: Development/Unknown\n"
-"POT-Creation-Date: Thu Aug  6 16:10:14 2009\n"
+"POT-Creation-Date: Fri Jan 29 17:24:33 2010\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
 "Language-Team: Zope 3 Developers <zope3-dev at zope.org>\n"
@@ -23,177 +23,205 @@
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: zope/app/locales/extract.py\n"
 
-#: src/z3c/password/interfaces.py:122
+#: z3c/password/interfaces.py:114
+msgid "Description"
+msgstr ""
+
+#: z3c/password/interfaces.py:115
+msgid "A description of the password utility."
+msgstr ""
+
+#: z3c/password/interfaces.py:145
 msgid "Minimum Length"
 msgstr ""
 
-#: src/z3c/password/interfaces.py:123
+#: z3c/password/interfaces.py:146
 msgid "The minimum length of the password."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:128
+#: z3c/password/interfaces.py:151
 msgid "Maximum Length"
 msgstr ""
 
-#: src/z3c/password/interfaces.py:129
+#: z3c/password/interfaces.py:152
 msgid "The maximum length of the password."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:141
+#: z3c/password/interfaces.py:164
 msgid "Maximum Characters of Group"
 msgstr ""
 
-#: src/z3c/password/interfaces.py:142
+#: z3c/password/interfaces.py:165
 msgid "The maximum amount of characters that a password can have from one group. The groups are: digits, letters, punctuation."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:149
+#: z3c/password/interfaces.py:172
 msgid "Old/New Similarity"
 msgstr ""
 
-#: src/z3c/password/interfaces.py:155
+#: z3c/password/interfaces.py:178
 msgid "Minimum Number of Lowercase letters"
 msgstr ""
 
-#: src/z3c/password/interfaces.py:156
+#: z3c/password/interfaces.py:179
 msgid "The minimum amount of lowercase letters that a password must have."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:162
+#: z3c/password/interfaces.py:185
 msgid "Minimum Number of Uppercase letters"
 msgstr ""
 
-#: src/z3c/password/interfaces.py:163
+#: z3c/password/interfaces.py:186
 msgid "The minimum amount of uppercase letters that a password must have."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:169
+#: z3c/password/interfaces.py:192
 msgid "Minimum Number of Numeric digits"
 msgstr ""
 
-#: src/z3c/password/interfaces.py:170
+#: z3c/password/interfaces.py:193
 msgid "The minimum amount of numeric digits that a password must have."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:176
+#: z3c/password/interfaces.py:199
 msgid "Minimum Number of Special characters"
 msgstr ""
 
-#: src/z3c/password/interfaces.py:177
+#: z3c/password/interfaces.py:200
 msgid "The minimum amount of special characters that a password must have."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:184
+#: z3c/password/interfaces.py:207
 msgid "Minimum Number of Other characters"
 msgstr ""
 
-#: src/z3c/password/interfaces.py:185
+#: z3c/password/interfaces.py:208
 msgid "The minimum amount of other characters that a password must have."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:236
+#: z3c/password/interfaces.py:259
 msgid "Minimum Number of Unique letters"
 msgstr ""
 
-#: src/z3c/password/interfaces.py:237
+#: z3c/password/interfaces.py:260
 msgid "The minimum amount of unique letters that a password must have. This is against passwords like `aAaA0000`. All characters taken lowercase."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:253
+#: z3c/password/interfaces.py:276
 msgid "Minimum Number of Unique characters"
 msgstr ""
 
-#: src/z3c/password/interfaces.py:254
+#: z3c/password/interfaces.py:277
 msgid "The minimum amount of unique characters that a password must have. This is against passwords like `aAaA0000`. All characters taken lowercase."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:279
-#: src/z3c/password/interfaces.py:280
+#: z3c/password/interfaces.py:29
+msgid "No new password specified."
+msgstr ""
+
+#: z3c/password/interfaces.py:302
+#: z3c/password/interfaces.py:303
 msgid "Password must be changed on next login"
 msgstr ""
 
-#: src/z3c/password/interfaces.py:285
-#: src/z3c/password/interfaces.py:286
+#: z3c/password/interfaces.py:308
+#: z3c/password/interfaces.py:309
 msgid "Password expires after (days)"
 msgstr ""
 
-#: src/z3c/password/interfaces.py:29
-msgid "No new password specified."
-msgstr ""
-
-#: src/z3c/password/interfaces.py:291
+#: z3c/password/interfaces.py:314
 msgid "Lockout period (minutes)"
 msgstr ""
 
-#: src/z3c/password/interfaces.py:292
+#: z3c/password/interfaces.py:315
 msgid "Lockout the user after too many failed password entriesfor this many minutes. The user can try again after."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:298
+#: z3c/password/interfaces.py:32
+msgid "Password is too short."
+msgstr ""
+
+#: z3c/password/interfaces.py:321
 msgid "Max. number of failed password entries before account is locked"
 msgstr ""
 
-#: src/z3c/password/interfaces.py:299
+#: z3c/password/interfaces.py:322
 msgid "Specifies the amount of failed attempts allowed to check the password before the password is locked and no new password can be provided."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:306
+#: z3c/password/interfaces.py:329
+msgid "Failed password check method"
+msgstr ""
+
+#: z3c/password/interfaces.py:330
+msgid "Failed password check method. All requests, non-reqource requests, POST requests."
+msgstr ""
+
+#: z3c/password/interfaces.py:337
 msgid "Disallow Password Reuse"
 msgstr ""
 
-#: src/z3c/password/interfaces.py:307
+#: z3c/password/interfaces.py:338
 msgid "Do not allow to set a previously set password again."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:32
-msgid "Password is too short."
-msgstr ""
-
-#: src/z3c/password/interfaces.py:35
+#: z3c/password/interfaces.py:35
 msgid "Password is too long."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:38
+#: z3c/password/interfaces.py:38
 msgid "Password is too similar to old one."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:41
+#: z3c/password/interfaces.py:41
 msgid "Password contains too many characters of one group."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:44
+#: z3c/password/interfaces.py:44
 msgid "Password does not contain enough characters of one group."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:47
+#: z3c/password/interfaces.py:47
+msgid "Password does not contain enough characters of lowercase letters."
+msgstr ""
+
+#: z3c/password/interfaces.py:51
+msgid "Password does not contain enough characters of uppercase letters."
+msgstr ""
+
+#: z3c/password/interfaces.py:55
+msgid "Password does not contain enough characters of digits."
+msgstr ""
+
+#: z3c/password/interfaces.py:58
+msgid "Password does not contain enough characters of special characters."
+msgstr ""
+
+#: z3c/password/interfaces.py:62
+msgid "Password does not contain enough characters of other characters."
+msgstr ""
+
+#: z3c/password/interfaces.py:66
 msgid "Password does not contain enough unique characters."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:50
+#: z3c/password/interfaces.py:69
 msgid "Password does not contain enough unique letters."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:53
+#: z3c/password/interfaces.py:72
 msgid "The password has expired."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:60
+#: z3c/password/interfaces.py:79
 msgid "The password set was already used before."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:67
+#: z3c/password/interfaces.py:86
 msgid "The password was entered incorrectly too often."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:74
+#: z3c/password/interfaces.py:97
 msgid "The account is locked, because the password was entered incorrectly too often."
 msgstr ""
 
-#: src/z3c/password/interfaces.py:91
-msgid "Description"
-msgstr ""
-
-#: src/z3c/password/interfaces.py:92
-msgid "A description of the password utility."
-msgstr ""
-

Modified: z3c.password/trunk/src/z3c/password/password.py
===================================================================
--- z3c.password/trunk/src/z3c/password/password.py	2010-01-29 16:30:50 UTC (rev 108643)
+++ z3c.password/trunk/src/z3c/password/password.py	2010-01-29 17:28:55 UTC (rev 108644)
@@ -146,23 +146,23 @@
 
         if (self.minLowerLetter is not None
             and num_lower_letters < self.minLowerLetter):
-            raise interfaces.TooFewGroupCharacters()
+            raise interfaces.TooFewGroupCharactersLowerLetter()
 
         if (self.minUpperLetter is not None
             and num_upper_letters < self.minUpperLetter):
-            raise interfaces.TooFewGroupCharacters()
+            raise interfaces.TooFewGroupCharactersUpperLetter()
 
         if (self.minDigits is not None
             and num_digits < self.minDigits):
-            raise interfaces.TooFewGroupCharacters()
+            raise interfaces.TooFewGroupCharactersDigits()
 
         if (self.minSpecials is not None
             and num_specials < self.minSpecials):
-            raise interfaces.TooFewGroupCharacters()
+            raise interfaces.TooFewGroupCharactersSpecials()
 
         if (self.minOthers is not None
             and num_others < self.minOthers):
-            raise interfaces.TooFewGroupCharacters()
+            raise interfaces.TooFewGroupCharactersOthers()
 
         if (self.minUniqueCharacters is not None
             and len(uniqueChars) < self.minUniqueCharacters):
@@ -212,13 +212,17 @@
         interfaces.IPasswordOptionsUtility['maxFailedAttempts'])
     disallowPasswordReuse = FieldProperty(
         interfaces.IPasswordOptionsUtility['disallowPasswordReuse'])
+    failedAttemptCheck = FieldProperty(
+        interfaces.IPasswordOptionsUtility['failedAttemptCheck'])
 
     def __init__(self, changePasswordOnNextLogin=None,
                  passwordExpiresAfter=None,
                  lockOutPeriod=None, maxFailedAttempts=None,
-                 disallowPasswordReuse=None):
+                 disallowPasswordReuse=None,
+                 failedAttemptCheck=None):
         self.changePasswordOnNextLogin = changePasswordOnNextLogin
         self.passwordExpiresAfter = passwordExpiresAfter
         self.lockOutPeriod = lockOutPeriod
         self.maxFailedAttempts = maxFailedAttempts
-        self.disallowPasswordReuse = disallowPasswordReuse
\ No newline at end of file
+        self.disallowPasswordReuse = disallowPasswordReuse
+        self.failedAttemptCheck = failedAttemptCheck
\ No newline at end of file

Modified: z3c.password/trunk/src/z3c/password/principal.py
===================================================================
--- z3c.password/trunk/src/z3c/password/principal.py	2010-01-29 16:30:50 UTC (rev 108643)
+++ z3c.password/trunk/src/z3c/password/principal.py	2010-01-29 17:28:55 UTC (rev 108644)
@@ -19,6 +19,8 @@
 import datetime
 import persistent.list
 import zope.component
+from zope.security.management import getInteraction
+
 from z3c.password import interfaces
 
 class PrincipalMixIn(object):
@@ -31,6 +33,7 @@
                              #e.g. for changePasswordOnNextLogin
 
     failedAttempts = 0
+    failedAttemptCheck = interfaces.TML_CHECK_ALL
     maxFailedAttempts = None
     lastFailedAttempt = None
     lockOutPeriod = None
@@ -115,9 +118,7 @@
             add = 0
         else:
             #failed attempt, record it, increase counter
-            self.failedAttempts += 1
-            self.lastFailedAttempt = self.now()
-            add = 1
+            add = self.checkFailedAttempt()
 
         # If the maximum amount of failures has been reached notify the
         # system by raising an error.
@@ -132,6 +133,45 @@
 
         return same
 
+    def _getRequest(self):
+        interaction = getInteraction()
+        try:
+            return interaction.participations[0]
+        except IndexError:
+            return None
+
+    def checkFailedAttempt(self):
+        #failed attempt, record it, increase counter (in case we have to)
+        validRequest = True
+        fac = self._failedAttemptCheck()
+        if fac == interfaces.TML_CHECK_ALL:
+            validRequest = True
+        else:
+            request = self._getRequest()
+            if request is None:
+                validRequest = True
+            else:
+                if fac == interfaces.TML_CHECK_NONRESOURCE:
+                    url = request.getURL()
+                    if '/@@/' in url:
+                        #this is a resource
+                        validRequest = False
+                    else:
+                        validRequest = True
+                elif fac == interfaces.TML_CHECK_POSTONLY:
+                    if request.method == 'POST':
+                        #this is a POST request
+                        validRequest = True
+                    else:
+                        validRequest = False
+
+        if validRequest:
+            self.failedAttempts += 1
+            self.lastFailedAttempt = self.now()
+            return 1
+        else:
+            return 0
+
     def tooManyLoginFailures(self, add = 0):
         attempts = self._maxFailedAttempts()
         #this one needs to be >=, because... data just does not
@@ -199,6 +239,19 @@
             else:
                 return self.lockOutPeriod
 
+    def _failedAttemptCheck(self):
+        if self.failedAttemptCheck is not None:
+            return self.failedAttemptCheck
+
+        options = self._optionsUtility()
+        if options is None:
+            return self.failedAttemptCheck
+        else:
+            if options.failedAttemptCheck is not None:
+                return options.failedAttemptCheck
+            else:
+                return self.failedAttemptCheck
+
     def _maxFailedAttempts(self):
         if self.maxFailedAttempts is not None:
             return self.maxFailedAttempts

Modified: z3c.password/trunk/src/z3c/password/principal.txt
===================================================================
--- z3c.password/trunk/src/z3c/password/principal.txt	2010-01-29 16:30:50 UTC (rev 108643)
+++ z3c.password/trunk/src/z3c/password/principal.txt	2010-01-29 17:28:55 UTC (rev 108644)
@@ -104,6 +104,7 @@
 Let's now create a principal:
 
   >>> from zope.app.authentication import principalfolder
+  >>> from z3c.password import interfaces
   >>> from z3c.password import principal
 
   >>> class MyPrincipal(principal.PrincipalMixIn,
@@ -130,6 +131,9 @@
   >>> user.checkPassword('123123')
   True
 
+failedAttempts
+--------------
+
 Initially, the amount of failed attempts is zero, ...
 
   >>> user.failedAttempts
@@ -180,6 +184,132 @@
 
   >>> user.failedAttempts = 0
 
+
+failedAttemptCheck, non-resource
+---------------------------------
+
+  >>> import zope.security.management
+  >>> from z3c.password import testing
+
+Set the option on the user:
+
+  >>> user.failedAttemptCheck = interfaces.TML_CHECK_NONRESOURCE
+
+Create our dummy request:
+Watch out! this is a request for a resource (/@@/)
+
+  >>> request = testing.TestBrowserRequest('http://localhost/@@/logo.gif')
+  >>> zope.security.management.getInteraction().add(request)
+
+Reset the counter:
+
+  >>> user.failedAttempts = 0
+
+Here's the password checking.
+The password is wrong.
+
+  >>> user.checkPassword('456456')
+  False
+
+But the counter is not incremented.
+
+  >>> user.failedAttempts
+  0
+
+Try a non-resource request.
+
+  >>> zope.security.management.getInteraction().remove(request)
+  >>> request = testing.TestBrowserRequest('http://localhost/loginform.html',
+  ...     'POST')
+  >>> zope.security.management.getInteraction().add(request)
+
+Password is still wrong.
+
+  >>> user.checkPassword('456456')
+  False
+
+But now the counter is incremented.
+
+  >>> user.failedAttempts
+  1
+
+Try now without a request in effect (as an edge case):
+
+  >>> zope.security.management.getInteraction().remove(request)
+
+  >>> user.failedAttempts = 0
+
+A bad password gets counted.
+
+  >>> user.checkPassword('456456')
+  False
+  >>> user.failedAttempts
+  1
+
+failedAttemptCheck, POST
+-------------------------
+
+Set the option on the user:
+
+  >>> user.failedAttemptCheck = interfaces.TML_CHECK_POSTONLY
+
+Create our dummy request:
+Watch out! this is a normal GET request.
+
+  >>> request = testing.TestBrowserRequest('http://localhost/index.html', 'GET')
+  >>> zope.security.management.getInteraction().add(request)
+
+  >>> user.failedAttempts = 0
+
+Here's the password checking.
+The password is wrong.
+
+  >>> user.checkPassword('456456')
+  False
+
+But the counter is not incremented.
+
+  >>> user.failedAttempts
+  0
+
+Try a POST request. What a loginform usually is.
+(Note, that the request gets examined only if the password does not match.)
+
+  >>> zope.security.management.getInteraction().remove(request)
+  >>> request = testing.TestBrowserRequest('http://localhost/loginform.html',
+  ...     'POST')
+  >>> zope.security.management.getInteraction().add(request)
+
+Password is still wrong.
+
+  >>> user.checkPassword('456456')
+  False
+
+But now the counter is incremented.
+
+  >>> user.failedAttempts
+  1
+
+Try now without a request in effect (as an edge case):
+
+  >>> zope.security.management.getInteraction().remove(request)
+
+  >>> user.failedAttempts = 0
+
+A bad password gets counted.
+
+  >>> user.checkPassword('456456')
+  False
+  >>> user.failedAttempts
+  1
+
+Reset the option on the user:
+
+  >>> user.failedAttemptCheck = None
+
+Expired password
+----------------
+
 Next we expire the password:
 
   >>> NOW = datetime.datetime(2009, 6, 14, 13, 0) + datetime.timedelta(181)
@@ -220,7 +350,6 @@
 
   >>> import zope.interface
   >>> import zope.component
-  >>> from z3c.password import interfaces
   >>> from z3c.password.password import PasswordOptionsUtility
   >>> poptions = PasswordOptionsUtility()
   >>> zope.component.provideUtility(poptions)
@@ -400,7 +529,126 @@
   >>> user.checkPassword('234234')
   True
 
+failedAttemptCheck, non-resource
+---------------------------------
 
+Set the option on the utility:
+
+  >>> poptions.failedAttemptCheck = interfaces.TML_CHECK_NONRESOURCE
+
+Create our dummy request:
+Watch out! this is a request for a resource (/@@/)
+
+  >>> request = testing.TestBrowserRequest('http://localhost/@@/logo.gif')
+  >>> zope.security.management.getInteraction().add(request)
+
+Reset the counter:
+
+  >>> user.failedAttempts = 0
+
+Here's the password checking.
+The password is wrong.
+
+  >>> user.checkPassword('456456')
+  False
+
+But the counter is not incremented.
+
+  >>> user.failedAttempts
+  0
+
+Try a non-resource request.
+
+  >>> zope.security.management.getInteraction().remove(request)
+  >>> request = testing.TestBrowserRequest('http://localhost/loginform.html',
+  ...     'POST')
+  >>> zope.security.management.getInteraction().add(request)
+
+Password is still wrong.
+
+  >>> user.checkPassword('456456')
+  False
+
+But now the counter is incremented.
+
+  >>> user.failedAttempts
+  1
+
+Try now without a request in effect (as an edge case):
+
+  >>> zope.security.management.getInteraction().remove(request)
+
+  >>> user.failedAttempts = 0
+
+A bad password gets counted.
+
+  >>> user.checkPassword('456456')
+  False
+  >>> user.failedAttempts
+  1
+
+failedAttemptCheck, POST
+-------------------------
+
+Set the option on the utility:
+
+  >>> poptions.failedAttemptCheck = interfaces.TML_CHECK_POSTONLY
+
+Create our dummy request:
+Watch out! this is a normal GET request.
+
+  >>> request = testing.TestBrowserRequest('http://localhost/index.html', 'GET')
+  >>> zope.security.management.getInteraction().add(request)
+
+  >>> user.failedAttempts = 0
+
+Here's the password checking.
+The password is wrong.
+
+  >>> user.checkPassword('456456')
+  False
+
+But the counter is not incremented.
+
+  >>> user.failedAttempts
+  0
+
+Try a POST request. What a loginform usually is.
+(Note, that the request gets examined only if the password does not match.)
+
+  >>> zope.security.management.getInteraction().remove(request)
+  >>> request = testing.TestBrowserRequest('http://localhost/loginform.html',
+  ...     'POST')
+  >>> zope.security.management.getInteraction().add(request)
+
+Password is still wrong.
+
+  >>> user.checkPassword('456456')
+  False
+
+But now the counter is incremented.
+
+  >>> user.failedAttempts
+  1
+
+Try now without a request in effect (as an edge case):
+
+  >>> zope.security.management.getInteraction().remove(request)
+
+  >>> user.failedAttempts = 0
+
+A bad password gets counted.
+
+  >>> user.checkPassword('456456')
+  False
+  >>> user.failedAttempts
+  1
+
+Reset the option on the utility:
+
+  >>> poptions.failedAttemptCheck = None
+
+
 Timed lockout
 -------------
 

Modified: z3c.password/trunk/src/z3c/password/testing.py
===================================================================
--- z3c.password/trunk/src/z3c/password/testing.py	2010-01-29 16:30:50 UTC (rev 108643)
+++ z3c.password/trunk/src/z3c/password/testing.py	2010-01-29 17:28:55 UTC (rev 108644)
@@ -30,3 +30,15 @@
 
 def tearDown(test):
     placelesssetup.tearDown(test)
+
+
+class TestBrowserRequest():
+    """pretty dumb test request"""
+
+    def __init__(self, url, method='GET'):
+        self.URL = url
+        self.method = method
+        self.interaction = None
+
+    def getURL(self):
+        return self.URL



More information about the checkins mailing list