External authentication with old-Zope

I've been attempting to develop a SAML solution for our old-Zope 2.10 off and on since September. I've been grateful for the help I've been given throughout this process.

There are complications preventing us from upgrading to Zope 2.12 or 2.13, and I've been unable to get dm.zope.saml2 installed. The advice given back in September was to use the Apache mod_auth_mellon module to perform the SAML SP duties, then write a PAS plugin. check, and check

It was an adventure even getting my plugin to show up in the Add-list, but it is there now, thankfully.

My issue is Mellon unpacks all the SAML Attributes from the SAML token into the environment, but Zope seems to clobber the environment before my plugin even runs.

If I do not start Zope and instead run a simple Apache website, I can run a simple WSGI application and grab the SAML attributes out of the environment like this:

from webob import dec, Response

@dec.wsgify
def application(req):
  user = {'uid': '', 'role': '', 'phone': '', 'lastName': '', 'firstName': ''}
  user['uid'] = req.environ['MELLON_urn:oid:0.9.2342.19200300.100.1.1']
  user['role'] = req.environ['MELLON_https://samltest.id/attributes/role']
  user['phone'] = req.environ['MELLON_urn:oid:2.5.4.20']
  user['lastName'] = req.environ['MELLON_urn:oid:2.5.4.4']
  user['firstName'] = req.environ['MELLON_urn:oid:2.5.4.42']
  #return Response(pprint.pformat(req.environ), content_type='application/json')
  return Response(pprint.pformat(user), content_type='application/json')

The Zope we run is Zope 2.10.6, but it appears it was originally installed using the Plone 3.1.1 universal installer. But, we do not run a Plone-site, we run Zope.

Is there a way to get the environment into PAS? I've tried having my plugin implement zope.publisher.interfaces.http.IHTTPApplicationRequest, but when my plugin runs, it simply ignores any code using that interface. I have 3 logging statements in my authenticateCredentials function, and only one of those statements logs anything.

The Zope documentation tells that non-CGI header names are renamed (e.g. they get a HTTP_ prefix). At least modern versions (much more modern than yours) can also drop headers.

First step is to check what headers arrive in request.environ in your case. I use a DTMLMethod for this with the source <dtml-var REQUEST>.
With such a method, you can determine whether the SAML headers have been renamed or dropped. If they have been renamed, you can adapt your plugin to the correct names.

1 Like

Note that HTTP in based on MIME. This implies that an HTTP header has the form name:value. Your example above indicates that your name contains a colon. To achieve this special syntax is required (to prevent the embedded colon to terminate name). Zope may not support this special syntax and misinterpret the headers containing a colon in their names.

Can you configure your "MELLON" component to use more typical header names (at least names without a colon).

No. Those are the SAML attribute names straight out of the token. The only thing Mellon does is prepend the "MELLON_" string. That prefix is configurable, but not the rest.

There is nothing SAML related in the "environ" section of what gets dumped by that DTML Method, other than the HTTP_REFERER, which it does list as the IDP.

The header processing is in ZServer.HTTPServer.zhttp_handler.get_environment. You could add logging there to find out whether the SAML headers reach this place. If you look at the header processing in this function, you will notice that it is not prepared to correctly process header names with embedded colons (the header name stops at the first colon). It also lacks support for quoted colons. This implies that you will need changes there to get your SAML headers through to your application.

If you do not see the SAML headers there, then either the frontend does not send them or they have already been filtered out (I doubt that the latter case occurs).

1 Like

This assumes that you use HTTP to connect your web server with Zope. Should you use a different interface, locate the header processing in its source code and intervene as necessary.

Is HTTPS considered different?
ZPublisher handles http and https in the same funcitons. ZServer has no mention of https.

Also, I've attempted to add logging to ZServer. Just like in my Plugin, the logging statements do not log anything.

The standard ZServer does not support "https" (there an add-on for that). Usually, there is a web server in front of Zope which handles "https" (and "http") and communicates with Zope via one of a variety of interfaces -- "http", "cgi", "fcgi", "wsgi", ...

I've tried searching for the HTTPS add-on for ZServer you mentioned. I'm not finding it. Could you please give some more info?
We're taking another look at updating to a newer Zope. We may have to go through the pain at this point.

Also, with a newer Zope, I understand WSGI is the new default server. Would there be any gotchas we should look for bringing our app built for old-Zope over?

https://m2crypto.readthedocs.io/en/latest/ZServerSSL-HOWTO.html

Usually, you put a web server before Zope. It can handle HTTPS and communicate via HTTP (or other protocols like WSGI, FCGI, ...) with Zope. Thus, you usually do not need direct HTTPS support by Zope.

The answer is to put something like this in the /etc/httpd/conf.d/mellon.conf file:

<Location />
    RequestHeader Set SAML_UID %{MELLON_urn:oid:0.9.2342.19200300.100.1.1}e
    RequestHeader Set SAML_EMAIL %{MELLON_urn:oid:0.9.2342.19200300.100.1.3}e
</Location>

Now I need to figure out my PAS plugin for real...

I feel like I owe the community an update.

We are still running Zope 2.10.6.
My PAS Plugin on its own did not work. Using the Apache config above, I was able to get my plugin code called, but for whatever reason Zope never authenticated the SAML user.
I wound up adding another case inside SessionCrumbler to get the SAML user authenticated in the __ac cookie. But once again, the SessionCrumber modification on its own was not enough.
The combination of the SessionCrumbler mod and my PAS Authentication Plugin are working to authenticate a SAML user within Zope.

The one thing that is still eluding me is getting SAML to ignore everything in the ZMI. Because the ZMI works by adding a suffix to each URL, and SAML works by managing a path and everything underneath that path, the two methodologies clash. I built a regex which works to ignore all the ZMI suffixes. However, when you click on a ZMI action button (the buttons at the bottom of the page), [ edit ] the form action is simply the URL, which triggers SAML management, which is not desirable.

Christopher Biessener via Plone Community wrote at 2022-5-26 14:16 +0000:

...
The one thing that is still eluding me is getting SAML to ignore everything in the ZMI. Because the ZMI works by adding a suffix to each URL, and SAML works by managing a path and everything underneath that path, the two methodologies clash. I built a regex which works to ignore all the ZMI suffixes. However, when you click on a ZMI action button (the buttons at the bottom of the page), the onclick action uses ':method' which I don't fully understand. These 'methods' still trigger SAML management, which is not desirable.

Use 2 access routes to your Zope:
an official one via your Web server using SAML authentication
and an internal one without SAML authentication (e.g. direct
Zope access).

Thanks for the advice.
Are you suggesting starting up a browser from the command line when we need ZMI access?
We have some clients where our access method does not facilitate this workflow.

Christopher Biessener via Plone Community wrote at 2022-5-26 19:40 +0000:

...

Are you suggesting starting up a browser from the command line when we need ZMI access?

No.

Zope serves HTTP requests at some port, say 8080.
You have put a Web server in front of Zope which serves
HTTP requests usually at port 80 and relays them to Zope.
In this setup, you have two natural routes to Zope:
one via the web server (port 80) and one direct (port 8080).

For ZMI access, you could directly talk to Zope (no SAML);
for other things, you go via the Web server (with SAML).
You do not need an additional browser, only use different
URLs for the 2 routes.