diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e83ae97..7510505 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,42 +2,79 @@ name: tests on: push: - branches: [ main ] + branches: [ master, python3 ] pull_request: # Allow to run this workflow manually from the Actions tab workflow_dispatch: jobs: build: + runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - config: - # [Python version, tox env] - - ["2.7", "plone52-py27"] - - ["3.6", "plone52-py36"] - - ["3.7", "plone52-py37"] - - ["3.8", "plone52-py38"] - - ["3.7", "plone60-py37"] - - ["3.8", "plone60-py38"] - - ["3.9", "plone60-py39"] - runs-on: ubuntu-latest - name: ${{ matrix.config[1] }} + plone-version: + # - '5.2' + - '6.0' + python-version: + # - '2.7' + # - '3.7' + # - '3.8' + - '3.9' + # exclude: + # - plone-version: '5.2' + # python-version: '3.9' + # - plone-version: '6.0' + # python-version: '2.7' steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.config[0] }} - - name: Pip cache - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ matrix.config[0] }}-${{ hashFiles('setup.*', 'tox.ini') }} - restore-keys: | - ${{ runner.os }}-pip-${{ matrix.config[0] }}- - ${{ runner.os }}-pip- - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install tox - - name: Test - run: tox -e ${{ matrix.config[1] }} + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + sudo apt-get install -y libxml2-dev libxslt-dev python-dev xvfb + pip install virtualenv wheel + - uses: nanasess/setup-chromedriver@master + - name: Cache multiple paths + uses: actions/cache@v2 + with: + path: | + ~/buildout-cache + ~/extends + ~/.cache/pip + key: ${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.plone-version }}-${{ hashFiles('**/*.cfg') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.plone-version }}- + ${{ runner.os }}-${{ matrix.python-version }}- + ${{ runner.os }}- + - name: setup buildout cache + run: | + mkdir -p ~/buildout-cache/{eggs,downloads} + mkdir ~/.buildout + echo "[buildout]" > $HOME/.buildout/default.cfg + echo "download-cache = $HOME/buildout-cache/downloads" >> $HOME/.buildout/default.cfg + echo "eggs-directory = $HOME/buildout-cache/eggs" >> $HOME/.buildout/default.cfg + - name: bootstrap buildout py2 + if: ${{ matrix.python-version == '2.7'}} + run: | + virtualenv . + bin/pip install -r requirements.txt + - name: bootstrap buildout py3 + if: ${{ matrix.python-version != '2.7'}} + run: | + python -m venv . + bin/pip install -r requirements.txt + - name: buildout + run: | + bin/buildout -t 10 -Nc test-${{ matrix.plone-version }}.x.cfg + bin/pip install zest.pocompile + bin/pocompile src + - name: test + run: | + export DISPLAY=:99 + chromedriver --url-base=/wd/hub & + sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & + sleep 2 + PATH=$PATH:$PWD/chromedriver ROBOT_BROWSER=chrome ROBOT_HTTP_PORT=55001 bin/test + cat /home/runner/work/Products.PasswordStrength/Products.PasswordStrength/parts/test/password/Test_register_form/output.xml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4e9997c..0000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -language: - python -python: - 2.7 -env: - - PLONE_VERSION=4.3 - - PLONE_VERSION=5.0 -matrix: - include: - - python: 2.6 - env: PLONE_VERSION=4.2 - - python: 2.6 - env: PLONE_VERSION=4.1 - -install: - - sed -ie "s#travis-4.x.cfg#travis-$PLONE_VERSION.x.cfg#" travis.cfg - - sed -ie "s#buildout.cfg#buildout-$PLONE_VERSION.cfg#" travis.cfg - - mkdir -p buildout-cache/{downloads,eggs} - - python bootstrap.py -c travis.cfg - - bin/buildout -c travis.cfg -N -before_script: - - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" -script: - bin/test -after_success: - - test -v QA && bin/coverage.sh && pip install -q coveralls && coveralls || true diff --git a/CHANGES.rst b/CHANGES.rst index bc5b3a5..60926d4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,9 +1,12 @@ Changes ======= -0.4.1 (unreleased) +0.5.0 (unreleased) ------------------ +- Add support for Python 3, Plone 5.2 and Plone 6. Drop support for Plone 5.1 and older. + [pbauer, djay] + - Fix can not change weak password [ivanteoh] diff --git a/MANIFEST.in b/MANIFEST.in index cfba368..6a5d6cc 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,6 @@ include *.txt *.rst recursive-include Products *.zcml *.xml *.txt -recursive-include Products *.cpt *.metadata *.zpt +recursive-include Products *.cpt *.metadata *.zpt *.pt recursive-include Products *.po *.pot recursive-include Products *.robot exclude bootstrap.py *.cfg *.yml diff --git a/Products/PasswordStrength/__init__.py b/Products/PasswordStrength/__init__.py index d357fa1..130e9f4 100644 --- a/Products/PasswordStrength/__init__.py +++ b/Products/PasswordStrength/__init__.py @@ -8,7 +8,7 @@ from Products.PluggableAuthService import registerMultiPlugin from zope.i18nmessageid import MessageFactory _ = MessageFactory("Products.PasswordStrength") -from plugin import PasswordStrength, manage_addPasswordStrength, manage_addPasswordStrengthForm +from Products.PasswordStrength.plugin import PasswordStrength, manage_addPasswordStrength, manage_addPasswordStrengthForm def initialize(context): diff --git a/Products/PasswordStrength/backward.py b/Products/PasswordStrength/backward.py deleted file mode 100644 index bc2eb54..0000000 --- a/Products/PasswordStrength/backward.py +++ /dev/null @@ -1,69 +0,0 @@ -from Products.PluggableAuthService.interfaces.plugins import IValidationPlugin -from Products.CMFCore.utils import getToolByName -from Products.PasswordStrength import _ -from plone.app.users.browser.register import BaseRegistrationForm -try: - from plone.app.users.browser.personalpreferences import PasswordAccountPanel as PasswordPanel -except: - try: - from plone.app.users.browser.passwordpanel import PasswordPanel - except: - PasswordPanel = None - -# monkey patch to be used for Plone < 4.3 -def testPasswordValidity(self, password, confirm=None): - - """ Verify that the password satisfies the portal's requirements. - - o If the password is valid, return None. - o If not, return a string explaining why. - """ - if confirm is not None and confirm != password: - return _('Your password and confirmation did not match. Please try again.') - - if not password: - err = [_('You must enter a password')] - else: - err = [] - - # We escape the test if it looks like a generated password - if password and password.startswith('G-') and len(password) == len(self.origGeneratePassword()) + 2: - return None - - # Use PAS to test validity - pas_instance = self.acl_users - plugins = pas_instance._getOb('plugins') - validators = plugins.listPlugins(IValidationPlugin) - for validator_id, validator in validators: - user = None - set_id = '' - set_info = {'password': password} - errors = validator.validateUserInfo(user, set_id, set_info) - err += [info['error'] for info in errors if info['id'] == 'password'] - if err: - return '. '.join(err) - else: - return None - - -# monkey patch to be used for Plone < 4.3 -def updateRegistrationForm(self): - # Change the password description based on PAS Plugin - # The user needs a list of instructions on what kind of password is required. - # We'll reuse password errors as instructions e.g. "Must contain a letter and a number". - # Assume PASPlugin errors are already translated - if self.form_fields and self.form_fields.get('password', None): - registration = getToolByName(self.context, 'portal_registration') - err_str = registration.testPasswordValidity('') - if err_str: - self.form_fields['password'].field.description = err_str - super(BaseRegistrationForm, self).update() - -# for 4.3 and 5.0 -def updatePasswordAccountPanel(self): - if self.form_fields and self.form_fields.get('new_password', None): - registration = getToolByName(self.context, 'portal_registration') - err_str = registration.testPasswordValidity('') - if err_str: - self.form_fields['new_password'].field.description = err_str - super(PasswordPanel, self).update() diff --git a/Products/PasswordStrength/configure.zcml b/Products/PasswordStrength/configure.zcml index 90b2dc5..0db2da3 100644 --- a/Products/PasswordStrength/configure.zcml +++ b/Products/PasswordStrength/configure.zcml @@ -1,61 +1,22 @@ - - - - + - + - - - - - - - - - - - - - - - - - - - - - - - - + - + diff --git a/Products/PasswordStrength/interfaces.py b/Products/PasswordStrength/interfaces.py new file mode 100644 index 0000000..877b18e --- /dev/null +++ b/Products/PasswordStrength/interfaces.py @@ -0,0 +1,7 @@ +"""Module where all interfaces, events and exceptions live.""" + +from zope.publisher.interfaces.browser import IDefaultBrowserLayer + + +class IPasswordStrengthLayer(IDefaultBrowserLayer): + """Marker interface that defines a browser layer.""" diff --git a/Products/PasswordStrength/locales/de/LC_MESSAGES/Products.PasswordStrength.po b/Products/PasswordStrength/locales/de/LC_MESSAGES/Products.PasswordStrength.po new file mode 100644 index 0000000..8cab054 --- /dev/null +++ b/Products/PasswordStrength/locales/de/LC_MESSAGES/Products.PasswordStrength.po @@ -0,0 +1,71 @@ +msgid "" +msgstr "" +"Project-Id-Version: Products.PasswordStrength\n" +"POT-Creation-Date: 2015-07-03 13:29+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" +"Last-Translator: Steffen Lindner \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0\n" +"Language-Code: de\n" +"Language-Name: Germany\n" +"Preferred-Encodings: utf-8 latin1\n" +"Domain: Products.PasswordStrength\n" + +#. Default: "Add" +#: ../www/passwordStrengthAdd.zpt:37 +msgid " Add " +msgstr " Hinzufügen " + +#: ../www/passwordStrengthAdd.zpt:6 +msgid "A plugin that rejects passwords that don't conform to a certain strength" +msgstr "Ein Plugin welches keine unsichere Passwörter zulässt." + +#: ../plugin.py:31 +msgid "Create your own rules for enforcing password strength" +msgstr "Erstelle deine eigenen Passwort regeln" + +#: ../plugin.py:79 +msgid "Minimum 1 capital letter" +msgstr "Mindestens ein Grossbuchstabe" + +#: ../plugin.py:80 +msgid "Minimum 1 lower case letter" +msgstr "Mindestens ein Kleinbuchstabe" + +#: ../plugin.py:82 +msgid "Minimum 1 non-alpha character" +msgstr "Mindestens ein Sonderzeichen" + +#: ../plugin.py:81 +msgid "Minimum 1 number" +msgstr "Mindestens eine Nummer" + +#: ../plugin.py:80 +msgid "Minimum 10 characters" +msgstr "Mindestens 10 Zeichen" + +#. Default: "PAS plugin that ensures strong passwords." +#: ../configure.zcml +msgid "PAS plugin that ensures strong passwords." +msgstr "Module PAS proposant une politique forte des mots de passes" + +#. Default: "Password Strength Plugin" +#: ../configure.zcml +msgid "Password Strength Plugin" +msgstr "Module Password Strength" + +#: ../plugin.py:207 +msgid "This password doesn't match requirements for passwords" +msgstr "Dieses Passwort entspricht nicht den Passwort Vorgaben" + +#: ./backward.py:20 +msgid "You must enter a password" +msgstr "Sie müssen ein Passwort eingeben" + +#: ./backward.py:17 +msgid "Your password and confirmation did not match. Please try again." +msgstr "Dein Passwort und deine Bestätigung stimmen nicht überein. Bitte versuch es nochmal." + diff --git a/Products/PasswordStrength/overrides/Products.CMFPlone.browser.login.templates.pwreset_form.pt b/Products/PasswordStrength/overrides/Products.CMFPlone.browser.login.templates.pwreset_form.pt new file mode 100644 index 0000000..1f9f55e --- /dev/null +++ b/Products/PasswordStrength/overrides/Products.CMFPlone.browser.login.templates.pwreset_form.pt @@ -0,0 +1,141 @@ + + + + +

Set your password

+
+ + +
+ Please fill out the form below to set your password. +
+
+ + + + +
+
+ New Password + + +
+ + + + + + +
Validation error output
+ + + Enter your user name for verification. + +
+ + + + + + +
Validation error output
+ + + Enter your email address for verification. + +
+ +
+ +
+ + + + + +
Validation error output
+ + + Enter your new password. + + +
+ +
+ + + + + +
Validation error output
+ + + Re-enter the password. Make sure the passwords are identical. + + +
+ +
+
 
+ +
+
+
+ +
+
+ +
+
+ + + diff --git a/Products/PasswordStrength/plugin.py b/Products/PasswordStrength/plugin.py index af4cd56..6edc7b6 100644 --- a/Products/PasswordStrength/plugin.py +++ b/Products/PasswordStrength/plugin.py @@ -4,23 +4,23 @@ __author__ = "Dylan Jay " -import logging - from AccessControl import ClassSecurityInfo -from Globals import InitializeClass +from AccessControl.class_init import InitializeClass from OFS.Cache import Cacheable - -from Products.CMFPlone.RegistrationTool import RegistrationTool +from plone.api import portal from Products.CMFPlone import PloneMessageFactory as _p -from . import _ +from Products.CMFPlone.utils import safe_unicode +from Products.CMFPlone.RegistrationTool import RegistrationTool +from Products.PageTemplates.PageTemplateFile import PageTemplateFile +from Products.PasswordStrength import _ +from Products.PluggableAuthService.interfaces.plugins import IValidationPlugin from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin from Products.PluggableAuthService.utils import classImplements -from Products.PluggableAuthService.interfaces.plugins import IValidationPlugin -import re from zope.i18n import translate -from plone.api import portal -from Products.PageTemplates.PageTemplateFile import PageTemplateFile +import logging +import re + try: from plone.app.users.browser.personalpreferences import IPasswordSchema except: @@ -47,7 +47,7 @@ def testPasswordValidity(self, password, confirm=None): # We escape the test if it looks like a generated password (with default length of 56 chars) if password is not None and password.startswith('G-') and len(password) == len(self.origGeneratePassword()) + 2: return None - session = self.REQUEST.get('SESSION', {}) + session = self.REQUEST.get('SESSION', {}) or {} # We also skip the test if a skip_password_check session variable is set if password is not None and session.get('skip_password_check'): return None @@ -189,7 +189,7 @@ def validateUserInfo(self, user, set_id, set_info): if not re.match(reg, password): err = getattr(self, 'p%i_err' % i, None) if err: - errors += [translate(err.decode('utf8'), domain='Products.PasswordStrength', + errors += [translate(safe_unicode(err), domain='Products.PasswordStrength', context=site.REQUEST)] i += 1 @@ -224,7 +224,8 @@ def validate(self, value): return skip = False - # Do not validate existing passwords + + # Do not validate old password when changing passwords as logged in user if getattr(self, '__name__', '') == 'current_password': skip = True diff --git a/Products/PasswordStrength/profiles.zcml b/Products/PasswordStrength/profiles.zcml index 6e2e260..0458a6c 100644 --- a/Products/PasswordStrength/profiles.zcml +++ b/Products/PasswordStrength/profiles.zcml @@ -9,6 +9,7 @@ directory="profiles/default" description="PAS plugin that ensures strong passwords." provides="Products.GenericSetup.interfaces.EXTENSION" + post_handler=".setuphandlers.setupPasswordStrength" /> @@ -19,6 +20,7 @@ description='Uninstall profile for the "PasswordStrength" Plone theme.' provides="Products.GenericSetup.interfaces.EXTENSION" for="Products.CMFCore.interfaces.ISiteRoot" + post_handler=".setuphandlers.removePasswordStrength" /> diff --git a/Products/PasswordStrength/profiles/default/browserlayer.xml b/Products/PasswordStrength/profiles/default/browserlayer.xml new file mode 100644 index 0000000..25c9f48 --- /dev/null +++ b/Products/PasswordStrength/profiles/default/browserlayer.xml @@ -0,0 +1,7 @@ + + + + diff --git a/Products/PasswordStrength/profiles/default/import_steps.xml b/Products/PasswordStrength/profiles/default/import_steps.xml deleted file mode 100644 index ae2e514..0000000 --- a/Products/PasswordStrength/profiles/default/import_steps.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - PasswordStrength installation step. - - diff --git a/Products/PasswordStrength/profiles/default/metadata.xml b/Products/PasswordStrength/profiles/default/metadata.xml index f341fc6..b22370d 100644 --- a/Products/PasswordStrength/profiles/default/metadata.xml +++ b/Products/PasswordStrength/profiles/default/metadata.xml @@ -1,4 +1,4 @@ - 0.4 + 1000 diff --git a/Products/PasswordStrength/profiles/default/passwordstrength.txt b/Products/PasswordStrength/profiles/reset/.gitkeep similarity index 100% rename from Products/PasswordStrength/profiles/default/passwordstrength.txt rename to Products/PasswordStrength/profiles/reset/.gitkeep diff --git a/Products/PasswordStrength/profiles/reset/import_steps.xml b/Products/PasswordStrength/profiles/reset/import_steps.xml deleted file mode 100644 index 5a8532b..0000000 --- a/Products/PasswordStrength/profiles/reset/import_steps.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - PasswordStrength: users password reset. - - diff --git a/Products/PasswordStrength/profiles/reset/passwordstrength_reset.txt b/Products/PasswordStrength/profiles/reset/passwordstrength_reset.txt deleted file mode 100644 index e69de29..0000000 diff --git a/Products/PasswordStrength/profiles/uninstall/browserlayer.xml b/Products/PasswordStrength/profiles/uninstall/browserlayer.xml new file mode 100644 index 0000000..46e23d6 --- /dev/null +++ b/Products/PasswordStrength/profiles/uninstall/browserlayer.xml @@ -0,0 +1,7 @@ + + + + diff --git a/Products/PasswordStrength/profiles/uninstall/import_steps.xml b/Products/PasswordStrength/profiles/uninstall/import_steps.xml deleted file mode 100644 index dbc02ee..0000000 --- a/Products/PasswordStrength/profiles/uninstall/import_steps.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - PasswordStrength uninstallation step. - - diff --git a/Products/PasswordStrength/profiles/uninstall/passwordstrength-uninstall.txt b/Products/PasswordStrength/profiles/uninstall/passwordstrength-uninstall.txt deleted file mode 100644 index e69de29..0000000 diff --git a/Products/PasswordStrength/setuphandlers.py b/Products/PasswordStrength/setuphandlers.py index fa95767..5400f74 100644 --- a/Products/PasswordStrength/setuphandlers.py +++ b/Products/PasswordStrength/setuphandlers.py @@ -1,23 +1,20 @@ import logging from smtplib import SMTPException -from StringIO import StringIO +from six import StringIO from Products.CMFCore.utils import getToolByName +from plone import api from Products.PasswordStrength.plugin import PROJECTNAME, PLUGIN_ID, PLUGIN_TITLE PLONE_POLICY = 'password_policy' logger = logging.getLogger('Products.PasswordStrength: setuphandlers') def setupPasswordStrength(context): - if context.readDataFile('passwordstrength.txt') is None: - return - site = context.getSite() + site = api.portal.get() install(site) def removePasswordStrength(context): - if context.readDataFile('passwordstrength-uninstall.txt') is None: - return - site = context.getSite() + site = api.portal.get() uninstall(site) @@ -28,7 +25,7 @@ def install(portal): Different interfaces need to be activated for either case. """ out = StringIO() - print >> out, "Installing %s:" % PROJECTNAME + print("Installing %s:" % PROJECTNAME, file=out) plone_pas = getToolByName(portal, 'acl_users') @@ -42,13 +39,13 @@ def install(portal): activatePluginSelectedInterfaces(plone_pas, PLONE_POLICY, out, 'IValidationPlugin', disable=['IValidationPlugin']) - print >> out, "Successfully installed %s." % PROJECTNAME + print("Successfully installed %s." % PROJECTNAME, file=out) return out.getvalue() def uninstall(portal): out = StringIO() - print >> out, "Uninstalling %s:" % PROJECTNAME + print("Uninstalling %s:" % PROJECTNAME, file=out) plone_pas = getToolByName(portal, 'acl_users') existing = plone_pas.objectIds() if PLUGIN_ID in existing: @@ -75,12 +72,12 @@ def activatePluginSelectedInterfaces(pas, plugin, out, selected_interfaces, disa interface_name in selected_interfaces: if interface_name in disable: disable.append(interface_name) - print >> out, " - Disabling: " + info['title'] + print(" - Disabling: " + info['title'], file=out) else: activatable.append(interface_name) - print >> out, " - Activating: " + info['title'] + print(" - Activating: " + info['title'], file=out) plugin_obj.manage_activateInterfaces(activatable) - print >> out, plugin + " activated." + print(plugin + " activated.", file=out) def list_append(lst, elem): @@ -95,9 +92,7 @@ def reset_passwords(context): """ Reset all users passwords """ - if context.readDataFile('passwordstrength_reset.txt') is None: - return - portal = context.getSite() + portal = api.porta.get() regtool = portal.portal_registration logs = [] logger.info(list_append(logs, "Resetting all users passwords")) diff --git a/Products/PasswordStrength/skins.zcml b/Products/PasswordStrength/skins.zcml deleted file mode 100644 index d6821db..0000000 --- a/Products/PasswordStrength/skins.zcml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/Products/PasswordStrength/skins/PasswordStrength_public/README.txt b/Products/PasswordStrength/skins/PasswordStrength_public/README.txt deleted file mode 100644 index cdc4c1b..0000000 --- a/Products/PasswordStrength/skins/PasswordStrength_public/README.txt +++ /dev/null @@ -1,7 +0,0 @@ -Overrides are as follows - --
- Enter your new password. Minimum 5 characters. -
-+
- - - -

Set your password

- -
- Please fill out the form below to set your password. -
- -
-
-
- New Password - - - -
- - - -
Validation error output
- -
- Enter your user name for verification. -
-
- - -
Validation error output
- -
- Enter your email address for verification. -
-
- - -
- -
- - -
Validation error output
- -
- Enter your new password. -
- - -
- -
- - -
Validation error output
- -
- Re-enter the password. Make sure the passwords are identical. -
- - -
- -
-
 
- -
- -
-
- -
-
-
-
- - diff --git a/Products/PasswordStrength/skins/PasswordStrength_public/pwreset_form.cpt.metadata b/Products/PasswordStrength/skins/PasswordStrength_public/pwreset_form.cpt.metadata deleted file mode 100644 index 2cfdfe5..0000000 --- a/Products/PasswordStrength/skins/PasswordStrength_public/pwreset_form.cpt.metadata +++ /dev/null @@ -1,7 +0,0 @@ -[validators] -validators = validate_pwreset_userid, validate_pwreset_password - -[actions] -action.success = traverse_to:string:pwreset_action -action.invalid = traverse_to:string:pwreset_invalid -action.expired = traverse_to:string:pwreset_expired diff --git a/Products/PasswordStrength/testing.py b/Products/PasswordStrength/testing.py index c2a0f07..3030677 100644 --- a/Products/PasswordStrength/testing.py +++ b/Products/PasswordStrength/testing.py @@ -11,12 +11,12 @@ from plone.app.robotframework.testing import REMOTE_LIBRARY_BUNDLE_FIXTURE from plone.app.robotframework.testing import MOCK_MAILHOST_FIXTURE -from tests.robot_setup import PasswordStrengthRemoteKeywords +from Products.PasswordStrength.tests.robot_setup import PasswordStrengthRemoteKeywords try: from Products.CMFPlone.tests.robot.robot_setup import CMFPloneRemoteKeywords except ImportError: - from tests.backward_robot import CMFPloneRemoteKeywords + from Products.PasswordStrength.tests.backward_robot import CMFPloneRemoteKeywords class Fixture(PloneSandboxLayer): @@ -26,6 +26,8 @@ class Fixture(PloneSandboxLayer): def setUpZope(self, app, configurationContext): # Load ZCML import Products.PasswordStrength + import z3c.jbot + self.loadZCML(package=z3c.jbot) self.loadZCML(package=Products.PasswordStrength) # Install product and call its initialize() function z2.installProduct(app, 'Products.PasswordStrength') diff --git a/Products/PasswordStrength/tests/common.robot b/Products/PasswordStrength/tests/common.robot deleted file mode 100644 index 280b477..0000000 --- a/Products/PasswordStrength/tests/common.robot +++ /dev/null @@ -1,124 +0,0 @@ -*** Keywords *** - -## Actions - -Own passwords registration enabled - Enable autologin as Manager - Go to ${PLONE_URL}/@@security-controlpanel - Input for Let users select their own passwords Select checkbox - Click button Save - Input for Let users select their own passwords Checkbox should be selected - Disable autologin - -Own passwords registration disabled - Enable autologin as Manager - Go to ${PLONE_URL}/@@security-controlpanel - Input for Let users select their own passwords Unselect checkbox - Click button Save - Input for Let users select their own passwords Checkbox should not be selected - Disable autologin - -Input for - [arguments] ${title} ${extra_keyword} @{list} - ${for}= Get Element Attribute xpath=//label[contains(., "${title}")]@for - Run Keyword ${extra_keyword} id=${for} @{list} - -Hint for - [arguments] ${title} ${extra_keyword} @{list} - Run Keyword ${extra_keyword} xpath=//label[contains(., "${title}")]/span[@class="formHelp"] @{list} - -Error for - [arguments] ${title} ${extra_keyword} @{list} - Run Keyword ${extra_keyword} xpath=//label[contains(., "${title}")]/following-sibling::div[@class="fieldErrorBox"] @{list} - -## Common tests - -Test change password form - Log in test-user secret - Sleep 2 - Go to ${PLONE_URL}/@@change-password - # Element should contain css=h1.documentFirstHeading Reset Password - # Contains password description ? - Hint for New password Element should contain Minimum 1 capital letter - # In Plone 4.1, the current password can't be validated - # Products/PlonePAS/gruf_support.py(16)authenticate() : 'test_user_1_' used as login in place of 'test-user' - Run keyword if '${plone_version}' <= '4.1.99' Pass execution Stopped this test with Plone < 4.2 - # Reacts with bad password - Input for Current password Input text secret - Input for New password Input text 12345 - Input for Confirm password Input text 12345 - Click button Change Password - # Redirected on same page - Element should be visible jquery=div.error>div:contains("This password doesn't match requirements for passwords") - # Accepts well formed password - Input for Current password Input text secret - Input for New password Input text ABCDEFGHIJabcdefghij1! - Input for Confirm password Input text ABCDEFGHIJabcdefghij1! - Click button Change Password - # Redirected on same page - Element should be visible jquery=dl.portalMessage dd:contains('Password changed') - Log out - -Test register form without password - Own passwords registration disabled - Go to ${PLONE_URL}/@@register - # Contains password description ? - Input for Password Element should not be visible - # Fill form - Input for User Name Input text rocky - Input for E-mail Input text rocky@balboa.com - Click button Register - # Redirected - Wait until page contains Welcome 5 - Element should contain css=h1.documentFirstHeading Welcome - ${message} = Get The Last Sent Email - Should contain ${message} plone/passwordreset - ${reset_url} = Extract reset url ${message} - # go to reset form - Go to ${PLONE_URL}/${reset_url} - # Contains password description ? - Hint for Password Element should contain Minimum 1 capital letter. - # Fill form - Input for User id Input text rocky - Input for User name Input text 12345 - Input for Confirm password Input text 12345 - Click button Reset Password - # Reacts with bad password - Element should contain css=div.error div Minimum 1 capital letter. - Input text userid rocky - Input text password ABCDEFGHIJabcdefghij1! - Input text password2 ABCDEFGHIJabcdefghij1! - Click button css=form[name="pwreset_action"] input[type="submit"] - Element should contain css=div.documentDescription Your password has been set successfully. - -Test reset form - # create the user - Go to ${PLONE_URL}/@@register - Input for User Name Input text rocky - Input for E-mail Input text rocky@balboa.com - Input for Password Input text ABCDEFGHIJabcdefghij1! - Input for Confirm password Input text ABCDEFGHIJabcdefghij1! - Click button Register - Element should contain css=h1.documentFirstHeading Welcome - # do a reset - Go to ${PLONE_URL}/mail_password_form?userid=rocky - Click button Start password reset - ${message} = Get The Last Sent Email - Should contain ${message} plone/passwordreset - ${reset_url} = Extract reset url ${message} - # go to reset form - Go to ${PLONE_URL}/${reset_url} - # Contains password description ? - Hint for New Password Element should contain Minimum 1 capital letter. - # Fill form - Input for User id Input text rocky - Input for Password Input text 12345 - Input for Confirm password Input text 12345 - Click button Reset - # Reacts with bad password - Error for Password Element should contain Minimum 1 capital letter. - Input for User id Input text rocky - Input for Password Input text ABCDEFGHIJabcdefghij1! - Input for Confirm password Input text ABCDEFGHIJabcdefghij1! - Click button Reset Password - Element should contain css=div.documentDescription Your password has been set successfully. diff --git a/Products/PasswordStrength/tests/password.robot b/Products/PasswordStrength/tests/password.robot index 0da06d9..1ccd8b0 100644 --- a/Products/PasswordStrength/tests/password.robot +++ b/Products/PasswordStrength/tests/password.robot @@ -2,7 +2,6 @@ Resource plone/app/robotframework/keywords.robot Resource plone/app/robotframework/selenium.robot Resource plone/app/robotframework/saucelabs.robot -Resource Products/PasswordStrength/tests/common.robot Library Remote ${PLONE_URL}/RobotRemote Library plone.app.robotframework.keywords.Debugging @@ -18,7 +17,7 @@ Test register form Hint for Password Element should contain Minimum 1 capital letter. # Fill form Input for User Name Input text rocky - Input for E-mail Input text rocky@balboa.com + Input for Email Input text rocky@balboa.com # Reacts with bad password Input for Password Input text 12345 Input for Confirm password Input text 12345 @@ -44,7 +43,7 @@ Test new user form Hint for Password Element should contain Minimum 1 capital letter. # Fill form Input for User Name Input text rocky - Input for E-mail Input text rocky@balboa.com + Input for Email Input text rocky@balboa.com # Reacts with bad password Input for Password Input text 12345 Input for Confirm password Input text 12345 @@ -61,20 +60,121 @@ Test new user form Error for Confirm password Element should not be visible # Redirected Wait until page contains Add New User 5 - Element should contain css=h1.documentFirstHeading Users and Groups + Element should contain css=h1.documentFirstHeading Users Page should contain element css=input[value="rocky"] Disable autologin + Test change password form - Test change password form + # Log in test-user secret + Enable autologin as Manager + @{roles} = Create list Manager + Create User tester password=testTEST123$ roles=@{roles} + Disable autologin + Log in tester testTEST123$ + Sleep 2 + Go to ${PLONE_URL}/@@change-password + # Element should contain css=h1.documentFirstHeading Reset Password + # Contains password description ? + Hint for New password Element should contain Minimum 1 capital letter + # In Plone 4.1, the current password can't be validated + # Products/PlonePAS/gruf_support.py(16)authenticate() : 'test_user_1_' used as login in place of 'test-user' + Run keyword if '${plone_version}' <= '4.1.99' Pass execution Stopped this test with Plone < 4.2 + # Reacts with bad password + Input for Current password Input text secret + Input for New password Input text 12345 + Input for Confirm password Input text 12345 + Click button Change Password + # Redirected on same page + Element should be visible jquery=div.error>div:contains("This password doesn't match requirements for passwords") + # Accepts well formed password + Input for Current password Input text testTEST123$ + Input for New password Input text ABCDEFGHIJabcdefghij1! + Input for Confirm password Input text ABCDEFGHIJabcdefghij1! + Click button Change Password + # Redirected on same page + Element should be visible jquery=div.portalMessage:contains('Password changed') + Go to ${PLONE_URL}/logout Test register form without password - Test register form without password + Enable autologin as Manager + @{roles} = Create list Manager + Create User tester password=testTEST123$ roles=@{roles} + Disable autologin + + Own passwords registration disabled + Go to ${PLONE_URL}/@@register + # Contains password description ? + Element should not be visible Input for Password + # Fill form + Input for User Name Input text rocky + Input for Email Input text rocky@balboa.com + Click button Register + # Redirected + Wait until page contains Welcome 5 + Element should contain css=h1.documentFirstHeading Welcome + ${message_bytes} = Get The Last Sent Email + ${message} = Decode Bytes To String ${message_bytes} utf-8 + Should contain ${message} plone/passwordreset + ${reset_url} = Extract reset url ${message} + # go to reset form + Go to ${PLONE_URL}/${reset_url} + + # Contains password description ? + # passwordreset has different (manual) form markup than z3c-form based forms... + Element should contain xpath=//label[contains(., "New password")]/following::small Minimum 1 capital letter. + # Fill form + Input for My user name is Input text rocky + Input for New password Input text 12345 + Input for Confirm password Input text 12345 + Click button Set my password + # Reacts with bad password + Element should contain css=div.field.error div.invalid-feedback Minimum 1 capital letter. + Input for My user name is Input text rocky + Input for New password Input text ABCDEFGHIJabcdefghij1! + Input for Confirm password Input text ABCDEFGHIJabcdefghij1! + Click button Set my password + Element should contain css=div.portalMessage Password reset successful, you are logged in now! Test reset form - Test reset form + # create the user + Go to ${PLONE_URL}/@@register + Input for User Name Input text rocky + Input for Email Input text rocky@balboa.com + Input for Password Input text ABCDEFGHIJabcdefghij1! + Input for Confirm password Input text ABCDEFGHIJabcdefghij1! + Click button Register + Element should contain css=h1.documentFirstHeading Welcome + # do a reset + Go to ${PLONE_URL}/mail_password_form?userid=rocky + # DEBUG + Wait until page contains Lost Password + Click button Start password reset + ${message_bytes} = Get The Last Sent Email + ${message} = Decode Bytes To String ${message_bytes} utf-8 + Should contain ${message} plone/passwordreset + ${reset_url} = Extract reset url ${message} + # go to reset form + Go to ${PLONE_URL}/${reset_url} + # Contains password description ? + # passwordreset has different (manual) form markup than z3c-form based forms... + Element should contain xpath=//label[contains(., "New password")]/following::small Minimum 1 capital letter. + # Fill form + Input for My user name is Input text rocky + Input for New password Input text 12345! + Input for Confirm password Input text 12345 + Click button Set my password + # Reacts with bad password + Element should contain css=div.field.error div.invalid-feedback Minimum 1 capital letter. + Input for My user name is Input text rocky + Input for New password Input text ABCDEFGHIJabcdefghij1! + Input for Confirm password Input text ABCDEFGHIJabcdefghij1! + Click button Set my password + Element should contain css=div.portalMessage Password reset successful, you are logged in now! + *** Keywords *** + Test Setup Open SauceLabs test browser Go to ${PLONE_URL} @@ -82,7 +182,37 @@ Test Setup The self registration enabled The mail setup configured Own passwords registration enabled - The inline validation disabled ${plone_version} = Get plone version Set global variable ${plone_version} Disable autologin + +## Actions + +Own passwords registration enabled + Enable autologin as Manager + Go to ${PLONE_URL}/@@security-controlpanel + Input for Let users select their own passwords Select checkbox + Click button Save + Input for Let users select their own passwords Checkbox should be selected + Disable autologin + +Own passwords registration disabled + Enable autologin as Manager + Go to ${PLONE_URL}/@@security-controlpanel + Input for Let users select their own passwords Unselect checkbox + Click button Save + Input for Let users select their own passwords Checkbox should not be selected + Disable autologin + +Input for + [arguments] ${title} ${extra_keyword} @{list} + ${for}= Get Element Attribute xpath=//label[contains(., "${title}")] for + Run Keyword ${extra_keyword} id=${for} @{list} + +Hint for + [arguments] ${title} ${extra_keyword} @{list} + Run Keyword ${extra_keyword} xpath=//label[contains(., "${title}")]/following::div[@class="form-text"] @{list} + +Error for + [arguments] ${title} ${extra_keyword} @{list} + Run Keyword ${extra_keyword} xpath=//label[contains(., "${title}")]/following::div[@class="invalid-feedback"] @{list} diff --git a/Products/PasswordStrength/tests/password_inline_validation.robot b/Products/PasswordStrength/tests/password_inline_validation.robot deleted file mode 100644 index 7b105b5..0000000 --- a/Products/PasswordStrength/tests/password_inline_validation.robot +++ /dev/null @@ -1,90 +0,0 @@ -*** Settings *** -Resource plone/app/robotframework/keywords.robot -Resource plone/app/robotframework/selenium.robot -Resource plone/app/robotframework/saucelabs.robot -Resource Products/PasswordStrength/tests/common.robot - -Library Remote ${PLONE_URL}/RobotRemote -Library plone.app.robotframework.keywords.Debugging - -Test Setup Test Setup -Test Teardown Close all browsers - -*** Test Cases *** - -Test register form - Go to ${PLONE_URL}/@@register - # Contains password description ? - Hint for Password Element should contain Minimum 1 capital letter. - # Fill form - Input for User name Input text rocky - Input for E-mail Input text rocky@balboa.com - # Reacts with bad password - Input for Password Input text 12345 - Input for Confirm password Input text 12345 - Input for Confirm password Click element - Error for Password Element should be visible - Error for Password Element should contain This password doesn't match requirements for passwords - Error for Confirm password Element should be visible - Error for Confirm password Element should contain This password doesn't match requirements for passwords - # Accepts well formed password - Input for Password Input text ABCDEFGHIJabcdefghij1! - Input for Confirm password Input text ABCDEFGHIJabcdefghij1! - Click element css=#formfield-form-email - Error for Password Element should not be visible - Error for Confirm password Element should not be visible - Click button Register - # Redirected - Wait until page contains Welcome 5 - Element should contain css=h1.documentFirstHeading Welcome - -Test new user form - Enable autologin as Manager - Go to ${PLONE_URL}/@@new-user - # Contains password description ? - Hint for Password Element should contain Minimum 1 capital letter. - # Fill form - Input for User Name Input text rocky - Input for E-mail Input text rocky@balboa.com - # Reacts with bad password - Input for Password Input text 12345 - Input for Confirm password Input text 12345 - Input for Confirm password Click element - Error for Password Element should be visible - Error for Password Element should contain This password doesn't match requirements for passwords - Error for Confirm password Element should be visible - Error for Element should contain This password doesn't match requirements for passwords - # Accepts well formed password - Input for Password Input text ABCDEFGHIJabcdefghij1! - Input for Confirm password Input text ABCDEFGHIJabcdefghij1! - Click element Add user - Error for Password Element should not be visible - Error for Confirm password Element should not be visible - Click button Register - # Redirected - Wait until page contains Users and Groups 5 - Element should contain css=h1.documentFirstHeading Users and Groups - Page should contain element css=input[value="rocky"] - Disable autologin - -Test change password form with inline validation - Test change password form - -Test register form without password with inline validation - Test register form without password - -Test reset form with inline validation - Test reset form - -*** Keywords *** -Test Setup - Open SauceLabs test browser - Go to ${PLONE_URL} - Enable autologin as Manager - The self registration enabled - The mail setup configured - Own passwords registration enabled - The inline validation enabled - ${plone_version} = Get plone version - Set global variable ${plone_version} - Disable autologin diff --git a/Products/PasswordStrength/tests/robot_setup.py b/Products/PasswordStrength/tests/robot_setup.py index 3bd11c8..b1c315d 100644 --- a/Products/PasswordStrength/tests/robot_setup.py +++ b/Products/PasswordStrength/tests/robot_setup.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- from plone.app.robotframework.remote import RemoteLibrary - -from zope.component.hooks import getSite -from Products.CMFCore.utils import getToolByName from Products.PasswordStrength.testing import PLONE_VERSION @@ -10,34 +7,12 @@ class PasswordStrengthRemoteKeywords(RemoteLibrary): """Robot Framework remote keywords library """ - def the_inline_validation_disabled(self): - portal = getSite() - jst = getToolByName(portal, 'portal_javascripts') - js = jst.getResource('inline_validation.js') - if js and js.getEnabled(): - js.setEnabled(False) - - def the_inline_validation_enabled(self): - portal = getSite() - jst = getToolByName(portal, 'portal_javascripts') - js = jst.getResource('inline_validation.js') - if js and not js.getEnabled(): - js.setEnabled(True) - def get_plone_version(self): return PLONE_VERSION def extract_reset_url(self, mail): - url = '' - for line in mail.split('\n'): - line = line.strip() - if line.startswith('http'): - url = line[line.index('passwordreset'):] - if url.endswith('='): - url = url[:-1] - continue - else: - return url - elif url: - return url + line - return '' + mail = mail.replace('=\r\n', '').replace('=\n', '') + assert("passwordreset/" in mail) + mail = mail[mail.index('passwordreset/'):] + url = mail.split()[0] + return url diff --git a/Products/PasswordStrength/tests/test_robot.py b/Products/PasswordStrength/tests/test_robot.py index 812f275..a715573 100644 --- a/Products/PasswordStrength/tests/test_robot.py +++ b/Products/PasswordStrength/tests/test_robot.py @@ -1,10 +1,9 @@ -import unittest2 as unittest - -import robotsuite from Products.PasswordStrength.testing import ROBOT_TESTING -from Products.PasswordStrength.testing import PLONE_VERSION from plone.testing import layered +import robotsuite +import unittest + def test_suite(): suite = unittest.TestSuite() @@ -12,10 +11,4 @@ def test_suite(): layered(robotsuite.RobotTestSuite('password.robot'), layer=ROBOT_TESTING), ]) - # inline validation - if PLONE_VERSION >= '4.3': - suite.addTests([ - layered(robotsuite.RobotTestSuite('password_inline_validation.robot'), - layer=ROBOT_TESTING), - ]) return suite diff --git a/Products/PasswordStrength/tests/test_setup.py b/Products/PasswordStrength/tests/test_setup.py index eb88ab4..e414f5c 100644 --- a/Products/PasswordStrength/tests/test_setup.py +++ b/Products/PasswordStrength/tests/test_setup.py @@ -1,10 +1,15 @@ # -*- coding: utf-8 -*- """Setup/installation tests for this package.""" - -import unittest2 as unittest - from Products.PasswordStrength.testing import INTEGRATION_TESTING from Products.PasswordStrength.testing import PLONE_VERSION +from plone import api + +import unittest + +try: + from Products.CMFPlone.utils import get_installer +except ImportError: + get_installer = None class TestInstall(unittest.TestCase): @@ -14,18 +19,21 @@ class TestInstall(unittest.TestCase): def setUp(self): super(TestInstall, self).setUp() self.portal = self.layer['portal'] - self.installer = self.portal.portal_quickinstaller + if get_installer: + self.installer = get_installer(self.portal, self.layer['request']) + else: + self.installer = api.portal.get_tool('portal_quickinstaller') self.acl = self.portal.acl_users self.setup = self.portal.portal_setup def test_product_installed(self): - self.assertTrue(self.installer.isProductInstalled('PasswordStrength')) + self.assertTrue(self.installer.is_product_installed('PasswordStrength')) auth_plugins = self.acl.plugins.getAllPlugins(plugin_type='IValidationPlugin') self.assertEqual(auth_plugins['active'], ('password_strength_plugin', )) def test_uninstall(self): - self.installer.uninstallProducts(['PasswordStrength']) - self.assertFalse(self.installer.isProductInstalled('PasswordStrength')) + self.installer.uninstall_product('PasswordStrength') + self.assertFalse(self.installer.is_product_installed('PasswordStrength')) self.setup.runAllImportStepsFromProfile('profile-Products.PasswordStrength:uninstall') auth_plugins = self.acl.plugins.getAllPlugins(plugin_type='IValidationPlugin') if PLONE_VERSION >= '4.3': diff --git a/README.rst b/README.rst index 81d19e2..727e5c4 100644 --- a/README.rst +++ b/README.rst @@ -11,35 +11,27 @@ passwords during user registration. For example these rules can ensure a passwords strength such as minimum length and required letters or special characters. -In Plone 4.3 and above this plugin works directly with Plones inbuilt -password policy api. In Plone 4.2 and below this plugin contains a patch -to plone to use PAS validation. Tests ===== -This package is tested using Travis CI on Plone 4.1, 4.2, 4.3 -The current status is : - -.. image:: https://travis-ci.org/collective/Products.PasswordStrength.png - :target: http://travis-ci.org/collective/Products.PasswordStrength +This package is tested using Travis CI on Plone 5.2 and 6.0. +For older Requires ======== - PlonePAS and its dependencies - - Plone 4.1, 4.2 or 4.3 - - better: Products.PasswordResetTool >= 2.0.18 (clearer password reset mail) - - better: plone.app.locales >= 4.3.5 (clearer translations in password reset mail) + - Plone 5.2 or 6.0 + - For Plone 4.1, 4.2, 4.3 , 5.0 and 5.1 use Versions <> 0.5 or source-checkouts. Installation ============ -1. Install Products.PasswordStrength using buildout like any other Plone plugin. -2. Once activated within your site you select ZMI > acl_users > password_strength_plugin -3. Click on the properties tab and edit the validation rules. The rule error text will be used for both - the password field hint to tell the user what kind of password they can pick, and also if they fail - to enter a password that matches that rule. +1. Add Products.PasswordStrength to your buildout like any other Plone plugin. +2. Add Products.PasswordStrength in the addon-controlpanel (prefs_install_products_form) +3. You can configure the plugin in teh ZMI in + /acl_users/password_strength_plugin/manage_propertiesForm That's it! Test it out. @@ -50,18 +42,10 @@ A PAS plugin for Validation checks the password against each regular expression listed in the properties. Any rules that fail result in the associated error messages being returned. -Plone doesn't use PAS to validate passwords, so included is a patch to -Products.CMFPlone.RegistrationTool.RegistrationTool.testPasswordValidity -which makes plone use PAS validation plugins. - TODO ==== -1. Patch or modify login_password.cpt to display directly the password constraints - (
- Enter your new password.
) - -2. Do password expiration +1. Do password expiration? Contribute @@ -90,3 +74,4 @@ Thanks to the following for improvements to this plugin: - pysailor - regebro - macagua +- pbauer diff --git a/base.cfg b/base.cfg index 27a4602..ac3b4e0 100644 --- a/base.cfg +++ b/base.cfg @@ -1,11 +1,9 @@ [buildout] -extends = - versions.cfg package-name = Products.PasswordStrength package-extras = [test] test-eggs = show-picked-versions = true -allow-picked-versions = false +allow-picked-versions = true versions = versions @@ -15,7 +13,7 @@ extensions = auto-checkout = parts += - omelette +# omelette robot [instance] @@ -28,6 +26,9 @@ eggs = ${instance:eggs} ${test:eggs} +[code-analysis] +directory= ${buildout:directory}/Products.PasswordStrength + [remotes] collective = git://github.com/collective collective_push = git@github.com:collective @@ -40,3 +41,12 @@ eggs = Pillow ${test:eggs} plone.app.robotframework [debug] + +[versions] +Products.PasswordStrength = +# For Buildout related packages, it is easiest to keep them at the same version for all environments. +# Keep these empty will ensure it uses what got installed by requirements.txt +setuptools = +wheel = +zc.buildout = +pip = diff --git a/bootstrap.py b/bootstrap.py deleted file mode 100644 index a629566..0000000 --- a/bootstrap.py +++ /dev/null @@ -1,189 +0,0 @@ -############################################################################## -# -# Copyright (c) 2006 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Bootstrap a buildout-based project - -Simply run this script in a directory containing a buildout.cfg. -The script accepts buildout command-line options, so you can -use the -c option to specify an alternate configuration file. -""" - -import os -import shutil -import sys -import tempfile - -from optparse import OptionParser - -tmpeggs = tempfile.mkdtemp() - -usage = '''\ -[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] - -Bootstraps a buildout-based project. - -Simply run this script in a directory containing a buildout.cfg, using the -Python that you want bin/buildout to use. - -Note that by using --find-links to point to local resources, you can keep -this script from going over the network. -''' - -parser = OptionParser(usage=usage) -parser.add_option("-v", "--version", help="use a specific zc.buildout version") - -parser.add_option("-t", "--accept-buildout-test-releases", - dest='accept_buildout_test_releases', - action="store_true", default=False, - help=("Normally, if you do not specify a --version, the " - "bootstrap script and buildout gets the newest " - "*final* versions of zc.buildout and its recipes and " - "extensions for you. If you use this flag, " - "bootstrap and buildout will get the newest releases " - "even if they are alphas or betas.")) -parser.add_option("-c", "--config-file", - help=("Specify the path to the buildout configuration " - "file to be used.")) -parser.add_option("-f", "--find-links", - help=("Specify a URL to search for buildout releases")) -parser.add_option("--allow-site-packages", - action="store_true", default=False, - help=("Let bootstrap.py use existing site packages")) -parser.add_option("--setuptools-version", - help="use a specific setuptools version") - - -options, args = parser.parse_args() - -###################################################################### -# load/install setuptools - -try: - if options.allow_site_packages: - import setuptools - import pkg_resources - from urllib.request import urlopen -except ImportError: - from urllib2 import urlopen - -ez = {} -exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) - -if not options.allow_site_packages: - # ez_setup imports site, which adds site packages - # this will remove them from the path to ensure that incompatible versions - # of setuptools are not in the path - import site - # inside a virtualenv, there is no 'getsitepackages'. - # We can't remove these reliably - if hasattr(site, 'getsitepackages'): - for sitepackage_path in site.getsitepackages(): - sys.path[:] = [x for x in sys.path if sitepackage_path not in x] - -setup_args = dict(to_dir=tmpeggs, download_delay=0) - -if options.setuptools_version is not None: - setup_args['version'] = options.setuptools_version - -ez['use_setuptools'](**setup_args) -import setuptools -import pkg_resources - -# This does not (always?) update the default working set. We will -# do it. -for path in sys.path: - if path not in pkg_resources.working_set.entries: - pkg_resources.working_set.add_entry(path) - -###################################################################### -# Install buildout - -ws = pkg_resources.working_set - -cmd = [sys.executable, '-c', - 'from setuptools.command.easy_install import main; main()', - '-mZqNxd', tmpeggs] - -find_links = os.environ.get( - 'bootstrap-testing-find-links', - options.find_links or - ('http://downloads.buildout.org/' - if options.accept_buildout_test_releases else None) - ) -if find_links: - cmd.extend(['-f', find_links]) - -setuptools_path = ws.find( - pkg_resources.Requirement.parse('setuptools')).location - -requirement = 'zc.buildout' -version = options.version -if version is None and not options.accept_buildout_test_releases: - # Figure out the most recent final version of zc.buildout. - import setuptools.package_index - _final_parts = '*final-', '*final' - - def _final_version(parsed_version): - try: - return not parsed_version.is_prerelease - except AttributeError: - # Older setuptools - for part in parsed_version: - if (part[:1] == '*') and (part not in _final_parts): - return False - return True - - index = setuptools.package_index.PackageIndex( - search_path=[setuptools_path]) - if find_links: - index.add_find_links((find_links,)) - req = pkg_resources.Requirement.parse(requirement) - if index.obtain(req) is not None: - best = [] - bestv = None - for dist in index[req.project_name]: - distv = dist.parsed_version - if _final_version(distv): - if bestv is None or distv > bestv: - best = [dist] - bestv = distv - elif distv == bestv: - best.append(dist) - if best: - best.sort() - version = best[-1].version -if version: - requirement = '=='.join((requirement, version)) -cmd.append(requirement) - -import subprocess -if subprocess.call(cmd, env=dict(os.environ, PYTHONPATH=setuptools_path)) != 0: - raise Exception( - "Failed to execute command:\n%s" % repr(cmd)[1:-1]) - -###################################################################### -# Import and run buildout - -ws.add_entry(tmpeggs) -ws.require(requirement) -import zc.buildout.buildout - -if not [a for a in args if '=' not in a]: - args.append('bootstrap') - -# if -c was provided, we push it back into args for buildout' main function -if options.config_file is not None: - args[0:0] = ['-c', options.config_file] - -zc.buildout.buildout.main(args) -shutil.rmtree(tmpeggs) diff --git a/buildout-4.1.cfg b/buildout-4.1.cfg deleted file mode 100644 index e83ec42..0000000 --- a/buildout-4.1.cfg +++ /dev/null @@ -1,18 +0,0 @@ -[buildout] -extends = - https://raw.githubusercontent.com/collective/buildout.plonetest/master/test-4.1.x.cfg - base.cfg - -[instance] -eggs += - Pillow - -[versions] -Pillow = 2.4.0 -zc.buildout = 1.7.1 -collective.recipe.template = 1.09 -decorator = 3.4.0 -docutils = 0.8.1 -six = 1.6.1 -plone.api = 1.1.0 - diff --git a/buildout-4.2.cfg b/buildout-4.2.cfg deleted file mode 100644 index 8b2e1fe..0000000 --- a/buildout-4.2.cfg +++ /dev/null @@ -1,17 +0,0 @@ -[buildout] -extends = - https://raw.githubusercontent.com/collective/buildout.plonetest/master/test-4.2.x.cfg - base.cfg - -[instance] -eggs += - Pillow - -[versions] -Pillow = 2.4.0 -collective.recipe.template = 1.09 -decorator = 3.4.0 -six = 1.6.1 -babel = 1.3 -plone.api = 1.1.0 - diff --git a/buildout-4.3.cfg b/buildout-4.3.cfg deleted file mode 100644 index 3d74b20..0000000 --- a/buildout-4.3.cfg +++ /dev/null @@ -1,7 +0,0 @@ -[buildout] -extends = - https://raw.githubusercontent.com/collective/buildout.plonetest/master/test-4.3.x.cfg - base.cfg - -[versions] -plone.api = 1.1.0 diff --git a/buildout-5.0.cfg b/buildout-5.0.cfg deleted file mode 100644 index 3518525..0000000 --- a/buildout-5.0.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[buildout] -extends = - https://raw.githubusercontent.com/collective/buildout.plonetest/master/test-5.0.x.cfg - base.cfg diff --git a/buildout.cfg b/buildout.cfg deleted file mode 100644 index c56d16a..0000000 --- a/buildout.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[buildout] -extends = - https://raw.githubusercontent.com/collective/buildout.plonetest/master/test-5.0.x.cfg - base.cfg - diff --git a/refresh.txt b/refresh.txt deleted file mode 100644 index e69de29..0000000 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..64c3fcc --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +setuptools==44.1.1 +zc.buildout==3.0.0rc1 +wheel==0.37.1 +pip==20.3.4 diff --git a/setup.py b/setup.py index 68932f2..d54dcfe 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages import os -version = '0.4.1.dev0' +version = '0.5.0.dev0' def read(*rnames): @@ -24,16 +24,18 @@ def read(*rnames): "Development Status :: 5 - Production/Stable", "Environment :: Plugins", "Environment :: Web Environment", - "Framework :: Plone :: 4.1", - "Framework :: Plone :: 4.2", - "Framework :: Plone :: 4.3", + "Framework :: Plone :: 5.2", + "Framework :: Plone :: 6.0", "Framework :: Zope2", "Intended Audience :: System Administrators", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Programming Language :: Zope", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Systems Administration", @@ -52,8 +54,8 @@ def read(*rnames): 'setuptools', 'plone.api', 'collective.monkeypatcher', - # -*- Extra requirements: -*- - # Products.PluggableAuthService is a dep, but can't be explicit in Plone 3. + 'six', + 'z3c.jbot', ], extras_require={ 'test': [ @@ -61,7 +63,6 @@ def read(*rnames): 'plone.app.testing', 'plone.browserlayer', 'robotsuite', - 'unittest2', ], }, entry_points=""" diff --git a/test-5.2.x.cfg b/test-5.2.x.cfg new file mode 100644 index 0000000..f99e105 --- /dev/null +++ b/test-5.2.x.cfg @@ -0,0 +1,12 @@ +[buildout] +extends = + https://raw.githubusercontent.com/collective/buildout.plonetest/master/test-5.2.x.cfg + base.cfg + +[versions] +# block all incoming versions from the extends, we set up our env beforehand with the +# correct versions +pip = +setuptools = +zc.buildout = +wheels = diff --git a/test-6.0.x.cfg b/test-6.0.x.cfg new file mode 100644 index 0000000..b23fe46 --- /dev/null +++ b/test-6.0.x.cfg @@ -0,0 +1,12 @@ +[buildout] +extends = + https://raw.githubusercontent.com/collective/buildout.plonetest/master/test-6.0.x.cfg + base.cfg + +[versions] +# block all incoming versions from the extends, we set up our env beforehand with the +# correct versions +pip = +setuptools = +zc.buildout = +wheels = diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..14ebede --- /dev/null +++ b/tox.ini @@ -0,0 +1,23 @@ +[tox] +minversion = 3.18 +envlist = + plone52-py{27,36,37,38} + plone60-py{37,38,39} + +[testenv] +# We do not install with pip, but with buildout: +usedevelop = false +skip_install = true +deps = + -r requirements.txt +setenv = + ROBOT_BROWSER=chromedriver +commands_pre = + plone52: {envbindir}/buildout -Nc {toxinidir}/test-5.2.x.cfg buildout:directory={envdir} buildout:develop={toxinidir} install test + plone60: {envbindir}/buildout -Nc {toxinidir}/test-6.0.x.cfg buildout:directory={envdir} buildout:develop={toxinidir} install test +commands = + export DISPLAY=:99.0 + chromedriver --url-base=/wd/hub & + sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & + sleep 2 + {envbindir}/test diff --git a/travis.cfg b/travis.cfg deleted file mode 100644 index 0bca35a..0000000 --- a/travis.cfg +++ /dev/null @@ -1,11 +0,0 @@ -[buildout] -extends = - https://raw.github.com/collective/buildout.plonetest/master/travis-4.x.cfg - https://raw.github.com/collective/buildout.plonetest/master/qa.cfg - buildout.cfg - -parts += - createcoverage - coverage-sh - -package-min-coverage = 80 diff --git a/version.txt b/version.txt deleted file mode 100644 index 53c6424..0000000 --- a/version.txt +++ /dev/null @@ -1 +0,0 @@ -0.3 git/dev diff --git a/versions.cfg b/versions.cfg deleted file mode 100644 index 3fe6d79..0000000 --- a/versions.cfg +++ /dev/null @@ -1,17 +0,0 @@ -[versions] -zc.buildout = 2.3.1 -setuptools = 17.0 - -Babel = 1.3 -babel = 1.3 -createcoverage = 1.3.2 -#plone.api = 1.1.0 -#Pygments = 1.6 -plone.app.robotframework = 0.9.9 -robotframework = 2.8.7 -robotframework-debuglibrary = 0.3 -robotframework-selenium2library = 1.6.0 -robotframework-selenium2screenshots = 0.5.0 -robotsuite = 1.6.1 -selenium = 2.45.0 -sphinxcontrib-robotframework = 0.5.0