Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8dd0b11
Do not block access for ALL users when the license user limit is exce…
KebabRonin Jul 28, 2025
47e64be
Do not block access for ALL users when the license user limit is exce…
KebabRonin Jul 29, 2025
e4ac1ef
Do not block access for ALL users when the license user limit is exce…
KebabRonin Aug 6, 2025
52f9e35
Do not block access for ALL users when the license user limit is exce…
KebabRonin Aug 25, 2025
7fc507a
Do not block access for ALL users when the license user limit is exce…
KebabRonin Aug 25, 2025
c0cbb9f
Do not block access for ALL users when the license user limit is exce…
KebabRonin Aug 29, 2025
2de24c6
Do not block access for ALL users when the license user limit is exce…
KebabRonin Sep 2, 2025
d3eb916
Do not block access for ALL users when the license user limit is exce…
KebabRonin Sep 2, 2025
2d5a325
Do not block access for ALL users when the license user limit is exce…
KebabRonin Sep 2, 2025
2ba4b4b
Do not block access for ALL users when the license user limit is exce…
KebabRonin Sep 4, 2025
8051097
Do not block access for ALL users when the license user limit is exce…
KebabRonin Sep 17, 2025
762ea39
Disable users over license user limit for Auth extensions #206
KebabRonin Sep 18, 2025
a11d4e5
Disable users over license user limit for Auth extensions #206
KebabRonin Sep 19, 2025
6b4cc16
Merge branch 'master' into issue#206
KebabRonin Oct 9, 2025
46e0b37
Disable users over license user limit for Auth extensions #206
KebabRonin Oct 9, 2025
4cb3e21
Disable users over license user limit for Auth extensions #206
KebabRonin Oct 9, 2025
60db9d9
Do not block access for ALL users when the license user limit is exc…
KebabRonin Nov 7, 2025
461281a
Merge branch 'master' into issue#206
KebabRonin Nov 7, 2025
457509c
Do not block access for ALL users when the license user limit is exc…
KebabRonin Nov 7, 2025
fe20bee
Do not block access for ALL users when the license user limit is exc…
KebabRonin Nov 7, 2025
8c42396
Do not block access for ALL users when the license user limit is exc…
KebabRonin Nov 7, 2025
6ef265a
Do not block access for ALL users when the license user limit is exce…
KebabRonin Dec 18, 2025
82100e2
Update application-licensing-licensor/application-licensing-licensor-…
abrassat Jan 29, 2026
7433457
Do not block access for ALL users when the license user limit is exce…
abrassat Jan 29, 2026
661e2ea
Do not block access for ALL users when the license user limit is exce…
KebabRonin Feb 3, 2026
c28e399
Do not block access for ALL users when the license user limit is exce…
abrassat Mar 9, 2026
dc64443
Do not block access for ALL users when the license user limit is exce…
abrassat Mar 17, 2026
6b84ba7
Do not block access for ALL users when the license user limit is exce…
abrassat Mar 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,21 @@ public interface LicenseManager
{
/**
* Retrieve the currently applicable license for the given installed extension.
*
* @param extensionId identifier of an installed extension
* @return a license.
*/
License get(ExtensionId extensionId);

/**
* Retrieve the currently applicable license for the given installed extension.
*
* @param extensionId identifier of an installed extension
* @return a license.
* @since 1.33
*/
License get(String extensionId);

/**
* Add a new license to the current set of active license. The added license is checked to be applicable to the
* current wiki instance, else it will not be added. The license is also checked to be more interesting than the
Expand All @@ -55,6 +65,7 @@ public interface LicenseManager
/**
* Try to delete the given license from the persistence store. The license is not removed from the active set until
* the next restart.
*
* @param licenseId the id of the license to be removed.
*/
void delete(LicenseId licenseId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ public interface Licensor
*/
License getLicense();

/**
* Retrieve the currently applicable license for the given installed extension.
*
* @param extensionId name of an installed extension. This method automatically resolves the version of the
* extension which is installed
* @return a license, or null if the given installed extension is not subject to licensing.
* @since 1.33
*/
License getLicense(String extensionId);

/**
* Retrieve the currently applicable license for the given installed extension.
*
Expand Down Expand Up @@ -75,6 +85,15 @@ public interface Licensor
*/
boolean hasLicensure(ExtensionId extensionId);

/**
* Check if the given extension is covered by a valid license.
*
* @param extensionId the name of the extension for which licensure should be checked.
* @return true if the given extension has a valid license or is not subject to licensing.
* @since 1.33
*/
boolean hasLicensure(String extensionId);

/**
* Check if the given extension is expiring in less than 10 days.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;

import org.slf4j.Logger;
Expand Down Expand Up @@ -115,6 +116,9 @@ public void run()
@Inject
private LicensedExtensionManager licensedExtensionManager;

@Inject
private Provider<InstalledExtensionRepository> installedExtensionRepositoryProvider;

private final Map<LicenseId, License> licenses = new HashMap<>();

private final Map<LicenseId, Integer> licensesUsage = new HashMap<>();
Expand Down Expand Up @@ -327,6 +331,14 @@ public License get(ExtensionId extensionId)
return extensionToLicense.get(extId);
}

@Override
public License get(String extensionId)
{
InstalledExtension installedExtension =
this.installedExtensionRepositoryProvider.get().getInstalledExtension(extensionId, null);
return this.get(installedExtension.getId());
}

@Override
public boolean add(License license)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,20 @@

import java.util.Arrays;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.apache.commons.lang3.builder.CompareToBuilder;
import org.slf4j.Logger;
import org.xwiki.bridge.event.DocumentCreatedEvent;
import org.xwiki.bridge.event.DocumentDeletedEvent;
import org.xwiki.bridge.event.DocumentUpdatedEvent;
import org.xwiki.component.annotation.Component;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.LocalDocumentReference;
import org.xwiki.observation.AbstractEventListener;
import org.xwiki.observation.event.Event;
Expand All @@ -46,14 +50,18 @@

/**
* Component used to count the existing active users.
*
*
* @version $Id$
* @since 1.6
*/
@Component(roles = UserCounter.class)
@Singleton
public class UserCounter
{
protected static final String BASE_USER_QUERY = ", BaseObject as obj, IntegerProperty as prop "
+ "where doc.fullName = obj.name and obj.className = 'XWiki.XWikiUsers' and prop.id.id = obj.id "
+ "and prop.id.name = 'active' and prop.value = '1'";

@Inject
private Logger logger;

Expand All @@ -73,6 +81,9 @@ public class UserCounter

private Long cachedUserCount;

// A set of all users on the instance, from all subwikis, sorted by creation date.
private SortedSet<XWikiDocument> cachedSortedUsers;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you see how we could do some performance tests? We would need to know that this doesn't fail on an instance with a loot of users

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm guessing this depends on the JVM allocated memory. Iirc, this comment mentioned that the cache size should be ok (at least proportionally to the instance size).

I'll try to create a test with ~10.000 users on the docker instance, but I'll have to see if the test instance can really support 10k users.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would be nice if we could have it. I know there were some script to create multiple users / wikis
This could be done as an extra step after you finish the code part :)


/**
* Event listener that invalidates the cached user count when an user is added, deleted or the active property's
* value is changed.
Expand Down Expand Up @@ -124,11 +135,41 @@ public void onEvent(Event event, Object source, Object data)

if (newDocumentIsUser != oldDocumentIsUser || newActive != oldActive) {
// The user object is either added/removed or set to active/inactive. Invalidate the cached user count.
this.userCounter.cachedUserCount = null;
this.userCounter.flushCache();
}
}
}

/**
* Flush the cache of the user counter.
*
* @since 1.33
*/
public void flushCache()
{
this.cachedUserCount = null;
this.cachedSortedUsers = null;
}

/**
* Get all users on the instance, from all subwikis, sorted by creation date.
*
* @return the users, sorted by creation date.
* @since 1.33
*/
public SortedSet<XWikiDocument> getSortedUsers() throws WikiManagerException, QueryException
{
if (cachedSortedUsers == null) {
cachedSortedUsers = new TreeSet<>(
(e1, e2) -> new CompareToBuilder().append(e1.getCreationDate(), e2.getCreationDate()).build());
for (String wikiId : wikiDescriptorManager.getAllIds()) {
cachedSortedUsers.addAll(getUsersOnWiki(wikiId));
}
cachedUserCount = (long) cachedSortedUsers.size();
}
return cachedSortedUsers;
}

/**
* Counts the existing active users.
*
Expand All @@ -153,15 +194,46 @@ public long getUserCount() throws Exception
return cachedUserCount;
}

private long getUserCountOnWiki(String wikiId) throws QueryException
/**
* Return whether the given user is under the specified license user limit.
*
* @param user the user to check
* @param userLimit the license max user limit
* @return whether the given user is under the specified license user limit
* @since 1.33
*/
public boolean isUserUnderLimit(DocumentReference user, long userLimit) throws Exception
{
StringBuilder statement = new StringBuilder(", BaseObject as obj, IntegerProperty as prop ");
statement.append("where doc.fullName = obj.name and obj.className = 'XWiki.XWikiUsers' and ");
statement.append("prop.id.id = obj.id and prop.id.name = 'active' and prop.value = '1'");
if (null == user) {
return false;
}
if (userLimit < 0 || getUserCount() <= userLimit) {
// Unlimited licenses should always return true.
// Also, skip the checks for instances with fewer users than the limit.
return true;
}
SortedSet<XWikiDocument> sortedUsers = getSortedUsers();
XWikiDocument userDocument =
sortedUsers.stream().filter(userDoc -> userDoc.getDocumentReference().equals(user)).findFirst()
.orElse(null);
if (userDocument == null) {
return false;
} else {
return sortedUsers.headSet(userDocument).size() < userLimit;
}
}

Query query = this.queryManager.createQuery(statement.toString(), Query.HQL);
private long getUserCountOnWiki(String wikiId) throws QueryException
{
Query query = this.queryManager.createQuery(BASE_USER_QUERY, Query.HQL);
query.addFilter(this.uniqueFilter).addFilter(this.countFilter).setWiki(wikiId);
List<Long> results = query.execute();
return results.get(0);
}

private List<XWikiDocument> getUsersOnWiki(String wikiId) throws QueryException
{
return this.queryManager.createQuery("select doc from XWikiDocument doc" + BASE_USER_QUERY, Query.HQL)
.setWiki(wikiId).execute();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ public License getLicense(EntityReference reference)
return null;
}

@Override
public License getLicense(String extensionId)
{
try {
return licenseManager.get(extensionId);
} catch (Throwable e) {
// Ignored
}
return null;
}

@Override
public License getLicense(ExtensionId extensionId)
{
Expand Down Expand Up @@ -123,6 +134,13 @@ public boolean hasLicensure(ExtensionId extensionId)
return license == null || licenseValidator.isValid(license);
}

@Override
public boolean hasLicensure(String extensionId)
{
License license = getLicense(extensionId);
return license == null || licenseValidator.isValid(license);
}

@Override
public boolean isLicenseExpiring(ExtensionId extensionId)
{
Expand Down
Loading