diff --git a/ChangeLog.txt b/ChangeLog.txt index 6380a3f5c949c..632f6e05a77bd 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -5,6 +5,7 @@ * Removed internal dependency on javafx to be compatible with openjdk. * Fixed a bug that would cause downloading large files with the TransferManager to fail. * Fixed a bug in BlobURL.download() logic for setting up reliable download. This had the potential to download the wrong range when a download stream was retried. +* Added request Http Method, URL, and headers to logging messages. 2018.09.11 Version 10.1.0 * Interfaces for helper types updated to be more consistent throughout the library. All types, with the exception of the options for pipeline factories, use a fluent pattern. diff --git a/src/main/java/com/microsoft/azure/storage/blob/Constants.java b/src/main/java/com/microsoft/azure/storage/blob/Constants.java index 2221cf63f74e9..b3d2319658d13 100644 --- a/src/main/java/com/microsoft/azure/storage/blob/Constants.java +++ b/src/main/java/com/microsoft/azure/storage/blob/Constants.java @@ -55,6 +55,10 @@ final class Constants { * The query parameter for snapshots. */ static final String SNAPSHOT_QUERY_PARAMETER = "snapshot"; + /** + * The word redacted. + */ + static final String REDACTED = "REDACTED"; /** * The default amount of parallelism for TransferManager operations. */ @@ -152,6 +156,16 @@ static final class HeaderConstants { */ static final String RANGE_HEADER_FORMAT = "bytes=%d-%d"; + /** + * The copy source header. + */ + static final String COPY_SOURCE = "x-ms-copy-source"; + + /** + * The version header. + */ + static final String VERSION = "x-ms-version"; + /** * The current storage version header value. */ @@ -176,4 +190,91 @@ private HeaderConstants() { // Private to prevent construction. } } + + static final class UrlConstants { + + /** + * The SAS service version parameter. + */ + static final String SAS_SERVICE_VERSION = "sv"; + + /** + * The SAS services parameter. + */ + static final String SAS_SERVICES = "ss"; + + /** + * The SAS resource types parameter. + */ + static final String SAS_RESOURCES_TYPES = "srt"; + + /** + * The SAS protocol parameter. + */ + static final String SAS_PROTOCOL = "spr"; + + /** + * The SAS start time parameter. + */ + static final String SAS_START_TIME = "st"; + + /** + * The SAS expiration time parameter. + */ + static final String SAS_EXPIRY_TIME = "se"; + + /** + * The SAS IP range parameter. + */ + static final String SAS_IP_RANGE = "sip"; + + /** + * The SAS signed identifier parameter. + */ + static final String SAS_SIGNED_IDENTIFIER = "si"; + + /** + * The SAS signed resource parameter. + */ + static final String SAS_SIGNED_RESOURCE = "sr"; + + /** + * The SAS signed permissions parameter. + */ + static final String SAS_SIGNED_PERMISSIONS = "sp"; + + /** + * The SAS signature parameter. + */ + static final String SAS_SIGNATURE = "sig"; + + /** + * The SAS cache control parameter. + */ + static final String SAS_CACHE_CONTROL = "rscc"; + + /** + * The SAS content disposition parameter. + */ + static final String SAS_CONTENT_DISPOSITION = "rscd"; + + /** + * The SAS content encoding parameter. + */ + static final String SAS_CONTENT_ENCODING = "rsce"; + + /** + * The SAS content language parameter. + */ + static final String SAS_CONTENT_LANGUAGE = "rscl"; + + /** + * The SAS content type parameter. + */ + static final String SAS_CONTENT_TYPE = "rsct"; + + private UrlConstants() { + // Private to prevent construction. + } + } } \ No newline at end of file diff --git a/src/main/java/com/microsoft/azure/storage/blob/LoggingFactory.java b/src/main/java/com/microsoft/azure/storage/blob/LoggingFactory.java index f27e69b506c1c..2966e09fad0f5 100644 --- a/src/main/java/com/microsoft/azure/storage/blob/LoggingFactory.java +++ b/src/main/java/com/microsoft/azure/storage/blob/LoggingFactory.java @@ -24,6 +24,9 @@ import io.reactivex.Single; import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.UnknownHostException; import java.util.Locale; /** @@ -164,12 +167,89 @@ public Single sendAsync(final HttpRequest request) { } if (options.shouldLog(currentLevel)) { + String additionalMessageInfo = buildAdditionalMessageInfo(request); String messageInfo = String.format(Locale.ROOT, - "Request try:'%d', request duration:'%d' ms, operation duration:'%d' ms%n", - tryCount, requestCompletionTime, operationDuration); + "Request try:'%d', request duration:'%d' ms, operation duration:'%d' ms%n%s", + tryCount, requestCompletionTime, operationDuration, additionalMessageInfo); options.log(currentLevel, logMessage + messageInfo); } }); } + + private String buildAdditionalMessageInfo(final HttpRequest httpRequest) { + HttpRequest sanitizedRequest = buildSanitizedRequest(httpRequest); + StringBuilder stringBuilder = new StringBuilder(); + String format = "%s: %s" + System.lineSeparator(); + stringBuilder.append(String.format(format, sanitizedRequest.httpMethod().toString(), sanitizedRequest.url().toString())); + sanitizedRequest.headers().forEach((header) -> stringBuilder.append(String.format(format, header.name(), header.value()))); + return stringBuilder.toString(); + } + + private HttpRequest buildSanitizedRequest(final HttpRequest initialRequest) { + // Build new URL and redact SAS signature, if present + URL url = sanitizeURL(initialRequest.url()); + + // Build resultRequest + HttpRequest resultRequest = new HttpRequest( + initialRequest.callerMethod(), + initialRequest.httpMethod(), + url, + initialRequest.headers(), + initialRequest.body(), + initialRequest.responseDecoder()); + + // Redact Authorization header, if present + if(resultRequest.headers().value(Constants.HeaderConstants.AUTHORIZATION) != null) { + resultRequest.headers().set(Constants.HeaderConstants.AUTHORIZATION, Constants.REDACTED); + } + + // Redact Copy Source header SAS signature, if present + if(resultRequest.headers().value(Constants.HeaderConstants.COPY_SOURCE) != null) { + try { + URL copySourceUrl = sanitizeURL(new URL(resultRequest.headers().value(Constants.HeaderConstants.COPY_SOURCE))); + resultRequest.headers().set(Constants.HeaderConstants.COPY_SOURCE, copySourceUrl.toString()); + } catch(MalformedURLException e) { + throw new RuntimeException(e); + } + } + + return resultRequest; + } + + private URL sanitizeURL(URL initialURL) { + String urlString = initialURL.toString(); + URL resultURL = initialURL; + try { + BlobURLParts urlParts = URLParser.parse(initialURL); + if(urlParts.sasQueryParameters() == null || urlParts.sasQueryParameters().signature() == null) { + return resultURL; + } + urlParts.withSasQueryParameters(new SASQueryParameters( + urlParts.sasQueryParameters().version(), + urlParts.sasQueryParameters().services(), + urlParts.sasQueryParameters().resourceTypes(), + urlParts.sasQueryParameters().protocol(), + urlParts.sasQueryParameters().startTime(), + urlParts.sasQueryParameters().expiryTime(), + urlParts.sasQueryParameters().ipRange(), + urlParts.sasQueryParameters().identifier(), + urlParts.sasQueryParameters().resource(), + urlParts.sasQueryParameters().permissions(), + Constants.REDACTED, + urlParts.sasQueryParameters().cacheControl(), + urlParts.sasQueryParameters().contentDisposition(), + urlParts.sasQueryParameters().contentEncoding(), + urlParts.sasQueryParameters().contentLanguage(), + urlParts.sasQueryParameters().contentType() + )); + resultURL = urlParts.toURL(); + + /* We are only making valid changes to what has already been validated as a URL (since we got it from a URL object), + so there should be no need for either us or the caller to check this error. */ + } catch(UnknownHostException | MalformedURLException e) { + throw new RuntimeException(e); + } + return resultURL; + } } } diff --git a/src/main/java/com/microsoft/azure/storage/blob/SASQueryParameters.java b/src/main/java/com/microsoft/azure/storage/blob/SASQueryParameters.java index db5e043ff9017..acbe347b999bd 100644 --- a/src/main/java/com/microsoft/azure/storage/blob/SASQueryParameters.java +++ b/src/main/java/com/microsoft/azure/storage/blob/SASQueryParameters.java @@ -73,161 +73,161 @@ public final class SASQueryParameters { SASQueryParameters(Map queryParamsMap, boolean removeSASParametersFromMap) throws UnknownHostException { - String[] queryValue = queryParamsMap.get("sv"); + String[] queryValue = queryParamsMap.get(Constants.UrlConstants.SAS_SERVICE_VERSION); if (queryValue != null) { this.version = queryValue[0]; if (removeSASParametersFromMap) { - queryParamsMap.remove("sv"); + queryParamsMap.remove(Constants.UrlConstants.SAS_SERVICE_VERSION); } } else { this.version = null; } - queryValue = queryParamsMap.get("ss"); + queryValue = queryParamsMap.get(Constants.UrlConstants.SAS_SERVICES); if (queryValue != null) { this.services = queryValue[0]; if (removeSASParametersFromMap) { - queryParamsMap.remove("ss"); + queryParamsMap.remove(Constants.UrlConstants.SAS_SERVICES); } } else { this.services = null; } - queryValue = queryParamsMap.get("srt"); + queryValue = queryParamsMap.get(Constants.UrlConstants.SAS_RESOURCES_TYPES); if (queryValue != null) { this.resourceTypes = queryValue[0]; if (removeSASParametersFromMap) { - queryParamsMap.remove("srt"); + queryParamsMap.remove(Constants.UrlConstants.SAS_RESOURCES_TYPES); } } else { this.resourceTypes = null; } - queryValue = queryParamsMap.get("spr"); + queryValue = queryParamsMap.get(Constants.UrlConstants.SAS_PROTOCOL); if (queryValue != null) { this.protocol = SASProtocol.parse(queryValue[0]); if (removeSASParametersFromMap) { - queryParamsMap.remove("spr"); + queryParamsMap.remove(Constants.UrlConstants.SAS_PROTOCOL); } } else { this.protocol = null; } - queryValue = queryParamsMap.get("st"); + queryValue = queryParamsMap.get(Constants.UrlConstants.SAS_START_TIME); if (queryValue != null) { this.startTime = Utility.parseDate(queryValue[0]); if (removeSASParametersFromMap) { - queryParamsMap.remove("st"); + queryParamsMap.remove(Constants.UrlConstants.SAS_START_TIME); } } else { this.startTime = null; } - queryValue = queryParamsMap.get("se"); + queryValue = queryParamsMap.get(Constants.UrlConstants.SAS_EXPIRY_TIME); if (queryValue != null) { this.expiryTime = Utility.parseDate(queryValue[0]); if (removeSASParametersFromMap) { - queryParamsMap.remove("se"); + queryParamsMap.remove(Constants.UrlConstants.SAS_EXPIRY_TIME); } } else { this.expiryTime = null; } - queryValue = queryParamsMap.get("sip"); + queryValue = queryParamsMap.get(Constants.UrlConstants.SAS_IP_RANGE); if (queryValue != null) { this.ipRange = IPRange.parse(queryValue[0]); if (removeSASParametersFromMap) { - queryParamsMap.remove("sip"); + queryParamsMap.remove(Constants.UrlConstants.SAS_IP_RANGE); } } else { this.ipRange = null; } - queryValue = queryParamsMap.get("si"); + queryValue = queryParamsMap.get(Constants.UrlConstants.SAS_SIGNED_IDENTIFIER); if (queryValue != null) { this.identifier = queryValue[0]; if (removeSASParametersFromMap) { - queryParamsMap.remove("si"); + queryParamsMap.remove(Constants.UrlConstants.SAS_SIGNED_IDENTIFIER); } } else { this.identifier = null; } - queryValue = queryParamsMap.get("sr"); + queryValue = queryParamsMap.get(Constants.UrlConstants.SAS_SIGNED_RESOURCE); if (queryValue != null) { this.resource = queryValue[0]; if (removeSASParametersFromMap) { - queryParamsMap.remove("sr"); + queryParamsMap.remove(Constants.UrlConstants.SAS_SIGNED_RESOURCE); } } else { this.resource = null; } - queryValue = queryParamsMap.get("sp"); + queryValue = queryParamsMap.get(Constants.UrlConstants.SAS_SIGNED_PERMISSIONS); if (queryValue != null) { this.permissions = queryValue[0]; if (removeSASParametersFromMap) { - queryParamsMap.remove("sp"); + queryParamsMap.remove(Constants.UrlConstants.SAS_SIGNED_PERMISSIONS); } } else { this.permissions = null; } - queryValue = queryParamsMap.get("sig"); + queryValue = queryParamsMap.get(Constants.UrlConstants.SAS_SIGNATURE); if (queryValue != null) { this.signature = queryValue[0]; if (removeSASParametersFromMap) { - queryParamsMap.remove("sig"); + queryParamsMap.remove(Constants.UrlConstants.SAS_SIGNATURE); } } else { this.signature = null; } - queryValue = queryParamsMap.get("rscc"); + queryValue = queryParamsMap.get(Constants.UrlConstants.SAS_CACHE_CONTROL); if (queryValue != null) { this.cacheControl = queryValue[0]; if (removeSASParametersFromMap) { - queryParamsMap.remove("rscc"); + queryParamsMap.remove(Constants.UrlConstants.SAS_CACHE_CONTROL); } } else { this.cacheControl = null; } - queryValue = queryParamsMap.get("rscd"); + queryValue = queryParamsMap.get(Constants.UrlConstants.SAS_CONTENT_DISPOSITION); if (queryValue != null) { this.contentDisposition = queryValue[0]; if (removeSASParametersFromMap) { - queryParamsMap.remove("rscd"); + queryParamsMap.remove(Constants.UrlConstants.SAS_CONTENT_DISPOSITION); } } else { this.contentDisposition = null; } - queryValue = queryParamsMap.get("rsce"); + queryValue = queryParamsMap.get(Constants.UrlConstants.SAS_CONTENT_ENCODING); if (queryValue != null) { this.contentEncoding = queryValue[0]; if (removeSASParametersFromMap) { - queryParamsMap.remove("rsce"); + queryParamsMap.remove(Constants.UrlConstants.SAS_CONTENT_ENCODING); } } else { this.contentEncoding = null; } - queryValue = queryParamsMap.get("rscl"); + queryValue = queryParamsMap.get(Constants.UrlConstants.SAS_CONTENT_LANGUAGE); if (queryValue != null) { this.contentLanguage = queryValue[0]; if (removeSASParametersFromMap) { - queryParamsMap.remove("rscl"); + queryParamsMap.remove(Constants.UrlConstants.SAS_CONTENT_LANGUAGE); } } else { this.contentLanguage = null; } - queryValue = queryParamsMap.get("rsct"); + queryValue = queryParamsMap.get(Constants.UrlConstants.SAS_CONTENT_TYPE); if (queryValue != null) { this.contentType = queryValue[0]; if (removeSASParametersFromMap) { - queryParamsMap.remove("rsct"); + queryParamsMap.remove(Constants.UrlConstants.SAS_CONTENT_TYPE); } } else { this.contentType = null; @@ -422,59 +422,75 @@ public String encode() { We should be url-encoding each key and each value, but because we know all the keys and values will encode to themselves, we cheat except for the signature value. */ - String[] params = {"sv", "ss", "srt", "spr", "st", "se", "sip", "si", "sr", "sp", "sig", "rscc", "rscd", "rsce", - "rscl", "rsct"}; + String[] params = { + Constants.UrlConstants.SAS_SERVICE_VERSION, + Constants.UrlConstants.SAS_SERVICES, + Constants.UrlConstants.SAS_RESOURCES_TYPES, + Constants.UrlConstants.SAS_PROTOCOL, + Constants.UrlConstants.SAS_START_TIME, + Constants.UrlConstants.SAS_EXPIRY_TIME, + Constants.UrlConstants.SAS_IP_RANGE, + Constants.UrlConstants.SAS_SIGNED_IDENTIFIER, + Constants.UrlConstants.SAS_SIGNED_RESOURCE, + Constants.UrlConstants.SAS_SIGNED_PERMISSIONS, + Constants.UrlConstants.SAS_SIGNATURE, + Constants.UrlConstants.SAS_CACHE_CONTROL, + Constants.UrlConstants.SAS_CONTENT_DISPOSITION, + Constants.UrlConstants.SAS_CONTENT_ENCODING, + Constants.UrlConstants.SAS_CONTENT_LANGUAGE, + Constants.UrlConstants.SAS_CONTENT_TYPE + }; StringBuilder sb = new StringBuilder(); for (String param : params) { switch (param) { - case "sv": + case Constants.UrlConstants.SAS_SERVICE_VERSION: tryAppendQueryParameter(sb, param, this.version); break; - case "ss": + case Constants.UrlConstants.SAS_SERVICES: tryAppendQueryParameter(sb, param, this.services); break; - case "srt": + case Constants.UrlConstants.SAS_RESOURCES_TYPES: tryAppendQueryParameter(sb, param, this.resourceTypes); break; - case "spr": + case Constants.UrlConstants.SAS_PROTOCOL: tryAppendQueryParameter(sb, param, this.protocol); break; - case "st": + case Constants.UrlConstants.SAS_START_TIME: tryAppendQueryParameter(sb, param, this.startTime == null ? null : Utility.ISO8601UTCDateFormatter.format(this.startTime)); break; - case "se": + case Constants.UrlConstants.SAS_EXPIRY_TIME: tryAppendQueryParameter(sb, param, this.expiryTime == null ? null : Utility.ISO8601UTCDateFormatter.format(this.expiryTime)); break; - case "sip": + case Constants.UrlConstants.SAS_IP_RANGE: tryAppendQueryParameter(sb, param, this.ipRange); break; - case "si": + case Constants.UrlConstants.SAS_SIGNED_IDENTIFIER: tryAppendQueryParameter(sb, param, this.identifier); break; - case "sr": + case Constants.UrlConstants.SAS_SIGNED_RESOURCE: tryAppendQueryParameter(sb, param, this.resource); break; - case "sp": + case Constants.UrlConstants.SAS_SIGNED_PERMISSIONS: tryAppendQueryParameter(sb, param, this.permissions); break; - case "sig": + case Constants.UrlConstants.SAS_SIGNATURE: tryAppendQueryParameter(sb, param, this.signature); break; - case "rscc": + case Constants.UrlConstants.SAS_CACHE_CONTROL: tryAppendQueryParameter(sb, param, this.cacheControl); break; - case "rscd": + case Constants.UrlConstants.SAS_CONTENT_DISPOSITION: tryAppendQueryParameter(sb, param, this.contentDisposition); break; - case "rsce": + case Constants.UrlConstants.SAS_CONTENT_ENCODING: tryAppendQueryParameter(sb, param, this.contentEncoding); break; - case "rscl": + case Constants.UrlConstants.SAS_CONTENT_LANGUAGE: tryAppendQueryParameter(sb, param, this.contentLanguage); break; - case "rsct": + case Constants.UrlConstants.SAS_CONTENT_TYPE: tryAppendQueryParameter(sb, param, this.contentType); break; } diff --git a/src/test/java/com/microsoft/azure/storage/APISpec.groovy b/src/test/java/com/microsoft/azure/storage/APISpec.groovy index c8e41f60a9e47..9a5872747ec46 100644 --- a/src/test/java/com/microsoft/azure/storage/APISpec.groovy +++ b/src/test/java/com/microsoft/azure/storage/APISpec.groovy @@ -367,6 +367,14 @@ class APISpec extends Specification { } } + def getMockRequest() { + HttpHeaders headers = new HttpHeaders() + headers.set(Constants.HeaderConstants.CONTENT_ENCODING, "en-US") + URL url = new URL("http://devtest.blob.core.windows.net/test-container/test-blob") + HttpRequest request = new HttpRequest(null, HttpMethod.POST, url, headers, null, null) + return request + } + def waitForCopy(BlobURL bu, CopyStatusType status) { OffsetDateTime start = OffsetDateTime.now() while (status != CopyStatusType.SUCCESS) { @@ -420,6 +428,8 @@ class APISpec extends Specification { sleep(30000) // Wait for the policy to take effect. } + + /* This method returns a stub of an HttpResponse. This is for when we want to test policies in isolation but don't care about the status code, so we stub a response that always returns a given value for the status code. We never care diff --git a/src/test/java/com/microsoft/azure/storage/blob/LoggingTest.groovy b/src/test/java/com/microsoft/azure/storage/blob/LoggingTest.groovy index be0ca8a41f9b6..37276188a17af 100644 --- a/src/test/java/com/microsoft/azure/storage/blob/LoggingTest.groovy +++ b/src/test/java/com/microsoft/azure/storage/blob/LoggingTest.groovy @@ -16,6 +16,8 @@ package com.microsoft.azure.storage.blob import com.microsoft.azure.storage.APISpec +import com.microsoft.rest.v2.http.HttpHeaders +import com.microsoft.rest.v2.http.HttpMethod import com.microsoft.rest.v2.http.HttpPipelineLogLevel import com.microsoft.rest.v2.http.HttpPipelineLogger import com.microsoft.rest.v2.http.HttpRequest @@ -55,7 +57,7 @@ class LoggingTest extends APISpec { def policy = factory.create(mockDownstream, requestPolicyOptions) when: - policy.sendAsync(new HttpRequest(null, null, null, null)).blockingGet() + policy.sendAsync(getMockRequest()).blockingGet() then: /* @@ -106,7 +108,7 @@ class LoggingTest extends APISpec { def policy = factory.create(mockDownstream, requestPolicyOptions) when: - policy.sendAsync(new HttpRequest(null, null, null, null)).blockingGet() + policy.sendAsync(getMockRequest()).blockingGet() then: logCount * logger.log(HttpPipelineLogLevel.WARNING, _, _) >> @@ -138,7 +140,7 @@ class LoggingTest extends APISpec { def policy = factory.create(mockDownstream, requestPolicyOptions) when: - policy.sendAsync(new HttpRequest(null, null, null, null)).blockingGet() + policy.sendAsync(getMockRequest()).blockingGet() then: 1 * logger.log(HttpPipelineLogLevel.ERROR, _, _) >> @@ -172,7 +174,7 @@ class LoggingTest extends APISpec { def policy = factory.create(mockDownstream, requestPolicyOptions) when: - policy.sendAsync(new HttpRequest(null, null, null, null)).blockingGet() + policy.sendAsync(getMockRequest()).blockingGet() then: /* @@ -211,7 +213,7 @@ class LoggingTest extends APISpec { def policy = factory.create(mockDownstream, requestPolicyOptions) when: - policy.sendAsync(new HttpRequest(null, null, null, null)).blockingGet() + policy.sendAsync(getMockRequest()).blockingGet() then: thrown(RuntimeException) // Because we return this from the downstream, it will be thrown when we blockingGet. @@ -244,4 +246,137 @@ class LoggingTest extends APISpec { then: 2 * logger.log(*_) } + + def "Shared key logs"() { + setup: + def factory = new LoggingFactory(new LoggingOptions(2000)) + + def logger = getMockLogger(HttpPipelineLogLevel.INFO) + def requestPolicyOptions = new RequestPolicyOptions(logger) + + def mockDownstream = Mock(RequestPolicy) { + sendAsync(_) >> Single.just(getStubResponse(200)) + } + + def policy = factory.create(mockDownstream, requestPolicyOptions) + + def userAgentValue = "Azure-Storage/0.1 " + def authorizationValue = "authorizationValue" + def dateValue = "Mon, 29 Oct 2018 21:12:12 GMT" + def requestId = UUID.randomUUID().toString() + def httpHeaders = new HttpHeaders() + httpHeaders.set(Constants.HeaderConstants.VERSION, Constants.HeaderConstants.TARGET_STORAGE_VERSION) + httpHeaders.set(Constants.HeaderConstants.USER_AGENT, userAgentValue) + httpHeaders.set(Constants.HeaderConstants.AUTHORIZATION, authorizationValue) + httpHeaders.set(Constants.HeaderConstants.DATE, dateValue) + httpHeaders.set(Constants.HeaderConstants.CLIENT_REQUEST_ID_HEADER, requestId) + def urlString = "http://devtest.blob.core.windows.net/test-container/test-blob" + def url = new URL(urlString) + + when: + policy.sendAsync(new HttpRequest(null, HttpMethod.HEAD, url, httpHeaders, null, null)).blockingGet() + + then: + 1 * logger.log(HttpPipelineLogLevel.INFO, _, _) >> + { HttpPipelineLogLevel level, String message, Object[] params -> + if (!message.contains("OUTGOING REQUEST")) { + throw new IllegalArgumentException(message) + } + } + 1 * logger.log(HttpPipelineLogLevel.INFO, _, _) >> + { HttpPipelineLogLevel level, String message, Object[] params -> + if (!(message.contains("Success") + && message.contains("Request try") + && message.contains(HttpMethod.HEAD.toString()) + && message.contains(urlString) + && message.contains(url.toString()) + && message.contains(Constants.HeaderConstants.VERSION) + && message.contains(Constants.HeaderConstants.TARGET_STORAGE_VERSION) + && message.contains(Constants.HeaderConstants.DATE) + && message.contains(dateValue) + && message.contains(Constants.HeaderConstants.CLIENT_REQUEST_ID_HEADER) + && message.contains(requestId) + && message.contains(Constants.HeaderConstants.USER_AGENT) + && message.contains(userAgentValue) + && message.contains(Constants.HeaderConstants.AUTHORIZATION) + && message.contains(Constants.REDACTED) + && !message.contains(authorizationValue))) { + throw new IllegalArgumentException(message) + } + } + } + + def "SAS logs"() { + setup: + def factory = new LoggingFactory(new LoggingOptions(2000)) + + def logger = getMockLogger(HttpPipelineLogLevel.INFO) + def requestPolicyOptions = new RequestPolicyOptions(logger) + + def mockDownstream = Mock(RequestPolicy) { + sendAsync(_) >> Single.just(getStubResponse(200)) + } + + def policy = factory.create(mockDownstream, requestPolicyOptions) + + def userAgentValue = "Azure-Storage/0.1 " + def dateValue = "Mon, 29 Oct 2018 21:12:12 GMT" + def requestId = UUID.randomUUID().toString() + def copySource = "http://dev.blob.core.windows.net/test-container/test-blob?snapshot=2018-10-30T19:19:22.1016437Z&sv=2018-03-28&ss=b&srt=co&st=2018-10-29T20:45:11Z&se=2018-10-29T22:45:11Z&sp=rwdlac&sig=copySourceSignature" + def httpHeaders = new HttpHeaders() + httpHeaders.set(Constants.HeaderConstants.VERSION, Constants.HeaderConstants.TARGET_STORAGE_VERSION) + httpHeaders.set(Constants.HeaderConstants.USER_AGENT, userAgentValue) + httpHeaders.set(Constants.HeaderConstants.DATE, dateValue) + httpHeaders.set(Constants.HeaderConstants.CLIENT_REQUEST_ID_HEADER, requestId) + httpHeaders.set(Constants.HeaderConstants.COPY_SOURCE, copySource) + def urlString = "http://dev.blob.core.windows.net/test-container/test-blob?sv=2018-03-29&ss=f&srt=s&st=2018-10-30T20%3A45%3A11Z&se=2019-10-29T22%3A45%3A11Z&sp=rw&sig=urlSignature&comp=incrementalcopy" + def url = new URL(urlString) + + when: + policy.sendAsync(new HttpRequest(null, HttpMethod.PUT, url, httpHeaders, null, null)).blockingGet() + + then: + 1 * logger.log(HttpPipelineLogLevel.INFO, _, _) >> + { HttpPipelineLogLevel level, String message, Object[] params -> + if (!message.contains("OUTGOING REQUEST")) { + throw new IllegalArgumentException(message) + } + } + 1 * logger.log(HttpPipelineLogLevel.INFO, _, _) >> + { HttpPipelineLogLevel level, String message, Object[] params -> + if (!(message.contains("Success") + && message.contains("Request try") + && message.contains(HttpMethod.PUT.toString()) + && message.contains(Constants.HeaderConstants.VERSION) + && message.contains(Constants.HeaderConstants.TARGET_STORAGE_VERSION) + && message.contains(Constants.HeaderConstants.DATE) + && message.contains(dateValue) + && message.contains(Constants.HeaderConstants.CLIENT_REQUEST_ID_HEADER) + && message.contains(requestId) + && message.contains(Constants.HeaderConstants.USER_AGENT) + && message.contains(userAgentValue) + + // SAS URL parameters + && message.contains("sv=2018-03-29") + && message.contains("ss=f") + && message.contains("srt=s") + && message.contains("st=2018-10-30T20%3A45%3A11Z") + && message.contains("se=2019-10-29T22%3A45%3A11Z") + && message.contains("sp=rw") + && !message.contains("sig=urlSignature") + + // Copy Source URL parameters + && message.contains("sv=2018-03-28") + && message.contains("ss=b") + && message.contains("srt=co") + && message.contains("st=2018-10-29T20%3A45%3A11Z") + && message.contains("se=2018-10-29T22%3A45%3A11Z") + && message.contains("sp=rwdlac") + && message.contains("sig=REDACTED") + && !message.contains("copySourceSignature") + )) { + throw new IllegalArgumentException(message) + } + } + } }