Package: pas.plugins.headers
This PAS plugin reads request headers and uses them for authentication.
Think SAML headers that are set by a front web server like Apache or nginx.FeaturesOn install in Plone, the plugin is added to thePluggableAuthService.
It is registered for several plugin types:Challenge pluginExtraction pluginAuthentication pluginProperties pluginRoles pluginYou can configure them in the ZMI (Zope Management Interface) by editing properties in the plugin.InstallationInstall pas.plugins.headers by adding it to your buildout:[buildout]

...

eggs =
    pas.plugins.headersand then runningbin/buildout.
Start Plone and install the plugin in the Add-ons control panel.CompatibilityThis has been tested to work on Plone 4.3 and 5.1 and 5.2 (Python 2.7 and 3.7).Plain Zope?No, this does not work in plain Zope.
Theoretically it might work if you first installProducts.PluggableAuthServiceandProducts.GenericSetup.
But then you already almost have aCMFsite.Manual configurationYou can configure the plugin by going to the ZMI (Zope Management Interface).
Go to theacl_users(PluggableAuthService) folder in the Plone ZMI.
Click on therequest_headersplugin.
Go to the Properties tab.These are the properties that you can edit:userid_header:Header to use as user id.roles_header:Header to use as roles for the current user.
This can give multiple roles: it is split on white space.allowed_roles:Allowed roles.
Roles that we allow in theroles_header.
Any other roles that are in the header are ignored.
Ignored when empty: all roles are taken over, also when the role is not known in Plone.
The roles from the header and theallowed_rolesproperty are compared lowercase.
The reported roles use the case from``allowed_roles``.
For example, if the header containsPUPIL rootandallowed_rolescontainsPupil Teacher, then the reported roles will be onlyPupil.required_headers:Required headers.
Without these, the extraction plugin does not extract headers, so the user is not authenticated.
These headers may have an empty value in the request, as long as they are all there.deny_unauthorized:Deny unauthorized access.
Default: false.
When this is true and the user is unauthorized, the Challenge plugin stops and presents an ugly error.
When false, the Challenge plugin will check theredirect_url.redirect_url:URL to redirect to when unauthorized.
When empty, it has no effect.
When set, the Challenge plugin redirects unauthorized users to this url.
Usually you would configure your frontend server to force SAML or CAS login on this url.
Theheaderloginpage defined by this plugin may be a good url.
We treat the redirect_url as relative to the Plone site root, unless it is a full url.
Ifdeny_unauthorizedis true, this option is ignored.memberdata_to_header:Mapping from memberdata property to request header, one per row.
Format:propname|header.
Or:propname|header|parser.
See theParserssection for information about parsers
You can also combine several headers:propname|header_with_firstname header_with_lastname.create_ticket:Create an authentication ticket (__accookie) withplone.session.
Default: false.
When reading headers, this checks if Plone knows this user.
If so, we create an authentication ticket.
Then you could let your frontend server (nginx, Apache) only set the headers for some urls, instead of for all.
Note that this does not work for root Zope users, and it does not take over properties and roles.
See alsoissue 6.default_roles:Default roles to add when a user is successfully authenticated by this plugin.
You always automatically get the Authenticated role.
But you may want to give everyone the Member role.
This is not checked against the allowed roles.Configuration via GenericSetupThe package has a GS (GenericSetup) import step with idpas.plugins.headers.
In the GS profile of your add-on, it looks for a file with namepas.plugins.headers.json.
This file must contain a json string.
Full example:{
    "purge": true,
    "allowed_roles": [
        "Member",
        "Zebra"
    ],
    "create_ticket": true,
    "default_roles": [
        "Member"
    ],
    "deny_unauthorized": true,
    "memberdata_to_header": [
        "uid|HEADER_uid|lower",
        "fullname|HEADER_firstname HEADER_lastname"
    ],
    "redirect_url": "https://maurits.vanrees.org",
    "required_headers": [
        "uid",
        "test"
    ],
    "roles_header": "roles",
    "userid_header": "uid"
}Some remarks:When the contents cannot be parsed as json, or when the result is not a dictionary, aValueErroris raised.purgeis optional.  When it is true, the default settings are restored before handling the rest of the file.purgeis only valid for the entire file.
It does not work in individual lists.
So you cannot add one required header and keep the current ones.
You need to specify them all.The keys are the properties that you see in the ZMI.When an unknown key is used, it is silently ignored.Inmemberdata_to_header, the importer does not check if the parsers are registered.ParsersIn thememberdata_to_headerproperty, you can use parsers.
For example:age|HEADER_age|intWhen getting the properties for the current user, the properties plugin will calculate theageproperty.
It reads theHEADER_ageheader, which may give a string like'42'.
It then calls theintparser to turn this into integer42.Note: the properties plugin is currently the only part where the parsers are used.
So it is not used when getting for example the user id from a header.If you specify a parser that does not exist, the parser is ignored and you get the unmodified header value.A few basic parsers are available:bool:Returns either True or False.
When the first character of the lowercase header value isy/j/t/1, the parser return True, else False.int:Returns an integer.
When parsing as integer fails, it returns zero.lower:Returns the value in lowercase.upper:Returns the value in uppercase.split:Splits the value on whitespace, so you get a list.You can register an own parser:def extra_parser(value):
    return value + ' extra'

from pas.plugins.headers.parsers import register_parser
register_parser('extra', extra_parser)Note: you get a warning when you override an existing parser.ContributeIssue tracker:https://github.com/collective/pas.plugins.headers/issuesSource code:https://github.com/collective/pas.plugins.headersSupportIf you are having issues, please let us know by adding an issue to the tracker:https://github.com/collective/pas.plugins.headers/issuesLicenseThe project is licensed under the GPLv2.ContributorsMaurits van Rees,m.van.rees@zestsoftware.nlChangelog1.5.0 (2022-01-06)Add support for being a Credentials Reset plugin.
This part is called when a user logs out.
[maurits]1.4.0 (2021-12-14)Added optiondefault_roles.
These roles are given when a user is successfully authenticated by this plugin.
You always automatically get the Authenticated role.
But you may want to give everyone the Member role.
This is not checked against the allowed roles.
[maurits]1.3.2 (2021-12-02)Add tox.ini, use GitHub Actions, test with Plone 4.3, 5.1, 5.2, 6.0.
[maurits]1.3.1 (2020-04-03)Python 3: whenredirect_urlunexpectedly is bytes, turn it into native string before comparing.
[maurits]Fixed problem withredirect_urlnot starting with a slash.
Withheaderloginas value we would redirect tohttp://localhost:8080/Ploneheaderlogin.
[maurits]1.3.0 (2020-03-26)We requiresix>=1.12.0because we useensure_str.
Note that Plone 4.3 and 5.1 currently pin older versions.
[maurits]Fixed NotFound error when redirect_url started with a slash, for example/headerlogin.
In local development it would redirect to /headerlogin on the Zope root, where it does not exist.
We now always treat the redirect_url as relative to the Plone site root, unless it is a full url.
[maurits]Fixed exportimport to always set native strings.
On Python 3 we were setting bytes, which was wrong.
Fixed same problem in member properties.
[maurits]1.2.0 (2020-02-24)Addcame_fromquery parameter when the challenge plugin redirects.
[maurits]Addedheaderloginpage.
This redirects to thecame_fromquery parameter or the referer or the navigation root.
You can use this in theredirect_urloption, and have your frontend server force SAML or CAS login on it.
When a user arrives on this page and is still anonymous, then apparently SAML/CAS has not worked.
We then redirect to the standard login page.
Care is taken to avoid a redirect loop between the login and headerlogin pages.
[maurits]Added optioncreate_ticket.  When reading headers, this checks if Plone knows this user.
If so, we create an authentication ticket (__accookie) withplone.session.
Then you could let your frontend server only set the headers for some urls, instead of for all.
Note that this does not work for root Zope users, and it does not take over properties and roles.
Seeissue 6.
[maurits]1.1.0 (2020-02-19)Added Plone 5.2, Python 3 compatibility.  [maurits]1.0.1 (2019-11-01)Replaced import ofGlobals, which is gone in Plone 5.2.  [maurits]1.0.0 (2019-04-25)Make final release, no changes.  [maurits]1.0.0a2 (2018-08-03)Documented ourGenericSetupimport step.
Unlike most importers, this reads ajsonfile, calledpas.plugins.headers.json.
[maurits]1.0.0a1 (2018-08-03)Initial release.
[maurits]