Add user to two sites

I want to add the same user to another site (site 2) when they are added to 'site 1'.
I assume I could use a subscribe or even a content rule (user added) and use restapi to add a user to the other site.

I assume I will need to send an email (or sort it another way) if the user is not added (the other site beeing down or busy (?) and maybe I could add my own restapi endpoint to avoid logging in (and check for IP or similar), alternatively, add a user that can 'just add users'.

Is there something I should keep in mind (especially for security)

Or use one instance as a user provider for the other. A PAS plugin that check the user on the other site, verify the password and create it locally if missing.

These sites are on different servers (if it matters).

Are there any docs / examples on how to do this?

Sure y don't want something like ldap? Whatcha actually need from this setup?

I dont really know, as I have never done something like this before.

Basically, I have one Plone site that is a dashboard for other sites (showing notifications and 'tasks' that are present on another site.

So this site will just have some pages that shows info from 3 (or more) other sites (your tasks, your notifaction etc).

So, when a user is added on one of the 3 sites (or all of them), the same user should be created on the other. It is not important that the passwords / login is the same on all sites.

I tried adding a subscriber:

  <!-- Subscriber for user created  -->
  <subscriber     
   for = "Products.PluggableAuthService.interfaces.events.IPrincipalCreatedEvent"
   handler=".subscribers.user_created_handler"
  />

Which party works, but strangely, I dont have access to the email (it is empty ( '' ). Name etc are correct.

def user_created_handler(event):
    """Handles a new user creation."""
    #user = event.principal
    user = event.object
    user_id = user.getUserId()
    name = user.getName()
    username = user.getUserName()
    mail = user.getProperty('email')
    #member = api.user.get(userid=user_id) 
    #email  = member.getProperty('email')

The user object returned by event.principal is the basic user object (from Zope). I use it to get the user id and then use the user id to get the plone user object as follows:

    user_id = event.principal.getId()
    mtool = api.portal.get_tool(name='portal_membership')
    plone_user = mtool.getMemberById(user_id)

Other strategies you can consider to use are the following:

  • shared session secrets via plone.session (if using the same base domains)
    see ZMI>acl_users>session>secrets and properties tab
    Both sites will see users registered in one or the other as authenticated users when logged-in. The problem, IIRC, is mapping users from another site to a group or if you need to use their properties in the other site since the information will not be available there. You may use dynamic groups as a solution for this wherein users are mapped to a group depending on username characteristics like prefix/suffix or domain name. See collective.dynamicgroupsplugin

  • Use JWT tokens and embed the group and properties that you need in the other sites in its data payload. Plone REST API uses JWT tokens but you may need to extend it so you can add custom payload. You will still need to map users to groups as above if needed.

  • Use third party authentication for all sites and use something like pas.plugins.memberpropertytogroup for common group mapping.

The issue with your current approach is the central management of user accounts. They may not be in sync later on.

That does not give me the email (also). I (also) think that is similar to the lines I commeted out in my example.
I dont know if this is related to 'use email for log in is selected'.

It seems like all other 'things' are working, for example:

user. getGroups()

UPDATE: I can get the email if I use:

   <subscriber
     for = "Products.PluggableAuthService.interfaces.events.IPropertiesUpdatedEvent"
     handler = ".subscribers.user_created_handler"

/>

1 Like

I use the above event as well. IIRC, user events will loop through a changed property one at a time so I monitor their availability before I get their values.

# use IPropertiesUpdatedEvent as properties are not yet available in IPrincipalCreatedEvent
# warning: IPropertiesUpdatedEvent loops through each changed property
def setup_account(event):
    """ Create user or group folder and enroll to group
    """
...

    props = list(event.properties.keys())
    valid_props = ['acc_type','acc_name','dept_name']

    # exit if changed property is not related to property being monitored
    has_valid_prop = any(x in props for x in valid_props)
    if not has_valid_prop:
        return
...
# get value here

1 Like

"never done before" is not an excuse for not doing things right and not a justification for tinkering (once again). A proper solution involves an external storage for your user data like a RDBMS (Postgres or MySQL), LDAP or whatever. There are PAS plugins for almost every usecase. Provide proper solutions instead of building workarounds at are your customer's legacy of today (and not of tomorrow). Such half-baked solutions (and there are so many out there) in the Plone world, are on of the reasons why Plone projects, in particular migration projects became so expensive for existing Plone customers.

2 Likes