User API

Last modified by Vincent Massol on 2024/02/26 17:54

 XWiki
 Feature
 Completed
 
 

https://forum.xwiki.org/t/add-a-new-xwikiuser-displayhiddendocuments-api/6327

Description

Package: org.xwiki.user
Modules: 

  • xwiki-platform-user-api
  • xwiki-platform-user-script
  • xwiki-platform-user-resource

Use Cases

Here are use cases that drove the design of the User API:

  • Requirement 1: Make the API as simple and short to use as possible (e.g. $services.user.resolveUser('...').configuration.editor is more complex than $services.user.properties.editor)
  • Requirement 2: Make the API as performant as possible in 2 areas:
    • Execution time: make it be as fast as possible to execute for the main needs (i.e. getting user properties)
    • Memory: Make it use the less memory possible
  • Requirement 3: Ability to retrieve user properties
  • Requirement 4: Ability to create, delete, update users and their properties and check for user existence
  • Requirement 5: Ability to generate URLs that points to User profile pages
  • Requirement 6: Ability to resolve not only direct user properties but also with fallbacks at different levels (current space, current wiki, xwiki.properties, etc)
  • Requirement 7: The API has to be independent of the User store implementation (ie users stored in wiki pages or elsewhere)
  • Requirement 8: The API has to be both read (get properties) and write (create users, update properties)
  • Requirement 9 (added Nov 2020): Ability to use several stores at the same time (i.e. possibility to have one user reference coming from one store and another coming from another store). This allows for example to use User References from both XWiki and the Fediverse (webfinger) at the same time and have a user picker for both.

Iteration Nov 2020

Requirement 9 implies adding a new textual format for user references in the form (<store hint><delimitation character(s)>)+<store-dependent reference id>.

We need to find a delimitation character that is backward-compatible with the "document" store references (which is of the form <wiki>:<space1>.<spaceN>.<page>, <space1>.<spaceN>.<page> or <page>).

Issue to resolve:

  • Imagine that we have a user reference that is dependent on the store. It means this reference will be saved in various DB tables (author of a document, etc). Then imagine we change the store! Suddenly all existing references become invalid and cannot be used => We need a fixed reference format.

Iteration N

See:

Iteration 2

See https://github.com/xwiki/xwiki-platform/commit/d361507b181272ed37c57e7aaf2c64338e2c8b01

Iteration 1

/**
 * Represents an XWiki user. Note that it's independent from where users are stored, and should remain that way, so
 * that we can switch the user store in the future.
 *
 * @version $Id$
 * @since 12.2RC1
 */

@Unstable
public interface User
{
   /**
     * @return true if the user is configured to display hidden documents in the wiki
     */

   boolean displayHiddenDocuments();

   /**
     * @return true if the user is active in the wiki. An active user can log in.
     */

   boolean isActive();

   /**
     * @return the first name of the user or null if not set
     */

    String getFirstName();

   /**
     * @return the last name of the user or null if not set
     */

    String getLastName();

   /**
     * @return the email address of the user and null if not set
     */

    String getEmail();

   /**
     * @return the type of the user (simple user, advanced user)
     * @see <a href="https://bit.ly/37TUlCp">user profile</a>
     */

    UserType getType();

   /**
     * @return true if the user's email has been checked. In some configurations, users must have had their emails
     *         verified before they can access the wiki. Also, disabled users must have their emails checked to be
     *         able to view pages.
     */

   boolean isEmailChecked();

   /**
     * @return true if this user is the guest user (i.e. not a real user)
     */

   boolean isGuest();

   /**
     * @return true if this user is registered in the main wiki (i.e. it's a global user)
     */

   boolean isGlobal();

   /**
     * @return true if this user is the {@code superadmin} user
     */

   boolean isSuperAdmin();

   /**
     * @param propertyName the name of the user property to look for
     * @return the value of the passed user property
     */

    Object getProperty(String propertyName);

   /**
     * @return the reference to his user (i.e. a way to retrieve this user's data)
     */

    UserReference getUserReference();
}
/**
 * CRUD operations on users.
 *
 * @version $Id$
 * @since 12.2RC1
 */

@Unstable
@Role
public interface UserManager
{
   /**
     * @param userReference the user to retrieve and if null then retrieve the current user
     * @return the user object representing the user pointed to by the passed reference
     */

    User getUser(UserReference userReference);
}
/**
 * The type of the user (simple user, advanced user).
 *
 * @see <a href="https://bit.ly/37TUlCp">user profile</a>
 * @version $Id$
 * @since 12.2RC1
 */

@Unstable
public enum UserType
{
   /**
     * Simple user (hides complex actions in the UI for simplicity).
     */

    SIMPLE,

   /**
     * Advanced user (sees all possible actions in the UI).
     */

    ADVANCED;

   /**
     * @param typeAsString the user type represented as a string ("simple", "advanced")
     * @return the {@link UserType} object matching the passed string representation. All values different than
     *         {@code advanced} are considered to represent a simple user
     */

   public static UserType fromString(String typeAsString)
   {
        UserType result;
       if (typeAsString != null && "advanced".equals(typeAsString)) {
            result = ADVANCED;
       } else {
            result = SIMPLE;
       }
       return result;
   }
}
/**
 * Abstracts the concept of User reference. This allows to support several store implementations for users.
 * For example for an implementation storing the users in wiki pages, the internal reference would be a
 * {@link org.xwiki.model.reference.DocumentReference}. The reference allows retrieving all the data about a user.
 * Another internal reference implementation could be an ActivityPub URL for example.
 *
 * @version $Id$
 * @since 12.2RC1
 */

@Unstable
public interface UserReference
{
   /**
     * @param <T> the internal type of the reference
     * @return the internal reference (e.g. a {@link org.xwiki.model.reference.DocumentReference} for an implementation
     *         storing users in wiki pages)
     */

   <T> T getReference();
}

Notes

  • I've removed the UserManager#getCurrentUser() because it's not the purpose of UserManager to do that. Normally the current User should be put in the Execution Context and retrieved directly from there. However I don't know how to implement this for the moment. Currently the old XWikiUser object is put in the XWikiContext and it's complex to replace it with the User object (This requires changing the Authentication code (which currently returns a XWikiUser object). The other problem is that XWikiContext is in oldcore and thus we cannot have it depend on the user-default module (since that modules depends on oldcore itself). So we need to refactor how the user is set in the context and find a way to inject that information from either some authentication module (ie move authentication code currently located in oldcore - XWikiAuthServiceImpl - to some other module outside of oldcore) or from the user-default module. Thus FTM, I've chosen to offer a convenience way to get the current User object, by passing null to UserManager#getUser(). This should be modified in the future when we can directly get the current User object from the context.

TODO

  • Store the current User object in the Execution Context (or in the XWikiContext as a start)
  • Add createUser() and deleteUser() to UserManager
  • Add setters to User and add a saveUser() (or updateUser() to UserManager)

 


Tags:
    

Get Connected