Skip to content

Commit

Permalink
New dependencies on licensed extensions are not covered by existing l…
Browse files Browse the repository at this point in the history
…icenses when upgrading licensed extensions #174

* code refactorings
  • Loading branch information
oanalavinia committed Oct 2, 2024
1 parent f9e0f1a commit 5f6d688
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 148 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,29 @@
*/
package com.xwiki.licensing;

public interface LicensedExtensionUpdateManager
import org.xwiki.component.annotation.Role;
import org.xwiki.extension.ExtensionId;
import org.xwiki.stability.Unstable;

/**
* Description in progress.
*
* @version $Id$
* @since 1.27
*/
@Role
@Unstable
public interface LicenseUpdater
{
/**
* Make request to store for renewing this extension's license. Description in progress.
*
* @param extensionId extension for which the license needs an update
*/
void renewLicense(ExtensionId extensionId);

/**
* Retrieve licenses updates from the XWiki Store.
*/
void getLicensesUpdates();
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
package com.xwiki.licensing;

import java.util.Collection;
import java.util.Set;

import org.xwiki.component.annotation.Role;
import org.xwiki.extension.ExtensionId;
import org.xwiki.extension.InstalledExtension;
import org.xwiki.stability.Unstable;

/**
Expand Down Expand Up @@ -70,4 +72,14 @@ public interface LicensedExtensionManager
* @since 1.21.1
*/
void invalidateMandatoryLicensedExtensionsCache();

/**
* Get the list of licensed dependencies of an extension from a specific namespace.
*
* @param installedExtension the installed extension for which to look for licensed dependencies
* @param namespace the namespace where to look for dependencies
* @return a set of installed licensed dependencies
* @since 1.27
*/
Set<ExtensionId> getLicensedDependencies(InstalledExtension installedExtension, String namespace);
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public interface LicensingConfiguration
* Get the URL used in renewing an existing license of an extension from store.
*
* @return the URL used in renewing an existing license or null if the value of the property is not filled up
* @since 1.27
*/
String getStoreLicenseRenewURL();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,45 @@
*/
package com.xwiki.licensing.internal;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collection;
import java.util.Set;
import java.util.Stack;
import java.util.List;

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

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.http.client.utils.URIBuilder;
import org.slf4j.Logger;
import org.xwiki.component.annotation.Component;
import org.xwiki.extension.ExtensionDependency;
import org.xwiki.crypto.BinaryStringEncoder;
import org.xwiki.extension.ExtensionId;
import org.xwiki.extension.InstalledExtension;
import org.xwiki.extension.repository.InstalledExtensionRepository;
import org.xwiki.instance.InstanceIdManager;
import org.xwiki.properties.converter.Converter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.xpn.xwiki.XWikiContext;
import com.xwiki.licensing.License;
import com.xwiki.licensing.LicenseManager;
import com.xwiki.licensing.LicenseUpdater;
import com.xwiki.licensing.LicensedExtensionManager;
import com.xwiki.licensing.LicensingConfiguration;
import com.xwiki.licensing.Licensor;

/**
* In progress: these methods might be moved to other components at the refactoring step.
* Handle license updates processes. Description in progress.
*
* @version $Id$
* @since 1.27
*/
@Component(roles = LicenseRenew.class)
@Component
@Singleton
public class LicenseRenew
public class DefaultLicenseUpdater implements LicenseUpdater
{
private static final String FEATURE_ID = "featureId";

Expand All @@ -72,37 +82,25 @@ public class LicenseRenew
private Provider<InstanceIdManager> instanceIdManagerProvider;

@Inject
private TrialLicenseGenerator trialLicenseGenerator;
private Provider<Licensor> licensorProvider;

public void getLicensedDependencies(Set<ExtensionId> licensedDependencies, Stack<ExtensionId> dependencyPath,
InstalledExtension installedExtension, String namespace)
{
Collection<ExtensionDependency> dependencies = installedExtension.getDependencies();

for (ExtensionDependency dep : dependencies) {
InstalledExtension installedDep =
installedExtensionRepository.getInstalledExtension(dep.getId(), namespace);
if (installedDep == null || licensedDependencies.contains(installedDep.getId())
|| dependencyPath.search(installedDep.getId()) > 0)
{
return;
}
@Inject
private Provider<LicenseManager> licenseManagerProvider;

if (licensedExtensionManager.getLicensedExtensions().contains(installedDep.getId())) {
licensedDependencies.add(installedDep.getId());
}
@Inject
@Named("Base64")
private BinaryStringEncoder base64decoder;

dependencyPath.push(installedDep.getId());
getLicensedDependencies(licensedDependencies, dependencyPath, installedDep, namespace);
}
}
@Inject
private Converter<License> converter;

@Override
public void renewLicense(ExtensionId extensionId)
{
try {
URL licenseRenewURL = getLicenseRenewURL(extensionId);
if (licenseRenewURL == null) {
logger.debug("Failed to add renew license for [{}] because the licensor configuration is not complete. "
logger.debug("Failed to renew license for [{}] because the licensor configuration is not complete. "
+ "Check your store license renew URL and owner details.", extensionId.getId());
return;
}
Expand All @@ -114,15 +112,73 @@ public void renewLicense(ExtensionId extensionId)
logger.debug("Failed to renew license for [{}] on store.", extensionId.getId());
} else {
logger.debug("License renewed for [{}]", extensionId.getId());
// This will be moved to other component
//trialLicenseGenerator.updateLicenses();
// getLicensesUpdates();
}
} catch (Exception e) {
logger.warn("Failed to update license for [{}]. Root cause is [{}]", extensionId,
ExceptionUtils.getRootCauseMessage(e));
}
}

@Override
@SuppressWarnings("unchecked")
public void getLicensesUpdates()
{
try {
URL licensesUpdateURL = getLicensesUpdatesURL();
if (licensesUpdateURL == null) {
logger.warn("Failed to update licenses because the licensor configuration is not complete. "
+ "Check your store update URL.");
return;
}

XWikiContext xcontext = contextProvider.get();
String licensesUpdateResponse = xcontext.getWiki().getURLContent(licensesUpdateURL.toString(), xcontext);
ObjectMapper objectMapper = new ObjectMapper();

List<String> retriedLicenses = (List<String>) objectMapper.readValue(licensesUpdateResponse, Object.class);
for (String license : retriedLicenses) {
License retrivedLicense = converter.convert(License.class, base64decoder.decode(license));
if (retrivedLicense != null) {
licenseManagerProvider.get().add(retrivedLicense);
}
}
} catch (URISyntaxException | IOException e) {
logger.warn("Error while updating licenses. Root cause [{}]", ExceptionUtils.getRootCauseMessage(e));
}
}

/**
* Construct the URL for updating licenses.
*
* @return the URL for updating licenses, or null if it cannot be constructed
* @throws URISyntaxException if the URL is not valid
* @throws MalformedURLException if an error occurred while constructing the URL
*/
private URL getLicensesUpdatesURL() throws URISyntaxException, MalformedURLException
{
String storeUpdateURL = licensingConfig.getStoreUpdateURL();
// In case the property has no filled value, the URL cannot be constructed.
if (storeUpdateURL == null) {
return null;
}

URIBuilder builder = new URIBuilder(storeUpdateURL);
builder.addParameter(INSTANCE_ID, instanceIdManagerProvider.get().getInstanceId().toString());
builder.addParameter("outputSyntax", "plain");

for (ExtensionId paidExtensionId : licensedExtensionManager.getMandatoryLicensedExtensions()) {
builder.addParameter(FEATURE_ID, paidExtensionId.getId());

License license = licensorProvider.get().getLicense(paidExtensionId);
if (license != null && !License.UNLICENSED.equals(license)) {
builder.addParameter(String.format("expirationDate:%s", paidExtensionId.getId()),
Long.toString(license.getExpirationDate()));
}
}
return builder.build().toURL();
}

private URL getLicenseRenewURL(ExtensionId extensionId) throws Exception
{
String storeLicenseRenewURL = licensingConfig.getStoreLicenseRenewURL();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.xwiki.extension.InstalledExtension;
import org.xwiki.extension.ResolveException;
import org.xwiki.extension.repository.InstalledExtensionRepository;
import org.xwiki.stability.Unstable;

import com.xwiki.licensing.LicensedExtensionManager;
import com.xwiki.licensing.LicensedFeatureId;
Expand Down Expand Up @@ -196,4 +197,44 @@ public void invalidateMandatoryLicensedExtensionsCache()
{
this.cachedMandatoryLicensedExtensions = null;
}

@Override
@Unstable
public Set<ExtensionId> getLicensedDependencies(InstalledExtension installedExtension, String namespace)
{
Set<ExtensionId> licensedDependencies = new HashSet<>();
Set<ExtensionId> verifiedExtensions = new HashSet<>();

getLicensedDependencies(installedExtension, namespace, getLicensedExtensions(), licensedDependencies,
verifiedExtensions);
logger.debug("Found licensed dependencies for extension [{}] : [{}]", installedExtension.getId(),
licensedDependencies);

return licensedDependencies;
}

private void getLicensedDependencies(InstalledExtension installedExtension, String namespace,
Collection<ExtensionId> installedLicensedExtensions, Set<ExtensionId> licensedDependencies,
Set<ExtensionId> verifiedExtensions)
{
Collection<ExtensionDependency> dependencies = installedExtension.getDependencies();

for (ExtensionDependency dep : dependencies) {
InstalledExtension installedDep =
installedExtensionRepository.getInstalledExtension(dep.getId(), namespace);
if (installedDep == null || licensedDependencies.contains(installedDep.getId())
|| verifiedExtensions.contains(installedDep.getId()))
{
return;
}

if (installedLicensedExtensions.contains(installedDep.getId())) {
licensedDependencies.add(installedDep.getId());
}

verifiedExtensions.add(installedDep.getId());
getLicensedDependencies(installedDep, namespace, installedLicensedExtensions, licensedDependencies,
verifiedExtensions);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ public String getLicensingOwnerEmail()
return this.ownerConfig.getProperty("email");
}

@Override
public String getStoreLicenseRenewURL()
{
return this.storeConfig.getProperty("storeLicenseRenewURL");
}

@SuppressWarnings("unchecked")
private List<String> convertObjectToStringList(Object list)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ public class GetTrialLicenseListener implements EventListener
@Inject
private LicensedExtensionManager licensedExtensionManager;

@Inject
private DefaultLicenseUpdater licenseUpdater;

@Override
public List<Event> getEvents()
{
Expand All @@ -83,7 +86,7 @@ public void onEvent(Event event, Object source, Object data)

licensedExtensionManager.invalidateMandatoryLicensedExtensionsCache();
// Retrieve license updates to be sure that we don't override an existing license.
trialLicenseGenerator.updateLicenses();
licenseUpdater.getLicensesUpdates();

for (ExtensionId extensionId : extensions) {
InstalledExtension installedExtension = installedExtensionRepository.getInstalledExtension(extensionId);
Expand Down
Loading

0 comments on commit 5f6d688

Please sign in to comment.