From b90cd8331a3eaafb05a8f34c7318c4d499df7e61 Mon Sep 17 00:00:00 2001 From: netwolfuk Date: Mon, 8 Aug 2022 22:08:36 +1200 Subject: [PATCH] Issue 53 hide password (#203) * Update Velocity Templating Engine from 1.7 to 2.3 * Bump jetty-webapp in /tcwebhooks-core (#195) * Format secure parameters correctly so that TeamCity obscures them Previously, the value was prefixed with `secure:`. This was incorrect. It is supposed to be a parameters name that is prefixed with `secure:`. This fix changes that, so that TeamCity correctly swaps out the values with tokens transparently. * Track secure value access and hide URL from UI Keep a flag to determine when a secure: value is accessed. If so, hide the full URL in the webUI so that secure values in the URL are not leaked. It's not possible to determine why the value was accessed. It could have been to build the payload, or the URL. Therefore, if either of these have resolved a secure value, we hide that URL. * Add support for hide-secure-values in the WebHookConfig Adds ability to store/retreive the value of hide-secure-values. This will be used as a flag to determine whether the UI and logs should container secure values. * Add support for hiding values from log and UI * Show/Hide URL in UI when secure values checkbox is toggled. * Populate external and internal project IDs when executing test * Don't log Parameter values. They might contain a secure value. --- sonar-project.properties | 8 ++- tcwebhooks-core/pom.xml | 15 ++++-- .../src/main/java/webhook/WebHook.java | 5 ++ .../java/webhook/WebHookExecutionStats.java | 1 + .../src/main/java/webhook/WebHookImpl.java | 17 ++++++ .../teamcity/WebHookContentBuilder.java | 11 ++++ .../teamcity/WebHookExecutionException.java | 1 + .../webhook/teamcity/WebHookFactoryImpl.java | 1 + .../WebHookTemplateParsingException.java | 18 +++++++ .../executor/AbstractWebHookExecutor.java | 54 ++++++++++++++----- .../WebHookHistoryItemFactoryImpl.java | 9 +++- .../payload/content/ExtraParameters.java | 22 ++++++-- ...WebHookVelocityVariableMessageBuilder.java | 27 ++++++---- ...ooksBeanUtilsVelocityVariableResolver.java | 20 +++++-- .../teamcity/settings/WebHookConfig.java | 19 ++++++- .../settings/WebHookProjectSettings.java | 11 ++-- .../settings/WebHookSecureValuesEnquirer.java | 7 +++ .../settings/WebHookSettingsManager.java | 4 +- .../settings/WebHookSettingsManagerImpl.java | 27 ++++++++-- .../project/WebHookParameterFactory.java | 6 +-- .../statistics/StatisticsManagerImpl.java | 2 +- .../testing/WebHookConfigFactoryImpl.java | 5 +- .../webhook/teamcity/WebHookListenerTest.java | 12 ++--- .../WebHookHistoryRepositoryImplTest.java | 22 ++++++-- ...ookVelocityVariableMessageBuilderTest.java | 25 +++++++++ .../teamcity/settings/WebHookConfigTest.java | 12 +++++ .../settings/WebHookProjectSettingsTest.java | 4 +- .../webhook/testframework/MockWebHook.java | 1 + ...ject-settings-test-all-states-disabled.xml | 2 +- ...project-settings-test-webhook-disabled.xml | 2 +- .../rest/model/webhook/ProjectWebhook.java | 16 +++--- .../WebHookAjaxEditPageController.java | 8 ++- .../WebHookBuildTypeTabExtension.java | 15 ++++-- .../extension/WebHookProjectTabExtension.java | 13 ++++- .../extension/bean/ProjectWebHooksBean.java | 4 +- .../WebhookConfigAndBuildTypeListHolder.java | 2 + .../WebHookSecureValuesHelperService.java | 32 +++++++++++ .../history/WebHookHistoryController.java | 7 ++- .../build-server-plugin-WebHookListener.xml | 4 ++ .../WebHook/css/styles.css | 6 ++- .../WebHook/js/editWebhook.js | 1 + .../WebHook/viewHistory.jsp | 12 ++++- .../WebHook/webHookInclude.jsp | 9 ++++ .../WebHook/webHookTabWithHistory.jsp | 12 ++++- 44 files changed, 422 insertions(+), 89 deletions(-) create mode 100644 tcwebhooks-core/src/main/java/webhook/teamcity/WebHookTemplateParsingException.java create mode 100644 tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookSecureValuesEnquirer.java create mode 100644 tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/util/WebHookSecureValuesHelperService.java diff --git a/sonar-project.properties b/sonar-project.properties index 86bf9852..b793795a 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -18,4 +18,10 @@ sonar.jacoco.reportPath=target/jacoco.exec sonar.java.coveragePlugin=jacoco sonar.dynamicAnalysis=reuseReports -sonar.surefire.reportsPath=target/surefire-reports \ No newline at end of file +sonar.surefire.reportsPath=target/surefire-reports + +sonar.issue.ignore.multicriteria=e1 + +# Console usage - ignore a single class +sonar.issue.ignore.multicriteria.e1.ruleKey=java:S5411 +sonar.issue.ignore.multicriteria.e1.resourceKey=**/*.java \ No newline at end of file diff --git a/tcwebhooks-core/pom.xml b/tcwebhooks-core/pom.xml index 7d7332aa..a21cd151 100644 --- a/tcwebhooks-core/pom.xml +++ b/tcwebhooks-core/pom.xml @@ -148,7 +148,7 @@ org.eclipse.jetty jetty-webapp - 9.4.43.v20210629 + 9.4.44.v20210927 test @@ -190,10 +190,17 @@ org.apache.velocity - velocity - 1.7 + velocity-engine-core + 2.3 - + + + + org.slf4j + slf4j-simple + 1.7.30 + + commons-lang commons-lang diff --git a/tcwebhooks-core/src/main/java/webhook/WebHook.java b/tcwebhooks-core/src/main/java/webhook/WebHook.java index 1b2d141f..0bc0424d 100644 --- a/tcwebhooks-core/src/main/java/webhook/WebHook.java +++ b/tcwebhooks-core/src/main/java/webhook/WebHook.java @@ -124,6 +124,11 @@ public interface WebHook { public abstract void setVariableResolverFactory(VariableResolverFactory variableResolverFactory); + public abstract boolean shouldHideSecureData(); + + public abstract boolean isHideSecureValues(); + public abstract void setHideSecureValues(boolean hideSecureValues); + } \ No newline at end of file diff --git a/tcwebhooks-core/src/main/java/webhook/WebHookExecutionStats.java b/tcwebhooks-core/src/main/java/webhook/WebHookExecutionStats.java index 59b50d64..6d4d7646 100644 --- a/tcwebhooks-core/src/main/java/webhook/WebHookExecutionStats.java +++ b/tcwebhooks-core/src/main/java/webhook/WebHookExecutionStats.java @@ -28,6 +28,7 @@ public class WebHookExecutionStats { boolean errored = false; boolean enabled = true; BuildStateEnum buildState; + boolean secureValueAccessed = false; public WebHookExecutionStats(String url) { this.url = url; diff --git a/tcwebhooks-core/src/main/java/webhook/WebHookImpl.java b/tcwebhooks-core/src/main/java/webhook/WebHookImpl.java index 3fa57d42..617ef4a3 100644 --- a/tcwebhooks-core/src/main/java/webhook/WebHookImpl.java +++ b/tcwebhooks-core/src/main/java/webhook/WebHookImpl.java @@ -71,6 +71,7 @@ public class WebHookImpl implements WebHook { @Getter private UUID requestId = UUID.randomUUID(); private VariableResolverFactory variableResolverFactory; + private boolean hideSecureValues; public WebHookImpl (String url, WebHookProxyConfig proxyConfig, HttpClient client){ @@ -481,5 +482,21 @@ public VariableResolverFactory getVariableResolverFactory() { public void setVariableResolverFactory(VariableResolverFactory variableResolverFactory) { this.variableResolverFactory = variableResolverFactory; } + + @Override + public boolean shouldHideSecureData() { + return this.hideSecureValues && this.getExecutionStats().isSecureValueAccessed(); + } + + @Override + public boolean isHideSecureValues() { + return this.hideSecureValues; + } + + @Override + public void setHideSecureValues(boolean hideSecureValues) { + this.hideSecureValues = hideSecureValues; + + } } diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/WebHookContentBuilder.java b/tcwebhooks-core/src/main/java/webhook/teamcity/WebHookContentBuilder.java index 9232a398..daabbeff 100644 --- a/tcwebhooks-core/src/main/java/webhook/teamcity/WebHookContentBuilder.java +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/WebHookContentBuilder.java @@ -75,6 +75,7 @@ public WebHook buildWebHookContent(WebHook wh, WebHookConfig whc, SQueuedBuild s wh.setUrl(builder.build(whc.getUrl())); wh.checkFilters(builder); wh.resolveHeaders(builder); + wh.getExecutionStats().setSecureValueAccessed(extraParameters.wasSecureValueAccessed()); } } else if (state.equals(BuildStateEnum.BUILD_REMOVED_FROM_QUEUE) ){ wh.setEnabledForBuildState(state, overrideIsEnabled || (whc.isEnabledForBuildType(sBuild.getBuildType()) && wh.getBuildStates().enabled(state))); @@ -90,6 +91,7 @@ public WebHook buildWebHookContent(WebHook wh, WebHookConfig whc, SQueuedBuild s wh.setUrl(builder.build(whc.getUrl())); wh.checkFilters(builder); wh.resolveHeaders(builder); + wh.getExecutionStats().setSecureValueAccessed(extraParameters.wasSecureValueAccessed()); } } return wh; @@ -135,6 +137,7 @@ public WebHook buildWebHookContent(WebHook wh, WebHookConfig whc, SBuild sBuild, wh.setUrl(builder.build(whc.getUrl())); wh.checkFilters(builder); wh.resolveHeaders(builder); + wh.getExecutionStats().setSecureValueAccessed(extraParameters.wasSecureValueAccessed()); } } else if (state.equals(BuildStateEnum.CHANGES_LOADED)){ wh.setEnabledForBuildState(BuildStateEnum.CHANGES_LOADED, isOverrideEnabled || (whc.isEnabledForBuildType(sBuild.getBuildType()) && wh.getBuildStates().enabled(BuildStateEnum.CHANGES_LOADED))); @@ -151,6 +154,7 @@ public WebHook buildWebHookContent(WebHook wh, WebHookConfig whc, SBuild sBuild, wh.setUrl(builder.build(whc.getUrl())); wh.checkFilters(builder); wh.resolveHeaders(builder); + wh.getExecutionStats().setSecureValueAccessed(extraParameters.wasSecureValueAccessed()); } } else if (state.equals(BuildStateEnum.SERVICE_MESSAGE_RECEIVED)){ wh.setEnabledForBuildState(BuildStateEnum.SERVICE_MESSAGE_RECEIVED, isOverrideEnabled || (whc.isEnabledForBuildType(sBuild.getBuildType()) && wh.getBuildStates().enabled(BuildStateEnum.SERVICE_MESSAGE_RECEIVED))); @@ -167,6 +171,7 @@ public WebHook buildWebHookContent(WebHook wh, WebHookConfig whc, SBuild sBuild, wh.setUrl(builder.build(whc.getUrl())); wh.checkFilters(builder); wh.resolveHeaders(builder); + wh.getExecutionStats().setSecureValueAccessed(extraParameters.wasSecureValueAccessed()); } } else if (state.equals(BuildStateEnum.BUILD_INTERRUPTED)){ wh.setEnabledForBuildState(BuildStateEnum.BUILD_INTERRUPTED, isOverrideEnabled || (whc.isEnabledForBuildType(sBuild.getBuildType()) && wh.getBuildStates().enabled(BuildStateEnum.BUILD_INTERRUPTED))); @@ -183,6 +188,7 @@ public WebHook buildWebHookContent(WebHook wh, WebHookConfig whc, SBuild sBuild, wh.setUrl(builder.build(whc.getUrl())); wh.checkFilters(builder); wh.resolveHeaders(builder); + wh.getExecutionStats().setSecureValueAccessed(extraParameters.wasSecureValueAccessed()); } } else if (state.equals(BuildStateEnum.BEFORE_BUILD_FINISHED)){ wh.setEnabledForBuildState(BuildStateEnum.BEFORE_BUILD_FINISHED, isOverrideEnabled || (whc.isEnabledForBuildType(sBuild.getBuildType()) && wh.getBuildStates().enabled(BuildStateEnum.BEFORE_BUILD_FINISHED))); @@ -199,6 +205,7 @@ public WebHook buildWebHookContent(WebHook wh, WebHookConfig whc, SBuild sBuild, wh.setUrl(builder.build(whc.getUrl())); wh.checkFilters(builder); wh.resolveHeaders(builder); + wh.getExecutionStats().setSecureValueAccessed(extraParameters.wasSecureValueAccessed()); } } else if (state.equals(BuildStateEnum.BUILD_FINISHED) || state.equals(BuildStateEnum.BUILD_SUCCESSFUL) || state.equals(BuildStateEnum.BUILD_FAILED) || state.equals(BuildStateEnum.BUILD_FIXED) || state.equals(BuildStateEnum.BUILD_BROKEN)){ wh.setEnabledForBuildState(BuildStateEnum.BUILD_FINISHED, isOverrideEnabled || (whc.isEnabledForBuildType(sBuild.getBuildType()) && wh.getBuildStates().enabled( @@ -219,6 +226,7 @@ public WebHook buildWebHookContent(WebHook wh, WebHookConfig whc, SBuild sBuild, wh.setUrl(builder.build(whc.getUrl())); wh.checkFilters(builder); wh.resolveHeaders(builder); + wh.getExecutionStats().setSecureValueAccessed(extraParameters.wasSecureValueAccessed()); } } else if (state.equals(BuildStateEnum.BUILD_PINNED)) { wh.setEnabledForBuildState(state, isOverrideEnabled || (whc.isEnabledForBuildType(sBuild.getBuildType()) && wh.getBuildStates().enabled(state))); @@ -235,6 +243,7 @@ public WebHook buildWebHookContent(WebHook wh, WebHookConfig whc, SBuild sBuild, wh.setUrl(builder.build(whc.getUrl())); wh.checkFilters(builder); wh.resolveHeaders(builder); + wh.getExecutionStats().setSecureValueAccessed(extraParameters.wasSecureValueAccessed()); } } else if (state.equals(BuildStateEnum.BUILD_UNPINNED)) { wh.setEnabledForBuildState(state, isOverrideEnabled || (whc.isEnabledForBuildType(sBuild.getBuildType()) && wh.getBuildStates().enabled(state))); @@ -251,6 +260,7 @@ public WebHook buildWebHookContent(WebHook wh, WebHookConfig whc, SBuild sBuild, wh.setUrl(builder.build(whc.getUrl())); wh.checkFilters(builder); wh.resolveHeaders(builder); + wh.getExecutionStats().setSecureValueAccessed(extraParameters.wasSecureValueAccessed()); } } return wh; @@ -278,6 +288,7 @@ public WebHook buildWebHookContent(WebHook wh, WebHookConfig whc, wh.setUrl(builder.build(whc.getUrl())); wh.checkFilters(builder); wh.resolveHeaders(builder); + wh.getExecutionStats().setSecureValueAccessed(extraParameters.wasSecureValueAccessed()); } return wh; } diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/WebHookExecutionException.java b/tcwebhooks-core/src/main/java/webhook/teamcity/WebHookExecutionException.java index f11e7e9a..9ef00f55 100644 --- a/tcwebhooks-core/src/main/java/webhook/teamcity/WebHookExecutionException.java +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/WebHookExecutionException.java @@ -18,6 +18,7 @@ public class WebHookExecutionException extends RuntimeException { public static final int WEBHOOK_PAYLOAD_CONTENT_ASSEMBLY_EXCEPTION_ERROR_CODE = 904; public static final int WEBHOOK_CONFIGURATION_NOT_FOUND_EXCEPTION_ERROR_CODE = 905; public static final int WEBHOOK_VARIABLE_RESOLVER_NOT_FOUND_EXCEPTION_ERROR_CODE = 906; + public static final int WEBHOOK_TEMPLATE_PARSING_EXCEPTION_ERROR_CODE = 907; public static final String WEBHOOK_UNEXPECTED_EXCEPTION_MESSAGE = "Unexpected exception. Please log a bug on GitHub tcplugins/tcWebHooks. Exception was: "; public static final int WEBHOOK_UNEXPECTED_EXCEPTION_ERROR_CODE = 999; diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/WebHookFactoryImpl.java b/tcwebhooks-core/src/main/java/webhook/teamcity/WebHookFactoryImpl.java index bcd4b68c..fe4634a5 100644 --- a/tcwebhooks-core/src/main/java/webhook/teamcity/WebHookFactoryImpl.java +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/WebHookFactoryImpl.java @@ -29,6 +29,7 @@ public WebHook getWebHook(WebHookConfig webHookConfig, WebHookProxyConfig proxyC WebHook webHook = new WebHookImpl(webHookConfig.getUrl(), proxyConfig, myWebHookHttpClientFactory.getHttpClient()); webHook.setUrl(webHookConfig.getUrl()); webHook.setEnabled(webHookConfig.getEnabled()); + webHook.setHideSecureValues(webHookConfig.isHideSecureValues()); if (!webHookConfig.getEnabled()) { webHook.getExecutionStats().setStatusReason(WebHookExecutionException.WEBHOOK_DISABLED_INFO_MESSAGE); webHook.getExecutionStats().setStatusCode(WebHookExecutionException.WEBHOOK_DISABLED_INFO_CODE); diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/WebHookTemplateParsingException.java b/tcwebhooks-core/src/main/java/webhook/teamcity/WebHookTemplateParsingException.java new file mode 100644 index 00000000..102621c5 --- /dev/null +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/WebHookTemplateParsingException.java @@ -0,0 +1,18 @@ +package webhook.teamcity; + +import lombok.Getter; + +@Getter +public class WebHookTemplateParsingException extends WebHookContentResolutionException { + + private static final long serialVersionUID = -3373028723068101280L; + + public WebHookTemplateParsingException(String message) { + super(message, WEBHOOK_TEMPLATE_PARSING_EXCEPTION_ERROR_CODE); + } + + public WebHookTemplateParsingException(String message, int errorCode) { + super(message, errorCode); + } + +} diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/executor/AbstractWebHookExecutor.java b/tcwebhooks-core/src/main/java/webhook/teamcity/executor/AbstractWebHookExecutor.java index 60d03046..d480550c 100644 --- a/tcwebhooks-core/src/main/java/webhook/teamcity/executor/AbstractWebHookExecutor.java +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/executor/AbstractWebHookExecutor.java @@ -72,7 +72,15 @@ public void run() { } catch (WebHookExecutionException ex){ webhook.getExecutionStats().setErrored(true); webhook.getExecutionStats().setRequestCompleted(ex.getErrorCode(), ex.getMessage()); - Loggers.SERVER.error(CLASS_NAME + webhook.getExecutionStats().getTrackingIdAsString() + " :: " + ex.getMessage()); + Loggers.SERVER.error( + String.format("%s trackingId: %s :: projectId: %s :: webhookId: %s :: templateId: %s, errorCode: %s, errorMessage: %s", + CLASS_NAME, + webhook.getExecutionStats().getTrackingIdAsString(), + whc.getProjectExternalId(), + whc.getUniqueKey(), + whc.getPayloadTemplate(), + ex.getErrorCode(), + ex.getMessage())); Loggers.SERVER.debug(CLASS_NAME + webhook.getExecutionStats().getTrackingIdAsString() + " :: URL: " + webhook.getUrl(), ex); this.webHookHistoryItem = buildWebHookHistoryItem(new WebHookErrorStatus(ex, ex.getMessage(), ex.getErrorCode())); webHookHistoryRepository.addHistoryItem(webHookHistoryItem); @@ -81,8 +89,15 @@ public void run() { } catch (Exception ex){ webhook.getExecutionStats().setErrored(true); webhook.getExecutionStats().setRequestCompleted(WebHookExecutionException.WEBHOOK_UNEXPECTED_EXCEPTION_ERROR_CODE, WebHookExecutionException.WEBHOOK_UNEXPECTED_EXCEPTION_MESSAGE + ex.getMessage()); - Loggers.SERVER.error(CLASS_NAME + webhook.getExecutionStats().getTrackingIdAsString() + " :: " + ex.getMessage()); - Loggers.SERVER.debug(CLASS_NAME + webhook.getExecutionStats().getTrackingIdAsString() + " :: URL: " + webhook.getUrl(), ex); + Loggers.SERVER.error( + String.format("%s trackingId: %s :: projectId: %s :: webhookId: %s :: templateId: %s, errorCode: %s, errorMessage: %s", + CLASS_NAME, + webhook.getExecutionStats().getTrackingIdAsString(), + whc.getProjectExternalId(), + whc.getUniqueKey(), + whc.getPayloadTemplate(), + WebHookExecutionException.WEBHOOK_UNEXPECTED_EXCEPTION_ERROR_CODE, + ex.getMessage())); Loggers.SERVER.debug(CLASS_NAME + webhook.getExecutionStats().getTrackingIdAsString() + " :: URL: " + webhook.getUrl(), ex); this.webHookHistoryItem = buildWebHookHistoryItem(new WebHookErrorStatus(ex, ex.getMessage(), WebHookExecutionException.WEBHOOK_UNEXPECTED_EXCEPTION_ERROR_CODE)); webHookHistoryRepository.addHistoryItem(this.webHookHistoryItem); @@ -102,41 +117,54 @@ public void run() { * @param payloadTemplate */ public static void doPost(WebHook wh, String payloadTemplate) { + boolean shouldHideSecureData = wh.shouldHideSecureData(); try { if (Boolean.TRUE.equals(wh.isEnabled())){ wh.post(); Loggers.SERVER.info(CLASS_NAME + " :: WebHook triggered : " - + wh.getUrl() + " using template " + payloadTemplate + + determineSecureUrl(wh, shouldHideSecureData) + " using template " + payloadTemplate + " returned " + wh.getStatus() - + " " + wh.getErrorReason()); - Loggers.SERVER.debug(CLASS_NAME + ":doPost :: content dump: " + wh.getPayload()); - if (Loggers.SERVER.isDebugEnabled()) Loggers.SERVER.debug("WebHook execution stats: " + wh.getExecutionStats().toString()); + + " " + wh.getErrorReason()); + if (Loggers.SERVER.isDebugEnabled()) { + if (shouldHideSecureData) { + Loggers.SERVER.debug(CLASS_NAME + ":doPost :: Hiding content payload because it may contain secured values. To log content to this log file uncheck 'Secure Values' in the WebHook edit dialog."); + } else if (wh.getExecutionStats().isSecureValueAccessed()) { + Loggers.SERVER.debug(CLASS_NAME + ":doPost :: Logging content payload even though it may contain secured values. To hide content in this log file check 'Secure Values' in the WebHook edit dialog.\n--- begin webhook payload ---\n" + wh.getPayload() + "\n--- end webhook payload ---"); + Loggers.SERVER.debug("WebHook execution stats: " + wh.getExecutionStats().toString()); + } else { + Loggers.SERVER.debug(CLASS_NAME + ":doPost :: Logging content payload because it contains no secured values.\n--- begin webhook payload ---\n" + wh.getPayload() + "\n--- end webhook payload ---"); + } + } if (Boolean.TRUE.equals(wh.isErrored())){ Loggers.SERVER.error(wh.getErrorReason()); } if (wh.getStatus() == null) { - Loggers.SERVER.warn(CLASS_NAME + wh.getParam("projectId") + " WebHook (url: " + wh.getUrl() + " proxy: " + wh.getProxyHost() + ":" + wh.getProxyPort()+") returned HTTP status " + wh.getStatus().toString()); + Loggers.SERVER.warn(CLASS_NAME + wh.getParam("projectId") + " WebHook (url: " + determineSecureUrl(wh, shouldHideSecureData) + " proxy: " + wh.getProxyHost() + ":" + wh.getProxyPort()+") returned HTTP status " + wh.getStatus().toString()); throw new WebHookHttpExecutionException("WebHook endpoint returned null response code"); } else if (wh.getStatus() < HttpStatus.SC_OK || wh.getStatus() >= HttpStatus.SC_MULTIPLE_CHOICES) { - Loggers.SERVER.warn(CLASS_NAME + wh.getParam("projectId") + " WebHook (url: " + wh.getUrl() + " proxy: " + wh.getProxyHost() + ":" + wh.getProxyPort()+") returned HTTP status " + wh.getStatus().toString()); + Loggers.SERVER.warn(CLASS_NAME + wh.getParam("projectId") + " WebHook (url: " + determineSecureUrl(wh, shouldHideSecureData) + " proxy: " + wh.getProxyHost() + ":" + wh.getProxyPort()+") returned HTTP status " + wh.getStatus().toString()); throw new WebHookHttpResponseException("WebHook endpoint returned non-2xx response (" + EnglishReasonPhraseCatalog.INSTANCE.getReason(wh.getStatus(), null) +")", wh.getStatus()); } } else { - if (Loggers.SERVER.isDebugEnabled()) Loggers.SERVER.debug("WebHook NOT triggered: " + wh.getDisabledReason() + " " + wh.getParam("buildStatus") + " " + wh.getUrl()); + if (Loggers.SERVER.isDebugEnabled()) Loggers.SERVER.debug("WebHook NOT triggered: " + wh.getDisabledReason() + " " + wh.getParam("buildStatus") + " " + determineSecureUrl(wh, shouldHideSecureData)); } } catch (FileNotFoundException e) { Loggers.SERVER.warn(CLASS_NAME + ":doPost :: " - + "A FileNotFoundException occurred while attempting to execute WebHook (" + wh.getUrl() + "). See the following debug stacktrace"); + + "A FileNotFoundException occurred while attempting to execute WebHook (" + determineSecureUrl(wh, shouldHideSecureData) + "). See the following debug stacktrace"); Loggers.SERVER.debug(e); - throw new WebHookHttpExecutionException("A FileNotFoundException occurred while attempting to execute WebHook (" + wh.getUrl() + ")", e); + throw new WebHookHttpExecutionException("A FileNotFoundException occurred while attempting to execute WebHook (" + determineSecureUrl(wh, shouldHideSecureData) + ")", e); } catch (IOException e) { Loggers.SERVER.warn(CLASS_NAME + ":doPost :: " - + "An IOException occurred while attempting to execute WebHook (" + wh.getUrl() + "). See the following debug stacktrace"); + + "An IOException occurred while attempting to execute WebHook (" + determineSecureUrl(wh, shouldHideSecureData) + "). See the following debug stacktrace"); Loggers.SERVER.debug(e); throw new WebHookHttpExecutionException("Error " + e.getMessage() + " occurred while attempting to execute WebHook.", e); } } + + private static String determineSecureUrl(WebHook wh, boolean shouldHideSecureData) { + return shouldHideSecureData ? "********" : wh.getUrl(); + } protected abstract WebHook getWebHookContent(); protected abstract WebHookHistoryItem buildWebHookHistoryItem(WebHookErrorStatus errorStatus); diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/history/WebHookHistoryItemFactoryImpl.java b/tcwebhooks-core/src/main/java/webhook/teamcity/history/WebHookHistoryItemFactoryImpl.java index c9704346..b147e829 100644 --- a/tcwebhooks-core/src/main/java/webhook/teamcity/history/WebHookHistoryItemFactoryImpl.java +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/history/WebHookHistoryItemFactoryImpl.java @@ -90,8 +90,13 @@ public WebHookHistoryItem getWebHookHistoryTestItem(WebHookConfig whc, WebHookEx private void addGeneralisedWebAddress(WebHookConfig whc, WebHookHistoryItem item) { try { - URL url = new URL(whc.getUrl()); - item.setGeneralisedWebAddress(myWebAddressTransformer.getGeneralisedHostName(url)); + if (item.getWebHookExecutionStats().getUrl() != null) { + URL url = new URL(item.getWebHookExecutionStats().getUrl()); + item.setGeneralisedWebAddress(myWebAddressTransformer.getGeneralisedHostName(url)); + } else { + URL url = new URL(whc.getUrl()); + item.setGeneralisedWebAddress(myWebAddressTransformer.getGeneralisedHostName(url)); + } } catch (MalformedURLException ex) { item.setGeneralisedWebAddress(null); } diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/payload/content/ExtraParameters.java b/tcwebhooks-core/src/main/java/webhook/teamcity/payload/content/ExtraParameters.java index 26737c22..652304be 100644 --- a/tcwebhooks-core/src/main/java/webhook/teamcity/payload/content/ExtraParameters.java +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/payload/content/ExtraParameters.java @@ -25,6 +25,8 @@ public class ExtraParameters extends ArrayList { protected static final boolean FORCE_RESOLVE_TEAMCITY_VARIABLE = false; protected static final String TEMPLATE_ENGINE_TYPE = PayloadTemplateEngineType.STANDARD.toString(); private static final long serialVersionUID = -2947332186712049416L; + + private boolean secureValueAccessed = false; public ExtraParameters() { super(); @@ -80,7 +82,7 @@ public void put(String context, String key, String value) { WebHookParameterModel previous = getActual(context, key); if (previous != null) { this.remove(previous); - Loggers.SERVER.debug("WebHookExtraParameters :: Removed existing WebHookParameter: " + previous.getContext() + " : " + previous.getName() + " : " + previous.getValue()); + Loggers.SERVER.debug("WebHookExtraParameters :: Removed existing WebHookParameter: " + previous.getContext() + " : " + previous.getName()); } WebHookParameterModel newHookParameterModel = new WebHookParameterModel(context, context, @@ -91,7 +93,7 @@ public void put(String context, String key, String value) { FORCE_RESOLVE_TEAMCITY_VARIABLE, TEMPLATE_ENGINE_TYPE); add(newHookParameterModel); - Loggers.SERVER.debug("WebHookExtraParameters :: Added WebHookParameter: " + newHookParameterModel.getContext() + " : " + newHookParameterModel.getName() + " : " + newHookParameterModel.getValue()); + Loggers.SERVER.debug("WebHookExtraParameters :: Added WebHookParameter: " + newHookParameterModel.getContext() + " : " + newHookParameterModel.getName()); } public void putAll(String context, Map paramMap) { @@ -105,7 +107,7 @@ public void putAll(String context, List webHookParameters) { WebHookParameterModel previous = getActual(context, parameter.getName()); if (previous != null) { this.remove(previous); - Loggers.SERVER.debug("WebHookExtraParameters :: Removed existing WebHookParameter: " + previous.getContext() + " : " + previous.getName() + " : " + previous.getValue()); + Loggers.SERVER.debug("WebHookExtraParameters :: Removed existing WebHookParameter: " + previous.getContext() + " : " + previous.getName()); } WebHookParameterModel newHookParameterModel = new WebHookParameterModel(context, context, @@ -116,7 +118,7 @@ public void putAll(String context, List webHookParameters) { parameter.getForceResolveTeamCityVariable(), parameter.getTemplateEngine()); add(newHookParameterModel); - Loggers.SERVER.debug("WebHookExtraParameters :: Added WebHookParameter: " + newHookParameterModel.toString()); + Loggers.SERVER.debug("WebHookExtraParameters :: Added WebHookParameter: " + previous.getContext() + " : " + previous.getName()); } } @@ -148,6 +150,10 @@ public String get(String key) { } else { for (WebHookParameter param : this) { if (key.equals(param.getName())) { + if (Boolean.TRUE.equals(param.getSecure())) { + this.secureValueAccessed = true; + Loggers.SERVER.debug(String.format("WebHookExtraParameters :: Secure value accessed for '%s' : '%s'", "no_context", key)); + } return param.getValue(); } } @@ -171,6 +177,10 @@ public String getParameter(String context, String key) { for (WebHookParameterModel webHookParameter : this) { if (webHookParameter.getContext().equalsIgnoreCase(context) && webHookParameter.getName().equals(key)) { + if (Boolean.TRUE.equals(webHookParameter.getSecure())) { + this.secureValueAccessed = true; + Loggers.SERVER.debug(String.format("WebHookExtraParameters :: Secure value accessed for '%s' : '%s'", context, key)); + } return webHookParameter.getValue(); } } @@ -247,4 +257,8 @@ protected Map getForceResolvableVariables() { } return variablesToResolve; } + + public boolean wasSecureValueAccessed() { + return this.secureValueAccessed; + } } diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/payload/variableresolver/velocity/WebHookVelocityVariableMessageBuilder.java b/tcwebhooks-core/src/main/java/webhook/teamcity/payload/variableresolver/velocity/WebHookVelocityVariableMessageBuilder.java index f83d467a..02995def 100644 --- a/tcwebhooks-core/src/main/java/webhook/teamcity/payload/variableresolver/velocity/WebHookVelocityVariableMessageBuilder.java +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/payload/variableresolver/velocity/WebHookVelocityVariableMessageBuilder.java @@ -4,8 +4,11 @@ import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.context.Context; +import org.apache.velocity.exception.ParseErrorException; import org.apache.velocity.runtime.RuntimeConstants; +import org.slf4j.impl.SimpleLoggerFactory; +import webhook.teamcity.WebHookTemplateParsingException; import webhook.teamcity.payload.variableresolver.VariableMessageBuilder; import webhook.teamcity.settings.secure.WebHookSecretResolver; @@ -19,7 +22,7 @@ public static WebHookVelocityVariableMessageBuilder create(Context resolver, Web WebHookVelocityVariableMessageBuilder builder = new WebHookVelocityVariableMessageBuilder(); builder.ve = new VelocityEngine(); - builder.ve.setProperty("userdirective", PACKAGE + "VelocitySanitiseDirective, " + builder.ve.setProperty(RuntimeConstants.CUSTOM_DIRECTIVES, PACKAGE + "VelocitySanitiseDirective, " + PACKAGE + "VelocitySanitizeDirective, " + PACKAGE + "VelocityEscapeJsonDirective, " + PACKAGE + "VelocityCapitaliseDirective, " @@ -29,12 +32,10 @@ public static WebHookVelocityVariableMessageBuilder create(Context resolver, Web + PACKAGE + "VelocityToJsonDirective," + PACKAGE + "VelocitySecureDirective"); - builder.ve.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, - "org.apache.velocity.runtime.log.Log4JLogChute" ); - builder.ve.setProperty("runtime.log.logsystem.log4j.logger", "webhook.teamcity.Loggers"); - builder.ve.setApplicationAttribute("webhook.teamcity.settings.secure.WebHookSecretResolver", webHookSecretResolver); - + builder.ve.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, new SimpleLoggerFactory().getLogger("jetbrains.buildServer.SERVER")); + builder.ve.setApplicationAttribute("webhook.teamcity.settings.secure.WebHookSecretResolver", webHookSecretResolver); + builder.ve.init(); builder.resolver = resolver; return builder; @@ -42,11 +43,15 @@ public static WebHookVelocityVariableMessageBuilder create(Context resolver, Web @Override public String build(String template) { - StringWriter swParse1 = new StringWriter(); - this.ve.evaluate(resolver, swParse1, "WebHookVelocityVariableMessageBuilder", template); - StringWriter swParse2 = new StringWriter(); - this.ve.evaluate(resolver, swParse2, "WebHookVelocityVariableMessageBuilder", swParse1.toString()); - return swParse2.toString(); + try { + StringWriter swParse1 = new StringWriter(); + this.ve.evaluate(resolver, swParse1, "WebHookVelocityVariableMessageBuilder", template); + StringWriter swParse2 = new StringWriter(); + this.ve.evaluate(resolver, swParse2, "WebHookVelocityVariableMessageBuilder", swParse1.toString()); + return swParse2.toString(); + } catch (ParseErrorException ex) { + throw new WebHookTemplateParsingException(ex.getMessage()); + } } } diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/payload/variableresolver/velocity/WebHooksBeanUtilsVelocityVariableResolver.java b/tcwebhooks-core/src/main/java/webhook/teamcity/payload/variableresolver/velocity/WebHooksBeanUtilsVelocityVariableResolver.java index 45d782d1..806b9745 100644 --- a/tcwebhooks-core/src/main/java/webhook/teamcity/payload/variableresolver/velocity/WebHooksBeanUtilsVelocityVariableResolver.java +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/payload/variableresolver/velocity/WebHooksBeanUtilsVelocityVariableResolver.java @@ -1,5 +1,6 @@ package webhook.teamcity.payload.variableresolver.velocity; import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; @@ -32,8 +33,10 @@ public class WebHooksBeanUtilsVelocityVariableResolver implements VariableResolv private static final String SUFFIX = ")"; private Object bean; private VelocityContext velocityContext = new VelocityContext(); + private Map nameMappings = new HashMap<>(); private SProject sProject; private WebHookSecretResolver webHookSecretResolver; + private ExtraParameters extraParameters; public WebHooksBeanUtilsVelocityVariableResolver( SProject sProject, @@ -44,9 +47,12 @@ public WebHooksBeanUtilsVelocityVariableResolver( this.sProject = sProject; this.bean = javaBean; this.webHookSecretResolver = webHookSecretResolver; + this.extraParameters = extraAndTeamCityProperties; for (Map.Entry entry : extraAndTeamCityProperties.asMap().entrySet()) { - velocityContext.put(entry.getKey().replaceAll("\\.", "_"), entry.getValue()); + String newKey = entry.getKey().replace(".", "_"); + velocityContext.put(newKey, entry.getValue()); + nameMappings.put(newKey, entry.getKey()); } try { @@ -90,21 +96,27 @@ public Object put(String key, Object value) { @Override public Object get(String key) { + Loggers.SERVER.debug(String.format("WebHooksBeanUtilsVelocityVariableResolver :: Value requested from Velocity context. 'key=%s'", key)); + if (nameMappings.containsKey(key)) { + // Call extraParameters.get(), so that accessing secure parameters is logged. + // Resolve any underscore names to dot names via the nameMappings. + extraParameters.get(nameMappings.get(key)); + } return this.velocityContext.get(key); } @Override - public boolean containsKey(Object key) { + public boolean containsKey(String key) { return this.velocityContext.containsKey(key); } @Override - public Object[] getKeys() { + public String[] getKeys() { return this.velocityContext.getKeys(); } @Override - public Object remove(Object key) { + public Object remove(String key) { return this.velocityContext.remove(key); } diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookConfig.java b/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookConfig.java index 9e1a11ef..f0ac02f3 100644 --- a/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookConfig.java +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookConfig.java @@ -55,6 +55,7 @@ public class WebHookConfig { private static final String ATTR_TEMPLATE = "template"; private static final String ATTR_FORMAT = "format"; private static final String ATTR_ENABLED = "enabled"; + private static final String ATTR_HIDE_SECURE = "hide-secure-values"; private static final String EL_STATES = "states"; private static final String EL_BUILD_TYPES = "build-types"; private static final String ATTR_ENABLED_FOR_ALL = "enabled-for-all"; @@ -79,6 +80,7 @@ public class WebHookConfig { private List headers; @Builder.Default private String projectInternalId = null; @Builder.Default private String projectExternalId = null; + @Builder.Default private boolean hideSecureValues = true; @SuppressWarnings("unchecked") public WebHookConfig (Element e) { @@ -96,6 +98,7 @@ public WebHookConfig (Element e) { this.authPreemptive = true; this.filters = new ArrayList<>(); this.headers = new ArrayList<>(); + this.hideSecureValues = true; if (e.getAttribute("url") != null){ this.setUrl(e.getAttributeValue("url")); @@ -123,6 +126,10 @@ public WebHookConfig (Element e) { if (e.getAttribute(ATTR_TEMPLATE) != null){ this.setPayloadTemplate(e.getAttributeValue(ATTR_TEMPLATE)); } + + if (e.getAttribute(ATTR_HIDE_SECURE) != null){ + this.setHideSecureValues(Boolean.parseBoolean(e.getAttributeValue(ATTR_HIDE_SECURE))); + } // Transform payload and template to template. this.setPayloadTemplate(PayloadToTemplateConverter.transformPayloadToTemplate(this.getPayloadFormat(), this.getPayloadTemplate())); @@ -315,7 +322,7 @@ private String getRandomKey() { * @param enabledBuildTypes * @param webHookAuthConfig */ - public WebHookConfig (String projectInternalId, String projectExternalId, String url, Boolean enabled, BuildState states, String payloadTemplate, boolean buildTypeAllEnabled, boolean buildTypeSubProjects, Set enabledBuildTypes, WebHookAuthConfig webHookAuthConfig){ + public WebHookConfig (String projectInternalId, String projectExternalId, String url, Boolean enabled, BuildState states, String payloadTemplate, boolean buildTypeAllEnabled, boolean buildTypeSubProjects, Set enabledBuildTypes, WebHookAuthConfig webHookAuthConfig, boolean hideSecureValues){ this.uniqueKey = "id_" + getRandomKey(); this.setProjectInternalId(projectInternalId); this.setProjectExternalId(projectExternalId); @@ -344,6 +351,7 @@ public WebHookConfig (String projectInternalId, String projectExternalId, String this.authEnabled = true; this.authParameters.putAll(webHookAuthConfig.getParameters()); } + this.hideSecureValues = hideSecureValues; } private Element getKeyAndValueAsElement(Map map, String key, String elementName){ @@ -361,6 +369,7 @@ public Element getAsElement(){ el.setAttribute(ATTR_ENABLED, String.valueOf(this.enabled)); //el.setAttribute(ATTR_FORMAT, String.valueOf(this.payloadFormat).toLowerCase()); el.setAttribute(ATTR_TEMPLATE, String.valueOf(this.payloadTemplate)); + el.setAttribute(ATTR_HIDE_SECURE, String.valueOf(this.hideSecureValues)); Element statesEl = new Element(EL_STATES); for (BuildStateEnum state : states.getStateSet()){ @@ -732,4 +741,12 @@ public String getProjectExternalId() { public void setProjectExternalId(String projectExternalId) { this.projectExternalId = projectExternalId; } + + public boolean isHideSecureValues() { + return hideSecureValues; + } + + public void setHideSecureValues(boolean hideSecureValues) { + this.hideSecureValues = hideSecureValues; + } } diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookProjectSettings.java b/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookProjectSettings.java index e984672a..2e7df623 100644 --- a/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookProjectSettings.java +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookProjectSettings.java @@ -135,7 +135,7 @@ public WebHookUpdateResult deleteWebHook(String webHookId, String projectId){ return new WebHookUpdateResult(updateSuccess, configToDelete); } - public WebHookUpdateResult updateWebHook(String projectId, String webHookId, String url, Boolean enabled, BuildState buildState, String template, boolean buildTypeAll, boolean buildSubProjects, Set buildTypesEnabled, WebHookAuthConfig webHookAuthConfig) { + public WebHookUpdateResult updateWebHook(String projectId, String webHookId, String url, Boolean enabled, BuildState buildState, String template, boolean buildTypeAll, boolean buildSubProjects, Set buildTypesEnabled, WebHookAuthConfig webHookAuthConfig, boolean hideSecureValues) { boolean updateSuccess = false; WebHookConfig configToUpdate = null; if(this.webHooksConfigs != null) @@ -145,6 +145,7 @@ public WebHookUpdateResult updateWebHook(String projectId, String webHookId, Str if (whc.getUniqueKey().equals(webHookId)){ whc.setEnabled(enabled); whc.setUrl(url); + whc.setHideSecureValues(hideSecureValues); whc.setBuildStates(buildState); whc.setPayloadTemplate(template); whc.enableForSubProjects(buildSubProjects); @@ -175,12 +176,12 @@ public WebHookUpdateResult updateWebHook(String projectId, String webHookId, Str return new WebHookUpdateResult(updateSuccess, configToUpdate); } - public void addNewWebHook(String projectInternalId, String projectExternalId, String url, Boolean enabled, BuildState buildState, String template, boolean buildTypeAll, boolean buildTypeSubProjects, Set buildTypesEnabled) { - addNewWebHook(projectInternalId, projectExternalId, url, enabled, buildState, template, buildTypeAll, buildTypeSubProjects, buildTypesEnabled, null); + public void addNewWebHook(String projectInternalId, String projectExternalId, String url, Boolean enabled, BuildState buildState, String template, boolean buildTypeAll, boolean buildTypeSubProjects, Set buildTypesEnabled, boolean hideSecureValues) { + addNewWebHook(projectInternalId, projectExternalId, url, enabled, buildState, template, buildTypeAll, buildTypeSubProjects, buildTypesEnabled, null, hideSecureValues); } - public WebHookUpdateResult addNewWebHook(String projectInternalId, String projectExternalId, String url, Boolean enabled, BuildState buildState, String template, boolean buildTypeAll, boolean buildTypeSubProjects, Set buildTypesEnabled, WebHookAuthConfig webHookAuthConfig) { - WebHookConfig newWebHook = new WebHookConfig(projectInternalId, projectExternalId, url, enabled, buildState, template, buildTypeAll, buildTypeSubProjects, buildTypesEnabled, webHookAuthConfig); + public WebHookUpdateResult addNewWebHook(String projectInternalId, String projectExternalId, String url, Boolean enabled, BuildState buildState, String template, boolean buildTypeAll, boolean buildTypeSubProjects, Set buildTypesEnabled, WebHookAuthConfig webHookAuthConfig, boolean hideSecureValues) { + WebHookConfig newWebHook = new WebHookConfig(projectInternalId, projectExternalId, url, enabled, buildState, template, buildTypeAll, buildTypeSubProjects, buildTypesEnabled, webHookAuthConfig, hideSecureValues); this.webHooksConfigs.add(newWebHook); Loggers.SERVER.debug(NAME + ":addNewWebHook :: Adding webhook to " + projectExternalId + " with URL " + url); return new WebHookUpdateResult(true, newWebHook); diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookSecureValuesEnquirer.java b/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookSecureValuesEnquirer.java new file mode 100644 index 00000000..9be89da1 --- /dev/null +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookSecureValuesEnquirer.java @@ -0,0 +1,7 @@ +package webhook.teamcity.settings; + +public interface WebHookSecureValuesEnquirer { + + public boolean isHideSecureValuesEnabled(String webhookId); + +} diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookSettingsManager.java b/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookSettingsManager.java index 1df93dc3..51d161f9 100644 --- a/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookSettingsManager.java +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookSettingsManager.java @@ -18,13 +18,13 @@ public interface WebHookSettingsManager { public WebHookUpdateResult addNewWebHook(String projectInternalId, String projectExternalId, String url, Boolean enabled, BuildState buildState, String template, boolean buildTypeAll, - boolean buildTypeSubProjects, Set buildTypesEnabled, WebHookAuthConfig webHookAuthConfig); + boolean buildTypeSubProjects, Set buildTypesEnabled, WebHookAuthConfig webHookAuthConfig, boolean hideSecureValues); public WebHookUpdateResult deleteWebHook(String webHookId, String projectId); public WebHookUpdateResult updateWebHook(String projectId, String webHookId, String url, Boolean enabled, BuildState buildState, String template, boolean buildTypeAll, boolean buildSubProjects, - Set buildTypesEnabled, WebHookAuthConfig webHookAuthConfig); + Set buildTypesEnabled, WebHookAuthConfig webHookAuthConfig, boolean hideSecureValues); public List findWebHooks(WebHookSearchFilter filter); diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookSettingsManagerImpl.java b/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookSettingsManagerImpl.java index ed05376d..43ba778c 100644 --- a/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookSettingsManagerImpl.java +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/settings/WebHookSettingsManagerImpl.java @@ -32,7 +32,7 @@ import webhook.teamcity.payload.WebHookTemplateManager.TemplateState; import webhook.teamcity.settings.WebHookSearchResult.Match; -public class WebHookSettingsManagerImpl implements WebHookSettingsManager { +public class WebHookSettingsManagerImpl implements WebHookSettingsManager, WebHookSecureValuesEnquirer { @NotNull private final ProjectManager myProjectManager; @NotNull private final ConfigActionFactory myConfigActionFactory; @@ -90,11 +90,11 @@ public WebHookProjectSettings getSettings(String projectInternalId) { @Override public WebHookUpdateResult addNewWebHook(String projectInternalId, String projectExternalId, String url, Boolean enabled, BuildState buildState, String template, boolean buildTypeAll, - boolean buildTypeSubProjects, Set buildTypesEnabled, WebHookAuthConfig webHookAuthConfig) { + boolean buildTypeSubProjects, Set buildTypesEnabled, WebHookAuthConfig webHookAuthConfig, boolean hideSecureValues) { WebHookUpdateResult result = getSettings(projectInternalId).addNewWebHook( projectInternalId, projectExternalId, url, enabled, buildState, template, buildTypeAll, - buildTypeSubProjects, buildTypesEnabled, webHookAuthConfig + buildTypeSubProjects, buildTypesEnabled, webHookAuthConfig, hideSecureValues ); if (result.updated) { if (persist(projectInternalId, "Added new WebHook")) { @@ -125,11 +125,11 @@ public WebHookUpdateResult deleteWebHook(String webHookId, String projectInterna @Override public WebHookUpdateResult updateWebHook(String projectInternalId, String webHookId, String url, Boolean enabled, BuildState buildState, String template, boolean buildTypeAll, boolean buildSubProjects, - Set buildTypesEnabled, WebHookAuthConfig webHookAuthConfig) { + Set buildTypesEnabled, WebHookAuthConfig webHookAuthConfig, boolean hideSecureValues) { WebHookUpdateResult result = getSettings(projectInternalId).updateWebHook( projectInternalId, webHookId, url, enabled, buildState, template, buildTypeAll, buildSubProjects, - buildTypesEnabled, webHookAuthConfig + buildTypesEnabled, webHookAuthConfig, hideSecureValues ); if (result.updated) { if (persist(projectInternalId, "Edited existing WebHook")) { @@ -408,6 +408,7 @@ private void rebuildWebHooksEnhanced(String projectInternalId) { .build(); configEnhanced.addTag(templateFormat) .addTag(Boolean.TRUE.equals(c.getEnabled()) ? "enabled" : "disabled") + .addTag(Boolean.TRUE.equals(c.isHideSecureValues()) ? "hideSecure" : "showSecure") .addTag(c.getPayloadTemplate()) .addTag(configEnhanced.getGeneralisedWebAddress().getGeneralisedAddress()); if (c.getAuthenticationConfig() != null) { @@ -453,4 +454,20 @@ private void addTagIfPresent(WebHookConfigEnhanced config, Map map, String tagNa } } + @Override + public boolean isHideSecureValuesEnabled(String webhookId) { + if (webhookId == null) { + return true; + } + for ( WebHookProjectSettings settings : this.projectSettingsMap.values()) { + for (WebHookConfig config: settings.getWebHooksConfigs()) { + if (webhookId.equals(config.getUniqueKey())) { + return config.isHideSecureValues(); + } + } + + } + return true; + } + } diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/settings/project/WebHookParameterFactory.java b/tcwebhooks-core/src/main/java/webhook/teamcity/settings/project/WebHookParameterFactory.java index d45d3ece..62afd330 100644 --- a/tcwebhooks-core/src/main/java/webhook/teamcity/settings/project/WebHookParameterFactory.java +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/settings/project/WebHookParameterFactory.java @@ -28,8 +28,8 @@ public static WebHookParameter readFrom(String id, Map parameter model.setId(id); model.setSecure(Boolean.valueOf(parameters.get(SECURE_KEY))); model.setName(parameters.get(NAME_KEY)); - if (Boolean.TRUE.equals(model.getSecure()) && parameters.get(VALUE_KEY).startsWith(SECURE_PROPERTY_PREFIX)) { - model.setValue(parameters.get(VALUE_KEY).substring(SECURE_PROPERTY_PREFIX.length())); + if (Boolean.TRUE.equals(model.getSecure()) && parameters.containsKey(SECURE_PROPERTY_PREFIX + VALUE_KEY)) { + model.setValue(parameters.get(SECURE_PROPERTY_PREFIX + VALUE_KEY)); } else { model.setValue(parameters.get(VALUE_KEY)); } @@ -44,7 +44,7 @@ public static Map asMap(WebHookParameter model) { properties.put(NAME_KEY, model.getName()); if (Boolean.TRUE.equals(model.getSecure())) { - properties.put(VALUE_KEY, SECURE_PROPERTY_PREFIX + model.getValue()); + properties.put(SECURE_PROPERTY_PREFIX + VALUE_KEY, model.getValue()); } else { properties.put(VALUE_KEY, model.getValue()); } diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/statistics/StatisticsManagerImpl.java b/tcwebhooks-core/src/main/java/webhook/teamcity/statistics/StatisticsManagerImpl.java index 5b3ffaf7..0f7460c3 100644 --- a/tcwebhooks-core/src/main/java/webhook/teamcity/statistics/StatisticsManagerImpl.java +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/statistics/StatisticsManagerImpl.java @@ -151,7 +151,7 @@ public void reportStatistics(LocalDateTime now) { // if it was more than 4 days ago, then build stats until yesterday. if (unreportedStats.size() >= this.myWebHookMainSettings.getReportStatisticsFrequency()) { try { - WebHookConfig whc = new WebHookConfig("_Root", "_Root", "http://localhost:8111/webhooks/endpoint.html", Boolean.TRUE, new BuildState().enable(BuildStateEnum.REPORT_STATISTICS), "statistics-report", false, false, null, null); + WebHookConfig whc = new WebHookConfig("_Root", "_Root", "http://localhost:8111/webhooks/endpoint.html", Boolean.TRUE, new BuildState().enable(BuildStateEnum.REPORT_STATISTICS), "statistics-report", false, false, null, null, true); StatisticsReport report = this.myStatisticReportAssembler.assembleStatisticsReports(new CryptValueHasher(), unreportedStats); myStatisticsEventListener.reportStatistics(whc, report); markStatisticsAsReported(unreportedStats); diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/testing/WebHookConfigFactoryImpl.java b/tcwebhooks-core/src/main/java/webhook/teamcity/testing/WebHookConfigFactoryImpl.java index d7bd7edb..466bad06 100644 --- a/tcwebhooks-core/src/main/java/webhook/teamcity/testing/WebHookConfigFactoryImpl.java +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/testing/WebHookConfigFactoryImpl.java @@ -118,7 +118,10 @@ private WebHookConfig findWebHookWithId(String projectExternalId, String webHook if (whc.getUniqueKey().equals(webHookConfigUniqueId)) { if (myWebHookTemplateManager.isRegisteredTemplate(whc.getPayloadTemplate())){ - return whc.copy(); + WebHookConfig whconfig = whc.copy(); + whconfig.setProjectExternalId(myProject.getExternalId()); + whconfig.setProjectInternalId(myProject.getProjectId()); + return whconfig; } else { throw new WebHookConfigNotFoundException("No registered Template " + whc.getPayloadTemplate()); } diff --git a/tcwebhooks-core/src/test/java/webhook/teamcity/WebHookListenerTest.java b/tcwebhooks-core/src/test/java/webhook/teamcity/WebHookListenerTest.java index b41d6dc0..16cebf0d 100644 --- a/tcwebhooks-core/src/test/java/webhook/teamcity/WebHookListenerTest.java +++ b/tcwebhooks-core/src/test/java/webhook/teamcity/WebHookListenerTest.java @@ -162,7 +162,7 @@ public void testRegister() { public void testBuildStartedSRunningBuild() throws FileNotFoundException, IOException { BuildState state = new BuildState(); state.enable(BuildStateEnum.BUILD_STARTED); - projSettings.addNewWebHook("project1", "MyProject", "http://text/test", true, state, "testXMLtemplate", true, true, new HashSet()); + projSettings.addNewWebHook("project1", "MyProject", "http://text/test", true, state, "testXMLtemplate", true, true, new HashSet(), true); //when(webhook.isEnabled()).thenReturn(state.enabled(BuildStateEnum.BUILD_STARTED)); when(buildHistory.getEntriesBefore(sRunningBuild, false)).thenReturn(finishedSuccessfulBuilds); @@ -175,7 +175,7 @@ public void testBuildStartedSRunningBuild() throws FileNotFoundException, IOExce @Test @Ignore public void testBuildFinishedSRunningBuild() throws FileNotFoundException, IOException { BuildState state = new BuildState().setAllEnabled(); - projSettings.addNewWebHook("1234", "MyProject", "http://text/test", true, state , "testXMLtemplate", true, true, new HashSet()); + projSettings.addNewWebHook("1234", "MyProject", "http://text/test", true, state , "testXMLtemplate", true, true, new HashSet(), true); when(webhook.isEnabled()).thenReturn(state.allEnabled()); when(buildHistory.getEntriesBefore(sRunningBuild, false)).thenReturn(finishedSuccessfulBuilds); @@ -189,7 +189,7 @@ public void testBuildFinishedSRunningBuildSuccessAfterFailure() throws FileNotFo state.enable(BuildStateEnum.BUILD_FIXED); state.enable(BuildStateEnum.BUILD_FINISHED); state.enable(BuildStateEnum.BUILD_SUCCESSFUL); - projSettings.addNewWebHook("1234", "MyProject", "http://text/test", true, state, "testXMLtemplate", true, true, new HashSet()); + projSettings.addNewWebHook("1234", "MyProject", "http://text/test", true, state, "testXMLtemplate", true, true, new HashSet(), true); when(webhook.isEnabled()).thenReturn(state.enabled(BuildStateEnum.BUILD_FIXED)); when(buildHistory.getEntriesBefore(sRunningBuild, false)).thenReturn(finishedFailedBuilds); @@ -201,7 +201,7 @@ public void testBuildFinishedSRunningBuildSuccessAfterFailure() throws FileNotFo public void testBuildFinishedSRunningBuildSuccessAfterSuccess() throws FileNotFoundException, IOException { BuildState state = new BuildState(); state.enable(BuildStateEnum.BUILD_FIXED); - projSettings.addNewWebHook("1234", "MyProject", "http://text/test", true, state, "testXMLtemplate", true, true, new HashSet()); + projSettings.addNewWebHook("1234", "MyProject", "http://text/test", true, state, "testXMLtemplate", true, true, new HashSet(), true); when(webhook.isEnabled()).thenReturn(state.enabled(BuildStateEnum.BUILD_FIXED)); when(buildHistory.getEntriesBefore(sRunningBuild, false)).thenReturn(finishedSuccessfulBuilds); @@ -212,7 +212,7 @@ public void testBuildFinishedSRunningBuildSuccessAfterSuccess() throws FileNotFo @Test @Ignore public void testBuildInterruptedSRunningBuild() throws FileNotFoundException, IOException { BuildState state = new BuildState().setAllEnabled(); - projSettings.addNewWebHook("1234", "MyProject", "http://text/test", true, state, "testXMLtemplate", true, true, new HashSet()); + projSettings.addNewWebHook("1234", "MyProject", "http://text/test", true, state, "testXMLtemplate", true, true, new HashSet(), true); when(buildHistory.getEntriesBefore(sRunningBuild, false)).thenReturn(finishedSuccessfulBuilds); whl.buildInterrupted(sRunningBuild); @@ -223,7 +223,7 @@ public void testBuildInterruptedSRunningBuild() throws FileNotFoundException, IO public void testBeforeBuildFinishSRunningBuild() throws FileNotFoundException, IOException { BuildState state = new BuildState(); state.enable(BuildStateEnum.BEFORE_BUILD_FINISHED); - projSettings.addNewWebHook("1234", "MyProject", "http://text/test", true, state, "testXMLtemplate", true, true, new HashSet()); + projSettings.addNewWebHook("1234", "MyProject", "http://text/test", true, state, "testXMLtemplate", true, true, new HashSet(), true); when(buildHistory.getEntriesBefore(sRunningBuild, false)).thenReturn(finishedSuccessfulBuilds); whl.beforeBuildFinish(sRunningBuild); diff --git a/tcwebhooks-core/src/test/java/webhook/teamcity/history/WebHookHistoryRepositoryImplTest.java b/tcwebhooks-core/src/test/java/webhook/teamcity/history/WebHookHistoryRepositoryImplTest.java index 5cac3c26..ee5e83e8 100644 --- a/tcwebhooks-core/src/test/java/webhook/teamcity/history/WebHookHistoryRepositoryImplTest.java +++ b/tcwebhooks-core/src/test/java/webhook/teamcity/history/WebHookHistoryRepositoryImplTest.java @@ -241,8 +241,8 @@ private WebHookHistoryRepository setupMocks() { when(sBuild01.getBuildId()).thenReturn(01L); when(sBuild02.getBuildId()).thenReturn(02L); - whc1 = new WebHookConfig("project01", "MyProject", "http://url/1", true, new BuildState().setAllEnabled(), "testFormat", true, true, null, null); - whc2 = new WebHookConfig("project01", "MyProject", "http://url/2", true, new BuildState().setAllEnabled(), "testFormat", true, true, null, null); + whc1 = new WebHookConfig("project01", "MyProject", "http://url/1", true, new BuildState().setAllEnabled(), "testFormat", true, true, null, null, true); + whc2 = new WebHookConfig("project01", "MyProject", "http://url/2", true, new BuildState().setAllEnabled(), "testFormat", true, true, null, null, true); WebHookHistoryRepository historyRepository = new WebHookHistoryRepositoryImpl(); historyRepository.addHistoryItem(new WebHookHistoryItem(whc1, webhook01.getExecutionStats(), sBuild01, null)); @@ -292,7 +292,7 @@ public WebHookStatisticsRunner(WebHookHistoryRepository historyRepository, SBuil @Override public void run() { - WebHookConfig whc = new WebHookConfig("project01", "MyProject", "http://url/1", true, new BuildState().setAllEnabled(), "testFormat", true, true, null, null); + WebHookConfig whc = new WebHookConfig("project01", "MyProject", "http://url/1", true, new BuildState().setAllEnabled(), "testFormat", true, true, null, null, true); WebHook webhook = new SimpleMockedWebHook(stats); historyRepository.addHistoryItem(new WebHookHistoryItem(whc, webhook.getExecutionStats(), sBuild, null)); atomic.addAndGet(1); @@ -307,6 +307,7 @@ public static class SimpleMockedWebHook implements WebHook { private String url; private Integer status; private WebHookExecutionStats stats; + private boolean hideSecureValues; public SimpleMockedWebHook(WebHookExecutionStats stats) { this.stats = stats; @@ -578,5 +579,20 @@ public void setVariableResolverFactory(VariableResolverFactory variableResolverF notImplemented(); } + @Override + public boolean shouldHideSecureData() { + return false; + } + + @Override + public boolean isHideSecureValues() { + return this.hideSecureValues; + } + + @Override + public void setHideSecureValues(boolean hideSecureValues) { + this.hideSecureValues = hideSecureValues; + } + } } diff --git a/tcwebhooks-core/src/test/java/webhook/teamcity/payload/variableresolver/velocity/WebHookVelocityVariableMessageBuilderTest.java b/tcwebhooks-core/src/test/java/webhook/teamcity/payload/variableresolver/velocity/WebHookVelocityVariableMessageBuilderTest.java index 8c1b21f1..8459aa01 100644 --- a/tcwebhooks-core/src/test/java/webhook/teamcity/payload/variableresolver/velocity/WebHookVelocityVariableMessageBuilderTest.java +++ b/tcwebhooks-core/src/test/java/webhook/teamcity/payload/variableresolver/velocity/WebHookVelocityVariableMessageBuilderTest.java @@ -1,6 +1,8 @@ package webhook.teamcity.payload.variableresolver.velocity; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import org.apache.velocity.context.Context; import org.junit.Test; @@ -10,8 +12,10 @@ import jetbrains.buildServer.serverSide.SProject; import lombok.AllArgsConstructor; import lombok.Data; +import webhook.teamcity.payload.PayloadTemplateEngineType; import webhook.teamcity.payload.content.ExtraParameters; import webhook.teamcity.payload.content.WebHookPayloadContent; +import webhook.teamcity.settings.project.WebHookParameterModel; import webhook.teamcity.settings.secure.WebHookSecretResolver; public class WebHookVelocityVariableMessageBuilderTest { @@ -57,6 +61,27 @@ public void testBuildSecure() { assertEquals("myPass", builder.build("#secure($myKey)")); } + + @Test + public void testAccessingSecureParameter() { + + SProject sProject = Mockito.mock(SProject.class); + ExtraParameters extraParameters = new ExtraParameters(); + extraParameters.put("myString", "${buildId} is in project ${projectId}"); + extraParameters.put("myKey", "abc123"); + extraParameters.add(new WebHookParameterModel("1", "project", "my.Secret.Url", "http://some.secret.com/place", Boolean.TRUE, Boolean.FALSE, Boolean.FALSE, PayloadTemplateEngineType.VELOCITY.toString())); + Context resolver = new WebHooksBeanUtilsVelocityVariableResolver( + sProject, + new WebHookPayloadContent.SimpleSerialiser(), + new JavaBean("bt01", "project01", sProject), + extraParameters, + null + ); + WebHookVelocityVariableMessageBuilder builder = WebHookVelocityVariableMessageBuilder.create(resolver, null); + assertFalse(extraParameters.wasSecureValueAccessed()); + assertEquals("bt01 is in project project01-http://some.secret.com/place", builder.build("${myString}-${my_Secret_Url}")); + assertTrue(extraParameters.wasSecureValueAccessed()); + } @Data @AllArgsConstructor public class JavaBean { diff --git a/tcwebhooks-core/src/test/java/webhook/teamcity/settings/WebHookConfigTest.java b/tcwebhooks-core/src/test/java/webhook/teamcity/settings/WebHookConfigTest.java index 99ca1a83..6bc785bc 100644 --- a/tcwebhooks-core/src/test/java/webhook/teamcity/settings/WebHookConfigTest.java +++ b/tcwebhooks-core/src/test/java/webhook/teamcity/settings/WebHookConfigTest.java @@ -58,6 +58,7 @@ public void testGetAsElement() { WebHookConfig whc = new WebHookConfig(e); assertTrue(whc.getParams().containsKey("color")); assertTrue(whc.getParams().containsKey("notify")); + assertTrue(whc.isHideSecureValues()); } @Test @@ -215,4 +216,15 @@ public void testWebHookWithoutStatsIsDisabledForStats(){ assertTrue(webhookWithoutStats.getEnabled()); assertFalse(webhookWithoutStats.getBuildStates().enabled(BuildStateEnum.REPORT_STATISTICS)); } + + @Test + public void testWebHookHideSecureValuesDefaultsToTrue(){ + assertTrue(webhookAllEnabled.isHideSecureValues()); + assertTrue(webhookDisabled.isHideSecureValues()); + } + + @Test + public void testThatHideSecureValueIsFalseWhenSet() { + assertFalse(webhookAllDisabled.isHideSecureValues()); + } } diff --git a/tcwebhooks-core/src/test/java/webhook/teamcity/settings/WebHookProjectSettingsTest.java b/tcwebhooks-core/src/test/java/webhook/teamcity/settings/WebHookProjectSettingsTest.java index 43916fdc..20bc7326 100644 --- a/tcwebhooks-core/src/test/java/webhook/teamcity/settings/WebHookProjectSettingsTest.java +++ b/tcwebhooks-core/src/test/java/webhook/teamcity/settings/WebHookProjectSettingsTest.java @@ -35,7 +35,7 @@ public void TestUpdateToWebhookConfigToRemoveAuthenicationUpdatesCorrectlyWhenNu WebHookConfig config = settings.getWebHooksConfigs().get(0); assertTrue("Auth should be enabled", config.getAuthEnabled()); - settings.updateWebHook("project01", config.getUniqueKey(), config.getUrl(), config.getEnabled(), new BuildState(), config.getPayloadTemplate(), config.isEnabledForAllBuildsInProject(), config.isEnabledForSubProjects(), null, null); + settings.updateWebHook("project01", config.getUniqueKey(), config.getUrl(), config.getEnabled(), new BuildState(), config.getPayloadTemplate(), config.isEnabledForAllBuildsInProject(), config.isEnabledForSubProjects(), null, null, true); WebHookConfig config2 = settings.getWebHooksConfigs().get(0); assertFalse("Auth should now be disabled", config2.getAuthEnabled()); @@ -56,7 +56,7 @@ public void TestUpdateToWebhookConfigToAddAuthenicationUpdatesCorrectlyWhenValid authConfig.setPreemptive(true); authConfig.getParameters().put("username", "usernamey"); - settings.updateWebHook("project01", config.getUniqueKey(), config.getUrl(), config.getEnabled(), new BuildState(), config.getPayloadTemplate(), config.isEnabledForAllBuildsInProject(), config.isEnabledForSubProjects(), null, authConfig); + settings.updateWebHook("project01", config.getUniqueKey(), config.getUrl(), config.getEnabled(), new BuildState(), config.getPayloadTemplate(), config.isEnabledForAllBuildsInProject(), config.isEnabledForSubProjects(), null, authConfig, true); WebHookConfig config2 = settings.getWebHooksConfigs().get(0); assertTrue("Auth should now be enabled", config2.getAuthEnabled()); diff --git a/tcwebhooks-core/src/test/java/webhook/testframework/MockWebHook.java b/tcwebhooks-core/src/test/java/webhook/testframework/MockWebHook.java index 16e5b923..6a3cc3c4 100644 --- a/tcwebhooks-core/src/test/java/webhook/testframework/MockWebHook.java +++ b/tcwebhooks-core/src/test/java/webhook/testframework/MockWebHook.java @@ -22,6 +22,7 @@ public MockWebHook(WebHookConfig webHookConfig, WebHookProxyConfig pc) { this.setUrl(webHookConfig.getUrl()); this.setEnabled(webHookConfig.getEnabled()); this.setBuildStates(webHookConfig.getBuildStates()); + this.setHideSecureValues(webHookConfig.isHideSecureValues()); } @Override diff --git a/tcwebhooks-core/src/test/resources/project-settings-test-all-states-disabled.xml b/tcwebhooks-core/src/test/resources/project-settings-test-all-states-disabled.xml index 23492e7c..ae8aed46 100644 --- a/tcwebhooks-core/src/test/resources/project-settings-test-all-states-disabled.xml +++ b/tcwebhooks-core/src/test/resources/project-settings-test-all-states-disabled.xml @@ -1,7 +1,7 @@ - + diff --git a/tcwebhooks-core/src/test/resources/project-settings-test-webhook-disabled.xml b/tcwebhooks-core/src/test/resources/project-settings-test-webhook-disabled.xml index bfed3d92..4e8e221a 100644 --- a/tcwebhooks-core/src/test/resources/project-settings-test-webhook-disabled.xml +++ b/tcwebhooks-core/src/test/resources/project-settings-test-webhook-disabled.xml @@ -1,7 +1,7 @@ - + diff --git a/tcwebhooks-rest-api/src/main/java/webhook/teamcity/server/rest/model/webhook/ProjectWebhook.java b/tcwebhooks-rest-api/src/main/java/webhook/teamcity/server/rest/model/webhook/ProjectWebhook.java index 09458ccb..45e39d5b 100644 --- a/tcwebhooks-rest-api/src/main/java/webhook/teamcity/server/rest/model/webhook/ProjectWebhook.java +++ b/tcwebhooks-rest-api/src/main/java/webhook/teamcity/server/rest/model/webhook/ProjectWebhook.java @@ -24,7 +24,7 @@ import webhook.teamcity.settings.WebHookConfig; /* - + @@ -49,7 +49,7 @@ @NoArgsConstructor @Getter @Setter @XmlAccessorType(XmlAccessType.FIELD) -@XmlType (propOrder = { "url", "id", "projectId", "enabled", "template", "webUrl", "href", "states", "buildTypes", "parameters", "customTemplates", "authentication" }) +@XmlType (propOrder = { "url", "id", "projectId", "enabled", "template", "hideSecureValues", "webUrl", "href", "states", "buildTypes", "parameters", "customTemplates", "authentication" }) public class ProjectWebhook { @XmlAttribute @@ -67,6 +67,9 @@ public class ProjectWebhook { @XmlAttribute private String template; + @XmlAttribute + private Boolean hideSecureValues; + @XmlElement public List states; @@ -95,11 +98,12 @@ public ProjectWebhook(WebHookConfig config, final String projectExternalId, fina this.enabled = ValueWithDefault.decideDefault(fields.isIncluded("enabled", true, true), config.getEnabled()); this.projectId = ValueWithDefault.decideDefault(fields.isIncluded("projectId", false, true), projectExternalId); this.template = ValueWithDefault.decideDefault(fields.isIncluded("template", true, true), config.getPayloadTemplate()); - webUrl = ValueWithDefault.decideDefault(fields.isIncluded("webUrl", false, false), beanContext.getSingletonService(WebHookWebLinks.class).getWebHookUrl(projectExternalId)); - href = ValueWithDefault.decideDefault(fields.isIncluded("href"), beanContext.getApiUrlBuilder().getHref(projectExternalId, config)); - buildTypes = ValueWithDefault.decideDefault(fields.isIncluded("buildTypes", true, true), new ProjectWebHookBuildType(config.isEnabledForAllBuildsInProject(), config.isEnabledForSubProjects(), enabledBuildTypes)); + this.hideSecureValues = ValueWithDefault.decideDefault(fields.isIncluded("hideSecureValues", true, true), config.isHideSecureValues()); + this.webUrl = ValueWithDefault.decideDefault(fields.isIncluded("webUrl", false, false), beanContext.getSingletonService(WebHookWebLinks.class).getWebHookUrl(projectExternalId)); + this.href = ValueWithDefault.decideDefault(fields.isIncluded("href"), beanContext.getApiUrlBuilder().getHref(projectExternalId, config)); + this.buildTypes = ValueWithDefault.decideDefault(fields.isIncluded("buildTypes", true, true), new ProjectWebHookBuildType(config.isEnabledForAllBuildsInProject(), config.isEnabledForSubProjects(), enabledBuildTypes)); - if (fields.isIncluded("states", false, true)) { + if (Boolean.TRUE.equals(fields.isIncluded("states", false, true))) { states = new ArrayList<>(); for (BuildStateEnum state : config.getBuildStates().getStateSet()) { if (config.getBuildStates().enabled(state)) { diff --git a/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/WebHookAjaxEditPageController.java b/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/WebHookAjaxEditPageController.java index c41848c7..7bd4574c 100644 --- a/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/WebHookAjaxEditPageController.java +++ b/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/WebHookAjaxEditPageController.java @@ -124,6 +124,7 @@ protected ModelAndView doHandle(HttpServletRequest request, HttpServletResponse Boolean enabled = false; Boolean buildTypeAll = false; Boolean buildTypeSubProjects = false; + Boolean hideSecureValues = false; Set buildTypes = new HashSet(); if ((request.getParameter("webHooksEnabled") != null ) && (request.getParameter("webHooksEnabled").equalsIgnoreCase("on"))){ @@ -151,6 +152,9 @@ protected ModelAndView doHandle(HttpServletRequest request, HttpServletResponse if ((request.getParameter("buildTypeSubProjects") != null ) && (request.getParameter("buildTypeSubProjects").equalsIgnoreCase("on"))){ buildTypeSubProjects = true; } + if ((request.getParameter("hideSecureValues") != null ) && (request.getParameter("hideSecureValues").equalsIgnoreCase("on"))){ + hideSecureValues = true; + } if ((request.getParameter("buildTypeAll") != null ) && (request.getParameter("buildTypeAll").equalsIgnoreCase("on"))){ buildTypeAll = true; } else { @@ -193,7 +197,7 @@ protected ModelAndView doHandle(HttpServletRequest request, HttpServletResponse if (noErrors && request.getParameter("webHookId").equals("new")){ WebHookUpdateResult result = mySettings.addNewWebHook(myProject.getProjectId(), myProject.getExternalId(), request.getParameter("URL"), enabled, states, request.getParameter("payloadTemplate"), - buildTypeAll, buildTypeSubProjects, buildTypes, webHookAuthConfig); + buildTypeAll, buildTypeSubProjects, buildTypes, webHookAuthConfig, hideSecureValues); if(result.isUpdated()){ params.put(PARAMS_MESSAGES_KEY, ""); } else { @@ -203,7 +207,7 @@ protected ModelAndView doHandle(HttpServletRequest request, HttpServletResponse WebHookUpdateResult result = mySettings.updateWebHook(myProject.getProjectId(),request.getParameter("webHookId"), request.getParameter("URL"), enabled, states, request.getParameter("payloadTemplate"), - buildTypeAll, buildTypeSubProjects, buildTypes, webHookAuthConfig); + buildTypeAll, buildTypeSubProjects, buildTypes, webHookAuthConfig, hideSecureValues); if(result.isUpdated()){ params.put(PARAMS_MESSAGES_KEY, ""); } else { diff --git a/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/WebHookBuildTypeTabExtension.java b/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/WebHookBuildTypeTabExtension.java index f1cbec28..b7abde74 100644 --- a/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/WebHookBuildTypeTabExtension.java +++ b/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/WebHookBuildTypeTabExtension.java @@ -18,6 +18,9 @@ import jetbrains.buildServer.web.openapi.buildType.BuildTypeTab; import webhook.teamcity.TeamCityIdResolver; import webhook.teamcity.extension.bean.ProjectWebHooksBean; +import webhook.teamcity.extension.util.WebHookSecureValuesHelperService; +import webhook.teamcity.history.PagedList; +import webhook.teamcity.history.WebHookHistoryItem; import webhook.teamcity.history.WebHookHistoryRepository; import webhook.teamcity.payload.WebHookPayloadManager; import webhook.teamcity.payload.WebHookTemplateResolver; @@ -31,7 +34,8 @@ public class WebHookBuildTypeTabExtension extends BuildTypeTab { private final String myPluginPath; private final WebHookHistoryRepository myWebHookHistoryRepository; private final WebHookPayloadManager myPayloadManager; - private final WebHookTemplateResolver myTemplateResolver; + private final WebHookTemplateResolver myTemplateResolver; + private final WebHookSecureValuesHelperService myWebHookSecureValuesHelperService; public WebHookBuildTypeTabExtension( @NotNull ProjectManager projectManager, @@ -40,13 +44,15 @@ public WebHookBuildTypeTabExtension( @NotNull PluginDescriptor pluginDescriptor, @NotNull WebHookHistoryRepository webHookHistoryRepository, @NotNull WebHookPayloadManager webhookPayloadManager, - @NotNull WebHookTemplateResolver webHookTemplateResolver) { + @NotNull WebHookTemplateResolver webHookTemplateResolver, + @NotNull WebHookSecureValuesHelperService webHookSecureValuesHelperService) { super("webHooks", "WebHooks", manager, projectManager); myWebHookSettingsManager = webHookSettingsManager; myPluginPath = pluginDescriptor.getPluginResourcesPath(); myWebHookHistoryRepository = webHookHistoryRepository; myPayloadManager = webhookPayloadManager; myTemplateResolver = webHookTemplateResolver; + myWebHookSecureValuesHelperService = webHookSecureValuesHelperService; addCssFile(myPluginPath+ "WebHook/css/styles.css"); } @@ -92,7 +98,10 @@ protected void fillModel(Map model, HttpServletRequest request, model.put("buildTypeId", buildType.getBuildTypeId()); model.put("buildExternalId", TeamCityIdResolver.getExternalBuildId(buildType)); model.put("buildName", buildType.getName()); - model.put("items", myWebHookHistoryRepository.findHistoryItemsForBuildType(buildType.getBuildTypeId(), 1, 50)); + PagedList historyItems = myWebHookHistoryRepository.findHistoryItemsForBuildType(buildType.getBuildTypeId(), 1, 50); + model.put("items", historyItems); + model.put("webhookSecureEnabledMap", myWebHookSecureValuesHelperService.assembleWebHookSecureValues(historyItems)); + } @Override diff --git a/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/WebHookProjectTabExtension.java b/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/WebHookProjectTabExtension.java index 99d37514..84f00000 100644 --- a/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/WebHookProjectTabExtension.java +++ b/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/WebHookProjectTabExtension.java @@ -15,6 +15,9 @@ import jetbrains.buildServer.web.openapi.PluginDescriptor; import jetbrains.buildServer.web.openapi.project.ProjectTab; import webhook.teamcity.extension.bean.ProjectWebHooksBean; +import webhook.teamcity.extension.util.WebHookSecureValuesHelperService; +import webhook.teamcity.history.PagedList; +import webhook.teamcity.history.WebHookHistoryItem; import webhook.teamcity.history.WebHookHistoryRepository; import webhook.teamcity.payload.WebHookPayloadManager; import webhook.teamcity.payload.WebHookTemplateResolver; @@ -28,6 +31,7 @@ public class WebHookProjectTabExtension extends ProjectTab { private final WebHookHistoryRepository myWebHookHistoryRepository; private final WebHookPayloadManager myPayloadManager; private final WebHookTemplateResolver myTemplateResolver; + private final WebHookSecureValuesHelperService myWebHookSecureValuesHelperService; public WebHookProjectTabExtension( @NotNull PagePlaces pagePlaces, @@ -36,13 +40,15 @@ public WebHookProjectTabExtension( @NotNull PluginDescriptor pluginDescriptor, @NotNull WebHookHistoryRepository webHookHistoryRepository, @NotNull WebHookPayloadManager webhookPayloadManager, - @NotNull WebHookTemplateResolver webHookTemplateResolver) { + @NotNull WebHookTemplateResolver webHookTemplateResolver, + @NotNull WebHookSecureValuesHelperService webHookSecureValuesHelperService) { super("webHooks", "WebHooks", pagePlaces, projectManager); myWebHookSettingsManager = webHookSettingsManager; myPluginPath = pluginDescriptor.getPluginResourcesPath(); myWebHookHistoryRepository = webHookHistoryRepository; myPayloadManager = webhookPayloadManager; myTemplateResolver = webHookTemplateResolver; + myWebHookSecureValuesHelperService = webHookSecureValuesHelperService; addCssFile(myPluginPath+ "WebHook/css/styles.css"); } @@ -68,7 +74,10 @@ protected void fillModel(Map model, HttpServletRequest request, @ model.put("projectAndParents", projectWebHooks); model.put("project", currentProject); - model.put("items", myWebHookHistoryRepository.findHistoryItemsForProject(currentProject.getProjectId(), 1, 50)); + PagedList historyItems = myWebHookHistoryRepository.findHistoryItemsForProject(currentProject.getProjectId(), 1, 50); + model.put("items", historyItems); + model.put("webhookSecureEnabledMap", myWebHookSecureValuesHelperService.assembleWebHookSecureValues(historyItems)); + } @Override diff --git a/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/bean/ProjectWebHooksBean.java b/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/bean/ProjectWebHooksBean.java index 1d0df772..e0a5d794 100644 --- a/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/bean/ProjectWebHooksBean.java +++ b/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/bean/ProjectWebHooksBean.java @@ -59,7 +59,7 @@ public static ProjectWebHooksBean build(WebHookProjectSettings projSettings, SPr List projectBuildTypes = TeamCityIdResolver.getOwnBuildTypes(project); /* Create a "new" config with blank stuff so that clicking the "new" button has a bunch of defaults to load in */ - WebHookConfig newBlankConfig = new WebHookConfig(project.getProjectId(), project.getExternalId(), "", true, new BuildState().setAllEnabled(), null, true, true, null, null); + WebHookConfig newBlankConfig = new WebHookConfig(project.getProjectId(), project.getExternalId(), "", true, new BuildState().setAllEnabled(), null, true, true, null, null, true); newBlankConfig.setUniqueKey("new"); /* And add it to the list */ addWebHookConfigHolder(bean, projectBuildTypes, newBlankConfig, registeredPayloads, templateList); @@ -80,7 +80,7 @@ public static ProjectWebHooksBean build(WebHookProjectSettings projSettings, SBu enabledBuildTypes.add(sBuildType.getBuildTypeId()); /* Create a "new" config with blank stuff so that clicking the "new" button has a bunch of defaults to load in */ - WebHookConfig newBlankConfig = new WebHookConfig(project.getProjectId(), project.getExternalId(), "", true, new BuildState().setAllEnabled(), null, false, false, enabledBuildTypes, null); + WebHookConfig newBlankConfig = new WebHookConfig(project.getProjectId(), project.getExternalId(), "", true, new BuildState().setAllEnabled(), null, false, false, enabledBuildTypes, null, true); newBlankConfig.setUniqueKey("new"); /* And add it to the list */ addWebHookConfigHolder(bean, projectBuildTypes, newBlankConfig, registeredPayloads, templateList); diff --git a/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/bean/WebhookConfigAndBuildTypeListHolder.java b/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/bean/WebhookConfigAndBuildTypeListHolder.java index 0cc869f9..13179174 100644 --- a/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/bean/WebhookConfigAndBuildTypeListHolder.java +++ b/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/bean/WebhookConfigAndBuildTypeListHolder.java @@ -45,11 +45,13 @@ public class WebhookConfigAndBuildTypeListHolder { private WebhookAuthenticationConfigBean authConfig = null; private GeneralisedWebAddress generalisedWebAddress; private Set tags = new LinkedHashSet<>(); + private boolean hideSecureValues; public WebhookConfigAndBuildTypeListHolder(WebHookConfig config, Collection registeredPayloads, List templateList) { url = config.getUrl(); uniqueKey = config.getUniqueKey(); enabled = config.getEnabled(); + hideSecureValues = config.isHideSecureValues(); payloadTemplate = config.getPayloadTemplate(); enabledBuildIds = config.getEnabledBuildTypesSet(); setEnabledEventsListForWeb(config.getEnabledListAsString()); diff --git a/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/util/WebHookSecureValuesHelperService.java b/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/util/WebHookSecureValuesHelperService.java new file mode 100644 index 00000000..37c3655e --- /dev/null +++ b/tcwebhooks-web-ui/src/main/java/webhook/teamcity/extension/util/WebHookSecureValuesHelperService.java @@ -0,0 +1,32 @@ +package webhook.teamcity.extension.util; + +import java.util.HashMap; +import java.util.Map; + +import webhook.teamcity.history.PagedList; +import webhook.teamcity.history.WebHookHistoryItem; +import webhook.teamcity.settings.WebHookSecureValuesEnquirer; + +public class WebHookSecureValuesHelperService { + + private WebHookSecureValuesEnquirer webHookSecureValuesEnquirer; + + public WebHookSecureValuesHelperService(WebHookSecureValuesEnquirer webHookSecureValuesEnquirer) { + this.webHookSecureValuesEnquirer = webHookSecureValuesEnquirer; + } + + + public Map assembleWebHookSecureValues(PagedList pagedList) { + Map isHideSecureEnabledMap = new HashMap<>(); + for (WebHookHistoryItem historyItem : pagedList.getItems()) { + if (! isHideSecureEnabledMap.containsKey(historyItem.getWebHookConfig().getUniqueKey())) { + isHideSecureEnabledMap.put( + historyItem.getWebHookConfig().getUniqueKey(), + this.webHookSecureValuesEnquirer.isHideSecureValuesEnabled(historyItem.getWebHookConfig().getUniqueKey()) + ); + } + } + return isHideSecureEnabledMap; + } + +} diff --git a/tcwebhooks-web-ui/src/main/java/webhook/teamcity/history/WebHookHistoryController.java b/tcwebhooks-web-ui/src/main/java/webhook/teamcity/history/WebHookHistoryController.java index fc829064..79c46e1b 100644 --- a/tcwebhooks-web-ui/src/main/java/webhook/teamcity/history/WebHookHistoryController.java +++ b/tcwebhooks-web-ui/src/main/java/webhook/teamcity/history/WebHookHistoryController.java @@ -14,6 +14,7 @@ import jetbrains.buildServer.util.Pager; import jetbrains.buildServer.web.openapi.PluginDescriptor; import jetbrains.buildServer.web.openapi.WebControllerManager; +import webhook.teamcity.extension.util.WebHookSecureValuesHelperService; @SuppressWarnings("squid:MaximumInheritanceDepth") public class WebHookHistoryController extends BaseController { @@ -25,17 +26,20 @@ public class WebHookHistoryController extends BaseController { private static final String MY_URL = "/webhooks/history.html"; private String myPluginPath; private WebHookHistoryRepository myWebHookHistoryRepository; + private WebHookSecureValuesHelperService webHookSecureValuesHelperService; public WebHookHistoryController( @NotNull SBuildServer server, @NotNull PluginDescriptor pluginDescriptor, @NotNull WebControllerManager webControllerManager, - @NotNull WebHookHistoryRepository webHookHistoryRepository + @NotNull WebHookHistoryRepository webHookHistoryRepository, + @NotNull WebHookSecureValuesHelperService webHookSecureValuesHelperService ) { super(server); this.myPluginPath = pluginDescriptor.getPluginResourcesPath(); this.myWebHookHistoryRepository = webHookHistoryRepository; + this.webHookSecureValuesHelperService = webHookSecureValuesHelperService; webControllerManager.registerController(MY_URL, this); } @@ -105,6 +109,7 @@ protected ModelAndView doHandle(HttpServletRequest request, HttpServletResponse pager.setRecordsPerPage(pageSize); pager.setCurrentPage(pageNumber); params.put("historyPager", pager); + params.put("webhookSecureEnabledMap", this.webHookSecureValuesHelperService.assembleWebHookSecureValues(pagedList)); } return new ModelAndView(myPluginPath + "WebHook/viewHistory.jsp", params); diff --git a/tcwebhooks-web-ui/src/main/resources/META-INF/build-server-plugin-WebHookListener.xml b/tcwebhooks-web-ui/src/main/resources/META-INF/build-server-plugin-WebHookListener.xml index 299642e6..9b40c900 100644 --- a/tcwebhooks-web-ui/src/main/resources/META-INF/build-server-plugin-WebHookListener.xml +++ b/tcwebhooks-web-ui/src/main/resources/META-INF/build-server-plugin-WebHookListener.xml @@ -39,6 +39,10 @@ class="webhook.teamcity.history.WebHookHistoryRepositoryImpl" /> + + - '"> + + + '"> + + + '"> + + + ** + + diff --git a/tcwebhooks-web-ui/src/main/resources/buildServerResources/WebHook/webHookInclude.jsp b/tcwebhooks-web-ui/src/main/resources/buildServerResources/WebHook/webHookInclude.jsp index cd245a98..fa828871 100644 --- a/tcwebhooks-web-ui/src/main/resources/buildServerResources/WebHook/webHookInclude.jsp +++ b/tcwebhooks-web-ui/src/main/resources/buildServerResources/WebHook/webHookInclude.jsp @@ -242,6 +242,15 @@ +   + + + + + + diff --git a/tcwebhooks-web-ui/src/main/resources/buildServerResources/WebHook/webHookTabWithHistory.jsp b/tcwebhooks-web-ui/src/main/resources/buildServerResources/WebHook/webHookTabWithHistory.jsp index b202ad91..6c8e94e7 100644 --- a/tcwebhooks-web-ui/src/main/resources/buildServerResources/WebHook/webHookTabWithHistory.jsp +++ b/tcwebhooks-web-ui/src/main/resources/buildServerResources/WebHook/webHookTabWithHistory.jsp @@ -133,7 +133,17 @@ - '"> + + + '"> + + + '"> + + + ** + +