Skip to content

Commit

Permalink
Consume start and end partition and row keys (#25293)
Browse files Browse the repository at this point in the history
### Packages impacted by this PR
@azure/data-tables

### Issues associated with this PR
#25252

### Describe the problem that is addressed by this PR
We accept startPartitionKey, startRowKey, endPartitionKey and endRowKey parameters when generating table sas token. However internally we are ignoring those provided values.

### What are the possible designs available to address the problem? If there are more than one possible design, why was the one in this PR chosen?
To fix this problem we simply need to plumb the values through

### Are there test cases added in this PR? _(If not, why?)_
YES

### Provide a list of related PRs _(if any)_
N/A
  • Loading branch information
joheredi authored Mar 23, 2023
1 parent de692a6 commit a290531
Show file tree
Hide file tree
Showing 21 changed files with 304 additions and 205 deletions.
106 changes: 87 additions & 19 deletions sdk/tables/data-tables/src/sas/sasQueryParameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,30 @@ export class SasQueryParameters {
*/
public readonly correlationId?: string;

/**
* Optional, but startPartitionKey must accompany startRowKey. The minimum partition and row keys that are accessible with this shared access signature.
* Key values are inclusive. If they're omitted, there's no lower bound on the table entities that can be accessed.
*/
public readonly startPartitionKey?: string;

/**
* Optional, but startPartitionKey must accompany startRowKey. The minimum partition and row keys that are accessible with this shared access signature.
* Key values are inclusive. If they're omitted, there's no lower bound on the table entities that can be accessed.
*/
public readonly startRowKey?: string;

/**
* Optional, but endPartitionKey must accompany endRowKey. The maximum partition and row keys that are accessible with this shared access signature.
* Key values are inclusive. If they're omitted, there's no upper bound on the table entities that can be accessed.
*/
public readonly endPartitionKey?: string;

/**
* Optional, but endPartitionKey must accompany endRowKey. The maximum partition and row keys that are accessible with this shared access signature.
* Key values are inclusive. If they're omitted, there's no upper bound on the table entities that can be accessed.
*/
public readonly endRowKey?: string;

/**
* Optional. IP range allowed for this SAS.
*
Expand Down Expand Up @@ -166,6 +190,10 @@ export class SasQueryParameters {
this.ipRangeInner = options.ipRange;
this.identifier = options.identifier;
this.tableName = options.tableName;
this.endPartitionKey = options.endPartitionKey;
this.endRowKey = options.endRowKey;
this.startPartitionKey = options.startPartitionKey;
this.startRowKey = options.startRowKey;

if (options.userDelegationKey) {
this.signedOid = options.userDelegationKey.signedObjectId;
Expand All @@ -186,31 +214,35 @@ export class SasQueryParameters {
*/
public toString(): string {
const params: string[] = [
"sv",
"ss",
"srt",
"spr",
"st",
"se",
"sip",
"si",
"sv", // SignedVersion
"ss", // SignedServices
"srt", // SignedResourceTypes
"spr", // SignedProtocol
"st", // SignedStart
"se", // SignedExpiry
"sip", // SignedIP
"si", // SignedIdentifier
"skoid", // Signed object ID
"sktid", // Signed tenant ID
"skt", // Signed key start time
"ske", // Signed key expiry time
"sks", // Signed key service
"skv", // Signed key version
"sr",
"sp",
"sig",
"rscc",
"rscd",
"rsce",
"rscl",
"rsct",
"saoid",
"scid",
"tn", // TableName
"sr", // signedResource
"sp", // SignedPermission
"sig", // Signature
"rscc", // Cache-Control
"rscd", // Content-Disposition
"rsce", // Content-Encoding
"rscl", // Content-Language
"rsct", // Content-Type
"saoid", // signedAuthorizedObjectId
"scid", // signedCorrelationId
"tn", // TableName,
"srk", // StartRowKey
"spk", // StartPartitionKey
"epk", // EndPartitionKey
"erk", // EndRowKey
];
const queries: string[] = [];

Expand Down Expand Up @@ -293,6 +325,18 @@ export class SasQueryParameters {
case "tn":
this.tryAppendQueryParameter(queries, param, this.tableName);
break;
case "spk":
this.tryAppendQueryParameter(queries, param, this.startPartitionKey);
break;
case "srk":
this.tryAppendQueryParameter(queries, param, this.startRowKey);
break;
case "epk":
this.tryAppendQueryParameter(queries, param, this.endPartitionKey);
break;
case "erk":
this.tryAppendQueryParameter(queries, param, this.endRowKey);
break;
}
}
return queries.join("&");
Expand Down Expand Up @@ -386,4 +430,28 @@ export interface SasQueryParametersOptions {
* This is only used for User Delegation SAS.
*/
correlationId?: string;

/**
* Optional, but startPartitionKey must accompany startRowKey. The minimum partition and row keys that are accessible with this shared access signature.
* Key values are inclusive. If they're omitted, there's no lower bound on the table entities that can be accessed.
*/
startPartitionKey?: string;

/**
* Optional, but startPartitionKey must accompany startRowKey. The minimum partition and row keys that are accessible with this shared access signature.
* Key values are inclusive. If they're omitted, there's no lower bound on the table entities that can be accessed.
*/
startRowKey?: string;

/**
* Optional, but endPartitionKey must accompany endRowKey. The maximum partition and row keys that are accessible with this shared access signature.
* Key values are inclusive. If they're omitted, there's no upper bound on the table entities that can be accessed.
*/
endPartitionKey?: string;

/**
* Optional, but endPartitionKey must accompany endRowKey. The maximum partition and row keys that are accessible with this shared access signature.
* Key values are inclusive. If they're omitted, there's no upper bound on the table entities that can be accessed.
*/
endRowKey?: string;
}
4 changes: 4 additions & 0 deletions sdk/tables/data-tables/src/sas/tableSasSignatureValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ export function generateTableSasQueryParameters(
ipRange: tableSasSignatureValues.ipRange,
identifier: tableSasSignatureValues.identifier,
tableName,
startPartitionKey: tableSasSignatureValues.startPartitionKey,
startRowKey: tableSasSignatureValues.startRowKey,
endPartitionKey: tableSasSignatureValues.endPartitionKey,
endRowKey: tableSasSignatureValues.endRowKey,
});
}

Expand Down
4 changes: 2 additions & 2 deletions sdk/tables/data-tables/test/internal/apiVersionPolicy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
import { apiVersionPolicy } from "../../src/utils/apiVersionPolicy";
import { assert } from "chai";

describe("apiVersionPolicy", () => {
it("should override the default api-version", async () => {
describe("apiVersionPolicy", function () {
it("should override the default api-version", async function () {
const expectedVersion = "2020-12-06";
const fakeClient: HttpClient = {
async sendRequest(req) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { AzureNamedKeyCredential, generateTableSas } from "../../../src";
import { assert } from "chai";

// This file is empty as sas generation is not supported in browsers
describe("generateSas Browser", () => {
describe("generateSas Browser", function () {
it("should throw", function () {
try {
generateTableSas("testTable", new AzureNamedKeyCredential("keyName", "keySecret"));
Expand Down
14 changes: 7 additions & 7 deletions sdk/tables/data-tables/test/internal/continuationToken.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,35 @@ import {

import { assert } from "chai";

describe("continuation token utils", () => {
it("should encode nextPartitionKey and nextRowKey", () => {
describe("continuation token utils", function () {
it("should encode nextPartitionKey and nextRowKey", function () {
const encoded = encodeContinuationToken("foo", "bar");
assert.equal(encoded, "eyJuZXh0UGFydGl0aW9uS2V5IjoiZm9vIiwibmV4dFJvd0tleSI6ImJhciJ9");
});

it("should not encode nextRowKey if it is empty string", () => {
it("should not encode nextRowKey if it is empty string", function () {
const encoded = encodeContinuationToken("foo", "");
assert.deepEqual(decodeContinuationToken(encoded!), { nextPartitionKey: "foo" });
});

it("should encode nextPartitionKey and undefined nextRowKey", () => {
it("should encode nextPartitionKey and undefined nextRowKey", function () {
const encoded = encodeContinuationToken("foo");
assert.equal(encoded, "eyJuZXh0UGFydGl0aW9uS2V5IjoiZm9vIn0=");
});

it("should return undefined if nextPartitionKey and nextRowKey are empty", () => {
it("should return undefined if nextPartitionKey and nextRowKey are empty", function () {
const encoded = encodeContinuationToken();
assert.equal(encoded, undefined);
});

it("should decode nextPartitionKey and nextRowKey", () => {
it("should decode nextPartitionKey and nextRowKey", function () {
const decoded = decodeContinuationToken(
"eyJuZXh0UGFydGl0aW9uS2V5IjoiZm9vIiwibmV4dFJvd0tleSI6ImJhciJ9"
);
assert.deepEqual(decoded, { nextPartitionKey: "foo", nextRowKey: "bar" });
});

it("should decode nextPartitionKey and undefined nextRowKey", () => {
it("should decode nextPartitionKey and undefined nextRowKey", function () {
const decoded = decodeContinuationToken("eyJuZXh0UGFydGl0aW9uS2V5IjoiZm9vIn0=");
assert.deepEqual(decoded, { nextPartitionKey: "foo" } as any);
});
Expand Down
14 changes: 7 additions & 7 deletions sdk/tables/data-tables/test/internal/errorHandling.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { HttpClient, PipelineResponse, createHttpHeaders } from "@azure/core-res
import { TableClient, TableServiceClient } from "../../src";
import { assert } from "chai";

describe("ErrorHandling", () => {
describe("TableClient", () => {
it("should not throw on delete table not found", async () => {
describe("ErrorHandling", function () {
describe("TableClient", function () {
it("should not throw on delete table not found", async function () {
const client = new TableClient("https://example.org", "fakeTable", {
httpClient: buildTestHttpClient({ status: 404 }),
});
Expand All @@ -23,7 +23,7 @@ describe("ErrorHandling", () => {
}
});

it("should throw on delete table with non 404 error", async () => {
it("should throw on delete table with non 404 error", async function () {
const client = new TableClient("https://example.org", "fakeTable", {
httpClient: buildTestHttpClient({ status: 400 }),
});
Expand All @@ -39,8 +39,8 @@ describe("ErrorHandling", () => {
});
});

describe("TableServiceClient", () => {
it("should not throw on delete table not found", async () => {
describe("TableServiceClient", function () {
it("should not throw on delete table not found", async function () {
const client = new TableServiceClient("https://example.org", {
httpClient: buildTestHttpClient({ status: 404 }),
});
Expand All @@ -55,7 +55,7 @@ describe("ErrorHandling", () => {
}
});

it("should throw on delete table with non 404 error", async () => {
it("should throw on delete table with non 404 error", async function () {
const client = new TableServiceClient("https://example.org", {
httpClient: buildTestHttpClient({ status: 400 }),
});
Expand Down
4 changes: 4 additions & 0 deletions sdk/tables/data-tables/test/internal/fakeTestSecrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ export const expectedSas6 =
"sv=2019-02-02&ss=t&srt=sco&se=2021-12-12T01%3A00%3A00Z&sp=da&sig=TmE8AOQFacynVIVR5ljBYqY3Y3K6olfdDMLl09iRvvs%3D";
export const expectedSas7 =
"sv=2019-02-02&ss=t&srt=sco&se=2022-12-12T00%3A00%3A00Z&sp=rl&sig=y42pmN9E%2FgA2O3nGn25lx%2B%2BqQmhvh0WqFi4%2BkOPitwA%3D";
export const expectedSas8 =
"sv=2019-02-02&se=2021-12-12T01%3A00%3A00Z&sp=r&sig=FmE9Q8KiLIJUxmY2k8NHCSwApy2Y1VY17Mls1dIIJcI%3D&tn=testTable&srk=1&spk=P1";
export const expectedSas9 =
"sv=2019-02-02&se=2021-12-12T01%3A00%3A00Z&sp=r&sig=h0xXlQhXumE5Litei9gWdY3ECCORubPLUFqcbWa6Tus%3D&tn=testTable&epk=P1&erk=1";
50 changes: 37 additions & 13 deletions sdk/tables/data-tables/test/internal/node/generateSas.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,25 @@ import {
expectedSas5,
expectedSas6,
expectedSas7,
expectedSas8,
expectedSas9,
} from "../fakeTestSecrets";
import { assert } from "chai";

describe("SAS generation", function () {
describe("generateTableSAS", () => {
describe("generateTableSAS", function () {
let clock: sinon.SinonFakeTimers;
beforeEach(() => {
beforeEach(function () {
clock = sinon.useFakeTimers(new Date("2021-12-12"));
});

afterEach(() => {
afterEach(function () {
if (clock) {
clock.restore();
}
});

it("should generate a SAS token with default values", async () => {
it("should generate a SAS token with default values", async function () {
// Create the table SAS token
const tableSas = generateTableSas(
"testTable",
Expand All @@ -37,7 +39,29 @@ describe("SAS generation", function () {
assert.equal(tableSas, expectedSas1);
});

it("should generate a SAS token with explicit permissions", async () => {
it("should generate a SAS token with start partition and row keys", async function () {
// Create the table SAS token
const tableSas = generateTableSas(
"testTable",
new AzureNamedKeyCredential("keyName", "keySecret"),
{ startPartitionKey: "P1", startRowKey: "1" }
);

assert.equal(tableSas, expectedSas8);
});

it("should generate a SAS token with end partition and row keys", async function () {
// Create the table SAS token
const tableSas = generateTableSas(
"testTable",
new AzureNamedKeyCredential("keyName", "keySecret"),
{ endPartitionKey: "P1", endRowKey: "1" }
);

assert.equal(tableSas, expectedSas9);
});

it("should generate a SAS token with explicit permissions", async function () {
// Create the table SAS token
const tableSas = generateTableSas(
"testTable",
Expand All @@ -53,7 +77,7 @@ describe("SAS generation", function () {
assert.equal(tableSas, expectedSas2);
});

it("should generate a SAS token with explicit expiry", async () => {
it("should generate a SAS token with explicit expiry", async function () {
// Create the table SAS token
const tableSas = generateTableSas(
"testTable",
Expand All @@ -66,7 +90,7 @@ describe("SAS generation", function () {
assert.equal(tableSas, expectedSas3);
});

it("should generate a SAS token with identifier", async () => {
it("should generate a SAS token with identifier", async function () {
// Create the table SAS token
const tableSas = generateTableSas(
"testTable",
Expand All @@ -81,26 +105,26 @@ describe("SAS generation", function () {
});
});

describe("generateAccountSAS", () => {
describe("generateAccountSAS", function () {
let clock: sinon.SinonFakeTimers;
beforeEach(() => {
beforeEach(function () {
clock = sinon.useFakeTimers(new Date("2021-12-12"));
});

afterEach(() => {
afterEach(function () {
if (clock) {
clock.restore();
}
});

it("should generate account SAS token with default values", async () => {
it("should generate account SAS token with default values", async function () {
// Create the table SAS token
const tableSas = generateAccountSas(new AzureNamedKeyCredential("keyName", "keySecret"));

assert.equal(tableSas, expectedSas5);
});

it("should generate a SAS token with explicit permissions", async () => {
it("should generate a SAS token with explicit permissions", async function () {
// Create the table SAS token
const tableSas = generateAccountSas(new AzureNamedKeyCredential("keyName", "keySecret"), {
permissions: {
Expand All @@ -112,7 +136,7 @@ describe("SAS generation", function () {
assert.equal(tableSas, expectedSas6);
});

it("should generate a SAS token with explicit expiry", async () => {
it("should generate a SAS token with explicit expiry", async function () {
// Create the table SAS token
const tableSas = generateAccountSas(new AzureNamedKeyCredential("keyName", "keySecret"), {
expiresOn: new Date("2022-12-12"),
Expand Down
Loading

0 comments on commit a290531

Please sign in to comment.