Skip to content

Commit

Permalink
test(auth): Zope root cookie login form auth
Browse files Browse the repository at this point in the history
Integrate upstream `Products.PluggableAuthService` and `Products.PlonePAS` changes to
the Zope root `/acl_users`:

- fix some broken plugin configuration on install and upgrade

- add a `GenericSetup` profile to convert to a simple cookie login form which fixes
  logout issues when used with other plugins such as the JWT token plugin

Note that the only changes here are adding test coverage confirming the upstream changes
have the intended effect when combined with the JWT token plugin.

Upstream links:

zopefoundation/Products.PluggableAuthService#107 (comment)
zopefoundation/Products.PluggableAuthService@44ac67f
  • Loading branch information
rpatterson committed Feb 14, 2022
1 parent f9097a0 commit 971b3b1
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 0 deletions.
4 changes: 4 additions & 0 deletions base.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ parts =
develop = .
sources-dir = extras
auto-checkout =
Products.PlonePAS
# plone.rest

allow-hosts =
Expand All @@ -35,6 +36,8 @@ allow-hosts =
[versions]
# Do not use a release of plone.restapi:
plone.restapi =
# Fix Zope root `/acl_users` logout
Products.PluggableAuthService = 2.7.0

[instance]
recipe = plone.recipe.zope2instance
Expand Down Expand Up @@ -204,6 +207,7 @@ output = ${buildout:directory}/bin/zpretty-run
mode = 755

[sources]
Products.PlonePAS = git https://github.com/plone/Products.PlonePAS.git pushurl=git@github.com:plone/Products.PlonePAS.git branch=feat-zope-root-cookie-challenge
plone.rest = git git://github.com/plone/plone.rest.git pushurl=git@github.com:plone/plone.rest.git branch=master
plone.schema = git git://github.com/plone/plone.schema.git pushurl=git@github.com:plone/plone.schema.git branch=master
Products.ZCatalog = git git://github.com/zopefoundation/Products.ZCatalog.git pushurl=git@github.com:zopefoundation/Products.ZCatalog.git
115 changes: 115 additions & 0 deletions src/plone/restapi/tests/test_functional_auth.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from plone.app import testing as pa_testing
from plone.app.testing import login
from plone.app.testing import setRoles
from plone.app.testing import SITE_OWNER_NAME
from plone.app.testing import TEST_USER_ID
from plone.app.testing import TEST_USER_NAME
from plone.app.testing import TEST_USER_PASSWORD
from plone.restapi.testing import PLONE_RESTAPI_DX_FUNCTIONAL_TESTING
from plone.testing import zope
from Products.PluggableAuthService.interfaces import plugins as plugins_ifaces
from Products.PluggableAuthService.plugins import CookieAuthHelper

import base64
import requests
Expand All @@ -17,14 +21,28 @@ class TestFunctionalAuth(unittest.TestCase):
layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING

def setUp(self):
"""
Set initial test conditions and convenience attributes.
"""
# Zope root references
self.app = self.layer["app"]
self.root_acl_users = self.app.acl_users

# Plone portal references
self.portal = self.layer["portal"]
self.portal_url = self.portal.absolute_url()

# User permissions and authentication
setRoles(self.portal, TEST_USER_ID, ["Manager"])
login(self.portal, SITE_OWNER_NAME)

# Create a page that can't be publicly accessed
self.private_document = self.portal[
self.portal.invokeFactory("Document", id="doc1", title="My Document")
]
self.private_document_url = self.private_document.absolute_url()

# This is a functional fixture, have to commit our changes
transaction.commit()

def test_login_without_credentials_fails(self):
Expand Down Expand Up @@ -172,6 +190,66 @@ def test_api_login_grants_zmi(self):
"Wrong ZMI view response content",
)

def test_root_cookie_login_sets_api_token(self):
"""
Zope root ZMI uses cookie login form and also sets the API token cookie.
"""
# Install the GenericSetup profile that performs the actual switch
pa_testing.applyProfile(self.portal, "Products.PlonePAS:root-cookie")
transaction.commit()

# Check the Zope root PAS plugin configuration
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",
)
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",
)

# Submit the login form in the browser
browser = zope.Browser(self.app)
browser.open(self.app.absolute_url() + "/manage_main")
login_form = browser.getForm()
login_form.getControl(name="__ac_name").value = SITE_OWNER_NAME
login_form.getControl(name="__ac_password").value = 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",
)

# Both the Zope root login cookie and the API token cookie are set
self.assertIn(
"__ac",
browser.cookies,
"Zope root auth cookie missing from Zope root basic login form response",
)
self.assertIn(
"auth_token",
browser.cookies,
"API token cookie missing from Zope root basic login form response",
)

def test_root_zmi_login_grants_api(self):
"""
Logging in via the Zope root ZMI also grants access to the API.
Expand Down Expand Up @@ -291,6 +369,43 @@ def test_api_logout_expires_both_cookies(self):
"API token cookie remains after API logout POST response",
)

def test_root_zmi_logout_expires_api_token(self):
"""
Zope root ZMI logout succeeds and deletes all auth cookies.
"""
# Install the GenericSetup profile that performs the actual switch
pa_testing.applyProfile(self.portal, "Products.PlonePAS:root-cookie")
transaction.commit()

# Submit the login form in the browser
browser = zope.Browser(self.app)
browser.open(self.app.absolute_url() + "/manage_main")
login_form = browser.getForm()
login_form.getControl(name="__ac_name").value = SITE_OWNER_NAME
login_form.getControl(name="__ac_password").value = TEST_USER_PASSWORD
login_form.controls[-1].click()

# Click the ZMI logout link
browser.raiseHttpErrors = False
logout_link = browser.getLink(url="manage_zmi_logout")
logout_link.click()
browser.raiseHttpErrors = True
self.assertNotIn(
"__ac",
browser.cookies,
"Zope root auth cookie missing from Zope root basic login form response",
)
self.assertNotIn(
"auth_token",
browser.cookies,
"API token cookie missing from Zope root basic login form response",
)

# Confirm the browser is, in fact, logged out
browser.open(self.app.absolute_url() + "/manage_main")
logout_login_form = browser.getForm()
logout_login_form.getControl(name="__ac_name")

def test_accessing_private_document_with_valid_token_succeeds(self):
# login and generate a valid token
response = requests.post(
Expand Down

0 comments on commit 971b3b1

Please sign in to comment.