Skip to content

Conversation

@KebabRonin
Copy link
Contributor

@KebabRonin KebabRonin commented Sep 30, 2025

Mostly API changes to better support this functionality in Auth Extensions.

  • DefaultLicensor.java, DefaultLicenseManager.java - Some additional APIs which accept extension IDs as String instead of ExtensionId
  • UserCounter.java - Added an in-memory cache of active (non-disabled) users on the instance, storing the users in the order of their creation date
  • AuthExtensionUserManager.java - Added interface for auth extensions, with mostly unused methods

…eded xwikisas#192

* Add guest bypass
* Make user counter cache update more specific to users in the XWiki space
…eded xwikisas#192

* [Dev checkpoint] Fix for Active Directory issue
@KebabRonin
Copy link
Contributor Author

KebabRonin commented Oct 7, 2025

How to store the users managed by an Auth extension (like AD)?

The idea for this fix would be to add a mechanism so once the user limit is exceeded for an extension handling users, newer users are disabled automatically, and re-enabled once the license is upgraded. This should be done so we do not block access to the instance.

For this, we need the list of users which are part of an extension, to manage it in different situations. On every save of a page that contains a XWiki.XWikiUsers object, check:

  1. if the license user limit is exceeded -> get all AD users and block those over the limit
  2. when the license user limit has been upgraded / downgraded -> get all AD users and block / unblock users
  3. an user is manually enabled -> check if changes are needed for AD users
  4. an AD user is manually enabled -> prevent the change

The question right now is how should we handle this list of users, how to store / mark them, in order for it to be reliable without impacting performance. We identified 3 ways:


Solr

The idea would be to add a custom solr field using SolrEntityMetadataExtractor (example in Admin Tools).

In short, for a page containing XWiki.XWikiUser class, we would add a custom field in SOLR in order to mention if it's a AD / EntraID / .. user (details to be decided after). Like this, when we need to get AD users we can run a fast SOLR query.

PRO:

  • Should be faster and more efficient

CON:

  • Depends on unpredictable (re)indexing operations - could have unexpected results until the indexing is done and users' active statuses are recomputed. E.g. The reindex operation is still in progress when a new XWikiUser page is added or modified. On this moment we would need to query how many AD users we have, if the limit is exceeded. But since solr is reindexing, we won't have complete results.

QUESTIONS

  • What exactly is the result of a solr query while solr is reindexing, do we simply get the current result from the index, or an error that mentions the index is not ready?
  • Can we wait for reindex to be done (it can take more than a day for some instances)?
    • Is it a problem that temporarily the license limit could not be enforced, as it will be enforced at the next user operation?
    • Could it be used as a loophole by users, to be able to add multiple users?

Database Query (HQL)

PRO:

  • Information always reflects reality

CON:

  • Need to run the same query on all subwikis
  • Could be slow

In-Memory cache

PRO:

  • Easy access from java

CON:

  • Storing all users in memory may become costly for large instances - even if the cache is stored on disk when not in use
  • Need to implement cache updates/invalidation

Implementation detail:
Each Auth extension will probably need to implement a component which describes which users are managed by an app, and which users are still allowed after a license expiration.

@oanalavinia oanalavinia changed the title Disable users over license user limit for Auth extensions #206 Do not block access for ALL users when the license user limit is exceeded #192 Oct 10, 2025
@michitux
Copy link

An in-memory cache for all users feels perfectly fine. As an optimization, you could disable the feature (and the cache) when the license is unlimited, which should avoid issues on huge instances. Otherwise, we control what kind of licenses we provide so we can test the memory usage for the license limits we provide. But as usernames shouldn't be more than 100 bytes, even 1 million of them wouldn't consume more than 100MB, which doesn't sound much for an instance of 1 million users.

Solr should never fail queries while indexing is running, it just returns the results from the last time commit was called. Since XWiki 16.9.0RC1, you can wait for Solr to index everything that has been submitted to the indexing queue before your call, see SolrIndex#waitReady. Note that we already index all XObject properties, it might already be possible with this to search for users managed by the AD extension without any extra attributes.

I would be very careful with automatically enabling users as you need to be careful that you don't enable users that have been intentionally disabled by the admin. Instead, I would rather suggest blocking the login itself if that's possible. I wouldn't worry so much about admins temporarily bypassing restrictions, I would more worry about user experience. When you're starting using the wiki and your first experience is that after a few seconds your user is deactivated/on the next login your user is deactivated and you don't understand why, that's bad. There should be clear and understandable error messages for the affected users.

@KebabRonin KebabRonin marked this pull request as ready for review November 7, 2025 12:37
@KebabRonin KebabRonin marked this pull request as draft November 7, 2025 12:39
# Conflicts:
#	application-licensing-licensor/application-licensing-licensor-api/src/main/java/com/xwiki/licensing/internal/UserCounter.java
#	application-licensing-licensor/application-licensing-licensor-api/src/test/java/com/xwiki/licensing/internal/UserCounterTest.java
…eeded xwikisas#192

* Remove unnecessary changes
* Fix merge
* Remove licensing events
}

@Override
public License get(String extensionId)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method was moved here from Active Directory, in order to reduce class complexity on AD.

* @param context XWiki context, to help in querying pages
* @return a reference to the user page with the XWiki.XWikiUsers object, or null if not existent
*/
DocumentReference getUserDocFromUsername(String username, XWikiContext context);
Copy link
Contributor Author

@KebabRonin KebabRonin Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except for this method, all other methods in this interface are only 'nice to have'. They might be useful in the future but will likely need to be modified to fit a real requirement. They are not used in the Active Directory issue, so they can be removed.

In fact, this entire class could be removed from the licensor, unless there is a good reason to have a common interface for paid Authentication Extensions (like maybe altering the Admin section User list UI to show which users are licensed).

WDYT?

super(HINT,
Arrays.asList(new DocumentCreatedEvent(), new DocumentUpdatedEvent(), new DocumentDeletedEvent()));
super(HINT, Arrays.asList(new DocumentCreatedEvent(XWIKI_SPACE_FILTER),
new DocumentUpdatedEvent(XWIKI_SPACE_FILTER), new DocumentDeletedEvent(XWIKI_SPACE_FILTER)));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since valid users are only in the XWiki space, this filter might improve performance. But this change is not integral to this PR.

@Singleton
public class UserCounter
{
protected static final String BASE_USER_QUERY = ", BaseObject as obj, IntegerProperty as prop "
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protected only for being accessible from the UserCounterTest class.

// A set of users on the instance, sorted by creation date.
private SortedSet<XWikiDocument> cachedSortedUsers;

// Helper to find users in constant time.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the cachedSortedUsers is populated from the database, dummy XWikiDocuments can't be used to index into the set directly (equals fails because it checks for a bunch of fields like version, author, etc.).

This is the reason why the map is needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

Do not block access for ALL users when the license user limit is exceeded

2 participants