Initialize rights of user profiles

Last modified by Raphaël Jakse on 2026/06/02 17:55

cogSnippet to setup initialization of rights on user pages based on a listener
TypeSnippet
CategoryOther
Developed by

Anca Luca, slauriere

Rating
1 Votes
LicenseGNU Lesser General Public License 2.1

Description

This snippet describes how to create and setup a component that will initialize the user pages with additional rights upon creation.

The operations below need to be done with a user having programming rights and, in order for this to work properly, this creator needs to preserve their rights. If in doubt, you can use the superadmin user. You will also probably want to protect the edit rights on the pages created below, in order to prevent non-admins from modifying the script and give themselves rights.

It needs the Script Component extension in order to work. If you're on a farm and you need all user profiles to be protected, regardless of the wiki where they're created, you can install the Script Component only on the main wiki and follow the instructions below to setup the component on the main wiki only, that will apply everywhere.

After installing the Script Component extension, create a new page wherever you feel like. If in doubt put it in the XWiki subtree (which is already edit-protected for non-admins).

Edit this newly created page in object mode and then add an object of type XWiki.ScriptComponentClass, and fill in the following metadata:

  • Language: groovy
  • Scope: global - you can modify this if you want to apply this only to a wiki or something, this is applying it to the whole farm which is probably what you want most of the time
  • Script
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import javax.inject.Inject;
    import javax.inject.Named;
    import javax.inject.Singleton;
    
    import org.slf4j.Logger;
    import org.xwiki.bridge.event.DocumentCreatingEvent;
    import org.xwiki.component.annotation.Component;
    import org.xwiki.model.EntityType;
    import org.xwiki.model.reference.DocumentReferenceResolver;
    import org.xwiki.model.reference.EntityReference;
    import org.xwiki.model.reference.EntityReferenceSerializer;
    import org.xwiki.observation.AbstractEventListener;
    import org.xwiki.observation.event.Event;
    
    import com.xpn.xwiki.XWikiContext;
    import com.xpn.xwiki.XWikiException;
    import com.xpn.xwiki.doc.XWikiDocument;
    import com.xpn.xwiki.objects.BaseObject;
    import com.xpn.xwiki.objects.BaseProperty;
    import com.xpn.xwiki.objects.classes.PropertyClass;
    
    /**
     * Listener handling: allow some extra rights to the user pages when they're created. <br>
     * Note: failure to add these rights does not block the creation of the user profile page, it will be created but
     * without the extra rights.
     *
     * @version $Id$
     */
    @Component
    @Named(UserProfileRightsInitializerListener.LISTENER_NAME)
    @Singleton
    public class UserProfileRightsInitializerListener extends AbstractEventListener
    {
        static final Map<String, List<String>> extraAllowRightsMap = new HashMap<>();
    
        static {
            extraAllowRightsMap.put("XWiki.XWikiAdminGroup", Arrays.asList("view", "edit"));
        }
    
        static final EntityReference RIGHT_CLASS_REFERENCE =
            new EntityReference("XWikiRights", EntityType.DOCUMENT, new EntityReference("XWiki", EntityType.SPACE));
    
        static final EntityReference USER_CLASS_REFERENCE =
            new EntityReference("XWikiUsers", EntityType.DOCUMENT, new EntityReference("XWiki", EntityType.SPACE));
    
        static final String LISTENER_NAME = "userprofilerightsinitializer";
    
        @Inject
        protected Logger logger;
    
        @Inject
        @Named("compactwiki")
        protected EntityReferenceSerializer<String> compactWikiSerializer;
    
        @Inject
        @Named("currentmixed")
        protected DocumentReferenceResolver<String> referenceResolver;
    
        public UserProfileRightsInitializerListener()
        {
            super(LISTENER_NAME, new DocumentCreatingEvent());
        }
    
        @Override
        public void onEvent(Event event, Object source, Object data)
        {
            logger.debug("Event: [{}] - Source: [{}] - Data: [{}]", LISTENER_NAME, event, source, data);
    
            XWikiContext context = (XWikiContext) data;
            XWikiDocument page = (XWikiDocument) source;
            if (page != null) {
                BaseObject userObj = page.getXObject(USER_CLASS_REFERENCE);
                if (userObj != null) {
                    try {
                        updateAccessRights(page, context);
                    } catch (XWikiException e) {
                        logger.error("Error while allowing additional access rights for: [{}].",
                            page.getDocumentReference(), e);
                    }
                }
            }
        }
    
        /**
         * Allow the rights from the extraAllowRightsMap.
         *
         * @param page
         * @param context
         * @throws XWikiException
         */
        public void updateAccessRights(XWikiDocument page, XWikiContext context) throws XWikiException
        {
            if (page != null) {
                for (Map.Entry<String, List<String>> extraAllowRight : extraAllowRightsMap.entrySet()) {
                    BaseObject rightObj = page.newXObject(RIGHT_CLASS_REFERENCE, context);
                    rightObj.setLargeStringValue("groups", extraAllowRight.getKey());
                    BaseProperty levelsProp = ((PropertyClass) rightObj.getXClass(context).get("levels"))
                        .fromStringArray(extraAllowRight.getValue().toArray(new String[0]));
                    rightObj.set("levels", levelsProp.getValue(), context);
                    // always set only allow rights
                    rightObj.setIntValue("allow", 1);
                }
            }
        }
    }
    

Alternatively, you can import the page from this attachment (the page will be imported in XWiki.UserProfileRightsInitializationListener , as a hidden page) and modify it after, as described below.

Configuring the rights

By default, the user profiles are editable by the user themselves. As a consequence of this, the user can also see their own profile.

In order to restrict the view right, this script uses the whitelist strategy of XWiki rights, which is to give the right explicitly to some group in order to break the inheritance and remove the right from others.  All the rights set by this script will be added to the right of the profile owner, so the profile owner will keep their edit and view right.

By default, the script will add an additional right for the user profile, restricting the view and edit rights to the XWikiAdminGroup. Note: in a multiwiki environment, if the user is created on a subwiki, the AdminGroup will be the one from the subwiki, not the main wiki one.

If you want to adjust the rights being set, you only need to modify this section of the script, to modify this right or add your own additional rights:

static {
    extraAllowRightsMap.put("XWiki.XWikiAdminGroup", Arrays.asList("view", "edit"));
}

for example, if you'd like an additional group to have rights, you'd do this:

static {
    extraAllowRightsMap.put("XWiki.XWikiAdminGroup", Arrays.asList("view", "edit"));
    extraAllowRightsMap.put("XWiki.UserProfileViewersGroup", Arrays.asList("view"));
}

or if you'd like that the restriction to view and edit is set only for the AdminGroup of the main wiki, regardless of the wiki where the user profile is created, you'd do this:

static {
    extraAllowRightsMap.put("xwiki:XWiki.XWikiAdminGroup", Arrays.asList("view", "edit"));
}

Error handling

If the script encounters an error on setting the rights, it will not prevent the creation of the user profile page. The page will be created, it will just not have the additional rights set on it and you may need to set them manually. An error will be logged in the logs of the server. However, except for a bug or an untested situation, there should be no error on setting these rights under "normal conditions".

Handling existing user profiles

If you already have user profiles you want to protect, you can paste this code in a new document and click on proceed. This runs the same code on existing profiles. Once run, you can remove the document you used.

Note: this code requires the Job Macro.

{{job id="{{velocity}}$!request.jobId{{/velocity}}" start="{{velocity}}$!request.confirm{{/velocity}}"}}
  {{groovy}}
    import org.xwiki.model.reference.EntityReference;
    import org.xwiki.model.EntityType;
    import org.xwiki.logging.LogLevel;

    def log = services.logging.getLogger(doc.fullName)
    services.logging.setLevel(doc.fullName, LogLevel.INFO);
    def RIGHT_CLASS_REFERENCE = new EntityReference("XWikiRights", EntityType.DOCUMENT, new EntityReference("XWiki", EntityType.SPACE));
    def USER_CLASS_REFERENCE = new EntityReference("XWikiUsers", EntityType.DOCUMENT, new EntityReference("XWiki", EntityType.SPACE));

    def extraAllowRightsMap = new HashMap<>();
    extraAllowRightsMap.put("XWiki.XWikiAdminGroup", Arrays.asList("view", "edit"));

    def context = xcontext.context;

    def users = services.query.xwql("select doc.fullName from Document doc, doc.object(XWiki.XWikiUsers) user").execute()
    log.info("Updating [{}] users", users.size())
    services.progress.pushLevel(users.size())
    for (String user : users) {
      log.info("Updating user [{}]", user)
      services.progress.startStep()
      def page = xwiki.getDocument(user).getDocument()
      for (Map.Entry<String, List<String>> extraAllowRight : extraAllowRightsMap.entrySet()) {
        def rightObj = page.newXObject(RIGHT_CLASS_REFERENCE, context);
        rightObj.setLargeStringValue("groups", extraAllowRight.getKey());
        def levelsProp = rightObj.getXClass(context).get("levels").fromStringArray(extraAllowRight.getValue().toArray(new String[0]));
        rightObj.set("levels", levelsProp.getValue(), context);
        // always set only allow rights
        rightObj.setIntValue("allow", 1);
      }
      context.getWiki().saveDocument(page, "Protect user", true, context)
      services.progress.endStep()
    }
    services.progress.popLevel()
  {{/groovy}}
{{/job}}

{{velocity}}
#if ("$!request.jobId" == '')
  #set ($jobId = "$datetool.get('yyyy-MM-dd')/$datetool.get('HH-mm-ss-SSS')")
  [[Proceed>>$doc.fullName||queryString="jobId=$!{escapetool.url($jobId)}&confirm=true" class="btn btn-primary"]]
#elseif ($!request.confirm)
  $response.sendRedirect($doc.getURL('view',"jobId=$request.jobId"))
#end
{{/velocity}}

Compatibility

This example was tested on version 12.10.5 of XWiki.

Prerequisites & Installation Instructions

This snippet needs the Script Component Extension to be installed.

If you're on a farm, you can install the extension only on the main wiki and use the instructions above for creating the snippet on the main wiki as well.

Get Connected