The challenge, User as Content and OAuth2 Login Integration

Preface

Recently, I've been searching for an addon that allows OAuath2 login integrations for Github, Linkedin, Facebook, Twitter and Google Plus. Surprisingly, there's a handy add-on called pas.plugins.authomatic, which is base on the famous authomatic project. Concerningly, the tests are failing. No problem right, it can be patched, fixed or improved, so I tried it out on a dev site. It worked!!!! However, it replaced and disabled the standard login mechanism for Plone, and created custom acl_users.

The Problem

  • Users created via the pas.plugins.authomatic aren't Members (membrane users), which defeats the purpose of dexterity.membrane add-on
  • Users who didn't use social logins to signup will not be able to log in the standard way since the conventional login method has been disabled.
  • plone.login does not work with pas.plugins.authomatic; therefore, custom redirects after login becomes another problem.

Possible Solutions

Create a dexterity.membrane.authomatic add-on

This add-on should turn acl_users to membrane users. Upon on logging in without the given role, the system redirects the user to the profile registration page, where they will complete their profile.

I think this should be optional, meaning, site admin should be able to enable or disable profile registration after login.

I've already started to work on this package. However, I do know how to convert an existing acl_user to membrane user as yet. I think I might have to write a function to copy all the member's property. Any advice?

I've already started to work on this project; it might be useful even outside pas.plugins.authomatic usage. In fact, it might best to merge it with the dexterity.membrane package.

Improving pas.plugins.authomatic

I think its easier to add the following features to pas.plugins.authomatic

  • Allow developers to add custom portal pas.plugin tool for handling the result from the various auth providers.
  • Allow developers to set the redirect URL after logging in with their auth provider or ensure that plone.login is working with pas.plugins.authomatic

Other important changes:

  • Make the redirection from /login to /authomatic-handler optional. If site admins want only social logins, they can do so. Otherwise, keep the standard login method and place the auth providers on the login page.
  • Use jbot to override the login.pt template and place the auth providers on the login page.

Conclusion

Making these changes will allow a seamless process of logging in with various auth providers and creating a profile if the user wishes or forced to do so based on site settings.

Any thoughts and suggestions?

I am the author of dm.zope.saml2 which provides the SAML2 browser SSO profile for Plone, something similar to OAuth2. I know therefore, the principal problems and potential solutions, even though I am unfamiliar with your specific PAS plugin.

"pas" stands for "Pluggable Authentication Service". Its principal philosophy is the splitting of the authentication/authorization domain into a (rather extensive) set of small tasks which are each implemented by plugins. The plugins can be mixed quite flexibly.

OAuth2, like SAML2, essentially targets the authentication subtask; likely, it also provides some properties. In my view, you should not add additional features to those plugins but implement them in other plugins (e.g. a user property plugin to integrate membrane properties).

I would approach your current task as follows: OAuth2 users are recognized (=authenticated) in your portal, but they are not full members. In order to become full members, they must register to your portal. The corresponding process completes the profile for those members. After that, those users continue to be authenticated via OAuth2, but they now are full portal members and get a mix of the foreign properties and the local properties as their properties.

@b4oshany I would recommend listening to @dieter and understanding PAS more. Making a plugin specific to membrane would seem a shame when you can make one that plays nice with other PAS plugins. the saml2 code has examples of creating real users on login I believe and that can be done with PAS.

I would also encourage improving pas.plugins.authomatic rather than creating a new plugin. Multiple plugins for the same purpose just makes life harder for new users.

I'd also consider if your needs could be served by plugins that do seperate parts of what you want, such as using more generic plugins like https://pypi.org/project/collective.onlogin/ ?

This is one area PAS is not working well with plone is that there is no API to allow multiple kinds of SSO at the same time. saml2 takes over the login rather than presenting extra buttons let users login directly vs use other providers. This is an api that should be added to the plone core I believe. Perhaps via a portlet/viewlet manager on the login page?
I really discourage using jbot to do this. It will make your plugin not play nice with either plone upgrades or other plugins wanting to do similar. As soon as you are using jbot you are probably hitting a limitation of the plone api. Spend the extra time to do a PLIP and make plone better.

That is not really the case: however, it shows how to give the created (temporary) user object properties derived from SAML2 attributes.

The standard Plone portal_membership typically makes all users (whereever they come from) into members by allowing to add additional properties via portal_memberdata. portal_memberdata is likely the standard Plone way to manage a member profile. Membrane might be an alternative to portal_memberdata.

The high flexibility of Plone (in general) and PAS (in particular) may have as result that it is very difficult to give good advice from the distance.

Of course, this is only the default behavior of the SAML2 plugin. You can easily register your own challenge plugin and present there an identity provider choice; then "redirect" to the chosen identity provider. The SAML2 plugin does not do this because it does not know which identity providers should be incorporated and how to get them to perform an identification. It would burden the SAML2 plugin to provide configuration and implementation for this (general rather than SAML2 specific) feature. The PAS flexibility allows to strongly focus the functions of a plugin (e.g. to SAML2 related features for the SAML2 plugin).

You are right. I've misremembered it. In fact we've had problems with this feature of saml2 because you can create a local user with the same userid as the saml2 login but they aren't the same. Which means our admin users have to have both a local account and a saml2 account and be taught to use /login_form directly when they want admin access.

Probably there is a PAS way to fix this I guess by changing the order of the plugins most likely.

Having a generic PAS plugin that does create users using a given PAS plugin is perhaps not hard to create though.

Thanks, @dieter, SSO is another topic I wanted to touch on. I will definitely look into dm.zope.saml2 as *THE SSO PACKAGE.

I agree, in fact, this is the approach I'm taking with pas.plugin.authomatic add-on. The add-on will authenticate OAuth2 users to log-in and browse the site. However, in order for them to become a member, they should complete (register) their profile. Upon completing their profiles, the dexterity.membrane.authomatic will create a membrane user from their OAuth2 identities.

The pas.plugins.authomatic does the same thing. I think I used the wrong term in my initial post; I should not have said acl_users when it's actual users identities for OAuth2 users. pas.plugins.authomatic does not create Members, it creates user identities and grants these identities access to the site as logged in users.

I completely agree. My initial thought was to isolate the creation of membrane users from OAuth2 user identities into a package of its own, especially since there are membrane specific issues to deal with. Also, I thought it was best to have a package that installs dexterity.rmembrane, pas.plugins.authomatic and any other package that's useful for Members as content with social media login. However, for cross-functionality to create acl_users or membrane users from user identities (OAuth2, saml and other auth user identities), it is best to have a general purpose package with zcml:conditions. For instance, the package should have modules for saml and authmotic, which should only be available for use if their respective profiles are installed as seen below:

<include package=".saml" zcml:condition="have   dm.zope.saml2" />
<include package=".authomatic" zcml:condition="have   pas.plugins.authomatic" />

Each module should have it's own genericsetup:registerProfile profile to install their respective control panel and method of converting user identities to Members.

I completely agree. I cringe at JBot. I couldn't think of a friendly approach to resolve this issue. Viewlet should work. However, all the various technologies would have to agree to this instead of fighting over redirection and template override of the login page. If they don't then I don't know how I'm going to resolve having pas.plugin.authomatic and dm.zope.saml2 redirections.

Based on the code I've read from various PAS projects, that's a most likely no.

Thanks for the insights @djay and @dieter

You misunderstand. Change Plone. Give it the ability to for plugins to register different alternate login options on the login page. Or something similar.

Of course, users identified by different identity providers must be different (I would consider it a severe bug, if they could be confused). And as different identity providers are (in principle) independent, one cannot expect that base user ids are distinct. Therefore, the SAML2 plugin constructs artificial user ids, combinding the identity provider and its local uiser id.

By using special plugins (for the association of roles or the assignment to groups), you should be able to assign (e.g.) an admin role to a user identified by a foreign identity provider. Likeliy, this is not easy at the moment, because the SAML2 plugin does not support the "IUserEnumeration" interface, likely used by many Plone UI parts for the assignment to groups and the association of roles. But, it should be straight forward to assign roles to any user and put it into any group in a script.

Actually, I didn't. I understand. Even from my initial point, I said:

I just thought JBot was the way to go at first.

I want to get it done by later today, therefore, I'm going with viewlet for now.

All the other add-ons would have to be updated.

Plone also has this (general) ability: how a login is requested is controlled by the "challenge" plugins. Deactivate the challenfe plugins, you do not like/do not need; register and activate your own challenge plugin.

In my view, this should not be the task of an authentication plugin (neither for OAuth2 nor SAML2). Following the PAS philosophy, an authentication plugin should (only) handle authentication. I would address your use case by giving the "foreign" users limited rights; to become full members, they must "register" with your portal; the "tegistration" would complete the user profile and ensure that those users get full portal rights (using a role or group plugin).

Agreed......

In fact, that was the aim of dexterity.membrane.authomatic add-on, which will be rebranded into something more general purpose. :point_down:

My memory is so bad. You are right.
I had already created a plugin that solves this problem

It uses a combination of a challenge plugin and content actions to allow for customising where the login redirect goes to. So you could then create your own login form with various buttons for the different kinds of logins.

@b4oshany you could use customlogin to give you options on how to login and c.onlogin for them to complete a profile.

So the last part is how to get an external authentication provider to auth for internal/membrane users?

Maybe. but Zope already has problems with that in that acquisition means that deleting a plone user can allow it to be taken over by a root zope user with the same id.

So to solve this problem generically maybe there are some options

  • PAS plugin that creates a local user on first login with the same id. That would work for saml2 based on the above but would be a bit crappy since it would have saml2 in the id. (or extend c.onlogin)
  • PAS plugin that provides roles and properties by combining different users with different ids based on same rule or lookup?
  • plugins like saml2 should let you choose which id scheme it will use for the user within plone. It should be up to teh site admin to say that its ok to combine a saml2 user with the same id as a local user.

Just to make it clear: it does not have "saml2" in the id but instead the id of the identify provider (your portal may work together with different identity providers; the respective users must be distinguished).

If you associate something local (a profile, additional roles for specific users, etc.) with an external user, you must have some agreement with the respective identity provider - e.g. that he never reuses the same user id for a different user. If he does this, the local information is likely no longer adequate for the new user.

Plone Foundation Code of Conduct