You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2019/02/27 11:35:11 UTC
[jclouds] branch 2.1.x updated: [JCLOUDS-1428] Support for SAS
token based Authentication for Azure Blob Storage (#1270)
This is an automated email from the ASF dual-hosted git repository.
nacx pushed a commit to branch 2.1.x
in repository https://gitbox.apache.org/repos/asf/jclouds.git
The following commit(s) were added to refs/heads/2.1.x by this push:
new 0ce9261 [JCLOUDS-1428] Support for SAS token based Authentication for Azure Blob Storage (#1270)
0ce9261 is described below
commit 0ce926108e5b5469ca9235778321b475d63c837a
Author: Aliaksandra Kharushka <al...@sap.com>
AuthorDate: Wed Feb 27 12:20:22 2019 +0100
[JCLOUDS-1428] Support for SAS token based Authentication for Azure Blob Storage (#1270)
---
.../filters/SharedKeyLiteAuthentication.java | 78 +++++++++++++++++++--
.../blobstore/AzureBlobRequestSigner.java | 79 +++++++++++++++-------
.../azureblob/config/AzureBlobHttpApiModule.java | 26 +++++++
.../filters/SharedKeyLiteAuthenticationTest.java | 38 +++++++++++
.../config/AzureBlobHttpApiModuleTest.java | 50 ++++++++++++++
5 files changed, 240 insertions(+), 31 deletions(-)
diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthentication.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthentication.java
index 700c610..ad9f60d 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthentication.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthentication.java
@@ -21,10 +21,13 @@ import static com.google.common.io.ByteStreams.readBytes;
import static org.jclouds.crypto.Macs.asByteProcessor;
import static org.jclouds.util.Patterns.NEWLINE_PATTERN;
import static org.jclouds.util.Strings2.toInputStream;
+import org.jclouds.http.Uris.UriBuilder;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
+import org.jclouds.http.Uris;
+import java.net.URI;
import javax.annotation.Resource;
import javax.inject.Inject;
@@ -67,12 +70,15 @@ import com.google.common.net.HttpHeaders;
@Singleton
public class SharedKeyLiteAuthentication implements HttpRequestFilter {
private static final Collection<String> FIRST_HEADERS_TO_SIGN = ImmutableList.of(HttpHeaders.DATE);
-
private final SignatureWire signatureWire;
private final Supplier<Credentials> creds;
private final Provider<String> timeStampProvider;
private final Crypto crypto;
+ private final String credential;
+ private final String identity;
private final HttpUtils utils;
+ private final URI storageUrl;
+ private final boolean isSAS;
@Resource
@Named(Constants.LOGGER_SIGNATURE)
@@ -81,27 +87,73 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter {
@Inject
public SharedKeyLiteAuthentication(SignatureWire signatureWire,
@org.jclouds.location.Provider Supplier<Credentials> creds, @TimeStamp Provider<String> timeStampProvider,
- Crypto crypto, HttpUtils utils) {
+ Crypto crypto, HttpUtils utils, @Named("sasAuth") boolean sasAuthentication) {
this.crypto = crypto;
this.utils = utils;
this.signatureWire = signatureWire;
+ this.storageUrl = URI.create("https://" + creds.get().identity + ".blob.core.windows.net/");
this.creds = creds;
+ this.identity = creds.get().identity;
+ this.credential = creds.get().credential;
this.timeStampProvider = timeStampProvider;
+ this.isSAS = sasAuthentication;
}
-
+
+ /**
+ * this is an updated filter method, which decides whether the SAS or SharedKeyLite
+ * is used and applies the right filtering.
+ */
public HttpRequest filter(HttpRequest request) throws HttpException {
- request = replaceDateHeader(request);
- String signature = calculateSignature(createStringToSign(request));
- request = replaceAuthorizationHeader(request, signature);
+ request = this.isSAS ? filterSAS(request, this.credential) : filterKey(request);
utils.logRequest(signatureLog, request, "<<");
return request;
}
-
+
+ /**
+ * this filter method is applied only for the cases with SAS Authentication.
+ */
+ public HttpRequest filterSAS(HttpRequest request, String credential) throws HttpException, IllegalArgumentException {
+ URI requestUri = request.getEndpoint();
+ String formattedCredential = credential.startsWith("?") ? credential.substring(1) : credential;
+ String initialQuery = requestUri.getQuery();
+ String finalQuery = initialQuery == null ? formattedCredential : initialQuery + "&" + formattedCredential;
+ String[] parametersArray = cutUri(requestUri);
+ String containerName = parametersArray[1];
+ UriBuilder endpoint = Uris.uriBuilder(storageUrl).appendPath(containerName);
+ if (parametersArray.length == 3) {
+ endpoint.appendPath(parametersArray[2]).query(finalQuery);
+ } else {
+ endpoint.query("restype=container&" + finalQuery);
+ }
+ return removeAuthorizationHeader(
+ replaceDateHeader(request.toBuilder()
+ .endpoint(endpoint.build())
+ .build()));
+ }
+
+ /**
+ * this is a 'standard' filter method, applied when SharedKeyLite authentication is used.
+ */
+ public HttpRequest filterKey(HttpRequest request) throws HttpException {
+ request = replaceDateHeader(request);
+ String signature = calculateSignature(createStringToSign(request));
+ return replaceAuthorizationHeader(request, signature);
+ }
+
HttpRequest replaceAuthorizationHeader(HttpRequest request, String signature) {
return request.toBuilder()
.replaceHeader(HttpHeaders.AUTHORIZATION, "SharedKeyLite " + creds.get().identity + ":" + signature)
.build();
}
+
+ /**
+ * this method removes Authorisation header, since it is not needed for SAS Authentication
+ */
+ HttpRequest removeAuthorizationHeader(HttpRequest request) {
+ return request.toBuilder()
+ .removeHeader(HttpHeaders.AUTHORIZATION)
+ .build();
+ }
HttpRequest replaceDateHeader(HttpRequest request) {
Builder<String, String> builder = ImmutableMap.builder();
@@ -110,6 +162,18 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter {
request = request.toBuilder().replaceHeaders(Multimaps.forMap(builder.build())).build();
return request;
}
+
+ /**
+ * this is the method to parse container name and blob name from the HttpRequest.
+ */
+ public String[] cutUri(URI uri) throws IllegalArgumentException {
+ String path = uri.getPath();
+ String[] result = path.split("/");
+ if (result.length < 2) {
+ throw new IllegalArgumentException("there is neither ContainerName nor BlobName in the URI path");
+ }
+ return result;
+ }
public String createStringToSign(HttpRequest request) {
utils.logRequest(signatureLog, request, ">>");
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobRequestSigner.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobRequestSigner.java
index a31cb1c..994b609 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobRequestSigner.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobRequestSigner.java
@@ -23,6 +23,7 @@ import java.util.Date;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
+import javax.inject.Named;
import org.jclouds.azure.storage.filters.SharedKeyLiteAuthentication;
import org.jclouds.blobstore.BlobRequestSigner;
@@ -35,11 +36,11 @@ import org.jclouds.http.HttpRequest;
import org.jclouds.http.Uris;
import org.jclouds.http.options.GetOptions;
import org.jclouds.javax.annotation.Nullable;
-
import com.google.common.base.Supplier;
import com.google.common.net.HttpHeaders;
import com.google.inject.Provider;
+
@Singleton
public class AzureBlobRequestSigner implements BlobRequestSigner {
private static final int DEFAULT_EXPIRY_SECONDS = 15 * 60;
@@ -51,19 +52,23 @@ public class AzureBlobRequestSigner implements BlobRequestSigner {
private final Provider<String> timeStampProvider;
private final DateService dateService;
private final SharedKeyLiteAuthentication auth;
+ private final String credential;
+ private final boolean isSAS;
@Inject
public AzureBlobRequestSigner(
BlobToHttpGetOptions blob2HttpGetOptions, @TimeStamp Provider<String> timeStampProvider,
DateService dateService, SharedKeyLiteAuthentication auth,
- @org.jclouds.location.Provider Supplier<Credentials> creds)
+ @org.jclouds.location.Provider Supplier<Credentials> creds, @Named("sasAuth") boolean sasAuthentication)
throws SecurityException, NoSuchMethodException {
this.identity = creds.get().identity;
+ this.credential = creds.get().credential;
this.storageUrl = URI.create("https://" + creds.get().identity + ".blob.core.windows.net/");
this.blob2HttpGetOptions = checkNotNull(blob2HttpGetOptions, "blob2HttpGetOptions");
this.timeStampProvider = checkNotNull(timeStampProvider, "timeStampProvider");
this.dateService = checkNotNull(dateService, "dateService");
this.auth = auth;
+ this.isSAS = sasAuthentication;
}
@Override
@@ -107,12 +112,14 @@ public class AzureBlobRequestSigner implements BlobRequestSigner {
return sign("GET", container, name, blob2HttpGetOptions.apply(checkNotNull(options, "options")),
DEFAULT_EXPIRY_SECONDS, null, null);
}
-
- private HttpRequest sign(String method, String container, String name, @Nullable GetOptions options, long expires, @Nullable Long contentLength, @Nullable String contentType) {
+
+ /**
+ * method to sign HttpRequest when SharedKey Authentication is used
+ */
+ private HttpRequest signKey(String method, String container, String name, @Nullable GetOptions options, long expires, @Nullable Long contentLength, @Nullable String contentType) {
checkNotNull(method, "method");
checkNotNull(container, "container");
checkNotNull(name, "name");
-
String nowString = timeStampProvider.get();
Date now = dateService.rfc1123DateParse(nowString);
Date expiration = new Date(now.getTime() + TimeUnit.SECONDS.toMillis(expires));
@@ -125,7 +132,6 @@ public class AzureBlobRequestSigner implements BlobRequestSigner {
} else {
signedPermission = "r";
}
-
HttpRequest.Builder request = HttpRequest.builder()
.method(method)
.endpoint(Uris.uriBuilder(storageUrl).appendPath(container).appendPath(name).build())
@@ -134,23 +140,7 @@ public class AzureBlobRequestSigner implements BlobRequestSigner {
.addQueryParam("se", iso8601)
.addQueryParam("sr", "b") // blob resource
.addQueryParam("sp", signedPermission); // permission
-
- if (contentLength != null) {
- request.replaceHeader(HttpHeaders.CONTENT_LENGTH, contentLength.toString());
- }
-
- if (contentType != null) {
- request.replaceHeader("x-ms-blob-content-type", contentType);
- }
-
- if (options != null) {
- request.headers(options.buildRequestHeaders());
- }
-
- if (method.equals("PUT")) {
- request.replaceHeader("x-ms-blob-type", "BlockBlob");
- }
-
+ request = setHeaders(request, method, options, contentLength, contentType);
String stringToSign =
signedPermission + "\n" + // signedpermission
"\n" + // signedstart
@@ -165,9 +155,50 @@ public class AzureBlobRequestSigner implements BlobRequestSigner {
"\n" + // rsce
"\n" + // rscl
""; // rsct
-
String signature = auth.calculateSignature(stringToSign);
request.addQueryParam("sig", signature);
return request.build();
}
+
+ private HttpRequest.Builder setHeaders(HttpRequest.Builder request, String method, @Nullable GetOptions options, @Nullable Long contentLength, @Nullable String contentType){
+ if (contentLength != null) {
+ request.replaceHeader(HttpHeaders.CONTENT_LENGTH, contentLength.toString());
+ }
+ if (contentType != null) {
+ request.replaceHeader("x-ms-blob-content-type", contentType);
+ }
+ if (options != null) {
+ request.headers(options.buildRequestHeaders());
+ }
+ if (method.equals("PUT")) {
+ request.replaceHeader("x-ms-blob-type", "BlockBlob");
+ }
+ return request;
+ }
+
+ /**
+ * method, compatible with SAS Authentication
+ */
+ private HttpRequest signSAS(String method, String container, String name, @Nullable GetOptions options, long expires, @Nullable Long contentLength, @Nullable String contentType) {
+ checkNotNull(method, "method");
+ checkNotNull(container, "container");
+ checkNotNull(name, "name");
+ String nowString = timeStampProvider.get();
+ HttpRequest.Builder request = HttpRequest.builder()
+ .method(method)
+ .endpoint(Uris.uriBuilder(storageUrl).appendPath(container).appendPath(name).query(this.credential).build())
+ .replaceHeader(HttpHeaders.DATE, nowString);
+ request = setHeaders(request, method, options, contentLength, contentType);
+ return request.build();
+ }
+
+ /**
+ * modified sign() method, which acts depending on the Auth input.
+ */
+ public HttpRequest sign(String method, String container, String name, @Nullable GetOptions options, long expires, @Nullable Long contentLength, @Nullable String contentType) {
+ if (isSAS) {
+ return signSAS(method, container, name, options, expires, contentLength, contentType);
+ }
+ return signKey(method, container, name, options, expires, contentLength, contentType);
+ }
}
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/config/AzureBlobHttpApiModule.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/config/AzureBlobHttpApiModule.java
index 18365e1..fb6e2c0 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azureblob/config/AzureBlobHttpApiModule.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/config/AzureBlobHttpApiModule.java
@@ -18,7 +18,15 @@ package org.jclouds.azureblob.config;
import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
+import static com.google.common.base.Predicates.in;
+
+import static com.google.common.collect.Iterables.all;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+
import java.util.concurrent.TimeUnit;
+import java.util.Map;
+import java.util.List;
import javax.inject.Named;
@@ -36,6 +44,7 @@ import org.jclouds.json.config.GsonModule.DateAdapter;
import org.jclouds.json.config.GsonModule.Iso8601DateAdapter;
import org.jclouds.rest.ConfiguresHttpApi;
import org.jclouds.rest.config.HttpApiModule;
+import org.jclouds.domain.Credentials;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
@@ -63,6 +72,23 @@ public class AzureBlobHttpApiModule extends HttpApiModule<AzureBlobClient> {
protected String provideTimeStamp(@TimeStamp Supplier<String> cache) {
return cache.get();
}
+
+ /**
+ * checks which Authentication type is used
+ */
+ @Named("sasAuth")
+ @Provides
+ protected boolean authSAS(@org.jclouds.location.Provider Supplier<Credentials> creds) {
+ String credential = creds.get().credential;
+ String formattedCredential = credential.startsWith("?") ? credential.substring(1) : credential;
+ List<String> required = ImmutableList.of("sv", "se", "sig", "sp");
+ try {
+ Map<String, String> tokens = Splitter.on('&').withKeyValueSeparator('=').split(formattedCredential);
+ return all(required, in(tokens.keySet()));
+ } catch (Exception ex) {
+ return false;
+ }
+ }
/**
* borrowing concurrency code to ensure that caching takes place properly
diff --git a/providers/azureblob/src/test/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthenticationTest.java b/providers/azureblob/src/test/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthenticationTest.java
index 68ebaeb..5e0c226 100644
--- a/providers/azureblob/src/test/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthenticationTest.java
+++ b/providers/azureblob/src/test/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthenticationTest.java
@@ -42,6 +42,8 @@ public class SharedKeyLiteAuthenticationTest {
private static final String ACCOUNT = "foo";
private Injector injector;
private SharedKeyLiteAuthentication filter;
+ private SharedKeyLiteAuthentication filterSAS;
+ private SharedKeyLiteAuthentication filterSASQuestionMark;
@DataProvider(parallel = true)
public Object[][] dataProvider() {
@@ -52,6 +54,19 @@ public class SharedKeyLiteAuthenticationTest {
+ ".blob.core.windows.net/movies/MOV1.avi?comp=blocklist&timeout=120").build() },
{ HttpRequest.builder().method(HttpMethod.GET).endpoint("http://" + ACCOUNT + ".blob.core.windows.net/movies/MOV1.avi").build() } };
}
+
+ @DataProvider(name = "auth-sas-data", parallel = true)
+ public Object[][] requests(){
+ return new Object[][]{
+ { HttpRequest.builder().method(HttpMethod.PUT).endpoint("https://" + ACCOUNT
+ + ".blob.core.windows.net/movies/MOV1.avi?comp=block&blockid=BlockId1&timeout=60").build(), filterSAS, "https://foo.blob.core.windows.net/movies/MOV1.avi?comp=block&blockid=BlockId1&timeout=60&sv=2018-03-28&ss=b&srt=sco&sp=rwdlac&se=2019-02-13T17%3A18%3A22Z&st=2019-02-13T09%3A18%3A22Z&spr=https&sig=sMnaKSD94CzEPeGnWauTT0wBNIn%2B4ySkZO5PEAW7zs%3D"},
+ { HttpRequest.builder().method(HttpMethod.PUT).endpoint("https://" + ACCOUNT
+ + ".blob.core.windows.net/movies/MOV1.avi?comp=blocklist&timeout=120").build(), filterSAS, "https://foo.blob.core.windows.net/movies/MOV1.avi?comp=blocklist&timeout=120&sv=2018-03-28&ss=b&srt=sco&sp=rwdlac&se=2019-02-13T17%3A18%3A22Z&st=2019-02-13T09%3A18%3A22Z&spr=https&sig=sMnaKSD94CzEPeGnWauTT0wBNIn%2B4ySkZO5PEAW7zs%3D" },
+ { HttpRequest.builder().method(HttpMethod.GET).endpoint("https://" + ACCOUNT
+ + ".blob.core.windows.net/movies/MOV1.avi").build(), filterSAS, "https://foo.blob.core.windows.net/movies/MOV1.avi?sv=2018-03-28&ss=b&srt=sco&sp=rwdlac&se=2019-02-13T17%3A18%3A22Z&st=2019-02-13T09%3A18%3A22Z&spr=https&sig=sMnaKSD94CzEPeGnWauTT0wBNIn%2B4ySkZO5PEAW7zs%3D" },
+ { HttpRequest.builder().method(HttpMethod.GET).endpoint("https://" + ACCOUNT
+ + ".blob.core.windows.net/movies/MOV1.avi").build(), filterSASQuestionMark, "https://foo.blob.core.windows.net/movies/MOV1.avi?sv=2018-03-28&ss=b&srt=sco&sp=rwdlac&se=2019-02-13T17%3A18%3A22Z&st=2019-02-13T09%3A18%3A22Z&spr=https&sig=sMnaKSD94CzEPeGnWauTT0wBNIn%2B4ySkZO5PEAW7zs%3D" } };
+ }
/**
* NOTE this test is dependent on how frequently the timestamp updates. At
@@ -74,6 +89,15 @@ public class SharedKeyLiteAuthenticationTest {
System.out.printf("%s: %d iterations before the timestamp updated %n", Thread.currentThread().getName(),
iterations);
}
+
+ /**
+ * this test is similar to testIdempotent; it checks whether request is properly filtered when it comes to SAS Authentication
+ */
+ @Test(dataProvider = "auth-sas-data")
+ void testFilter(HttpRequest request, SharedKeyLiteAuthentication filter, String expected) {
+ request = filter.filter(request);
+ assertEquals(request.getEndpoint().toString(), expected);
+ }
@Test
void testAclQueryStringRoot() {
@@ -127,5 +151,19 @@ public class SharedKeyLiteAuthenticationTest {
.modules(ImmutableSet.<Module> of(new MockModule(), new NullLoggingModule()))
.buildInjector();
filter = injector.getInstance(SharedKeyLiteAuthentication.class);
+ injector = ContextBuilder
+ .newBuilder("azureblob")
+ .endpoint("https://${jclouds.identity}.blob.core.windows.net")
+ .credentials(ACCOUNT, "sv=2018-03-28&ss=b&srt=sco&sp=rwdlac&se=2019-02-13T17:18:22Z&st=2019-02-13T09:18:22Z&spr=https&sig=sMnaKSD94CzEPeGnWauTT0wBNIn%2B4ySkZO5PEAW7zs%3D")
+ .modules(ImmutableSet.<Module> of(new MockModule(), new NullLoggingModule()))
+ .buildInjector();
+ filterSAS = injector.getInstance(SharedKeyLiteAuthentication.class);
+ injector = ContextBuilder
+ .newBuilder("azureblob")
+ .endpoint("https://${jclouds.identity}.blob.core.windows.net")
+ .credentials(ACCOUNT, "?sv=2018-03-28&ss=b&srt=sco&sp=rwdlac&se=2019-02-13T17:18:22Z&st=2019-02-13T09:18:22Z&spr=https&sig=sMnaKSD94CzEPeGnWauTT0wBNIn%2B4ySkZO5PEAW7zs%3D")
+ .modules(ImmutableSet.<Module> of(new MockModule(), new NullLoggingModule()))
+ .buildInjector();
+ filterSASQuestionMark = injector.getInstance(SharedKeyLiteAuthentication.class);
}
}
diff --git a/providers/azureblob/src/test/java/org/jclouds/azureblob/config/AzureBlobHttpApiModuleTest.java b/providers/azureblob/src/test/java/org/jclouds/azureblob/config/AzureBlobHttpApiModuleTest.java
new file mode 100755
index 0000000..4ae8f87
--- /dev/null
+++ b/providers/azureblob/src/test/java/org/jclouds/azureblob/config/AzureBlobHttpApiModuleTest.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.azureblob.config;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.Test;
+import com.google.common.base.Suppliers;
+import org.jclouds.domain.Credentials;
+import org.testng.annotations.DataProvider;
+
+@Test(groups = "unit", testName = "AzureBlobHttpApiModuleTest")
+public class AzureBlobHttpApiModuleTest {
+
+ @DataProvider(name = "auth-sas-tokens")
+ public static Object[][] tokens() {
+ return new Object[][]{
+ {false, "sv=2018-03-28&se=2019-02-14T11:12:13Z"},
+ {false, "sv=2018-03-28&se=2019-02-14T11:12:13Z&sp=abc&st=2019-01-20T11:12:13Z"},
+ {false, "u2iAP01ARTewyK/MhOM1d1ASPpjqclkldsdkljfas2kfjkh895ssfslkjpXKfhg=="},
+ {false, "sadf;gjkhflgjkhfdlkfdljghskldjghlfdghw4986754ltjkghdlfkjghst;lyho56[09y7poinh"},
+ {false, "a=apple&b=banana&c=cucumber&d=diet"},
+ {false, "sva=swajak&sta=stancyja&spa=spakoj&sea=mora&sig=podpis"},
+ {true, "sv=2018-03-28&ss=b&srt=sco&sp=r&se=2019-02-13T17:03:09Z&st=2019-02-13T09:03:09Z&spr=https&sig=wNkWK%2GURTjHWhtqG6Q2Gu%2Qu%3FPukW6N4%2FIH4Mr%2F%2FO42M%3D"},
+ {true, "sp=rl&st=2019-02-14T08:50:26Z&se=2019-02-15T08:50:26Z&sv=2018-03-28&sig=Ukow8%2GtpQpAiVZBLcWp1%2RSpFq928MAqzp%2BdrdregaB6%3D&sr=b"},
+ {false, ""}
+ };
+ }
+
+ @Test(dataProvider = "auth-sas-tokens")
+ void testAuthSasNonSufficientParametersSvSe(boolean expected, String credential){
+ AzureBlobHttpApiModule module = new AzureBlobHttpApiModule();
+ Credentials creds = new Credentials("identity", credential);
+ assertEquals(module.authSAS(Suppliers.ofInstance(creds)), expected);
+ }
+}