{ "info": { "author": "Enfold Systems, Inc.", "author_email": "info@enfoldsystems.com", "bugtrack_url": null, "classifiers": [ "Framework :: Plone", "Framework :: Plone :: 4.3", "Framework :: Plone :: 5.0", "Framework :: Plone :: 5.1", "Framework :: Plone :: 5.2", "License :: OSI Approved :: Zope Public License", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.7", "Topic :: Security", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "collective.pwexpiry\n===================\n\nIntroduction\n============\n\nThe ``collective.pwexpiry`` package is an add-on Product for Plone that brings the\nfeature of controlling the password expiration in Plone. It is useful when there's\na need of forcing the portal's members to follow the specific password policy.\n\nFeatures\n========\n* Possibility to register and define custom password validation methods\n* Possibility to define user's passwords period of validity\n* Possibility to check if the password has been used in the last x variants. And disallow\n reuse.\n* Possibility to register custom notification actions to be triggered when the password's\n period of validity is getting closer\n* Provides a script that can be periodically executed from the command line (i.e. by cron).\n The script checks for the user's passwords expiration dates and triggers the registered\n notification actions (i.e. sending email to the relevant users).\n* Provides a protection mechanizm to avoid notifying given user twice the same day\n* Possibility to lock an account if too many invalid password attemps were tried\n\nInstallation\n============\n\n1. Add collective.pwexpiry to your plone.recipe.zope2instance section's eggs::\n\n [instance]\n recipe = plone.recipe.zope2instance\n ...\n eggs =\n ...\n collective.pwexpiry\n\n2. Install the Product via portal_quickinstaller.\n\nConfiguration and customization\n===============================\n\nPassword period of validity\n---------------------------\n\nThe password's period of validity is set in the configuration registry tool, and have\na default value of 90 days. It can be easily customized by creating a registry.xml file\nin your custom pakage's gereric setup profile containing the configuration code::\n\n \n \n 360\n \n \n\nTo disable password expiration, set `validity_period` to `0`.\n\nLast X Passwords check\n----------------------\n\nIt's possible to check if the new password has already been used (a history of the last `password_history_size` password hashes is kept).\n\n`password_history_size` defaults to 0, which means: there is no active check for re-used passwords.\n\nYou need to manualy activate that feature with a registry record in registry.xml::\n\n \n \n 10\n \n \n\n\nDefining notification actions\n-----------------------------\n\nBy default - there is a notification action defined that sends the notification email\nto the user when his password period of validity is going to end in 15 days.\nBut there is a possibility to register a custom methods that would be triggered\naccording to their implementation.\n\nTo register your own notification action you need to::\n 1. Register adapter providing ``IExpirationCheck`` interface::\n\n \n \n \n\n 2. Implement the adapter's ``__call__`` and ``notification_action`` methods::\n\n class LastFewDaysBeforeExpiration(object):\n implements(IExpirationCheck)\n\n # Trigger on number of days before password expiration\n notify_on = (7, 4, 3, 2, 1)\n\n def __init__(self, context):\n self.context = context\n\n def __call__(self, days_to_expire):\n \"\"\"\n Returns True whe n the notification_action\n method have to be executed\n \"\"\"\n try:\n notify_on = iter(self.notify_on)\n except TypeError:\n notify_on = (self.notify_on,)\n\n if days_to_expire in notify_on:\n return True\n else:\n return False\n\n def notification_action(self, userdata, days_to_expire):\n \"\"\"\n Implementation of the notification action.\n In this case it's sendin an email notification\n \"\"\"\n send_notification_email(userdata, days_to_expire)\n\n\nDefining custom password validation methods\n-------------------------------------------\n\nThe package allows to define your own password valdation methods\nexecuted when the user set his initial password on registration or\nchanging his actual password by in the change password form or throught\nthe password reset mechanizm.\n\nTo register your own notification action you need to::\n\n 1. Register adapter providing ``ICustomPasswordValidator`` interface::\n\n \n \n \n\n 2. Implement the adapter's ``__call__`` and ``notification_action`` methods::\n\n class MyPasswordValidator(object):\n implements(ICustomPasswordValidator)\n\n def __init__(self, context):\n self.context = context\n\n def validate(self, password, data):\n if len(password) < 8:\n return _(u'Passwords must be at least 8 characters in length.')\n\nExecuting the notification script\n---------------------------------\n\nThe notification script should be executed **once a day** to check the user's passwords\nexpiration dates and trigger relevant notification actions.\n\nFor convenience, a new command called ``notify_and_expire`` was added to zopectl,\nyou only need to provide the absolute path to your Plone instance as only argument.\n\nHere's an example of how the script can be executed from the command line::\n\n $ cd ${buildoout:directory}\n $ ./bin/instance notify_and_expire /Plone\n\nThis assuming your Plone site id is ``Plone`` and lives at the Zope root.\n\n\nProvide SERVER_URL and SERVER_NAME environment variables\n--------------------------------------------------------\n\nThe email template will try to get the server URL and server NAME from the request,\nand the notification script already puts them in there if it can find it as\nenvironment variables. So if you want to provide users with a better email,\nwhich includes links to reset or change the password, and a message detailing\nwhere the email is coming from, you need to define ``SERVER_URL`` and ``SERVER_NAME``\nenvironment variables.\nIn order to do this in buildout, you need to set your ``environment-vars`` in your\n``[instance]`` section.\n\n\nLocking out accounts if an invalid password is entered too many times\n---------------------------------------------------------------------\n\nWhen the package is installed, a new PAS plugin is included, which will count invalid password attempts when logging in.\nIf the number of invalid attempts is higher than a configurable threshold, the account will be locked out for a certain amount of hours.\nIf the account hasn't been locked yet, entering the password correctly will reset this counter to zero.\nAn account can be re-activated by an administrator changing its password.\n\n\nControlling the additional user's properties\n--------------------------------------------\n\nThe ``collective.pwexpiry`` package creates new user's properties:\n * ``password_date`` - the date when the user has changed his passoword\n * ``last_notification_date`` - the date when the last notification action has been performed for the user\n * ``account_locked_date`` - the date when the account was locked\n * ``account_locked`` - boolean telling if the account was locked or not\n * ``password_tries`` - the number of incorrect password attempts\n\nIn order to be able to control manually the new user's properties manually - there's a\ncontrol panel form available under url: ``/@@pwexpiry-controlpanel``.\n\n\nSetting how many tries before locking the account and for how much time\n-----------------------------------------------------------------------\n\nThis is managed with values in the registry:\n\n * ``collective.pwexpiry.allowed_tries`` - Allows you to choose how many attempts are allowed\n * ``collective.pwexpiry.disable_time`` - Allows you to specify for how many hours the user should be locked out\n\n\nTODO\n====\n\nWrite tests!\n------------\n\nAuthor & Contact\n================\n\n:Author:\n * Rados\u0142aw Jankiewicz ``radoslaw.jankiewicz@stxnext.pl``\n\nLicense\n=======\n\nThis package is licensed under the Zope Public License.\n\n.. _`Plone 4.2`: http://pypi.python.org/pypi/Plone/4.2\n\nChangelog\n=========\n\n\n0.15.0 (2019-08-01)\n-------------------\n\n- Add python 3 and Plone 5.2 support\n [swampmonkey,frapell]\n\n\n0.14.0 (2018-12-14)\n-------------------\n\n- Standrize error messages so it will be the same whether the user was\n locked or the password is incorrect\n [frapell]\n\n\n0.13.0 (2018-11-08)\n-------------------\n\n- Update i18n and add Brazilian Portuguese and Spanish translations.\n [hvelarde]\n\n- Deprecate Plone 4.1, Plone 4.2 and Python 2.6.\n [hvelarde]\n\n- Avoid ``TypeError`` on Password Expiry plugin and ``InvalidPasswordEntered`` subscriber when the list of whitelisted users has not being set.\n [csanahuja, hvelarde]\n\n- Restore compatibility with Plone 4.3.\n [hvelarde]\n\n- Add uninstall profile and tests.\n [hvelarde]\n\n\n0.12.0 (2018-05-30)\n-------------------\n\n- Update german translations\n [fRiSi]\n\n- Refactor the notify_and_expire script so it can be added as a zopectl command\n [frapell]\n\n- Use SERVER_URL and SERVER_NAME for including additional info in emails\n [frapell]\n\n\n0.11.3 (2017-12-06)\n-------------------\n\n- Password validation does not raise UnicodeDecodeError if password\n contains non-ascii characters [fRiSi]\n\n\n0.11.2 (2017-07-31)\n-------------------\n\n- Include upgrade step for the whitelist feature\n [frapell]\n\n\n0.11.1 (2017-07-31)\n-------------------\n\n- Re-release\n [frapell]\n\n\n0.11 (2017-07-31)\n-----------------\n\n- Include the ability to whitelist userids so they would not expire nor be locked\n [frapell]\n\n\n0.10 (2017-02-21)\n-----------------\n\n- Product now works on Plone 5\n [enfold-josh]\n\n- Javascript for login popup is only needed for Plone 4\n [frapell]\n\n\n0.9.1 (2016-05-23)\n------------------\n\n- fix rst2html for pypi page [fRiSi]\n\n\n0.9 (2016-05-23)\n----------------\n\n- Change the E-Mail Template to use a customizeable view.\n [pcdummy]\n\n- Fix an encoding problem with usernames in notify_and_expire.\n [pcdummy]\n\n- Fix javascripts with Plone 4.3.8, theres no more \"ieversion()\" function.\n [pcdummy]\n\n- Fix translations for \"password disabled\" statusmessage\n (this fixes #11)\n [fRiSi]\n\n- Update german translations.\n [pcdummy]\n\n- Change the notification e-mail to a translated text e-mail.\n [pcdummy]\n\n- Show status message \"your account has expired\" in login popup.\n [pcdummy]\n\n- Update german translations.\n [pcdummy]\n\n- Do not exipre passwords if `validity_period` is set to 0\n [fRiSi]\n\n- Add password history check (not in last x passwords).\n [pcdummy]\n\n- Enable the example_validator only when there is a browserlayer.\n [pcdummy]\n\n- Add a skins layer and remove the confusing > 5 chars message from\n pwreset_form.\n [pcdummy]\n\n- Update german translations and translate example_validator.\n [pcdummy]\n\n\n0.8.1 (2015-05-06)\n------------------\n\n- Template typo\n [frapell]\n\n- Update italian translation\n [giacomos]\n\n\n0.8 (2015-04-20)\n----------------\n\n- Update translations\n [frapell]\n\n- Improve control panel tool to allow admins to unlock accounts\n [frapell]\n\n\n0.7 (2015-03-25)\n----------------\n\n- Ignore Managers from password expiring\n [frapell]\n\n- Bugfix: If a wrong password was entered that goes over the limit, do not\n add a response header. Only do it when entering correctly\n [frapell]\n\n- When comparing dates, make sure both are timezone aware\n [frapell]\n\n- Ignore case when checking for name and username in password.\n [enfold-josh]\n\n- Change script to accept a path and use traversal to get plone site since it\n may not always be in the application root.\n [enfold-josh]\n\n- Redirect to $portal_url/mail_password_form instead of just /mail_password_form\n [enfold-josh]\n\n- Alter notification email to support days <= 0.\n [enfold-josh]\n\n\n0.6 (2015-03-13)\n----------------\n\n- Use a friendlier date format for the control panel\n [frapell]\n\n\n0.5 (2014-11-28)\n----------------\n\n- Add i18n and italian translation\n [giacomos]\n\n- No need to include 'control panel' in the control panel title\n [frapell]\n\n\n0.4 (2014-09-11)\n----------------\n\n- Add member properties, registry keys, events subscribers, and a PAS plugin\n to allow blocking a user if he enters too many incorrect passwords.\n [frapell]\n\n- Patch ZODBUserManager.authenticateCredentials so it fires events when entering\n valid or invalid credentials.\n [frapell]\n\n- Patch CMFPlone/RegistrationTool instead of CMFDefault/RegistrationTool.\n [frapell]\n\n\n0.3 (2014-09-10)\n----------------\n\n- Properly package\n [frapell]\n\n\n0.2 (2013-08-18)\n----------------\n\n- correct pypi classifiers\n\n- initial code", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://www.enfoldsystems.com", "keywords": "", "license": "ZPL", "maintainer": "", "maintainer_email": "", "name": "collective.pwexpiry", "package_url": "https://pypi.org/project/collective.pwexpiry/", "platform": "", "project_url": "https://pypi.org/project/collective.pwexpiry/", "project_urls": { "Homepage": "http://www.enfoldsystems.com" }, "release_url": "https://pypi.org/project/collective.pwexpiry/0.15.0/", "requires_dist": null, "requires_python": "", "summary": "Emulate Active Directory password complexity requirements in Plone", "version": "0.15.0" }, "last_serial": 5619938, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "de32c645b70126c35eb4bc18d72c1658", "sha256": "482798fdb1c913954da3748864cd5d79b701537ee14997951affcb62da9f5b31" }, "downloads": -1, "filename": "collective.pwexpiry-0.1.tar.gz", "has_sig": false, "md5_digest": "de32c645b70126c35eb4bc18d72c1658", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 9939, "upload_time": "2013-08-18T18:50:00", "url": "https://files.pythonhosted.org/packages/be/ec/e45baf5cb2488ed7f51c6f15cdacce05735295b60f28ec2cde1fb7f6cdb5/collective.pwexpiry-0.1.tar.gz" } ], "0.10": [ { "comment_text": "", "digests": { "md5": "62a0e4461b9baa479601e73087898e08", "sha256": "b8dc2aad8a2eef1bcc249d9e82294e1a4b2fb3394e9df743bb951a334bbfe6ba" }, "downloads": -1, "filename": "collective.pwexpiry-0.10.tar.gz", "has_sig": false, "md5_digest": "62a0e4461b9baa479601e73087898e08", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 35775, "upload_time": "2017-02-21T16:43:07", "url": "https://files.pythonhosted.org/packages/17/58/d87daf618c8742de3ae8453ef60fbd68af5460134a34b11d44d8de6135d8/collective.pwexpiry-0.10.tar.gz" } ], "0.11.3": [ { "comment_text": "", "digests": { "md5": "19caefcb523807d49dd2ced36ba01f3a", "sha256": "4c7531b30cf80c1c80928b3e6258f3da40a0a040c49fce7cecf48868becd22ff" }, "downloads": -1, "filename": "collective.pwexpiry-0.11.3.zip", "has_sig": false, "md5_digest": "19caefcb523807d49dd2ced36ba01f3a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 69590, "upload_time": "2017-12-06T10:30:47", "url": "https://files.pythonhosted.org/packages/cc/8d/c33301c7b71c78c4e62185a0000fd29b35eac5be6388195c1ee64fceafca/collective.pwexpiry-0.11.3.zip" } ], "0.12.0": [ { "comment_text": "", "digests": { "md5": "97a08885a0c5ea1475843171f3203178", "sha256": "307767ac075e9b7a0c0daaa43b04af00db82de39ed0ceb31df6070f8c96feda2" }, "downloads": -1, "filename": "collective.pwexpiry-0.12.0.zip", "has_sig": false, "md5_digest": "97a08885a0c5ea1475843171f3203178", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 68806, "upload_time": "2018-05-30T20:11:17", "url": "https://files.pythonhosted.org/packages/44/5c/911b67d6cb7659e01ea08780b6092fcbd2e0f3fdc27e1977452b06abceaa/collective.pwexpiry-0.12.0.zip" } ], "0.13.0": [ { "comment_text": "", "digests": { "md5": "c0015be0cb0f6c4c0675092f328c4b70", "sha256": "cbf4d1bae8200b145eb251c09808ec9f5622e8c9c2101a48d7c6c4d219b481db" }, "downloads": -1, "filename": "collective.pwexpiry-0.13.0.zip", "has_sig": false, "md5_digest": "c0015be0cb0f6c4c0675092f328c4b70", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 83220, "upload_time": "2018-11-08T15:43:04", "url": "https://files.pythonhosted.org/packages/77/d7/2cec4a025ff06c27ef9b1431876006464b2c31bff6e0e106257b2f1b589c/collective.pwexpiry-0.13.0.zip" } ], "0.14.0": [ { "comment_text": "", "digests": { "md5": "13563472d59001c62df5ecd6907176a4", "sha256": "ebb946fb13952bbf509f64a431500cc864e37fe98cfbb72ccd35d913816d4a52" }, "downloads": -1, "filename": "collective.pwexpiry-0.14.0.zip", "has_sig": false, "md5_digest": "13563472d59001c62df5ecd6907176a4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 92782, "upload_time": "2018-12-14T13:40:31", "url": "https://files.pythonhosted.org/packages/67/03/5fb8eb624bd4943eb3e58aaf83a242a2ad017ce575b22a8e80c374e817cf/collective.pwexpiry-0.14.0.zip" } ], "0.15.0": [ { "comment_text": "", "digests": { "md5": "34f0beb0edf1223f5b21fb39bfa45c15", "sha256": "8063ca39e3b7736db1d4d5bc25aa1cffa9f59d854da3758756c3166ee1fb695f" }, "downloads": -1, "filename": "collective.pwexpiry-0.15.0.zip", "has_sig": false, "md5_digest": "34f0beb0edf1223f5b21fb39bfa45c15", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 99100, "upload_time": "2019-08-01T17:49:17", "url": "https://files.pythonhosted.org/packages/bb/a7/e066c39cd0e7c4d77a3c48e6596c92a5150d74f897975ed75dec73d6a8b4/collective.pwexpiry-0.15.0.zip" } ], "0.2": [ { "comment_text": "", "digests": { "md5": "49d5d2fe82bf39e1d64565b889019f1a", "sha256": "1cba917db2f9e03b0aa98d806a86f0afb50fbf7955646a2a937189e0b22c2267" }, "downloads": -1, "filename": "collective.pwexpiry-0.2.tar.gz", "has_sig": false, "md5_digest": "49d5d2fe82bf39e1d64565b889019f1a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10102, "upload_time": "2013-08-18T18:58:31", "url": "https://files.pythonhosted.org/packages/a8/17/ca79e42f062baa47f5a606c41162946e181bb363ada3e1653dc087ffe94b/collective.pwexpiry-0.2.tar.gz" } ], "0.5": [ { "comment_text": "", "digests": { "md5": "71e3df049a63a332837efb70241c69da", "sha256": "6e5d9dde336046c2de4b4ee01f735ac94830930ce28951822f55015295d88dc9" }, "downloads": -1, "filename": "collective.pwexpiry-0.5.zip", "has_sig": false, "md5_digest": "71e3df049a63a332837efb70241c69da", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 53550, "upload_time": "2014-12-02T20:21:39", "url": "https://files.pythonhosted.org/packages/bd/98/5acd44637b46a50d5e80cf4125d630b9d494b8d6c27363f7ea928a1c0509/collective.pwexpiry-0.5.zip" } ], "0.6": [ { "comment_text": "", "digests": { "md5": "041a508c9c0f67a6f66434c7c758f735", "sha256": "263609a9a8eda06e93f861e6c4c3e59a52aaa65581f4d703019070342b49f88d" }, "downloads": -1, "filename": "collective.pwexpiry-0.6.zip", "has_sig": false, "md5_digest": "041a508c9c0f67a6f66434c7c758f735", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 50216, "upload_time": "2015-03-13T20:36:40", "url": "https://files.pythonhosted.org/packages/d1/56/2256bbf440c3c282a443fe94255c7c59b763dcf0f2c6518c9ae1f648608d/collective.pwexpiry-0.6.zip" } ], "0.7": [ { "comment_text": "", "digests": { "md5": "754c9a9c379e0187824d60acd8088369", "sha256": "a0e2a96a319475002a10edb101fdbf8513276117020e2e1f3219e9b334518305" }, "downloads": -1, "filename": "collective.pwexpiry-0.7.zip", "has_sig": false, "md5_digest": "754c9a9c379e0187824d60acd8088369", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 51297, "upload_time": "2015-03-25T14:19:56", "url": "https://files.pythonhosted.org/packages/69/a5/cdb9f03c498cb3e64abde8f2a98cf83263999675e6b0680ebab5f6877844/collective.pwexpiry-0.7.zip" } ], "0.8": [ { "comment_text": "", "digests": { "md5": "bc5a808733ef85d994cb4b7ec465e13d", "sha256": "5d009ed0a55fc50f07de70f596e6f9fde4a79416a8d8d16ca208e81fb84f7d65" }, "downloads": -1, "filename": "collective.pwexpiry-0.8.zip", "has_sig": false, "md5_digest": "bc5a808733ef85d994cb4b7ec465e13d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 52110, "upload_time": "2015-04-20T20:16:10", "url": "https://files.pythonhosted.org/packages/33/ed/5380d7e7fb0ae3915986b5a84429f744d3a4b4fcf62299fb9f0833157f06/collective.pwexpiry-0.8.zip" } ], "0.8.1": [ { "comment_text": "", "digests": { "md5": "bac145c8bc5d09203e04fdedbe82f9cc", "sha256": "8db2facb76636b502cef449e704c30c7d97cd66130ccb0b11e771e097dbc2604" }, "downloads": -1, "filename": "collective.pwexpiry-0.8.1.zip", "has_sig": false, "md5_digest": "bac145c8bc5d09203e04fdedbe82f9cc", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 52507, "upload_time": "2015-05-06T15:40:26", "url": "https://files.pythonhosted.org/packages/e2/f8/f2c04003429c07bf544aca9463b5ead9beb323d496aa7f699c489f8a45bc/collective.pwexpiry-0.8.1.zip" } ], "0.9": [ { "comment_text": "", "digests": { "md5": "7d7f5a203a3b10a65d986d280f9888f4", "sha256": "ee535a5bdaf80d2b57241ba3c96c87f1e803dd464ebccecd8f85f5905d1e5127" }, "downloads": -1, "filename": "collective.pwexpiry-0.9.tar.gz", "has_sig": false, "md5_digest": "7d7f5a203a3b10a65d986d280f9888f4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 35111, "upload_time": "2016-05-23T13:20:02", "url": "https://files.pythonhosted.org/packages/e0/de/b58b466ee225dd8225d84ff40a57dc204dfb09d686eec20b32bc8a20d889/collective.pwexpiry-0.9.tar.gz" } ], "0.9.1": [ { "comment_text": "", "digests": { "md5": "656a5599b1fde065ed2e61c0b4f332d4", "sha256": "75d7ca9bab531cc6c1f6ac224fa4b58c87da00b6621c8612404f403d932c7ada" }, "downloads": -1, "filename": "collective.pwexpiry-0.9.1.tar.gz", "has_sig": false, "md5_digest": "656a5599b1fde065ed2e61c0b4f332d4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 35929, "upload_time": "2016-05-23T13:31:32", "url": "https://files.pythonhosted.org/packages/22/df/88fd7190857a2e078aaf59fb3e3eaa2b5d8e483abeeed2414b1a7bb54271/collective.pwexpiry-0.9.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "34f0beb0edf1223f5b21fb39bfa45c15", "sha256": "8063ca39e3b7736db1d4d5bc25aa1cffa9f59d854da3758756c3166ee1fb695f" }, "downloads": -1, "filename": "collective.pwexpiry-0.15.0.zip", "has_sig": false, "md5_digest": "34f0beb0edf1223f5b21fb39bfa45c15", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 99100, "upload_time": "2019-08-01T17:49:17", "url": "https://files.pythonhosted.org/packages/bb/a7/e066c39cd0e7c4d77a3c48e6596c92a5150d74f897975ed75dec73d6a8b4/collective.pwexpiry-0.15.0.zip" } ] }