diff --git a/pom.xml b/pom.xml index 155fe6d..f2795f9 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.ngs.stash.externalhooks external-hooks - 7.5.0 + 8.0.0 reconquest @@ -149,7 +149,7 @@ UTF-8 - 6.6.1 + 6.8.0 ${bitbucket.version} 3.0.5 8.0.1 diff --git a/src/main/java/com/ngs/stash/externalhooks/hook/ExternalAsyncPostReceiveHook.java b/src/main/java/com/ngs/stash/externalhooks/hook/ExternalAsyncPostReceiveHook.java index 5d87c27..53c2cff 100644 --- a/src/main/java/com/ngs/stash/externalhooks/hook/ExternalAsyncPostReceiveHook.java +++ b/src/main/java/com/ngs/stash/externalhooks/hook/ExternalAsyncPostReceiveHook.java @@ -37,6 +37,7 @@ public class ExternalAsyncPostReceiveHook private ExternalHookScript externalHookScript; private RepositoryHookService repositoryHookService; + public static final String KEY_ID = "external-post-receive-hook"; public ExternalAsyncPostReceiveHook( AuthenticationContext authenticationContext, @@ -51,6 +52,27 @@ public ExternalAsyncPostReceiveHook( throws IOException { this.repositoryHookService = repositoryHookService; + this.externalHookScript = getExternalHookScript( + authenticationContext, + permissions, + pluginLicenseManager, + clusterService, + storageProperties, + hookScriptService, + pluginSettingsFactory, + securityService); + } + + public static ExternalHookScript getExternalHookScript( + AuthenticationContext authenticationContext, + PermissionService permissions, + PluginLicenseManager pluginLicenseManager, + ClusterService clusterService, + StorageService storageProperties, + HookScriptService hookScriptService, + PluginSettingsFactory pluginSettingsFactory, + SecurityService securityService) + throws IOException { List triggers = new ArrayList(); triggers.add(StandardRepositoryHookTrigger.REPO_PUSH); triggers.add(StandardRepositoryHookTrigger.FILE_EDIT); @@ -60,7 +82,7 @@ public ExternalAsyncPostReceiveHook( triggers.add(StandardRepositoryHookTrigger.BRANCH_CREATE); triggers.add(StandardRepositoryHookTrigger.PULL_REQUEST_MERGE); - this.externalHookScript = new ExternalHookScript( + return new ExternalHookScript( authenticationContext, permissions, pluginLicenseManager, @@ -69,7 +91,7 @@ public ExternalAsyncPostReceiveHook( hookScriptService, pluginSettingsFactory, securityService, - "external-post-receive-hook", + KEY_ID, HookScriptType.POST, triggers); } diff --git a/src/main/java/com/ngs/stash/externalhooks/hook/ExternalHookScript.java b/src/main/java/com/ngs/stash/externalhooks/hook/ExternalHookScript.java index 194e898..48ff362 100644 --- a/src/main/java/com/ngs/stash/externalhooks/hook/ExternalHookScript.java +++ b/src/main/java/com/ngs/stash/externalhooks/hook/ExternalHookScript.java @@ -41,11 +41,12 @@ public class ExternalHookScript { public static final String PLUGIN_KEY = "com.ngs.stash.externalhooks.external-hooks"; + private final PluginLicenseManager pluginLicenseManager; private static Logger log = LoggerFactory.getLogger(ExternalHookScript.class.getSimpleName()); public final Escaper SHELL_ESCAPE; private AuthenticationContext authCtx; - private PermissionService permissions; + private PermissionService permissionService; private ClusterService clusterService; private StorageService storageProperties; private HookScriptService hookScriptService; @@ -59,7 +60,7 @@ public class ExternalHookScript { public ExternalHookScript( AuthenticationContext authenticationContext, - PermissionService permissions, + PermissionService permissionService, PluginLicenseManager pluginLicenseManager, ClusterService clusterService, StorageService storageProperties, @@ -71,7 +72,7 @@ public ExternalHookScript( List repositoryHookTriggers) throws IOException { this.authCtx = authenticationContext; - this.permissions = permissions; + this.permissionService = permissionService; this.storageProperties = storageProperties; this.pluginLicenseManager = pluginLicenseManager; this.clusterService = clusterService; @@ -90,6 +91,10 @@ public ExternalHookScript( this.hookScriptTemplate = this.getResource("hook-script.template.bash"); } + public String getHookKey() { + return this.hookId; + } + private String getResource(String name) throws IOException { InputStream resource = ClassLoaderUtils.getResourceAsStream(name, this.getClass()); if (resource == null) { @@ -128,7 +133,7 @@ public void validate( } if (!settings.getBoolean("safe_path", false)) { - if (!permissions.hasGlobalPermission(Permission.SYS_ADMIN)) { + if (!permissionService.hasGlobalPermission(Permission.SYS_ADMIN)) { errors.addFieldError( "exe", "You should be a Bitbucket System Administrator to edit this field " + "without \"safe mode\" option."); @@ -244,7 +249,7 @@ public void install(@Nonnull Settings settings, @Nonnull Scope scope) { HookScriptSetConfigurationRequest hookScriptSetConfigurationRequest = configBuilder.build(); hookScriptService.setConfiguration(hookScriptSetConfigurationRequest); - log.info("Successfully created HookScript with id: {}", hookScript.getId()); + log.warn("Created HookScript with id: {}", hookScript.getId()); } public File getExecutable(String path, boolean safeDir) { diff --git a/src/main/java/com/ngs/stash/externalhooks/hook/ExternalMergeCheckHook.java b/src/main/java/com/ngs/stash/externalhooks/hook/ExternalMergeCheckHook.java index 5d130a9..be32299 100644 --- a/src/main/java/com/ngs/stash/externalhooks/hook/ExternalMergeCheckHook.java +++ b/src/main/java/com/ngs/stash/externalhooks/hook/ExternalMergeCheckHook.java @@ -36,6 +36,7 @@ public class ExternalMergeCheckHook implements RepositoryMergeCheck, SettingsValidator { private ExternalHookScript externalHookScript; private RepositoryHookService repositoryHookService; + public static final String KEY_ID = "external-merge-check-hook"; public ExternalMergeCheckHook( AuthenticationContext authenticationContext, @@ -49,11 +50,33 @@ public ExternalMergeCheckHook( SecurityService securityService) throws IOException { + this.repositoryHookService = repositoryHookService; + + this.externalHookScript = getExternalHookScript( + authenticationContext, + permissions, + pluginLicenseManager, + clusterService, + storageProperties, + hookScriptService, + pluginSettingsFactory, + securityService); + } + + public static ExternalHookScript getExternalHookScript( + AuthenticationContext authenticationContext, + PermissionService permissions, + PluginLicenseManager pluginLicenseManager, + ClusterService clusterService, + StorageService storageProperties, + HookScriptService hookScriptService, + PluginSettingsFactory pluginSettingsFactory, + SecurityService securityService) + throws IOException { List triggers = new ArrayList(); triggers.add(StandardRepositoryHookTrigger.PULL_REQUEST_MERGE); - this.repositoryHookService = repositoryHookService; - this.externalHookScript = new ExternalHookScript( + return new ExternalHookScript( authenticationContext, permissions, pluginLicenseManager, @@ -62,7 +85,7 @@ public ExternalMergeCheckHook( hookScriptService, pluginSettingsFactory, securityService, - "external-merge-check-hook", + KEY_ID, HookScriptType.PRE, triggers); } diff --git a/src/main/java/com/ngs/stash/externalhooks/hook/ExternalPreReceiveHook.java b/src/main/java/com/ngs/stash/externalhooks/hook/ExternalPreReceiveHook.java index 4925e2c..dd2d8f2 100644 --- a/src/main/java/com/ngs/stash/externalhooks/hook/ExternalPreReceiveHook.java +++ b/src/main/java/com/ngs/stash/externalhooks/hook/ExternalPreReceiveHook.java @@ -37,6 +37,7 @@ public class ExternalPreReceiveHook implements PreRepositoryHook, SettingsValidator { private ExternalHookScript externalHookScript; + public static final String KEY_ID = "external-pre-receive-hook"; private RepositoryHookService repositoryHookService; @@ -51,6 +52,28 @@ public ExternalPreReceiveHook( RepositoryHookService repositoryHookService, SecurityService securityService) throws IOException { + this.repositoryHookService = repositoryHookService; + this.externalHookScript = getExternalHookScript( + authenticationContext, + permissions, + pluginLicenseManager, + clusterService, + storageProperties, + hookScriptService, + pluginSettingsFactory, + securityService); + } + + public static ExternalHookScript getExternalHookScript( + AuthenticationContext authenticationContext, + PermissionService permissions, + PluginLicenseManager pluginLicenseManager, + ClusterService clusterService, + StorageService storageProperties, + HookScriptService hookScriptService, + PluginSettingsFactory pluginSettingsFactory, + SecurityService securityService) + throws IOException { List triggers = new ArrayList(); triggers.add(StandardRepositoryHookTrigger.REPO_PUSH); triggers.add(StandardRepositoryHookTrigger.FILE_EDIT); @@ -58,9 +81,7 @@ public ExternalPreReceiveHook( triggers.add(StandardRepositoryHookTrigger.TAG_CREATE); triggers.add(StandardRepositoryHookTrigger.BRANCH_DELETE); triggers.add(StandardRepositoryHookTrigger.BRANCH_CREATE); - - this.repositoryHookService = repositoryHookService; - this.externalHookScript = new ExternalHookScript( + return new ExternalHookScript( authenticationContext, permissions, pluginLicenseManager, @@ -69,7 +90,7 @@ public ExternalPreReceiveHook( hookScriptService, pluginSettingsFactory, securityService, - "external-pre-receive-hook", + KEY_ID, HookScriptType.PRE, triggers); } diff --git a/src/main/java/com/ngs/stash/externalhooks/hook/listeners/ExternalHooksListener.java b/src/main/java/com/ngs/stash/externalhooks/hook/listeners/ExternalHooksListener.java index ca43dbe..cd80eaa 100644 --- a/src/main/java/com/ngs/stash/externalhooks/hook/listeners/ExternalHooksListener.java +++ b/src/main/java/com/ngs/stash/externalhooks/hook/listeners/ExternalHooksListener.java @@ -1,11 +1,14 @@ package com.ngs.stash.externalhooks.hook.listeners; +import java.io.IOException; + import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Named; -import com.atlassian.bitbucket.hook.repository.EnableRepositoryHookRequest; +import com.atlassian.bitbucket.auth.AuthenticationContext; +import com.atlassian.bitbucket.cluster.ClusterService; import com.atlassian.bitbucket.hook.repository.GetRepositoryHookSettingsRequest; import com.atlassian.bitbucket.hook.repository.RepositoryHook; import com.atlassian.bitbucket.hook.repository.RepositoryHookSearchRequest; @@ -14,6 +17,7 @@ import com.atlassian.bitbucket.hook.repository.RepositoryHookType; import com.atlassian.bitbucket.hook.script.HookScriptService; import com.atlassian.bitbucket.permission.Permission; +import com.atlassian.bitbucket.permission.PermissionService; import com.atlassian.bitbucket.project.Project; import com.atlassian.bitbucket.project.ProjectService; import com.atlassian.bitbucket.repository.Repository; @@ -21,6 +25,8 @@ import com.atlassian.bitbucket.scope.ProjectScope; import com.atlassian.bitbucket.scope.RepositoryScope; import com.atlassian.bitbucket.scope.Scope; +import com.atlassian.bitbucket.server.StorageService; +import com.atlassian.bitbucket.setting.Settings; import com.atlassian.bitbucket.user.SecurityService; import com.atlassian.bitbucket.util.Page; import com.atlassian.bitbucket.util.PageRequest; @@ -37,7 +43,11 @@ import com.atlassian.scheduler.config.JobId; import com.atlassian.scheduler.config.JobRunnerKey; import com.atlassian.scheduler.config.Schedule; +import com.atlassian.upm.api.license.PluginLicenseManager; +import com.ngs.stash.externalhooks.hook.ExternalAsyncPostReceiveHook; import com.ngs.stash.externalhooks.hook.ExternalHookScript; +import com.ngs.stash.externalhooks.hook.ExternalMergeCheckHook; +import com.ngs.stash.externalhooks.hook.ExternalPreReceiveHook; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,32 +60,89 @@ public class ExternalHooksListener implements JobRunner { private final int jobInterval = 2000; private final JobId jobId = JobId.of("external-hooks-enable-job"); - private final String hookKeyPrefix = "com.ngs.stash.externalhooks.external-hooks:"; - - private HookScriptService hookScriptService; - private SecurityService securityService; - private RepositoryHookService repoHookService; - private ProjectService projectService; - private SchedulerService schedulerService; - private RepositoryService repositoryService; + + @ComponentImport private RepositoryService repositoryService; + @ComponentImport private SchedulerService schedulerService; + @ComponentImport private HookScriptService hookScriptService; + @ComponentImport private RepositoryHookService repoHookService; + @ComponentImport private ProjectService projectService; + @ComponentImport private PluginSettingsFactory pluginSettingsFactory; + @ComponentImport private SecurityService securityService; + @ComponentImport private AuthenticationContext authenticationContext; + + @ComponentImport("permissions") + private PermissionService permissions; + + @ComponentImport private PluginLicenseManager pluginLicenseManager; + + @ComponentImport private ClusterService clusterService; + @ComponentImport private StorageService storageProperties; + private PluginSettings pluginSettings; + private ExternalHookScript hookPreReceive; + private ExternalHookScript hookPostReceive; + private ExternalHookScript hookMergeCheck; + @Inject public ExternalHooksListener( - @ComponentImport RepositoryService repositoryService, - @ComponentImport SchedulerService schedulerService, - @ComponentImport HookScriptService hookScriptService, - @ComponentImport RepositoryHookService repoHookService, - @ComponentImport ProjectService projectService, - @ComponentImport PluginSettingsFactory pluginSettingsFactory, - @ComponentImport SecurityService securityService) { + RepositoryService repositoryService, + SchedulerService schedulerService, + HookScriptService hookScriptService, + RepositoryHookService repoHookService, + ProjectService projectService, + PluginSettingsFactory pluginSettingsFactory, + SecurityService securityService, + AuthenticationContext authenticationContext, + PermissionService permissions, + PluginLicenseManager pluginLicenseManager, + ClusterService clusterService, + StorageService storageProperties) + throws IOException { + this.repositoryService = repositoryService; this.schedulerService = schedulerService; this.hookScriptService = hookScriptService; this.repoHookService = repoHookService; this.projectService = projectService; + this.pluginSettingsFactory = pluginSettingsFactory; this.securityService = securityService; + this.authenticationContext = authenticationContext; + this.permissions = permissions; + this.pluginLicenseManager = pluginLicenseManager; + this.clusterService = clusterService; + this.storageProperties = storageProperties; + this.pluginSettings = pluginSettingsFactory.createGlobalSettings(); - this.repositoryService = repositoryService; + + this.hookPreReceive = ExternalPreReceiveHook.getExternalHookScript( + authenticationContext, + permissions, + pluginLicenseManager, + clusterService, + storageProperties, + hookScriptService, + pluginSettingsFactory, + securityService); + + this.hookPostReceive = ExternalAsyncPostReceiveHook.getExternalHookScript( + authenticationContext, + permissions, + pluginLicenseManager, + clusterService, + storageProperties, + hookScriptService, + pluginSettingsFactory, + securityService); + + this.hookMergeCheck = ExternalMergeCheckHook.getExternalHookScript( + authenticationContext, + permissions, + pluginLicenseManager, + clusterService, + storageProperties, + hookScriptService, + pluginSettingsFactory, + securityService); } @PostConstruct @@ -127,7 +194,7 @@ protected boolean isPluginLoaded() { boolean found = false; for (RepositoryHook hook : page.getValues()) { - if (hook.getDetails().getKey().startsWith(this.hookKeyPrefix)) { + if (hook.getDetails().getKey().startsWith(ExternalHookScript.PLUGIN_KEY)) { found = true; break; } @@ -221,7 +288,8 @@ protected void createHookScripts(Scope scope) { Integer created = 0; for (RepositoryHook hook : page.getValues()) { - if (!hook.getDetails().getKey().startsWith(this.hookKeyPrefix)) { + String hookKey = hook.getDetails().getKey(); + if (!hookKey.startsWith(ExternalHookScript.PLUGIN_KEY)) { continue; } @@ -235,32 +303,43 @@ protected void createHookScripts(Scope scope) { if (hook.getScope().getType() != scope.getType()) { log.warn( - "external-hooks: hook {} is enabled & configured (inherited: {} {})", - hook.getDetails().getKey(), + "Hook {} is enabled & configured (inherited: {} {})", + hookKey, hook.getScope().getType(), hook.getScope().getResourceId().orElse(-1)); continue; } - EnableRepositoryHookRequest.Builder enableHookBuilder = - new EnableRepositoryHookRequest.Builder(scope, hook.getDetails().getKey()); - GetRepositoryHookSettingsRequest.Builder getSettingsBuilder = - new GetRepositoryHookSettingsRequest.Builder(scope, hook.getDetails().getKey()); + new GetRepositoryHookSettingsRequest.Builder(scope, hookKey); - RepositoryHookSettings settings = + RepositoryHookSettings hookSettings = this.repoHookService.getSettings(getSettingsBuilder.build()); - if (settings != null) { - enableHookBuilder.settings(settings.getSettings()); + if (hookSettings == null) { + log.warn("Hook {} has no settings, can't be enabled", hookKey); + return; } + Settings settings = hookSettings.getSettings(); + try { - log.warn("external-hooks: creating HookScript for {}", hook.getDetails().getKey()); - this.repoHookService.enable(enableHookBuilder.build()); + if (hookKey.equals(hookPreReceive.getHookKey())) { + log.warn("Creating PRE_RECEIVE HookScript for {}", hookKey); + this.hookPreReceive.install(settings, scope); + } else if (hookKey.equals(hookPostReceive.getHookKey())) { + log.warn("Creating POST_RECEIVE HookScript for {}", hookKey); + this.hookPostReceive.install(settings, scope); + } else if (hookKey.equals(hookMergeCheck.getHookKey())) { + log.warn("Creating MERGE_CHECK HookScript for {}", hookKey); + this.hookMergeCheck.install(settings, scope); + } else { + log.warn("Unexpected hook key: {}", hookKey); + } + created++; } catch (Exception e) { - log.error("unable to enable hook: {}", e.toString()); + log.error("Unable to install hook script {}: {}", hookKey, e.toString()); } } diff --git a/testutils/issue100 b/testutils/issue100 old mode 100644 new mode 100755 index 2c840ab..b07d575 --- a/testutils/issue100 +++ b/testutils/issue100 @@ -2,6 +2,7 @@ set -euo pipefail +cd "$(dirname "$0")" URI="${URI:-http://admin:admin@localhost:7990/bitbucket}" stacket --uri "$URI" projects create pwrwo || true