package com.atlassian.labs.speakeasy.manager; import com.atlassian.labs.speakeasy.commonjs.descriptor.CommonJsModulesDescriptor; import com.atlassian.labs.speakeasy.data.SpeakeasyData; import com.atlassian.labs.speakeasy.manager.convention.JsonManifestHandler; import com.atlassian.labs.speakeasy.model.Extension; import com.atlassian.labs.speakeasy.model.UserExtension; import com.atlassian.labs.speakeasy.model.JsonManifest; import com.atlassian.labs.speakeasy.util.ExtensionValidate; import com.atlassian.plugin.ModuleDescriptor; import com.atlassian.plugin.Plugin; import com.atlassian.plugin.PluginAccessor; import com.atlassian.plugin.impl.UnloadablePlugin; import com.atlassian.sal.api.user.UserManager; import com.atlassian.sal.api.user.UserProfile; import org.osgi.framework.BundleContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; import java.util.Set; import static com.google.common.collect.Sets.newHashSet; /** * */ @Component class ExtensionBuilder { private final PermissionManager permissionManager; private final UserManager userManager; private final SpeakeasyData data; private final JsonManifestHandler jsonManifestHandler; private final PluginAccessor pluginAccessor; private final BundleContext bundleContext; @Autowired public ExtensionBuilder(PermissionManager permissionManager, UserManager userManager, SpeakeasyData data, JsonManifestHandler jsonManifestHandler, PluginAccessor pluginAccessor, BundleContext bundleContext) { this.permissionManager = permissionManager; this.userManager = userManager; this.data = data; this.jsonManifestHandler = jsonManifestHandler; this.pluginAccessor = pluginAccessor; this.bundleContext = bundleContext; } public Extension build(Plugin plugin) throws PluginOperationFailedException { return buildGlobal(plugin, new Extension(plugin)); } public UserExtension build(Plugin plugin, String userName, Iterable<Plugin> speakeasyPlugins) throws PluginOperationFailedException { UserExtension extension = buildGlobal(plugin, new UserExtension(plugin)); boolean canAuthor = permissionManager.canAuthorExtensions(userName); List<String> accessList = data.getUsersList(plugin.getKey()); boolean isAuthor = userName.equals(extension.getAuthor()); boolean hasFavorited = data.getFavorites(plugin.getKey()).contains(userName); boolean pureSpeakeasy = ExtensionValidate.isPureSpeakeasyExtension(bundleContext, plugin); if (extension.isAvailable()) { extension.setEnabled(accessList.contains(userName)); extension.setCanEnable(!extension.isEnabled()); extension.setCanDisable(extension.isEnabled()); } boolean canEdit = isAuthor && pureSpeakeasy && canAuthor; extension.setCanEdit(canEdit); extension.setCanUninstall(canEdit); extension.setCanFork(!extension.isFork() && pureSpeakeasy && canAuthor); extension.setCanFavorite(!hasFavorited); extension.setCanDownload(pureSpeakeasy && canAuthor); // if the user has already forked this, don't let them fork again if (!extension.isFork()) { for (Plugin plug : speakeasyPlugins) { if (extension.getKey().equals(Extension.getForkedPluginKey(plug.getKey())) && userName.equals(getPluginAuthor(plug))) { extension.setCanFork(false); } } } // if the user is an admin and admins aren't allowed to enable if (!permissionManager.canEnableExtensions(userName)) { extension.setCanEnable(false); extension.setCanFork(false); extension.setCanEdit(false); } // global extensions are quite limited final boolean isGlobalExtension = data.isGlobalExtension(plugin.getKey()); if (isGlobalExtension) { extension.setCanEnable(false); extension.setCanDisable(false); extension.setEnabled(true); extension.setCanEdit(false); extension.setCanUninstall(false); extension.setCanFork(false); } // forks of global extensions can't be enabled if (data.isGlobalExtension(extension.getForkedPluginKey())) { extension.setCanEnable(false); } if (userManager.isAdmin(userName)) { // if already a global extension, allow the admin to disable it if (isGlobalExtension) { extension.setCanDisableGlobally(true); extension.setCanEdit(true); extension.setCanUninstall(true); } else { extension.setCanEnableGlobally(true); } } return extension; } private <T extends Extension> T buildGlobal(Plugin plugin, T extension) throws PluginOperationFailedException { String author = getPluginAuthor(plugin); extension.setAuthor(author); UserProfile profile = userManager.getUserProfile(author); extension.setAuthorDisplayName(profile != null && profile.getFullName() != null ? profile.getFullName() : author); extension.setAuthorEmail(profile != null ? profile.getEmail() : "unknown@example.com"); List<String> accessList = data.getUsersList(plugin.getKey()); extension.setNumUsers(accessList.size()); extension.setNumFavorites(data.getFavorites(plugin.getKey()).size()); if (plugin.getResource("/" + JsonManifest.ATLASSIAN_EXTENSION_PATH) != null) { JsonManifest mf = jsonManifestHandler.read(plugin); extension.setDescription(mf.getDescription()); extension.setName(mf.getName()); extension.setExtension("zip"); } // try to detect a failed install of a zip plugin else if (plugin instanceof UnloadablePlugin && plugin.getModuleDescriptor("modules") != null && plugin.getModuleDescriptor("images") != null && plugin.getModuleDescriptor("css") != null) { extension.setExtension("zip"); extension.setName(plugin.getName()); extension.setDescription(((UnloadablePlugin) plugin).getErrorText()); } else if (plugin.getResource("/atlassian-plugin.xml") != null) { extension.setExtension("jar"); } else { extension.setExtension("xml"); } if (extension.getName() == null) { extension.setName(extension.getKey()); } if (extension.getDescription() == null) { extension.setDescription(""); } if (pluginAccessor.isPluginEnabled(plugin.getKey())) { Set<String> unresolvedExternalModuleDependencies = findUnresolvedCommonJsDependencies(plugin); if (unresolvedExternalModuleDependencies.isEmpty()) { extension.setAvailable(true); } else { extension.setDescription("Unable to find modules: " + unresolvedExternalModuleDependencies); } } else if (plugin instanceof UnloadablePlugin) { extension.setDescription(((UnloadablePlugin)plugin).getErrorText()); } extension.setFork(extension.getForkedPluginKey() != null); return extension; } private Set<String> findUnresolvedCommonJsDependencies(Plugin plugin) { Set<String> unresolved = newHashSet(); for (ModuleDescriptor descriptor : plugin.getModuleDescriptors()) { if (descriptor instanceof CommonJsModulesDescriptor) { unresolved.addAll(((CommonJsModulesDescriptor)descriptor).getUnresolvedExternalModuleDependencies()); } } return unresolved; } private String getPluginAuthor(Plugin plugin) { String author = data.getPluginAuthor(plugin.getKey()); if (author == null) { author = plugin.getPluginInformation().getVendorName(); } if (author == null) { author = "(unknown)"; } return author; } }