XWiki Rights API - API
Rights API - Public types and services. |
Type | JAR |
Category | API |
Developed by | Gabriel Răileanu, Anca Luca, Clément Desableau, Mohamed Boussaa |
Active Installs | 19 |
Rating | |
License | GNU Lesser General Public License 2.1 |
Bundled With | XWiki Standard |
Table of contents
Description
The current API is an attempt to solve (even partially) the need of having a higher level of abstraction over the rights objects from the wiki, as described in XWIKI-13466.
This API exposes a script API with methods for both storing and retrieving access & behavior rules for an entity. Also, it provides functions for creating an implementation of WritableSecurity rules starting either from a ReadableSecurityRule, either from a tuple of its component fields.
Concepts
Similar to the authorization API, this API uses an abstraction to model the access rights as triples (phrases), that all contain:
- a subject - who does the action that the access right is about. This is ultimately representing a user, even if a subject can also be a group which will endup representing all its members as a collective subject;
- a predicate - the actual action we're talking about. A predicate describes an action so it also includes the negation of the action (not editing, not viewing, etc.);
- a complement - the element concerned by the action done by the subject. More precisely, this will be about pages, hierarchies of pages, wikis or other content in the wiki, that can be seen by users, edited, etc. The inheritance is captured at this level of the abstraction, as a complement can be "a page and all its descendants, future and present".
This API (as the authorization API) assumes that whatever access schema is needed or implemented on the wiki, it can be fully described by a collection of phrases structured as above.
More precisely, this API will operate with Rule objects that can be read-only or writable. A Rule object will hold only the subject(s) and the predicate(s) and a collection of such rules will apply to a resource on the wiki (the complement), that is represented by its reference, as detailed below.
- a Rule object contains:
- a list of zero or more groups (part of the subject),
- a list of zero or more users (part of the subject),
- a list of access levels (part of the predicate),
- whether the access levels are allowed or denied (part of the predicate).
- a Readable Rule will only have methods to retrieve the groups, users, access level, allow/deny (only getters);
- a Writable Rule will also provide methods to write these (setters also).
- For a given entity on the wiki (identified by its entity reference) - the complement -, it's possible to:
- retrieve the collection of the Rule objects that are currently set on it, in order to read the rights;
- update the collection of Rule objects that apply, by saving the collection of Rule objects, in order to write the rights.
- By design, this API does not provide methods for partial manipulation of the Rules collection: when a save is done, the save must provide all the rules that will apply, the API does not allow to only add an additional new rule and leave the rest unchanged. This choice is because the access rights of a resource are determined based on all the rules set on the resource, so this is a way to make sure that the caller takes responsibility on the actual full set of rules. How the caller produces that set of rules (e.g. as a copy of the existing rules and addition of a new one) and whether they check what is the actual effect of those rules is fully the responsibility of the caller.
- The type of entity reference will determine the handling of the inheritance (see the model documentation):
- to manipulate rules on a document only, use a DocumentReference object to set/retrieve Rules;
- to manipulate rules on a page and all its children (on a full hierarchy), use a SpaceReference object to set/retrieve Rules;
- to manipulate rules on a whole wiki (and all its subtrees), use a WikiReference object to set/retrieve Rules;
- this API does not handle PageReferences, for now.
Note 1: Contrary to the abstraction above, a Rule object will actually be able to capture more than a single subject and more than a single predicate, so it will be able to represent more than a single phrase in the abstraction above; this is a choice made for simplicity. As a natural consequence, there will be more than one way to represent the same state of access rights, depending how one chooses to split the various accesses in phrases and describe them with Rule objects (one rule with multiple subjects, multiple rules with a single subject each, etc.). This is normal and it poses no problem in manipulating nor testing the rights. The only difficulty raised by this lack of canonical form is for the equality comparison of access rights of two resources, since it's hard to compare things that can be expressed in multiple ways; however, this is a much less frequent need than testing the access rights or setting / reading them.
Note 2: This API only introduces this layer of abstraction on top of the existing XWiki concepts, there is no new implementation of the rights or of their storage. It is fully compatible with all the other access rights APIs and features of XWiki (the authorization testing module, the rights set in the administration of the wiki, etc.). In its implementation (see details below), the access rights do endup stored as XWikiRights or XWikiGlobalRights objects, but those concepts are internal details from the pov of this API and should never be a concern of the caller of this API.
Script API
The latest version of the scripting API could be found on the api repo. You can use the current api from scripting environments, ie. groovy or velocity using services.security.rights.
...
@Component
@Named("security.rights")
@Singleton
@Unstable
public class RightsAPIService implements ScriptService
{
/**
* @param ref the {@link EntityReference} for which the rules will be retrieved. Depending on the {@link
* org.xwiki.model.EntityType} of the <code>ref</code>, the rules will be read from the wiki, space or
* document.
* @return the list of rules that are actually applying for <code>ref</code>.
*/
public List<ReadableSecurityRule> getActualRules(EntityReference ref)
{
...
}
/**
* @param ref the {@link EntityReference} for which the rules will be retrieved. Depending on the {@link
* org.xwiki.model.EntityType} of the <code>ref</code>, the rules will be read from the wiki, space or
* document.
* @param withImplied whether implied rules should also be returned or only persisted rules
* @return the list of security rules that apply to the passed entity
*/
public List<ReadableSecurityRule> getRules(EntityReference ref, Boolean withImplied)
{
...
}
/**
* Saves the passed rules, with the default recycling strategy.
*
* @param rules the rules to save.
* @param reference the reference to save rules on. In order to actually save the rules, the reference must be a
* Document, Space or a Wiki.
* @return whether the save was successful or not.
*/
public boolean saveRules(List<ReadableSecurityRule> rules, EntityReference reference)
{
...
}
/**
* Saves the passed rules, accordingly to the gives <code>strategy</code>.
*
* @param rules the rules to save
* @param reference the reference to save rules on. In order to actually save the rules, the reference must be a
* {@link org.xwiki.model.EntityType#DOCUMENT}, {@link org.xwiki.model.EntityType#SPACE} or {@link
* org.xwiki.model.EntityType#WIKI}
* @param strategyName a strategy for persisting the objects. It needs to be the name of an implementation of
* RulesObjectWriter. TODO: there should be a link to the interface, but needs to be fixed
* @return whether the save was successful or not.
*/
public boolean saveRules(List<ReadableSecurityRule> rules, EntityReference reference, String strategyName)
{
...
}
/**
* Converts a ReadableSecurityRule to a WritableSecurityRule.
*
* @param readableSecurityRule a rule to be converted in a modifiable (writable) one
* @return a writable rule, with the same properties as the rule passed as argument
*/
public WritableSecurityRule createWritableRule(ReadableSecurityRule readableSecurityRule)
{
...
}
/**
* @param groups
* @param users
* @param levels
* @param allowOrNot
* @return a writable/modifiable rule, according to the given parameters
*/
public WritableSecurityRule createWritableRule(List<String> groups, List<String> users,
List<String> levels, String allowOrNot)
{
...
}
/**
* Extract rules whose subject is a user from a set of rules. Returned rules are normalized, check the
* {@link #normalizeRulesBySubject(List) normalizeRulesBySubject(List<ReadableSecurityRule>)} method.
*
* @param rules A list of rules
* @return The list of user rules
*/
public List<ReadableSecurityRule> getUserRulesNormalized(List<ReadableSecurityRule> rules)
{
...
}
/**
* Extract rules whose subject is a group from a set of rules. Returned rules are normalized, check the
* {@link #normalizeRulesBySubject(List) normalizeRulesBySubject(List<ReadableSecurityRule>)} method.
*
* @param rules A list of rules
* @return The list of group rules
*/
public List<ReadableSecurityRule> getGroupRulesNormalized(List<ReadableSecurityRule> rules)
{
...
}
/**
* Organize a set of rules based on subject reference and rule state (Allow/Deny).
*
* @param rules A list of rules
* @return A map where the key is a subject (user/group) DocumentReference and the value is a Pair of rules where
* the left rule contains allowed rights and the right rule contains denied rights.
*/
public Map<DocumentReference, Pair<ReadableSecurityRule, ReadableSecurityRule>> organizeRulesBySubjectAndState(
List<ReadableSecurityRule> rules)
{
...
}
}
Usage examples
Retrieve the list of persisted rules applied to a specific
#set($rules = $services.security.rights.getRules($myDoc.documentReference, false))
Create new (writable)rule and add it to a specific reference
As explained above, as a caller, you'll have to first retrieve rules applied on that reference, add your new rule to previous list and after that save it. How you check / validate the existing rules to ensure there is no conflict with your new rule is your responsibility, as a caller:
#set ($ruleToAppend = $services.security.rights.createWritableRule([XWiki.XWikiAllGroup], [XWiki.admin, XWiki.jorje],
["view", "edit", "comment"], "allow")
#set ($rules = $services.security.rights.getRules(<entityReference>, false))
## append created rule to existing ones
#set ($discard = $rules.add($ruleToAppend))
#set ($result = $services.security.rights.saveRules($rules, <entityReference>))
Context of the current implementation
In order to make this work, we submitted a proposal when the platform was in version 12.6.5, which implied changing of some classes. Since the thread did not reach a conclusion, we ended up with just copying the body of those classes & modify them for the current version of the app.
In the future (maybe when the forum thread will reach its conclusions), some additional work will be necessary. For that there's an issue already created.
This section serves as a be aware in case of having customized versions for one of the following classes: AllowEditToNoOneRule, XWikiSecurityRule, DefaultSecurityEntryReader. There could be an undetermined behavior in the case when your custom-classes do not comply with the ones copied in the current api.
Updated Right Event
XWiki 2.0+ A dedicated event has been introduced that is triggered whenever a right is modified in XWiki: in such case a RightUpdatedEvent is triggered, with the following information:
- source: the SecurityReference of the entity impacted by the right update
- data: a list of SecurityRuleDiff entries corresponding to the changes performed by the update
UML Diagram
Modules
- api-rights-api: constains general interfaces and abstractions for rules, which are not aware of concepts specifc to the way of storing/serialization those rules
- api-rights-bridge: a bridge/adapter between high-level abstractions and the actual BaseObjects
ReadableSecurityRule
This API introduces the concept of a security rule that can be read. This new interface follows the need to have not only a SecurityRule which we can test (eg. to check if a rule matches to a specific Right or to a specifc GroupSecurityReference/UserSecurityReference).
Simply put, a ReadableSecurityRule is an interface that provides access to underlying subjects (groups or individual users). Also, it provides a method, `#isPersisted()`, which can be used in order to check that the current rule is actually stored (in an object) or not (some examples for the last case: implicit rules - eg. superadmin has global access on the wiki).
Prerequisites & Installation Instructions
We recommend using the Extension Manager to install this extension (Make sure that the text "Installable with the Extension Manager" is displayed at the top right location on this page to know if this extension can be installed with the Extension Manager).
You can also use the manual method which involves dropping the JAR file and all its dependencies into the WEB-INF/lib folder and restarting XWiki.
Release Notes
v2.3
v2.2
v2.1
v2.0
v1.1.1
v1.1
Dependencies
Dependencies for this extension (org.xwiki.contrib.api-rights:api-rights-api 2.3):
- org.xwiki.platform:xwiki-platform-security-api 13.10.11
- org.xwiki.platform:xwiki-platform-security-authorization-bridge 13.10.11
- org.apache.commons:commons-lang3 3.12.0
- org.xwiki.commons:xwiki-commons-component-api 13.10.11