How do I programmatically create new groups with specific set of rights on XWiki?

那年仲夏 提交于 2020-03-25 05:51:31

问题


I'm writing my own XWiki Authenticator (that extends XWikiAuthServiceImpl) and therein, I want to create few groups, each with different sets of rights. How do I do it programmatically?

Example,

  • XWiki.MyStandardGroup - view, edit, comment
  • XWiki.MyClassicGroup - view, edit, comment, script
  • XWiki.MyAdminGroup - view, edit, commit, script, delete, admin

Also, I create the users programmatically. How do I give different access rights to different sets of users?

On the conceptual level, how do users (with rights) work with pages (with rights)? I tried to read the following docs:

  • Access Rights
  • Permission types

They dont seem to explain these — or maybe, they do but written in a complex way without any concrete examples which makes it difficult to get the idea of how rights on different entities (pages, users and groups) work together. Here are some text from the Access Rights which needs example to be understood:

  • When a right has been allowed at a given level, it gets implicitly denied to anyone else at the same level. This only applies to the right allowed. If only "View" is set to a user/group at this level, all other rights like "Edit" are still inherited. Using this implicit deny behavior is recommended over applying explicit denial.

What does the bold part even mean? I think the term level is used in different sense on different bullet points under the same Basic rules section.


回答1:


I feel there are three questions in this post:

  1. How do I create Users and Groups programatically?
  2. How does the Access Rights system work?
  3. Is there an example for the text quoted from the access rights page

First an answer to the second one.

How does the Access Rights system work - with example

There are a fixed number of rights in XWiki, like view, edit, etc.

Users can get these rights assigned directly to them, or they can be member of a group, and the group has these rights assigned to them. This assignment of rights can happen in different places (which are called "levels" in the documentation).

The "level" structure is as follows:

Wiki levels

First there is the main wiki (that gets pre-installed when you install the wiki). Then there might be more wikis, called "sub-wikis", which you can create manually (via the "Wikis" secton from the "Burger" menu on the top right of every wiki page). This is a simple two layer hierarchy:

main wiki (always exists)
   |
   |--- subwiki1
   |
   |--- subwiki2
   |
   |--- subwiki3

Subwikis cannot be nested. I am not going into details why you might want them; oen can often go without them. Users and groups can exist in the main wiki (which means their profile pages are located in the main wiki), or they can exist in subwikis (i.e. their profile pages are there.) Users and Groups from the main wiki are visible in all subwikis (and can get rights assigned to them), but not the other way round - a user located in a subwiki cannot get special rights in the main wiki (and also not in another subwiki). If such users access the main wiki, they are treated as the anonymous user. They can only log in to the subwiki.

Page levels

Second, (nearly) all data in the wiki is stored in pages. These pages are also nested, and since XWiki 7.x they can be nested arbitrarily deep. This is the other part of the "levels" structure.

For every wiki, there is a set of "top level" pages, both preinstalled and user created. Then there are pages which are children of these "top level" pages, which in turn can have children, and so on. As an additional complication, not all pages can have subpages. By historical convention these pages with a full name ending in WebHome can have children pages, others can not. This is probably transparent to the user, but important for the programmer.

There is no single "root" page to start the hierarchy. As an example, for one wiki the structure might look like:

Top level                Third Level
            Second Level                    Fourth Level

Main.WebHome                                             (preinstalled "Start" page)
   |       
   |------ Main.Search                                   (preinstalled search page, no subpages)
   |
   |------ Main.SomePage.WebHome                         (user created page, can have children)

Sandbox.WebHome                                          (preinstalled playground page)
   |       
   |------ Sandbox.TestPage1                             (preinstalled demo page, no subpages)
   |       
   |------ Sandbox.TestPage2                             (preinstalled demo page, no subpages)
   |       
   |------ Sandbox.TestPage3                             (preinstalled demo page, no subpages)
   |       
   |------ Sandbox.SomePage.WebHome                      (user created 2nd level page, can have children)

Documentation.WebHome                                    (user created top level page)
   |
   |------ Documentation.Topic1.WebHome                  (user created 2nd level page, can have children)
   |           |
   |           |------ Documentation.Topic1.SubTopic1.WebHome   (user created 3rd lvl page, can have children, too)
   |           |
   |           |------ Documentation.Topic1.SubTopic2.WebHome   (user created 3rd lvl page, can have children, too)
   |           |
   |           |------ Documentation.Topic1.SubTopic3.WebHome   (user created 3rd lvl page, can have children, too)
   |           |                  |
   |           |                  |------ Documentation.Topic1.SubTopic3.EvenMore.WebHome   (user created 4th lvl page, can have children)
   |           |
   |           .
   |           .
   |           |
   |           |------ Documentation.Topic1.SubTopicN.WebHome   (user created 3rd lvl page, can have children, too)
   |
   |------ Documentation.Topic2.WebHome                  (user created 2nd lvl page, can have children)
   .
   .
   .
   |
   |------ Documentation.TopicN.WebHome                  (user created 2nd lvl page, can have children)

....

Granting rights

You now can grant a right to user or group on every page in this hierarchy by adding an Object of type XWiki.XWikiRights to the page itself, specifying the list of rights to grant (confusingly stored in the attribute levels of that object), the list of users and/or groups to grant the right to, and a allow/deny flag ... which we will come to later. How to do that programatically is discussed in the question: Set user and group rights to document in XWiki

In that case the right is only granted for the page itself, not its subpages. If you give the right edit on the page Main.WebHome to the group XWiki.HomepageEditorsGroup, then only members of this group can edit the page, but this does not affect subpages like Main.Search or Main.SomePage.WebHome.

That the attribute levels here actually stores the rights is maybe confusing - again this is another historical decision. (The software is developed since 15 years or so and the developers are commited to keep backwards compatibility). What ever the attribute is named, these are rights, and not the levels the documentation talks about.

To go on with the rights management: You can also grant a right on a page and all its subpages. This only works for pages which can have subpages. Technically this is done by adding an object of type XWiki.XWikiGlobalRights ... but not to the page itself, but to a subpage named WebPreferences. (Historical decision, again.)

So if you want to grant the view right to the group XWiki.Topic1ViewerGroup on the page Documentation.Topic1.WebHome and its subpages like Documentation.Topic1.SubTopic1.WebHome or Documentation.Topic1.SubTopic3.EvenMore.WebHome, then you take the page Documentation.Topic1.WebPreferences (creating it if it does not exist), and add an object of type XWiki.XWikiGlobalRights to it, with the attributes:

  • level : view
  • groups : XWiki.Topic1ViewerGroup
  • allow: 1

How the rights are checked

Now the check for a specific right usually looks at a given page itself, then looks at the WebPreferences for that page, then at the WebPreferences of the parent page, and so on. (It is "going up the levels".) The check stops as soon as it finds a "rights" object covering the right in question.

If no matching "rights" object has been found up to the top level page, then the wiki is checked. Rights on the wiki level are stored in the special page XWiki.XWikiPreferences, again as objects of class XWiki.XWikiGlobalRights.

Finally if the wiki happens to be a subwiki, the global righs on the main wiki might be consulted - again on the page names XWiki.XWikiPreferences, but this time in the main wiki.

Example 1: check for view right on Documentation.Topic1.SubTopic3.WebHome

  • Documentation.Topic1.SubTopic3.WebHome has no XWiki.XWikiRights - no decision
  • Documentation.Topic1.SubTopic3.WebPreferences has no XWiki.XWikiGlobalRights - no decision
  • Documentation.Topic1.WebPreferences has an XWiki.XWikiGlobalRights for view - stop to make a decision
  • Result: if the current user is in the group XWiki.Topic1ViewerGroup, she/he can view the page, otherwise not

Example 2: check for edit right on Main.WebHome

  • Main.WebHome has a XWiki.XWikiRights for edit - stop to make a decision
  • Result: only users in the XWiki.HomepageEditorsGroup can edit, others do not

Example 3: check for edit right on Main.SomePage.WebHome

  • Main.SomePage.WebHome has no XWiki.XWikiRights - no decision
  • Main.SomePage.WebPreferences has no XWiki.XWikiGlobalRights - no decision
  • up the page hierarchy: Main.WebPreferences has no XWiki.XWikiGlobalRights - no decision either
  • (that Main.WebHome has a XWiki.XWikiRights is not consulted, as the right applies only to the page itself)
  • up the page hierarchy: we are already at a top-level page, so go to the wiki instead
  • i.e. check XWiki.XWikiPreferences for a XWiki.XWikiGlobalRights for edit
  • usually there is an allow : 1 for the XWiki.XWikiAllGroup which means edit is allowed for all users
  • if there is no such settings, and we are in a subwiki: go up the wiki hierarchy and check the XWiki.XWikiPreferences of the main wiki
  • if even there is no decision made, the edit right is not allowed

admin is a special case

As a simplification for the users, but complication for the concept, the admin right works the other way round: if the admin right is granted on the wiki level, it is valid on all pages. Even more, it implicitely grants all the other rights, like view and edit. (The reason for this is that users too often locked themselves out before this special rule was introduced.)

How does the "implicit deny" work?

Now to the quote:

  • When a right has been allowed at a given level, it gets implicitly denied to anyone else at the same level. This only applies to the right allowed. If only "View" is set to a user/group at this level, all other rights like "Edit" are still inherited. Using this implicit deny behavior is recommended over applying explicit denial.

I try to explain by example, too:

In the Example 1 above I wrote:

  • Documentation.Topic1.WebPreferences has an XWiki.XWikiGlobalRights for view - stop to make a decision
  • Result: if the current user is in the group XWiki.Topic1ViewerGroup, she/he can view the page, otherwise not

Here the result is either:

  • allow the user to view the page (and its sub pages), if the user is member of the XWiki.Topic1ViewerGroup
  • deny the user the right to view the page (and its sub pages), if the user is not member of the XWiki.Topic1ViewerGroup (i.e is "everyone else")

That is, no matter what rights the user might have otherwise - as soon as the right is set here, then only the users fulfilling the criterion in the settings are allowed to view. Everybody else is out. This is an "implicit deny".

As an alternative, assume someone has set a rights object on Sandbox.WebPreferences (i.e. affecting the "Sandbox" and all subpages):

  • level : edit
  • groups : XWiki.Topic1ViewerGroup
  • allow: 1

and onSandbox.SomePage.WebHome (i.e. affecting this sub page only):

  • level : edit
  • groups : XWiki.Topic1ViewerGroup
  • allow: 0

The setting allow: 0 is an "explicit deny": as soon as you are member of the XWiki.Topic1ViewerGroup, you are not allowed to edit this page. The fact that there is an allow: 1 on a higher level in the page hierarchy (on "Sandbox" and all sub pages) does not matter, beacuse it is not on the same level.

How to do that programatically?

First, the groups should be created as "terminal" sub pages (i.e. pages not having children) in the XWiki space, like XWiki.MyCustomGroup. However they seem to work wherever you want to create them.

On the other hand, users must be created as pages XWiki.<LoginName> as unfortunately there is a lot of code around that expects users to be in this location and nowhere else.

After having created the page (in the API they are called Document), add an object of the proper class to the page, set the attributes you want and save the page.

When looking at your requirements, it does not look like you want to grant the rights to the groups in any special place in the page hierarchy; so I assume they will be set on the wiki level. Thus no need to understand all the other explanations; just grab the XWiki.XWikiPreferences page and add the required XWiki.XWikiGlobalRights there.

I recommend using an MandatoryDocumentInitializer for this; there is a nice example in the code base which makes sure the XWikiAllGroup is always present. This interface is meant to ensure that a single page is present in the wiki, but nobody keeps you from checking that other pages are set up properly, too. The only thing you need to have in mind is that the other pages are not saved automatically, but you can do that manually with the XWiki.saveDocument method.

To create a user, there is a convenience method XWiki.createUser(String userName, Map values, XWikiContext context) in the XWiki class. The values map contains the values for the attributes to be set on the new user; you can check which attributes are available on the XWiki.XWikiUsers page in your wiki.

To create a group, you can borrow code from the example above. Note that to create a new empty group, one adds an object of type XWiki.XWikiGroups; to add members to the group one should add one more object of type XWiki.XWikiGroups for each user and set the member attribute to the full name of the user (i.e. including the 'XWiki.` prefix).

So the class might start with:

@Component
@Named("XWiki.MyStandardGroup")
public class MyUserAndGroupsInitializer implements MandatoryDocumentInitializer
{
    private static final String GROUP_CLASS_NAME = "XWikiGroups";
    private static final String MEMBER_ATTR = "member";

    private static final String RIGHTS_CLASS_NAME = "XWikiGlobalRights";
    private static final String GROUPS_ATTR = "groups";
    private static final String USERS_ATTR = "users";
    private static final String RIGHTS_ATTR = "levels"; // ;)
    private static final String ALLOW_ATTR = "allow";

    @Inject
    Provider<XWikiContext> contextProvider;

    @Inject
    org.slf4j.Logger logger;

The @Named contains by convention the name of the page the initializer cares about. That avoids name clashes between initializers on the one hand and allows to overwrite an existing initializer for a page, if wanted. You can choose a differetn name here if you prefer.

The @Injected compontents are an accessor to the current "context", which allows us to access the data in the current wiki and maintans a database connection in the background. A logger cannot hurt either.

As we need to implement the MandatoryDocumentInitializer, we first need to tell the location of one of the pages we care about:

    @Override
    public EntityReference getDocumentReference()
    {
        return new LocalDocumentReference(XWiki.SYSTEM_SPACE, "MyStandardGroup");
    }

This makes XWiki pass us in the page as a parameter in the next method; we should return true here if that page needs to be saved afterwards. As we do e verything by ourselves, we can as well return false always.

    @Override
    public boolean updateDocument(XWikiDocument document)
    {
        logger.info("try to create users/groups");
        try {
            // here create your users
            // and your groups
        } catch (XWikiException xe) {
            // as we are not allowed to let this through:
            logger.error("failed to create groups", xe);
        }
        return false;
    }

That is it, basically. Oh, some possibly useful helpers:

Adding users is relatively easy:

    private void createUser(String userFullName)  throws XWikiException
    {
        XWikiContext context = contextProvider.get();
        XWiki xwiki = context.getWiki();

        Map<String,String> values = new HashMap<>();
        values.put("last_name", userFullName);
        values.put("password", "staple battery horses correct");

        int result = xwiki.createUser(userName, values, context);
        if (result > 0) {
            logger.info("user [{}] created", userFullName);
        } else {
            logger.debug("user [{}] aleady exists", userFullName);
        }
    }

ok, maybe not that simple, but you can start with that one.

It is nearly the same for groups:

    // pass in rights as comma separated string, e.g.: "view,comment,edit"
    // members should be the full page name of the user, including the "XWiki." part
    private void createGroup(String group, String rights, String... members)  throws XWikiException
    {
        logger.info("try to create group [{}]", group);
        XWikiDocument groupDoc = checkDocument(XWiki.SYSTEM_SPACE + '.' + group);
        if (groupDoc.isNew()) {
            addUserToGroup(groupDoc, "");
            for (String member : members) {
                addUserToGroup(groupDoc, member);
            }
            XWikiContext context = contextProvider.get();
            XWiki xwiki = context.getWiki();
            xwiki.saveDocument(groupDoc, "created", false, context);
            logger.info("group [{}] created", group);
        }
        setRightsForGroup(groupDoc, rights);
    }

and adding users to the group is also easy:

    // return true if group needs to be saved afterwards
    private boolean addUserToGroup(XWikiDocument groupDoc, String userName) throws XWikiException
    {
        XWikiContext context = contextProvider.get();
        LocalDocumentReference groupClassReference = new LocalDocumentReference(XWiki.SYSTEM_SPACE, GROUP_CLASS_NAME);

        // first check if the user is already member of the group
        if (groupDoc.getXObject(groupClassReference, MEMBER_ATTR, userName, false) != null) {
            // is already member, no changes necessary
            logger.debug("user [{}] is already member of group [{}]", userName, groupDoc.getFullName());
            return false;
        }

        logger.info("add user [{}] to group [{}]", userName, groupDoc.getFullName());
        BaseObject newGroupEntry = groupDoc.newXObject(groupClassReference, context);
        newGroupEntry.setStringValue(MEMBER_ATTR, userName);
        return true;
    }

... if it were not for the rights settings that I have moved into a separate helper

    // set rights settings for group if it is not set yet; saves the result right away
    private void setRightsForGroup(XWikiDocument groupDoc, String rights) throws XWikiException
    {
        XWikiContext context = contextProvider.get();
        XWiki xwiki = context.getWiki();

        LocalDocumentReference rightsClassReference = new LocalDocumentReference(XWiki.SYSTEM_SPACE, RIGHTS_CLASS_NAME);
        String groupName = groupDoc.getFullName();

        // check if the right is already set in the XWikiPreferences.
        // here we need to loop over all values instead   
        XWikiDocument xwikiPrefDocument = xwiki.getDocument(new DocumentReference(context.getWikiId(), XWiki.SYSTEM_SPACE, "XWikiPreferences"), context);
        boolean found = false;
        for (BaseObject rightsSetting : xwikiPrefDocument.getXObjects(rightsClassReference)) {
            if (rights.contentEquals(rightsSetting.getStringValue(RIGHTS_ATTR))
                && rightsSetting.getIntValue(ALLOW_ATTR) == 1) {
                // this is the right setting!
                String groups = rightsSetting.getStringValue(GROUPS_ATTR);
                if (!groups.contains(groupName)) {
                    // our group is missing: add group and save
                    rightsSetting.setStringValue(GROUPS_ATTR, groups + ',' + groupName);
                    xwiki.saveDocument(xwikiPrefDocument, "add rights for group [" + groupName + "]", true, context);
                    logger.info("amended rights for group [{}]", groupName);
                } else {
                    logger.info("rights for group [{}] already set", groupName);
                }
                found = true;
                break;
            }
        }
        if (!found) {
            BaseObject newRightsSetting = xwikiPrefDocument.newXObject(rightsClassReference, context);
            newRightsSetting.setStringValue(RIGHTS_ATTR, rights);
            newRightsSetting.setIntValue(ALLOW_ATTR, 1);
            newRightsSetting.setLargeStringValue(GROUPS_ATTR, groupName);
            if (newRightsSetting.getIntValue(ALLOW_ATTR) != 1) {
                logger.error("adding rights of class [{}] for group [{}] failed!", rightsClassReference, context);
            }
            xwiki.saveDocument(xwikiPrefDocument, "add rights for group [" + groupName + "]", true, context);
            logger.info("added new rights for group [{}]", groupName);
        }
    }

I have also used a checkDocument helper, which is basically the same as the updateDocument in the XWikiAllGroupInitializer, except that the name is input and the tediously newly set up page is the return value.

You might want to read the Component Guide to understand how the necessary dependencies get injected. Especially you will need to add the full class name of the initializer to the src/main/resources/META-INF/components.txt for the initializer to get activated.

Backup your database before you try this out. Except a few tries before everything is properly set up, and nothing gets saved unnecessarily on each wiki restart. Also fiddle with the WEB-INF/classes/logback.xml to set the level to INFO for your package, if you want to see the log messages.


Some random other advice

Instead of managing your users programatically you might consider storing then in a LDAP Server and use this for authentication with the LDAP Authenticator. (You still need to create the groups and manage their rights, however)

While developing I found it very useful to have the Scripting Reference Documentation extension installed in my development wiki. It is no a replacement for any documentation, but being able to brwose the API Javadoc interactively somehow helps me a lot.

The Admin Tools extension has a page that shows you all rights granted in the current wiki where this extension is installed. (Go to .../xwiki/bin/view/Admin/ and click "Show Rights".)



来源:https://stackoverflow.com/questions/60794694/how-do-i-programmatically-create-new-groups-with-specific-set-of-rights-on-xwiki

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!