Authorization API

Last modified by Michael Hamann on 2024/11/18 15:59

cogControls permissions to all the wiki elements
TypeJAR
Category
Developed by

XWiki Development Team

Rating
0 Votes
LicenseGNU Lesser General Public License 2.1
Bundled With

XWiki Standard

Installable with the Extension Manager

Description

This module is in charge of user authentication, access authorization and contextual security. Current status:

  • The Authorization API is implemented by providing a bridge to the (old) RightService APIs (see $xwiki.hasAccessLevel()). This bridge is used by default since release 5.0M2. This module is available since release 4.0M1, but it should be considered stable only since 5.0 release.
  • The Authentication API is not yet implemented (the old core code is currently used and documented). However, some authentication-related APIs have been introduced (see below).

Security Authorization API

For ensuring efficiency, this module use caching techniques to store both access rules, and resulting access decision made by the authorization settler. Checking multiple times access to the same object (or object of the same hierarchy not having special access) for the same user, require only very fast cache lookups. The bridged version of the old com.xpn.xwiki.user.impl.xwiki.XWikiRightServiceImpl is therefore called org.xwiki.security.authorization.internal.XWikiCachingRightService. It should only be used when the new API does not fit your needs.

Since the contextual aspect of the module is not yet available, this module does not provide any contextual answers, and only provide access answers based on static (during a request) access rules. (i.e. DropPermission is only supported by the bridged XWikiCachingRightService.)

General API interface

Provided by Role org.xwiki.security.authorization.AuthorizationManager:

    /**
     * Check if the user identified by {@code userReference} has the access identified by {@code right} on the
     * entity identified by {@code entityReference}.
     * This function should be used at security checkpoint.
     */

   void checkAccess(Right right, DocumentReference userReference, EntityReference entityReference)
       throws AccessDeniedException;

   /**
     * Verifies if the user identified by {@code userReference} has the access identified by {@code right} on the
     * entity identified by {@code entityReference}.
     * This function should be used for interface matters, use {@link #checkAccess} at security checkpoints.
     */

   boolean hasAccess(Right right, DocumentReference userReference, EntityReference entityReference);

   /**
     * Register a new custom {@link Right}.
     */

    Right register(RightDescription rightDescription) throws UnableToRegisterRightException;

   /**
     * Register a new custom {@link Right} and add it as an implied right to the given set of rights.
     *
     * @param rightDescription the full description of the new {@link Right}
     * @param impliedByRights the rights that should imply the new right.
     * @return the created {@link Right}
     * @throws UnableToRegisterRightException if an error prevent creation of the new right. Registering exactly
     * the same right does not cause an exception and return the existing right.
     * @since 12.6
     */

    Right register(RightDescription rightDescription, Set<Right> impliedByRights) throws UnableToRegisterRightException;

   /**
     * Unregister the given custom {@link Right}.
     *
     * @param right the custom right to unregister.
     * @throws AuthorizationException if the right is not custom.
     * @since 13.5RC1
     */

   void unregister(Right right) throws AuthorizationException;

Contextual API interface (since 6.1RC1)

Provided by Role org.xwiki.security.authorization.ContextualAuthorizationManager:

    /**
     * Check if access identified by {@code right} on the current entity is allowed in the current context.
     * This function should be used at security checkpoint.
     *
     * @param right the right needed for execution of the action
     * @throws AccessDeniedException if the action should be denied, which may also happen when an error occurs
     */

   void checkAccess(Right right) throws AccessDeniedException;

   /**
     * Verifies if access identified by {@code right} on the current entity would be allowed in the current context.
     * This function should be used for interface matters, use {@link #checkAccess} at security checkpoints.
     *
     * @param right the right to check .
     * @return {@code true} if the user has the specified right on the entity, {@code false} otherwise
     */

   boolean hasAccess(Right right);

   /**
     * Check if access identified by {@code right} on the given entity is allowed in the current context.
     * This function should be used at security checkpoint.
     *
     * @param right the right needed for execution of the action
     * @param entityReference the entity on which to check the right
     * @throws AccessDeniedException if the action should be denied, which may also happen when an error occurs
     */

   void checkAccess(Right right, EntityReference entityReference) throws AccessDeniedException;

   /**
     * Verifies if access identified by {@code right} on the given entity would be allowed in the current context.
     * This function should be used for interface matters, use {@link #checkAccess} at security checkpoints.
     *
     * @param right the right to check .
     * @param entityReference the entity on which to check the right
     * @return {@code true} if the user has the specified right on the entity, {@code false} otherwise
     */

   boolean hasAccess(Right right, EntityReference entityReference);

The context includes information like the authenticated user, the current macro being executed, the rendering context restriction, the dropping of rights by macro, etc...
If you doubt about which API to use, you should probably use the contextual one, unless you are really checking rights out of context.

XWiki 16.10.0+

Document authorization manager

The DocumentAuthorizationManager needs to be used when rights of a document author need to be checked, for example, to verify if a certain XObject that requires wiki admin right actually has wiki admin right. This authorization manager takes required rights into account when they are enforced. This authorization manager is also used by the contextual authorization manager for checking script and programming right but only for the context document/the current secure document. During a macro execution for example, it is thus enough to use the contextual authorization manager, but when registering components from page contents/objects, the document authorization manager needs to be used. Additionally, this authorization manager also allows checking if a document has a certain required right independent of the author.

In addition to the right, the methods also take an entity type. The main purpose of this parameter is to allow checking if the author has wiki admin right (vs. just space admin), as this right is frequently required by XObjects. This can be achieved by passing EntityType.WIKI. For script right, EntityType.DOCUMENT should be used, and for programming right, the passed entity type doesn't matter as programming right can only be defined on the farm for which the entity type should be null.

    /**
     * Tests if the given context author of the context document has the specified right at the given level, taking the
     * required rights of the document into account.
     *
     * @param right the right to check
     * @param level the level of the right, for example, for admin right, this could be {@link EntityType#DOCUMENT} for
     * admin right directly on the document itself or {@link EntityType#WIKI} for admin right on the whole wiki.
     * @param contextAuthor the author for which rights shall be checked
     * @param contextDocument the document for which the rights shall be checked
     * @return if the context author has the right
     */

   boolean hasAccess(Right right, EntityType level, DocumentReference contextAuthor,
        DocumentReference contextDocument);

   /**
     * Check if the given document has the given required right.
     *
     * @param right the right to check
     * @param level the level of the right
     * @param contextDocument the reference of the context document
     * @return if the document has the required right (independent of the actual author)
     * @throws AuthorizationException if loading the required rights fails
     */

   boolean hasRequiredRight(Right right, EntityType level, DocumentReference contextDocument)
       throws AuthorizationException;

   /**
     * Checks if the given context author of the context document has the specified right at the given level, taking the
     * required rights of the document into account. This function should be used at security checkpoint.
     *
     * @param right the right to check
     * @param level the level of the right, for example, for admin right, this could be {@link EntityType#DOCUMENT} for
     * admin right directly on the document itself or {@link EntityType#WIKI} for admin right on the whole wiki.
     * @param contextAuthor the author for which rights shall be checked
     * @param contextDocument the document for which the rights shall be checked
     * @throws AccessDeniedException if the context author does not have the right
     */

   void checkAccess(Right right, EntityType level, DocumentReference contextAuthor,
        DocumentReference contextDocument) throws AccessDeniedException;

Change current author (since 8.3RC1)

Provided by Role org.xwiki.security.authorization.AuthorExecutor:

    /**
     * Execute the passed {@link Callable} with the rights of the passed user.
     *
     * @param callable the the task to execute
     * @param authorReference the user to check rights on
     * @return computed result
     * @throws Exception if unable to compute a result
     * @param <V> the result type of method <tt>call</tt>
     */

   <V> V call(Callable<V> callable, DocumentReference authorReference) throws Exception;

   /**
     * Setup the context so that following code is executed with provided user rights.
     *
     * <pre>
     * {@code
     * try (AutoCloseable context = this.executor.before(author)) {
     *   ...
     * }
     * }
     * </pre>
     *
     * @param authorReference the user to check rights on
     * @return the context to restore
     * @see #after(AutoCloseable)
     */

    AutoCloseable before(DocumentReference authorReference);

   /**
     * Restore the context to it's previous state as defined by the provided {@link AutoCloseable}.
     *
     * @param context the context to restore
     * @see #before(DocumentReference)
     */

   void after(AutoCloseable context);

Scripting API (since 6.1RC1)

Provides access to the general and contextual API from script services.
Here is some sample codes:

#if ($services.security.authorization.hasAccess('edit'))
... show some UI that require edit access on the current document by the current user ...
#end
#if ($services.security.authorization.hasAccess('edit', 'xwiki:Sandbox.TestPage1'))
... show some UI that require edit access on Sandbox.TestPage1 document by the current user ...
#end
#if ($services.security.authorization.hasAccess('edit', 'xwiki:XWiki.User1', 'xwiki:Sandbox.TestPage1'))
... show some UI that require edit access on Sandbox.TestPage1 document by XWiki.User1 ...
#end
$services.security.authorization.checkAccess('edit')
... do some task that require edit access ...

checkAccess() versus hasAccess() usage

Both function provide the same access check, but differ in the way the result is reported. While hasAccess() method will simply provide true/false answer, the checkAccess() method does not need any return value check, since it will throw an AccessDeniedException if the resulting access has been denied.

In the old right service, there were no distinction between access check for UI purposes, and access check for really proceeding to action. Only the hasAccess() method was provided. Missing this distinction is not good because:

  • it does not allow easily finding all critical security access check points where access is checked for ensuring security. 
  • it leave the responsibility of properly checking return values by the caller at these security check point.
  • it leave the burden of managing access denied situation individually in all those security check points by the caller.

Therefore, you are strongly encouraged to use the new checkAccess() method everywhere you check an access before action. And you should refrain to catch the resulting exception, this should be done at higher level, by the UI which will therefore be able to centralize all access check violations. (Obviously this is a work in progress that we are starting now and will only properly work when the full migration of the old service is completed). Also note that, access violation done during checkAccess() are logged. In most situation, this should not happen, since the UI, or earlier code have already checked the possibility for access, and does not provide a path to the prohibited action. Remember also, that checking the same access several time is no more cost full, so checking access very near the action (even if you think there is no reason it would be denied) is good security measure to prevent security bugs. This is clearly the purpose of the checkAccess() method.

Some definitions as seen by this module

Entity
An entity is an object on which some actions may be realized. It could be a document, a wiki, a space, or even a xwiki object or properties. It may, depending on its nature, contains other entities. All entities being referenced by an EntityReference is supported. Reference to those entities are implemented as SecurityReference (see below).
User
A user is represented by a document entity and could act on entities. Reference to users are implemented as UserSecurityReference.
Group
A group is a user that represent group of user. A user is considered to receive the right of all group it is in, as well as all group this group is in and that recursively. Reference to group are implemented as GroupSecurityReference.
Wiki
A wiki is an entity that is a root for containing other entities (except wiki). Except for Global user/group (see below), all security information does not interact outside of a wiki. (ie: a user of a wiki cannot have access to an entity of another wiki, or be a member of a group of another wiki. 
Main Wiki
The main wiki is the first wiki created. It may contains sub-wiki. Access provided at wiki level, on the Main wiki is inherited by all sub-wikis. See SecurityReference below for more information.
Global user/group
A user or group defined in the main wiki. Such user may receive access to entities of sub-wikis and be members of group in sub-wikis. This is the only wiki that interact with other wikis.
Action
Anything you can code that act on an entity (general action act on wiki a whole) and that you would like to secure.
Right
It is a symbolic representation of an authorization to realize actions on an entity. Multiple different actions are usually secured by the same right. For example, the EDIT right allows editing document content, but also XObject or even access rules.
State (of a right)
It represent the state of authorization at a given point in time, which could be either allowed, denied, or undetermined. It is implemented by org.xwiki.security.authorization.RuleState.
Rule
It defined the state of a right for a set of user or group. So, a single rule may contains a single state for a single right (implementation allow multiple rights, but this should not change anything, each right being evaluated individually) for multiple users and groups at the same time.
Level
It represent a level of the hierarchy of EntityReference. Right may be inherit from upper level, the usual hierarchy being main wiki > wiki>space > document. The authorization module introduce the main wiki root to provided references (see SecurityEntity below), and ensure isolation of wikis (second level), but is entirely dependent of the hierarchy provided for the rest. However, User and Group should be at document level since these are provided as DocumentReference.

Rights and access decisions

The authorization system is now based on two key principles:

  • Declarative definitions of rights are defined and statically store in an "enum like" class org.xwiki.security.authorization.Right.
  • A configurable org.xwiki.security.authorization.AuthorizationSettler is in charge of interpreting for a given UserSecurityReference (see below), access to those  Right against a hierarchy of SecurityRuleEntry (see below). 

Therefore, the real task of settling access decisions is entirely delegated to customizable components that may be introduce by third parties and configured without rebuilding the platform. The component hint of the AuthorizationSettler may be configured from the ConfigurationSource (xwiki.properties) using the key security.authorization.settler. The default hint "default" is implemented by the DefaultAuthorizationSettler. Another experimental PriorityAuthorizationSettler (currently unmaintained, use at your own risk) is also bundled. Both are based on a AbstractAuthorizationSettler which provide a common foundation for merging access provide at each hierarchy level based on a customizable dynamic policy.

Right definitions

Since the settling process is delegated to the AuthorizationSettler, the interpretation of right definitions is up to it, and right definition is only a tool for helping the creation of an expandable authorization settler, allowing new rights to be easily externally defined. New Right maybe registered with the AuthorizationManager using a RightDescription. The meaning of the right definition explained here is mostly pertinent for the default implementation of the AuthorizationSettler, and should normally be followed by any other settler implementation, but this could not be guaranteed.

Name
Define a name that may be used in UI and log messages.
Default state
Define the state of a right when no rule have been defined for this right at any level of the hierarchy.
Tie resolution policy
Define the state a right should takes when two opposite rules enter in conflict.
Inheritance override policy
When true, a right allowed by a higher level (ie. wiki) could be denied by a lower level (ie. document).
Implied rights
A set of rights that are implied when this right is allowed. This only affect allowance, and not denial.
Targeted entities
A set of EntityType defining the type of entities where this right may be defined. A special farm level, meaning only on EntityType.WIKI of the Main wiki could be set using a null value. However, when definition is allowed on EntityType.WIKI, it is always allowed on the Main wiki as well.
Is read only
Is this right could be allowed on read-only wiki? When false, this right should not be allowed on a read-only mode wiki.

Default rights being predefined

The following right are bundled with the authorization modules and are used with the DefaultAuthorizationSettler for primary actions of XWiki.

 Right constant  Right name  Default state  Tie resolution  Inheritance policy  Implied rights  Targeted entities  Read-only  Comments
 VIEW  view  ALLOW  DENY  deniable  none  Wiki, Space, Document  may be allowed  Allow viewing entities
 EDIT  edit  ALLOW  DENY  deniable  VIEW  Wiki, Space, Document  always denied  Allow editing entities
 COMMENT  comment  ALLOW  DENY  deniable  none  Wiki, Space, Document  always denied  Allow commenting entities
 DELETE  delete  DENY  DENY  deniable  XWiki 15.1+ VIEW  Wiki, Space, Document  always denied  Allow deleting entities
 CREATOR  creator  DENY  ALLOW  not deniable  DELETE  Document  always denied  Internal right provided to document creators except if the creator is guest
 LOGIN  login  ALLOW  ALLOW  deniable  none  Wiki  may be allowed  Allow acces to login page
 REGISTER  register  ALLOW  ALLOW  deniable  none  Wiki  always deny  Allow user registration
 SCRIPT  script  DENY  DENY  deniable  none  Wiki, Space, Document  may be allowed  Allow execution of a user written scripts
 ADMIN  admin  DENY  ALLOW  not deniable  LOGIN, VIEW, EDIT, DELETE, REGISTER, COMMENT, SCRIPT  Wiki, Space  may be allowed  Admin user with all basic rights
 PROGRAM  programming  DENY  ALLOW  not deniable  LOGIN, VIEW, EDIT, DELETE, REGISTER, COMMENT, SCRIPT, ADMIN  Main wiki only  may be allowed  Allow programing
 CREATE WIKI  createwiki  DENY  ALLOW  not deniable  none  Main wiki only  always denied  Allow the creation of wikis (since 5.2 M2)

Default right settler additional policies

The default right settler provide the following additional rules during it decision process:

  • A right allowed by a rule matching the user could not be denied by another rule at the same level matching a group containing that user, whatever is the tie resolution policy. For example, on the same document, a Rule allowing edit right to userA of groupA, and a Rule denying edit right to groupA enter in conflict. The normal tie resolution policy (which is, for the edit right, to deny access) will apply to any user of groupA while userA will be allowed.  
  • Any rule matching both the user and a group containing the user will be considered as a rule matched for the user and will be implied as so in the above rule.
  • Implied right receive the tie resolution policy, the inheritance override policy, and their kind of matching (user or group) from the right and the rule of the right that have implied them. For example, an edit right implied by an admin right will not be overridden by a denial of the edit right at a lower level.
  • Implied right does not imply other rights. Implied rights are not recursively applied. This could be seen as a limitation, but this is more a feature, since this is not difficult to provide the cumulative list of implied right during right definition if this is really the wanted behavior.
  • When a right has been allowed at a given level, it get explicitly denied to anyone else at the same level. For example, if edit right is allow at document level to userA only, it will be denied to any other userB, unless this userB receive an implied edit right with a different inheritance policy at a higher level (userB is admin for example).

Limitations

To optimize efficiency and memory footprint of the authorization modules, special "enum like" set and map of Right are provided and used internally. RightSet and RightMap are current based on bit position in longs limiting to 64 the number of supported Right. While adding new Right dynamically is supported, removing Right has been made impossible. This ensure that two different Rights are never allocated the same bit position in a given ClassLoader environment.

API and bridge

The security API is mostly independent of the system in which it is integrated. Appart from types shown in the interface above, all interaction between the API and the rest of the system is done using some well defined interfaces. 

Security References

While the main interface of the API still use standard DocumentReference, and EntityReference for your convenience, those are quickly converted to SecurityReference. There is in fact a class hierarchy of them:

  • SecurityReference: a common class used for reference to any entity being secured, could be build based on a #EntityReference#
    • UserSecurityReference: use for users, it could only be build based on a #DocumentReference#
      • GroupSecurityReference: use for groups, and based on this class hierarchy groups are also seen as users by the API.

Compare to normal EntityReference, SecurityReference prepend all references to sub-wiki entities by the main wiki reference. So all sub-wikis have a root reference to the main wiki, followed by their own wiki reference. They also provide convenient methods to access those references, determine the kind of wiki you are in, access to the original reference used to build the SecurityReference

Another difference concerns the null value. There is no acceptable situation where a null value for SecurityReference is valid:

  • A null EntityReference (used for representing the main Wiki) is converted to a SecurityReference really referencing the main wiki. 
  • A null DocumentReference used to represent the public access (Guest user) is also converted to a UserSecurityReference referencing the main wiki (by inheritance of SecurityReference), but since a UserSecurityReference only provide original reference being DocumentReference, its original document reference is null. Thanks to this, the public access is transparently considered as a Global user, and follow the path of global users while being managed by the cache, in particular, for sub-wikis.

Since SecurityReference need to knows internally the main wiki reference to be build and for their internal work, and there is no way to know the reference of that wiki without being at least a component (unless you link to oldcore, which is excluded), there is no way to construct SecurityReference with new. A SecurityReferenceFactory component is provided in package org.xwiki.security to create new instance of SecurityReference,  UserSecurityReference and GroupSecurityReference. This factory currently use an internal XWikiBridge interface to retrieve the main wiki reference.

Public bridge interface

Most of the interface used to bridge the authorization API with the underlying system are public interfaces.

The firsts are used to acquire rules information and are defined in org.xwiki.security.authorization:

  • SecurityRule which define a security rule by linking a set of Right (i.e. VIEW), a set of UserSecurityReference, a set of GroupSecurityReference and a RuleState (i.e. Allow)
  • SecurityRuleEntry links a list of SecurityRules with a SecurityReference. It could be empty, meaning no rules are defined for that entity. 
  • SecurityEntryReader is used to acquire SecurityRuleEntrys from the underlying system. XWiki 10.5+ it's possible to extend it by implementing org.xwiki.security.authorization.SecurityEntryReaderExtra components and XWiki 13.10+ it's possible for an installed extension to provide such components.

The seconds are used to manage invalidation of entries stored in the security cache and are defined in org.xwiki.security.authorization.cache :

  • SecurityCache allows direct access to the SecurityCache to remove outdated entries. This should only be done by a component implementing the SecurityCacheRulesInvalidator interface to avoid conflict with the security cache loader.
  • SecurityCacheRulesInvalidator is in charge of informing the #SecurityCache# of changes in security rules to properly invalidate outdated cache entries. If the security invalidator does not properly invalidate entries, CacheConflictingException may occurs.

    Since the the authentication part of the security module (which should be in charge of user/group management) is currently missing, an internal UserBridge interface is used to get groups information as well. Another internal XWikiBridge interface is used to check if the wiki is in read-only mode and to retrieve the main wiki reference.

Events

XWiki 11.8+ An event org.xwiki.security.authorization.event.RightUpdatedEvent is sent when a modification may have an impact on the result of a right check (modified XWikiRights object, modified group, etc.).

Cache

A cache of name platform.security.authorization.cache is used to cache both security rule entries as well as security access entries that store the result of a right check for a user and document. Its maximum size is 10000 entries by default. Its maximum size can be configured as explained in the cache module documentation. If the system has enough RAM, it is a good idea to set the maximum cache size at least as 3 times the number of documents in the wiki both to improve the performanceXWiki <15.6  and to avoid bugs like . XWiki 15.6+ The security cache should have a more robust behavior when it is full. It is now only necessary to store security access entries in the actual cache, the rule entries can also be stored in a data structure outside the cache and thus a large capacity is no longer required to ensure that important rule entries aren't disposed by the cache. This means a good cache size for optimal performance would be the number of users that are active at the same time times the number of documents they frequently access. Note that the latter also includes documents that contain code or documents for which just the title is displayed, e.g., in a navigation or change log. The security cache size shouldn't be set below the number of documents that are frequently accessed or performance will severely suffer. If in doubt, check the cache statistics to see the hit rate. A hit rate below 80% (maybe even below 90%) can be a sign that performance could be improved with a larger cache. The reason for this is that if there is a miss for a particular page and user, this entry needs to be re-computed, thereby reading at least four, for nested spaces even more, entries from the cache. Even if all these accesses were hits, the whole computation could have been avoided.

The Security Inspector Application provides a visualization of the cache contents and also more explanations how the cache is structured.

XWiki 16.10.0+

Required Rights

Required rights allow defining which rights the content of a document needs. While regular content shouldn't require any rights, some technical pages could need script right for Velocity macros, or wiki admin right to allow the definition of a macro or a UI Extension with wiki scope. When required rights are enforced (controlled by a flag in the document), only those rights are allowed in the document authorization manager regardless if the last author has more rights. Further, edit right is denied to any user not having those rights. The purpose of required rights is two-fold:

  1. Avoid that when a user with more rights edits a document, malicious content created by a previous author is executed with more rights. If a document doesn't require any rights, script macros for example won't be executed independent of the rights of the last author.
  2. Prevent that users without the required right break a page by not allowing them to edit the page. If the last author should lose a required right after editing a page, the page is still broken, but the situation is much easier to detect.

Required rights are stored as XObjects of class XWiki.RequiredRightClass. Required rights are always the same for all translations of a page and defined on the untranslated document. While the API for required rights has been introduced in XWiki 16.10.0, there is no UI in this version yet, but it allows extensions depending on XWiki 16.10.0 to use the new APIs and to define required rights for their pages as it contains the necessary changes in the XAR Format Specifications.

The role org.xwiki.security.authorization.requiredrights.DocumentRequiredRightsManager allows retrieving the required rights that are defined on a document. Required rights should never be loaded from a modified document, only from a saved document. This is because there are safeguards to prevent unauthorized modifications of required rights during saving. They prevent that users who don't have a certain right to set or remove that right as required right. For this reason, DocumentRequiredRightsManager doesn't allow passing a concrete document instance but only a document reference.

Security Authentication API

See the Authentication API documentation.

Security URL API

See the URL API documentation.

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.

Dependencies

Dependencies for this extension (org.xwiki.platform:xwiki-platform-security-authorization-api 16.9.0):

  • org.xwiki.commons:xwiki-commons-configuration-api 16.9.0
  • org.xwiki.platform:xwiki-platform-security-api 16.9.0
  • org.xwiki.platform:xwiki-platform-model-api 16.9.0
  • org.xwiki.commons:xwiki-commons-cache-api 16.9.0
  • commons-collections:commons-collections 3.2.2
  • org.apache.commons:commons-lang3 3.17.0

Get Connected