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 @@ - '"> + + + '"> + + + '"> + + + ** + +