diff --git a/sdk/cosmosdb/cosmos/review/cosmos.api.md b/sdk/cosmosdb/cosmos/review/cosmos.api.md index 861865bffe50..73604214f9e9 100644 --- a/sdk/cosmosdb/cosmos/review/cosmos.api.md +++ b/sdk/cosmosdb/cosmos/review/cosmos.api.md @@ -376,6 +376,7 @@ export const Constants: { CollectionSize: string; }; Path: { + Root: string; DatabasesPathSegment: string; CollectionsPathSegment: string; UsersPathSegment: string; diff --git a/sdk/cosmosdb/cosmos/samples/SasToken/SasTokenAuth.ts b/sdk/cosmosdb/cosmos/samples/SasToken/SasTokenAuth.ts new file mode 100644 index 000000000000..2b133f9f5092 --- /dev/null +++ b/sdk/cosmosdb/cosmos/samples/SasToken/SasTokenAuth.ts @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { SasTokenProperties } from "../../dist-esm/client/SasToken/SasTokenProperties"; +import { SasTokenPermissionKind } from "../../dist-esm/common/constants"; +import { createAuthorizationSasToken } from "../../dist-esm/utils/SasToken" +import { handleError, finish, logStep } from "../Shared/handleError"; +import { CosmosClient } from "../../dist-esm/CosmosClient"; + +const endpoint = "your-endpoint"; +const masterKey = "your-master-key"; +const sasToken = "your-sas-token"; + +async function run() { + logStep("Create a SasToken object"); + + const sasTokenProperties = { + user: "your-user", + userTag: "your-userTag", + databaseName: "your-databaseName", + containerName: "your-containerName", + resourcePath: "your-resource-path", + partitionKeyValueRanges: [], + startTime: new Date(), + expiryTime: new Date(), + keyType: 0, + controlPlaneReaderScope: SasTokenPermissionKind.ContainerFullAccess, + controlPlaneWriterScope: 0, + dataPlaneReaderScope: SasTokenPermissionKind.ContainerFullAccess, + dataPlaneWriterScope: 0 + } + + const key = await createAuthorizationSasToken(masterKey, + sasTokenProperties); + + // If connecting to the Cosmos DB Emulator, disable TLS verification for your node process: + process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + const client = new CosmosClient({ + endpoint, + key: key + }); + + const database = client.database(sasTokenProperties.databaseName); + const container = database.container(sasTokenProperties.containerName); + const newItem = { + id: "your-itemId", + category: "your-category", + name: "your-name", + description: "your-description", + isComplete: false + }; + + const querySpec = { + query: "SELECT * from c" + }; + + await container.items.create(newItem); + + // read all items in the Items container + const { resources: items } = await container.items + .query(querySpec) + .fetchAll(); + + items.forEach((item: { id: any; description: any; }) => { + console.log(`${item.id} - ${item.description}`); + }); + + const dbs = await client.databases.readAll().fetchAll() + + logStep("Fetch all databases using existing user token"); + const sasTokenClient = new CosmosClient({ + endpoint, + key: sasToken + }); + + logStep("Fetch all databases"); + await sasTokenClient.databases.readAll().fetchAll() + + await finish() +} + +run().catch(handleError); diff --git a/sdk/cosmosdb/cosmos/src/client/SasToken/PermissionScopeValues.ts b/sdk/cosmosdb/cosmos/src/client/SasToken/PermissionScopeValues.ts new file mode 100644 index 000000000000..8af9a6f17381 --- /dev/null +++ b/sdk/cosmosdb/cosmos/src/client/SasToken/PermissionScopeValues.ts @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +/** + * Represents permission Scope Values. + */ +export enum PermissionScopeValues { + /** + * Values which set permission Scope applicable to control plane related operations. + */ + ScopeAccountReadValue = 0x0001, + ScopeAccountListDatabasesValue = 0x0002, + ScopeDatabaseReadValue = 0x0004, + ScopeDatabaseReadOfferValue = 0x0008, + ScopeDatabaseListContainerValue = 0x0010, + ScopeContainerReadValue = 0x0020, + ScopeContainerReadOfferValue = 0x0040, + + ScopeAccountCreateDatabasesValue = 0x0001, + ScopeAccountDeleteDatabasesValue = 0x0002, + ScopeDatabaseDeleteValue = 0x0004, + ScopeDatabaseReplaceOfferValue = 0x0008, + ScopeDatabaseCreateContainerValue = 0x0010, + ScopeDatabaseDeleteContainerValue = 0x0020, + ScopeContainerReplaceValue = 0x0040, + ScopeContainerDeleteValue = 0x0080, + ScopeContainerReplaceOfferValue = 0x0100, + + ScopeAccountReadAllAccessValue = 0xffff, + ScopeDatabaseReadAllAccessValue = PermissionScopeValues.ScopeDatabaseReadValue | + PermissionScopeValues.ScopeDatabaseReadOfferValue | + PermissionScopeValues.ScopeDatabaseListContainerValue | + PermissionScopeValues.ScopeContainerReadValue | + PermissionScopeValues.ScopeContainerReadOfferValue, + + ScopeContainersReadAllAccessValue = PermissionScopeValues.ScopeContainerReadValue | + PermissionScopeValues.ScopeContainerReadOfferValue, + + ScopeAccountWriteAllAccessValue = 0xffff, + ScopeDatabaseWriteAllAccessValue = PermissionScopeValues.ScopeDatabaseDeleteValue | + PermissionScopeValues.ScopeDatabaseReplaceOfferValue | + PermissionScopeValues.ScopeDatabaseCreateContainerValue | + PermissionScopeValues.ScopeDatabaseDeleteContainerValue | + PermissionScopeValues.ScopeContainerReplaceValue | + PermissionScopeValues.ScopeContainerDeleteValue | + PermissionScopeValues.ScopeContainerReplaceOfferValue, + + ScopeContainersWriteAllAccessValue = PermissionScopeValues.ScopeContainerReplaceValue | + PermissionScopeValues.ScopeContainerDeleteValue | + PermissionScopeValues.ScopeContainerReplaceOfferValue, + + /** + * Values which set permission Scope applicable to data plane related operations. + */ + ScopeContainerExecuteQueriesValue = 0x00000001, + ScopeContainerReadFeedsValue = 0x00000002, + ScopeContainerReadStoredProceduresValue = 0x00000004, + ScopeContainerReadUserDefinedFunctionsValue = 0x00000008, + ScopeContainerReadTriggersValue = 0x00000010, + ScopeContainerReadConflictsValue = 0x00000020, + ScopeItemReadValue = 0x00000040, + ScopeStoredProcedureReadValue = 0x00000080, + ScopeUserDefinedFunctionReadValue = 0x00000100, + ScopeTriggerReadValue = 0x00000200, + + ScopeContainerCreateItemsValue = 0x00000001, + ScopeContainerReplaceItemsValue = 0x00000002, + ScopeContainerUpsertItemsValue = 0x00000004, + ScopeContainerDeleteItemsValue = 0x00000008, + ScopeContainerCreateStoredProceduresValue = 0x00000010, + ScopeContainerReplaceStoredProceduresValue = 0x00000020, + ScopeContainerDeleteStoredProceduresValue = 0x00000040, + ScopeContainerExecuteStoredProceduresValue = 0x00000080, + ScopeContainerCreateTriggersValue = 0x00000100, + ScopeContainerReplaceTriggersValue = 0x00000200, + ScopeContainerDeleteTriggersValue = 0x00000400, + ScopeContainerCreateUserDefinedFunctionsValue = 0x00000800, + ScopeContainerReplaceUserDefinedFunctionsValue = 0x00001000, + ScopeContainerDeleteUserDefinedFunctionSValue = 0x00002000, + ScopeContainerDeleteCONFLICTSValue = 0x00004000, + ScopeItemReplaceValue = 0x00010000, + ScopeItemUpsertValue = 0x00020000, + ScopeItemDeleteValue = 0x00040000, + ScopeStoredProcedureReplaceValue = 0x00100000, + ScopeStoredProcedureDeleteValue = 0x00200000, + ScopeStoredProcedureExecuteValue = 0x00400000, + ScopeUserDefinedFunctionReplaceValue = 0x00800000, + ScopeUserDefinedFunctionDeleteValue = 0x01000000, + ScopeTriggerReplaceValue = 0x02000000, + ScopeTriggerDeleteValue = 0x04000000, + + ScopeContainerReadAllAccessValue = 0xffffffff, + ScopeItemReadAllAccessValue = PermissionScopeValues.ScopeContainerExecuteQueriesValue | + PermissionScopeValues.ScopeItemReadValue, + ScopeContainerWriteAllAccessValue = 0xffffffff, + ScopeItemWriteAllAccessValue = PermissionScopeValues.ScopeContainerCreateItemsValue | + PermissionScopeValues.ScopeContainerReplaceItemsValue | + PermissionScopeValues.ScopeContainerUpsertItemsValue | + PermissionScopeValues.ScopeContainerDeleteItemsValue | + PermissionScopeValues.ScopeItemReplaceValue | + PermissionScopeValues.ScopeItemUpsertValue | + PermissionScopeValues.ScopeItemDeleteValue, + + NoneValue = 0 +} diff --git a/sdk/cosmosdb/cosmos/src/client/SasToken/SasTokenProperties.ts b/sdk/cosmosdb/cosmos/src/client/SasToken/SasTokenProperties.ts new file mode 100644 index 000000000000..ca49a52629c9 --- /dev/null +++ b/sdk/cosmosdb/cosmos/src/client/SasToken/SasTokenProperties.ts @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { CosmosContainerChildResourceKind } from "../../common/constants"; +import { CosmosKeyType } from "../../common/constants"; + +export class SasTokenProperties { + user: string; + userTag: string; + databaseName: string; + containerName: string; + resourceName: string; + resourcePath: string; + resourceKind: CosmosContainerChildResourceKind; + partitionKeyValueRanges: []; + startTime: Date; + expiryTime: Date; + keyType: CosmosKeyType | number; + controlPlaneReaderScope: number; + controlPlaneWriterScope: number; + dataPlaneReaderScope: number; + dataPlaneWriterScope: number; + cosmosContainerChildResourceKind: CosmosContainerChildResourceKind; + cosmosKeyType: CosmosKeyType; +} diff --git a/sdk/cosmosdb/cosmos/src/common/constants.ts b/sdk/cosmosdb/cosmos/src/common/constants.ts index 49f9e26496ab..b9658b6c3771 100644 --- a/sdk/cosmosdb/cosmos/src/common/constants.ts +++ b/sdk/cosmosdb/cosmos/src/common/constants.ts @@ -11,8 +11,6 @@ export interface PartitionKeyRangePropertiesNames { /** * @hidden */ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. export const Constants = { HttpHeaders: { Authorization: "authorization", @@ -188,6 +186,7 @@ export const Constants = { }, Path: { + Root: "/", DatabasesPathSegment: "dbs", CollectionsPathSegment: "colls", UsersPathSegment: "users", @@ -266,3 +265,169 @@ export enum OperationType { Execute = "execute", Batch = "batch" } + +/** + * @hidden + */ +export enum CosmosKeyType { + PrimaryMaster = "PRIMARY_MASTER", + SecondaryMaster = "SECONDARY_MASTER", + PrimaryReadOnly = "PRIMARY_READONLY", + SecondaryReadOnly = "SECONDARY_READONLY" +} + +/** + * @hidden + */ +export enum CosmosContainerChildResourceKind { + Item = "ITEM", + StoredProcedure = "STORED_PROCEDURE", + UserDefinedFunction = "USER_DEFINED_FUNCTION", + Trigger = "TRIGGER" +} +/** + * @hidden + */ +export enum PermissionScopeValues { + /** + * Values which set permission Scope applicable to control plane related operations. + */ + ScopeAccountReadValue = 0x0001, + ScopeAccountListDatabasesValue = 0x0002, + ScopeDatabaseReadValue = 0x0004, + ScopeDatabaseReadOfferValue = 0x0008, + ScopeDatabaseListContainerValue = 0x0010, + ScopeContainerReadValue = 0x0020, + ScopeContainerReadOfferValue = 0x0040, + + ScopeAccountCreateDatabasesValue = 0x0001, + ScopeAccountDeleteDatabasesValue = 0x0002, + ScopeDatabaseDeleteValue = 0x0004, + ScopeDatabaseReplaceOfferValue = 0x0008, + ScopeDatabaseCreateContainerValue = 0x0010, + ScopeDatabaseDeleteContainerValue = 0x0020, + ScopeContainerReplaceValue = 0x0040, + ScopeContainerDeleteValue = 0x0080, + ScopeContainerReplaceOfferValue = 0x0100, + + ScopeAccountReadAllAccessValue = 0xffff, + ScopeDatabaseReadAllAccessValue = PermissionScopeValues.ScopeDatabaseReadValue | + PermissionScopeValues.ScopeDatabaseReadOfferValue | + PermissionScopeValues.ScopeDatabaseListContainerValue | + PermissionScopeValues.ScopeContainerReadValue | + PermissionScopeValues.ScopeContainerReadOfferValue, + + ScopeContainersReadAllAccessValue = PermissionScopeValues.ScopeContainerReadValue | + PermissionScopeValues.ScopeContainerReadOfferValue, + + ScopeAccountWriteAllAccessValue = 0xffff, + ScopeDatabaseWriteAllAccessValue = PermissionScopeValues.ScopeDatabaseDeleteValue | + PermissionScopeValues.ScopeDatabaseReplaceOfferValue | + PermissionScopeValues.ScopeDatabaseCreateContainerValue | + PermissionScopeValues.ScopeDatabaseDeleteContainerValue | + PermissionScopeValues.ScopeContainerReplaceValue | + PermissionScopeValues.ScopeContainerDeleteValue | + PermissionScopeValues.ScopeContainerReplaceOfferValue, + + ScopeContainersWriteAllAccessValue = PermissionScopeValues.ScopeContainerReplaceValue | + PermissionScopeValues.ScopeContainerDeleteValue | + PermissionScopeValues.ScopeContainerReplaceOfferValue, + + /** + * Values which set permission Scope applicable to data plane related operations. + */ + ScopeContainerExecuteQueriesValue = 0x00000001, + ScopeContainerReadFeedsValue = 0x00000002, + ScopeContainerReadStoredProceduresValue = 0x00000004, + ScopeContainerReadUserDefinedFunctionsValue = 0x00000008, + ScopeContainerReadTriggersValue = 0x00000010, + ScopeContainerReadConflictsValue = 0x00000020, + ScopeItemReadValue = 0x00000040, + ScopeStoredProcedureReadValue = 0x00000080, + ScopeUserDefinedFunctionReadValue = 0x00000100, + ScopeTriggerReadValue = 0x00000200, + + ScopeContainerCreateItemsValue = 0x00000001, + ScopeContainerReplaceItemsValue = 0x00000002, + ScopeContainerUpsertItemsValue = 0x00000004, + ScopeContainerDeleteItemsValue = 0x00000008, + ScopeContainerCreateStoredProceduresValue = 0x00000010, + ScopeContainerReplaceStoredProceduresValue = 0x00000020, + ScopeContainerDeleteStoredProceduresValue = 0x00000040, + ScopeContainerExecuteStoredProceduresValue = 0x00000080, + ScopeContainerCreateTriggersValue = 0x00000100, + ScopeContainerReplaceTriggersValue = 0x00000200, + ScopeContainerDeleteTriggersValue = 0x00000400, + ScopeContainerCreateUserDefinedFunctionsValue = 0x00000800, + ScopeContainerReplaceUserDefinedFunctionsValue = 0x00001000, + ScopeContainerDeleteUserDefinedFunctionSValue = 0x00002000, + ScopeContainerDeleteCONFLICTSValue = 0x00004000, + ScopeItemReplaceValue = 0x00010000, + ScopeItemUpsertValue = 0x00020000, + ScopeItemDeleteValue = 0x00040000, + ScopeStoredProcedureReplaceValue = 0x00100000, + ScopeStoredProcedureDeleteValue = 0x00200000, + ScopeStoredProcedureExecuteValue = 0x00400000, + ScopeUserDefinedFunctionReplaceValue = 0x00800000, + ScopeUserDefinedFunctionDeleteValue = 0x01000000, + ScopeTriggerReplaceValue = 0x02000000, + ScopeTriggerDeleteValue = 0x04000000, + + ScopeContainerReadAllAccessValue = 0xffffffff, + ScopeItemReadAllAccessValue = PermissionScopeValues.ScopeContainerExecuteQueriesValue | + PermissionScopeValues.ScopeItemReadValue, + ScopeContainerWriteAllAccessValue = 0xffffffff, + ScopeItemWriteAllAccessValue = PermissionScopeValues.ScopeContainerCreateItemsValue | + PermissionScopeValues.ScopeContainerReplaceItemsValue | + PermissionScopeValues.ScopeContainerUpsertItemsValue | + PermissionScopeValues.ScopeContainerDeleteItemsValue | + PermissionScopeValues.ScopeItemReplaceValue | + PermissionScopeValues.ScopeItemUpsertValue | + PermissionScopeValues.ScopeItemDeleteValue, + + NoneValue = 0 +} +/** + * @hidden + */ +export enum SasTokenPermissionKind { + ContainerCreateItems = PermissionScopeValues.ScopeContainerCreateItemsValue, + ContainerReplaceItems = PermissionScopeValues.ScopeContainerReplaceItemsValue, + ContainerUpsertItems = PermissionScopeValues.ScopeContainerUpsertItemsValue, + ContainerDeleteItems = PermissionScopeValues.ScopeContainerDeleteValue, + ContainerExecuteQueries = PermissionScopeValues.ScopeContainerExecuteQueriesValue, + ContainerReadFeeds = PermissionScopeValues.ScopeContainerReadFeedsValue, + ContainerCreateStoreProcedure = PermissionScopeValues.ScopeContainerCreateStoredProceduresValue, + ContainerReadStoreProcedure = PermissionScopeValues.ScopeContainerReadStoredProceduresValue, + ContainerReplaceStoreProcedure = PermissionScopeValues.ScopeContainerReplaceStoredProceduresValue, + ContainerDeleteStoreProcedure = PermissionScopeValues.ScopeContainerDeleteStoredProceduresValue, + ContainerCreateTriggers = PermissionScopeValues.ScopeContainerCreateTriggersValue, + ContainerReadTriggers = PermissionScopeValues.ScopeContainerReadTriggersValue, + ContainerReplaceTriggers = PermissionScopeValues.ScopeContainerReplaceTriggersValue, + ContainerDeleteTriggers = PermissionScopeValues.ScopeContainerDeleteTriggersValue, + ContainerCreateUserDefinedFunctions = PermissionScopeValues.ScopeContainerCreateUserDefinedFunctionsValue, + ContainerReadUserDefinedFunctions = PermissionScopeValues.ScopeContainerReadUserDefinedFunctionsValue, + ContainerReplaceUserDefinedFunctions = PermissionScopeValues.ScopeContainerReplaceUserDefinedFunctionsValue, + ContainerDeleteUserDefinedFunctions = PermissionScopeValues.ScopeContainerDeleteUserDefinedFunctionSValue, + ContainerExecuteStoredProcedure = PermissionScopeValues.ScopeContainerExecuteStoredProceduresValue, + ContainerReadConflicts = PermissionScopeValues.ScopeContainerReadConflictsValue, + ContainerDeleteConflicts = PermissionScopeValues.ScopeContainerDeleteCONFLICTSValue, + ContainerReadAny = PermissionScopeValues.ScopeContainerReadOfferValue, + ContainerFullAccess = PermissionScopeValues.ScopeContainerReadAllAccessValue, + ItemReadAny = PermissionScopeValues.ScopeItemReplaceValue, + ItemFullAccess = PermissionScopeValues.ScopeItemReadAllAccessValue, + ItemRead = PermissionScopeValues.ScopeItemReadValue, + ItemReplace = PermissionScopeValues.ScopeItemReplaceValue, + ItemUpsert = PermissionScopeValues.ScopeItemUpsertValue, + ItemDelete = PermissionScopeValues.ScopeItemDeleteValue, + StoreProcedureRead = PermissionScopeValues.ScopeStoredProcedureReadValue, + StoreProcedureReplace = PermissionScopeValues.ScopeStoredProcedureReplaceValue, + StoreProcedureDelete = PermissionScopeValues.ScopeStoredProcedureDeleteValue, + StoreProcedureExecute = PermissionScopeValues.ScopeStoredProcedureExecuteValue, + UserDefinedFuntionRead = PermissionScopeValues.ScopeUserDefinedFunctionReadValue, + UserDefinedFuntionReplace = PermissionScopeValues.ScopeUserDefinedFunctionReplaceValue, + UserDefinedFuntionDelete = PermissionScopeValues.ScopeUserDefinedFunctionDeleteValue, + TriggerRead = PermissionScopeValues.ScopeTriggerReadValue, + TriggerReplace = PermissionScopeValues.ScopeTriggerReplaceValue, + TriggerDelete = PermissionScopeValues.ScopeTriggerDeleteValue +} diff --git a/sdk/cosmosdb/cosmos/src/tsconfig.strict.json b/sdk/cosmosdb/cosmos/src/tsconfig.strict.json index 4b5855516de4..e577da9b4d93 100644 --- a/sdk/cosmosdb/cosmos/src/tsconfig.strict.json +++ b/sdk/cosmosdb/cosmos/src/tsconfig.strict.json @@ -119,6 +119,9 @@ "queryExecutionContext/Aggregators/index.ts", "queryExecutionContext/index.ts", "common/index.ts", - "routing/index.ts" + "routing/index.ts", + "utils/SasToken.ts", + "client/SasToken/SasTokenProperties.ts", + "client/SasToken/PermissionScopeValues.ts" ] } diff --git a/sdk/cosmosdb/cosmos/src/utils/SasToken.ts b/sdk/cosmosdb/cosmos/src/utils/SasToken.ts new file mode 100644 index 000000000000..fd0a7af84d14 --- /dev/null +++ b/sdk/cosmosdb/cosmos/src/utils/SasToken.ts @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { SasTokenProperties } from "../client/SasToken/SasTokenProperties"; +import { Constants, CosmosKeyType, SasTokenPermissionKind } from "../common"; +import { encodeUTF8 } from "./encode"; +import { hmac } from "./hmac"; + +/** + * Experimental internal only + * Generates the payload representing the permission configuration for the sas token. + */ + +export async function createAuthorizationSasToken( + masterKey: string, + sasTokenProperties: SasTokenProperties +): Promise { + let resourcePrefixPath = ""; + if ( + typeof sasTokenProperties.databaseName === "string" && + sasTokenProperties.databaseName !== "" + ) { + resourcePrefixPath += `/${Constants.Path.DatabasesPathSegment}/${sasTokenProperties.databaseName}`; + } + + if ( + typeof sasTokenProperties.containerName === "string" && + sasTokenProperties.containerName !== "" + ) { + if (sasTokenProperties.databaseName === "") { + throw new Error(`illegalArgumentException : ${sasTokenProperties.databaseName} \ + is an invalid database name`); + } + resourcePrefixPath += `/${Constants.Path.CollectionsPathSegment}/${sasTokenProperties.containerName}`; + } + + if ( + typeof sasTokenProperties.resourceName === "string" && + sasTokenProperties.resourceName !== "" + ) { + if (sasTokenProperties.containerName === "") { + throw new Error(`illegalArgumentException : ${sasTokenProperties.containerName} \ + is an invalid container name`); + } + switch (sasTokenProperties.resourceKind) { + case "ITEM": + resourcePrefixPath += `${Constants.Path.Root}${Constants.Path.DocumentsPathSegment}`; + break; + case "STORED_PROCEDURE": + resourcePrefixPath += `${Constants.Path.Root}${Constants.Path.StoredProceduresPathSegment}`; + break; + case "USER_DEFINED_FUNCTION": + resourcePrefixPath += `${Constants.Path.Root}${Constants.Path.UserDefinedFunctionsPathSegment}`; + break; + case "TRIGGER": + resourcePrefixPath += `${Constants.Path.Root}${Constants.Path.TriggersPathSegment}`; + break; + default: + throw new Error(`illegalArgumentException : ${sasTokenProperties.resourceKind} \ + is an invalid resource kind`); + break; + } + resourcePrefixPath += `${Constants.Path.Root}${sasTokenProperties.resourceName}${Constants.Path.Root}`; + } + sasTokenProperties.resourcePath = resourcePrefixPath.toString(); + + let partitionRanges = ""; + + if ( + sasTokenProperties.partitionKeyValueRanges !== undefined && + sasTokenProperties.partitionKeyValueRanges.length > 0 + ) { + if ( + typeof sasTokenProperties.resourceKind !== "string" && + sasTokenProperties.resourceKind !== "ITEM" + ) { + throw new Error(`illegalArgumentException : ${sasTokenProperties.resourceKind} \ + is an invalid partition key value range`); + } + sasTokenProperties.partitionKeyValueRanges.forEach((range) => { + partitionRanges += `${encodeUTF8(range)},`; + }); + } + + if (sasTokenProperties.controlPlaneReaderScope === 0) { + sasTokenProperties.controlPlaneReaderScope += SasTokenPermissionKind.ContainerReadAny; + sasTokenProperties.controlPlaneWriterScope += SasTokenPermissionKind.ContainerReadAny; + } + + if ( + sasTokenProperties.dataPlaneReaderScope === 0 && + sasTokenProperties.dataPlaneWriterScope === 0 + ) { + sasTokenProperties.dataPlaneReaderScope = SasTokenPermissionKind.ContainerFullAccess; + sasTokenProperties.dataPlaneWriterScope = SasTokenPermissionKind.ContainerFullAccess; + } + + if ( + typeof sasTokenProperties.keyType !== "number" || + typeof sasTokenProperties.keyType === undefined + ) { + switch (sasTokenProperties.keyType) { + case CosmosKeyType.PrimaryMaster: + sasTokenProperties.keyType = 1; + break; + case CosmosKeyType.SecondaryMaster: + sasTokenProperties.keyType = 2; + break; + case CosmosKeyType.PrimaryReadOnly: + sasTokenProperties.keyType = 3; + break; + case CosmosKeyType.SecondaryReadOnly: + sasTokenProperties.keyType = 4; + break; + default: + throw new Error(`illegalArgumentException : ${sasTokenProperties.keyType} \ + is an invalid key type`); + break; + } + } + + const payload = + sasTokenProperties.user + + "\n" + + sasTokenProperties.userTag + + "\n" + + sasTokenProperties.resourcePath + + "\n" + + partitionRanges + + "\n" + + utcsecondsSinceEpoch(sasTokenProperties.startTime).toString(16) + + "\n" + + utcsecondsSinceEpoch(sasTokenProperties.expiryTime).toString(16) + + "\n" + + sasTokenProperties.keyType + + "\n" + + sasTokenProperties.controlPlaneReaderScope.toString(16) + + "\n" + + sasTokenProperties.controlPlaneWriterScope.toString(16) + + "\n" + + sasTokenProperties.dataPlaneReaderScope.toString(16) + + "\n" + + sasTokenProperties.dataPlaneWriterScope.toString(16) + + "\n"; + + const signedPayload = await hmac(masterKey, Buffer.from(payload).toString("base64")); + return "type=sas&ver=1.0&sig=" + signedPayload + ";" + Buffer.from(payload).toString("base64"); +} +/** + * @hidden + */ +// TODO: utcMilllisecondsSinceEpoch +export function utcsecondsSinceEpoch(date: Date): number { + return Math.round(date.getTime() / 1000); +} diff --git a/sdk/cosmosdb/cosmos/src/utils/headers.ts b/sdk/cosmosdb/cosmos/src/utils/headers.ts index d504e4170294..756f987a23e8 100644 --- a/sdk/cosmosdb/cosmos/src/utils/headers.ts +++ b/sdk/cosmosdb/cosmos/src/utils/headers.ts @@ -13,6 +13,12 @@ export async function generateHeaders( ): Promise<{ [x: string]: string; }> { + if (masterKey.startsWith("type=sas&")) { + return { + [Constants.HttpHeaders.Authorization]: encodeURIComponent(masterKey), + [Constants.HttpHeaders.XDate]: date.toUTCString() + }; + } const sig = await signature(masterKey, method, resourceType, resourceId, date); return { diff --git a/sdk/cosmosdb/cosmos/test/internal/unit/sasToken.spec.ts b/sdk/cosmosdb/cosmos/test/internal/unit/sasToken.spec.ts new file mode 100644 index 000000000000..6186a8e7c730 --- /dev/null +++ b/sdk/cosmosdb/cosmos/test/internal/unit/sasToken.spec.ts @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import assert from "assert"; +import { CosmosClient } from "../../../dist-esm/"; +import { endpoint, masterKey } from "../../public/common/_testConfig"; +import { SasTokenPermissionKind } from "../../../dist-esm/common"; +import { createAuthorizationSasToken } from "../../../dist-esm/utils/SasToken"; +import { SasTokenProperties } from "../../../dist-esm/client/SasToken/SasTokenProperties"; + +describe.skip("SAS Token Authorization", function() { + const sasTokenProperties = { + user: "user1", + userTag: "", + databaseName: "db1", + containerName: "coll1", + resourcePath: "/dbs/db1/colls/coll1/", + partitionKeyValueRanges: [], + startTime: new Date(), + expiryTime: new Date(), + keyType: 0, + controlPlaneReaderScope: SasTokenPermissionKind.ContainerReadAny, + controlPlaneWriterScope: 0, + dataPlaneReaderScope: SasTokenPermissionKind.ContainerFullAccess, + dataPlaneWriterScope: 0 + }; + + it("should connect with sas token properties set", async function() { + const key = await createAuthorizationSasToken(masterKey, sasTokenProperties); + + // If connecting to the Cosmos DB Emulator, disable TLS verification for your node process: + process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + const client = new CosmosClient({ + endpoint, + key: key + }); + + const database = client.database(sasTokenProperties.databaseName); + const container = database.container(sasTokenProperties.containerName); + const newItem = { + id: "4", + category: "fun", + name: "Cosmos DB", + description: "Complete Cosmos DB Node.js Quickstart ⚡.", + isComplete: false + }; + + const item = await container.items.create(newItem); + assert(undefined !== item, "Should create an item based on sas token properties"); + + const dbs = await client.databases.readAll().fetchAll(); + assert(undefined !== dbs, "Should be able to fetch list of databases"); + }); + + it("should connect when a user set sas token", async function() { + const userSasTokenKey = + "type=sas&ver=1.0&sig=pCgZFxV9JQN1i3vzYNTfQldW1No7I+MSgN628TZcJAI=;dXNlcjEKCi9kYnMvZGIxL2NvbGxzL2NvbGwxLwoKNUZFRTY2MDEKNjIxM0I3MDEKMAo2MAowCkZGRkZGRkZGCjAK"; + const sasTokenClient = new CosmosClient({ + endpoint, + key: userSasTokenKey + }); + + const dbs = await sasTokenClient.databases.readAll().fetchAll(); + assert(undefined !== dbs, "Should be able to fetch list of databases"); + }); +});