Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(setup): Zope root cookie auth and login form #65

Merged
merged 2 commits into from
Dec 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions news/65.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix broken Zope root `/acl_users` cookie plugin on `PlonePAS` install.
[rpatterson]
30 changes: 24 additions & 6 deletions src/Products/PlonePAS/setuphandlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
from Acquisition import aq_base
from Acquisition import aq_parent
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone import interfaces as plone_ifaces
from Products.PlonePAS import config
from Products.PlonePAS.plugins import cookie_handler
from Products.PlonePAS.interfaces import group as igroup
from Products.PlonePAS.interfaces.plugins import ILocalRolesPlugin
from Products.PlonePAS.interfaces.plugins import IUserIntrospection
Expand All @@ -13,9 +15,11 @@
from Products.PluggableAuthService.interfaces.plugins import IChallengePlugin
from Products.PluggableAuthService.interfaces.plugins \
import ICredentialsResetPlugin
from Products.PluggableAuthService.plugins import CookieAuthHelper
from Products.PluggableAuthService.plugins.RecursiveGroupsPlugin \
import addRecursiveGroupsPlugin
from plone.session.plugins.session import manage_addSessionPlugin
from zope import component
import logging

logger = logging.getLogger('PlonePAS setup')
Expand Down Expand Up @@ -196,11 +200,25 @@ def setupAuthPlugins(portal, pas, plone_pas,
login_path = crumbler.auto_login_page
cookie_name = crumbler.auth_cookie

found = uf.objectIds(['Extended Cookie Auth Helper'])
if not found:
plone_pas.manage_addExtendedCookieAuthHelper('credentials_cookie_auth',
cookie_name=cookie_name)
logger.debug("Added Extended Cookie Auth Helper.")
is_plone_site = plone_ifaces.IPloneSiteRoot.providedBy(portal)
if is_plone_site:
cookie_meta_type = cookie_handler.ExtendedCookieAuthHelper.meta_type
add_cookie_plugin = plone_pas.manage_addExtendedCookieAuthHelper
else:
# Can't use the `ExtendedCookieAuthHelper` outside of a Plone portal.
cookie_meta_type = CookieAuthHelper.CookieAuthHelper.meta_type
add_cookie_plugin = pas.addCookieAuthHelper
cookie_auth_ids = uf.objectIds(cookie_meta_type)
if not cookie_auth_ids:
add_cookie_plugin(
"credentials_cookie_auth",
cookie_name=cookie_name,
)
logger.debug(
"Added %r: %r",
cookie_meta_type,
"/".join(uf.credentials_cookie_auth.getPhysicalPath()),
)
rpatterson marked this conversation as resolved.
Show resolved Hide resolved
if deactivate_basic_reset:
disable = ['ICredentialsResetPlugin', 'ICredentialsUpdatePlugin']
else:
Expand All @@ -212,7 +230,7 @@ def setupAuthPlugins(portal, pas, plone_pas,
)

credentials_cookie_auth = uf._getOb('credentials_cookie_auth')
if 'login_form' in credentials_cookie_auth:
if is_plone_site and 'login_form' in credentials_cookie_auth:
credentials_cookie_auth.manage_delObjects(ids=['login_form'])
logger.debug("Removed default login_form from credentials cookie "
"auth.")
Expand Down
134 changes: 134 additions & 0 deletions src/Products/PlonePAS/tests/test_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# -*- coding: utf-8 -*-
from plone.app import testing as pa_testing
from plone.testing import zope
from zope.component import hooks
from Products.PlonePAS import testing
from Products.PluggableAuthService.interfaces import plugins as plugins_ifaces
from Products.PluggableAuthService.plugins import CookieAuthHelper
from Products.PluggableAuthService.plugins import HTTPBasicAuthHelper

import transaction
import unittest
import urllib.parse


class PortalSetupTest(unittest.TestCase):

layer = testing.PRODUCTS_PLONEPAS_FUNCTIONAL_TESTING

def setUp(self):
"""
Set up convenience references to test fixture from the layer.
"""
self.app = self.layer["app"]
self.root_acl_users = self.app.acl_users

def test_zope_root_default_challenge(self):
"""
The Zope root `/acl_users` default challenge plugin works.
"""
# Check the Zope root PAS plugin configuration
self.assertIn(
"credentials_basic_auth",
self.root_acl_users.objectIds(),
"Basic auth plugin missing from Zope root `/acl_users`",
)
basic_plugin = self.root_acl_users.credentials_basic_auth
self.assertIsInstance(
basic_plugin,
HTTPBasicAuthHelper.HTTPBasicAuthHelper,
"Wrong Zope root `/acl_users` basic auth plugin type",
)
challenge_plugins = self.root_acl_users.plugins.listPlugins(
plugins_ifaces.IChallengePlugin,
)
_, default_challenge_plugin = challenge_plugins[0]
self.assertEqual(
"/".join(default_challenge_plugin.getPhysicalPath()),
"/".join(basic_plugin.getPhysicalPath()),
"Wrong Zope root `/acl_users` default challenge plugin",
)

# Check the challenge response in the actual browser
browser = zope.Browser(self.app)
browser.raiseHttpErrors = False
browser.open(self.app.absolute_url() + "/manage_main")
self.assertEqual(
browser.headers["Status"].lower(),
"401 unauthorized",
"Wrong Zope root `/acl_users` default challenge response status",
)

def test_zope_root_cookie_login(self):
"""
The Zope root `/acl_users` cookie login works.
"""
# Make the cookie plugin the default auth challenge
self.assertIn(
"credentials_cookie_auth",
self.root_acl_users.objectIds(),
"Cookie auth plugin missing from Zope root `/acl_users`",
)
cookie_plugin = self.root_acl_users.credentials_cookie_auth
self.assertIs(
type(cookie_plugin.aq_base),
CookieAuthHelper.CookieAuthHelper,
"Wrong Zope root `/acl_users` cookie auth plugin type",
)
self.root_acl_users.plugins.activatePlugin(
plugins_ifaces.IChallengePlugin,
cookie_plugin.id,
)
self.root_acl_users.plugins.movePluginsTop(
plugins_ifaces.IChallengePlugin,
[cookie_plugin.id],
)
transaction.commit()
challenge_plugins = self.root_acl_users.plugins.listPlugins(
plugins_ifaces.IChallengePlugin,
)
_, default_challenge_plugin = challenge_plugins[0]
self.assertEqual(
"/".join(default_challenge_plugin.getPhysicalPath()),
"/".join(cookie_plugin.getPhysicalPath()),
"Wrong Zope root `/acl_users` default challenge plugin",
)

# Check the challenge response in the actual browser
browser = zope.Browser(self.app)
browser.open(self.app.absolute_url() + "/manage_main")
self.assertEqual(
browser.headers["Status"].lower(),
"200 ok",
"Wrong Zope root `/acl_users` cookie challenge response status",
)
login_form_url = urllib.parse.urlsplit(browser.url)
self.assertEqual(
login_form_url._replace(query="").geturl(),
cookie_plugin.absolute_url() + "/login_form",
"Wrong Zope root `/acl_users` cookie challenge redirect",
)

# Workaround the fact that the `zope.component` site is still the Plone portal
# when the test browser handles requests.
hooks.setSite(None)
zope.login(self.root_acl_users, pa_testing.SITE_OWNER_NAME)
self.app.REQUEST.form["__ac_name"] = pa_testing.SITE_OWNER_NAME
self.app.REQUEST.form["__ac_password"] = pa_testing.TEST_USER_PASSWORD
cookie_plugin.login()

# Submit the login form in the browser
login_form = browser.getForm()
login_form.getControl(name="__ac_name").value = pa_testing.SITE_OWNER_NAME
login_form.getControl(name="__ac_password").value = pa_testing.TEST_USER_PASSWORD
login_form.controls[-1].click()
self.assertEqual(
browser.headers["Status"].lower(),
"200 ok",
"Wrong Zope root `/acl_users` cookie login response status",
)
self.assertEqual(
browser.url,
self.app.absolute_url() + "/manage_main",
"Wrong Zope root `/acl_users` cookie login redirect",
)