diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/AuthorizationTokenType.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/AuthorizationTokenType.java index 603496dcb09f..55d97f788bc7 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/AuthorizationTokenType.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/AuthorizationTokenType.java @@ -8,9 +8,10 @@ public enum AuthorizationTokenType { PrimaryReadonlyMasterKey, SecondaryMasterKey, SecondaryReadonlyMasterKey, - SystemReadOnly, + SystemReadOnly, SystemReadWrite, SystemAll, ResourceToken, - AadToken + AadToken, + SasToken } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Constants.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Constants.java index 5af5deb1448b..b63c157ef358 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Constants.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Constants.java @@ -170,6 +170,7 @@ public static final class Properties { public static final String MASTER_TOKEN = "master"; public static final String RESOURCE_TOKEN = "resource"; public static final String AAD_TOKEN = "aad"; + public static final String SAS_TOKEN = "sas"; public static final String TOKEN_VERSION = "1.0"; public static final String AUTH_SCHEMA_TYPE = "type"; public static final String AUTH_VERSION = "ver"; diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Paths.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Paths.java index 1a7e8cdb207b..352b25d7c327 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Paths.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Paths.java @@ -7,7 +7,7 @@ * Used internally. Contains string constants to work with the paths in the Azure Cosmos DB database service. */ public class Paths { - static final String ROOT = "/"; + public static final String ROOT = "/"; static final char ROOT_CHAR = '/'; static final char ESCAPE_CHAR = '\\'; diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java index 5dfeff17b83a..6b3c34961bee 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java @@ -48,6 +48,7 @@ import com.azure.cosmos.implementation.routing.PartitionKeyInternalHelper; import com.azure.cosmos.implementation.routing.PartitionKeyRangeIdentity; import com.azure.cosmos.implementation.routing.Range; +import com.azure.cosmos.implementation.sastokens.SasTokenAuthorizationHelper; import com.azure.cosmos.models.CosmosChangeFeedRequestOptions; import com.azure.cosmos.models.CosmosItemIdentity; import com.azure.cosmos.models.CosmosQueryRequestOptions; @@ -120,6 +121,7 @@ public class RxDocumentClientImpl implements AsyncDocumentClient, IAuthorization private final BaseAuthorizationTokenProvider authorizationTokenProvider; private final UserAgentContainer userAgentContainer; private final boolean hasAuthKeyResourceToken; + private final boolean hasAuthKeySasToken; private final Configs configs; private final boolean connectionSharingAcrossClientsEnabled; private AzureKeyCredential credential; @@ -291,19 +293,30 @@ private RxDocumentClientImpl(URI serviceEndpoint, this.authorizationTokenType = AuthorizationTokenType.Invalid; if (this.credential != null) { - hasAuthKeyResourceToken = false; + this.hasAuthKeyResourceToken = false; + this.hasAuthKeySasToken = false; this.authorizationTokenProvider = new BaseAuthorizationTokenProvider(this.credential); } else if (masterKeyOrResourceToken != null && ResourceTokenAuthorizationHelper.isResourceToken(masterKeyOrResourceToken)) { this.authorizationTokenProvider = null; - hasAuthKeyResourceToken = true; + this.hasAuthKeyResourceToken = true; + this.hasAuthKeySasToken = false; this.authorizationTokenType = AuthorizationTokenType.ResourceToken; - } else if(masterKeyOrResourceToken != null && !ResourceTokenAuthorizationHelper.isResourceToken(masterKeyOrResourceToken)) { + } else if (masterKeyOrResourceToken != null && SasTokenAuthorizationHelper.isSasToken(masterKeyOrResourceToken)) { + this.authorizationTokenProvider = null; + this.hasAuthKeyResourceToken = false; + this.hasAuthKeySasToken = true; + this.authorizationTokenType = AuthorizationTokenType.SasToken; + } else if(masterKeyOrResourceToken != null + && !ResourceTokenAuthorizationHelper.isResourceToken(masterKeyOrResourceToken) + && !SasTokenAuthorizationHelper.isSasToken(masterKeyOrResourceToken)) { this.credential = new AzureKeyCredential(this.masterKeyOrResourceToken); - hasAuthKeyResourceToken = false; + this.hasAuthKeyResourceToken = false; + this.hasAuthKeySasToken = false; this.authorizationTokenType = AuthorizationTokenType.PrimaryMasterKey; this.authorizationTokenProvider = new BaseAuthorizationTokenProvider(this.credential); } else { - hasAuthKeyResourceToken = false; + this.hasAuthKeyResourceToken = false; + this.hasAuthKeySasToken = false; this.authorizationTokenProvider = null; if (tokenCredential != null) { this.tokenCredentialScopes = new String[] { @@ -1500,7 +1513,7 @@ public String getUserAuthorizationToken(String resourceName, } else if (credential != null) { return this.authorizationTokenProvider.generateKeyAuthorizationSignature(requestVerb, resourceName, resourceType, headers); - } else if (masterKeyOrResourceToken != null && hasAuthKeyResourceToken && resourceTokensMap == null) { + } else if (masterKeyOrResourceToken != null && (hasAuthKeyResourceToken || hasAuthKeySasToken) && resourceTokensMap == null) { return masterKeyOrResourceToken; } else { assert resourceTokensMap != null; diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/BarrierRequestHelper.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/BarrierRequestHelper.java index 71f8ce5447e8..e0bcf95792c5 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/BarrierRequestHelper.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/BarrierRequestHelper.java @@ -99,6 +99,7 @@ public static Mono createAsync( case ResourceToken: + case SasToken: authorizationToken = request.getHeaders().get(HttpConstants.HttpHeaders.AUTHORIZATION); break; diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/ControlPlanePermissionScope.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/ControlPlanePermissionScope.java new file mode 100644 index 000000000000..6e1d7e22af51 --- /dev/null +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/ControlPlanePermissionScope.java @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.implementation.sastokens; + +import java.util.Locale; + +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_ACCOUNT_CREATE_DATABASES_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_ACCOUNT_DELETE_DATABASES_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_ACCOUNT_LIST_DATABASES_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_ACCOUNT_READ_ALL_ACCESS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_ACCOUNT_READ_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_ACCOUNT_WRITE_ALL_ACCESS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINERS_READ_ALL_ACCESS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINERS_WRITE_ALL_ACCESS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_DELETE_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_READ_OFFER_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_READ_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_REPLACE_OFFER_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_REPLACE_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_DATABASE_CREATE_CONTAINERS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_DATABASE_DELETE_CONTAINERS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_DATABASE_DELETE_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_DATABASE_LIST_CONTAINERS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_DATABASE_READ_ALL_ACCESS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_DATABASE_READ_OFFER_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_DATABASE_READ_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_DATABASE_REPLACE_OFFER_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_DATABASE_WRITE_ALL_ACCESS_VALUE; + +/** + * Represents permission scope settings applicable to control plane related operations. + */ +public enum ControlPlanePermissionScope { + // REQUIRED: This enum must be kept in sync with the ControlPlanePermissionScope enum in backend services. + + /** + * Cosmos account read scope. + */ + SCOPE_ACCOUNT_READ("AccountRead", SCOPE_ACCOUNT_READ_VALUE), + SCOPE_ACCOUNT_LIST_DATABASES("AccountListDatabases", SCOPE_ACCOUNT_LIST_DATABASES_VALUE), + + /** + * Cosmos database read scope. + */ + SCOPE_DATABASE_READ("DatabaseRead", SCOPE_DATABASE_READ_VALUE), + SCOPE_DATABASE_READ_OFFER("DatabaseReadOffer", SCOPE_DATABASE_READ_OFFER_VALUE), + SCOPE_DATABASE_LIST_CONTAINERS("DatabaseListContainers", SCOPE_DATABASE_LIST_CONTAINERS_VALUE), + + /** + * Cosmos Container read scope. + */ + SCOPE_CONTAINER_READ("ContainerRead", SCOPE_CONTAINER_READ_VALUE), + SCOPE_CONTAINER_READ_OFFER("ContainerReadOffer", SCOPE_CONTAINER_READ_OFFER_VALUE), + + /** + * Composite read scopes. + */ + SCOPE_ACCOUNT_READ_ALL_ACCESS("AccountReadAllAccess", SCOPE_ACCOUNT_READ_ALL_ACCESS_VALUE), + SCOPE_DATABASE_READ_ALL_ACCESS("DatabaseReadAllAccess", SCOPE_DATABASE_READ_ALL_ACCESS_VALUE), + SCOPE_CONTAINER_READ_ALL_ACCESS("ContainersReadAllAccess", SCOPE_CONTAINERS_READ_ALL_ACCESS_VALUE), + + /** + * Cosmos account write scope. + */ + SCOPE_ACCOUNT_CREATE_DATABASES("AccountCreateDatabases", SCOPE_ACCOUNT_CREATE_DATABASES_VALUE), + SCOPE_ACCOUNT_DELETE_DATABASES("AccountDeleteDatabases", SCOPE_ACCOUNT_DELETE_DATABASES_VALUE), + + /** + * Cosmos database write scope. + */ + SCOPE_DATABASE_DELETE("DatabaseDelete", SCOPE_DATABASE_DELETE_VALUE), + SCOPE_DATABASE_REPLACE_OFFER("DatabaseReplaceOffer", SCOPE_DATABASE_REPLACE_OFFER_VALUE), + SCOPE_DATABASE_CREATE_CONTAINERS("DatabaseCreateContainers", SCOPE_DATABASE_CREATE_CONTAINERS_VALUE), + SCOPE_DATABASE_DELETE_CONTAINERS("DatabaseDeleteContainers", SCOPE_DATABASE_DELETE_CONTAINERS_VALUE), + + /** + * Cosmos Container write scope. + */ + SCOPE_CONTAINER_REPLACE("ContainerReplace", SCOPE_CONTAINER_REPLACE_VALUE), + SCOPE_CONTAINER_DELETE("ContainerDelete", SCOPE_CONTAINER_DELETE_VALUE), + SCOPE_CONTAINER_REPLACE_OFFER("ContainerReplaceOffer", SCOPE_CONTAINER_REPLACE_OFFER_VALUE), + + /** + * Composite write scopes. + */ + SCOPE_ACCOUNT_WRITE_ALL_ACCESS("AccountFullAllAccess", SCOPE_ACCOUNT_WRITE_ALL_ACCESS_VALUE), + SCOPE_DATABASE_WRITE_ALL_ACCESS("DatabaseWriteAllAccess", SCOPE_DATABASE_WRITE_ALL_ACCESS_VALUE), + SCOPE_CONTAINER_WRITE_ALL_ACCESS("ContainersWriteAllAccess", SCOPE_CONTAINERS_WRITE_ALL_ACCESS_VALUE), + + NONE("None", (short) 0x0); + + + private final short value; + private final String stringValue; + private final String toLowerStringValue; + + ControlPlanePermissionScope(String stringValue, short scopeBitMask) { + this.stringValue = stringValue; + this.toLowerStringValue = stringValue.toLowerCase(Locale.ROOT); + this.value = scopeBitMask; + } + + @Override + public String toString() { + return this.stringValue; + } + + public String toLowerCase() { + return this.toLowerStringValue; + } + + public short value() { + return this.value; + } +} diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/DataPlanePermissionScope.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/DataPlanePermissionScope.java new file mode 100644 index 000000000000..003f129854ea --- /dev/null +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/DataPlanePermissionScope.java @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.implementation.sastokens; + +import java.util.Locale; + +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_CREATE_ITEMS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_CREATE_STORED_PROCEDURES_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_CREATE_TRIGGERS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_CREATE_USER_DEFINED_FUNCTIONS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_DELETE_CONFLICTS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_DELETE_ITEMS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_DELETE_STORED_PROCEDURES_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_DELETE_TRIGGERS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_DELETE_USER_DEFINED_FUNCTIONS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_EXECUTE_QUERIES_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_EXECUTE_STORED_PROCEDURES_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_READ_ALL_ACCESS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_READ_CONFLICTS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_READ_FEEDS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_READ_STORED_PROCEDURES_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_READ_TRIGGERS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_READ_USER_DEFINED_FUNCTIONS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_REPLACE_ITEMS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_REPLACE_STORED_PROCEDURES_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_REPLACE_TRIGGERS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_REPLACE_USER_DEFINED_FUNCTIONS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_UPSERT_ITEMS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_CONTAINER_WRITE_ALL_ACCESS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_ITEM_DELETE_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_ITEM_READ_ALL_ACCESS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_ITEM_READ_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_ITEM_REPLACE_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_ITEM_UPSERT_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_ITEM_WRITE_ALL_ACCESS_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_STORED_PROCEDURE_DELETE_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_STORED_PROCEDURE_EXECUTE_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_STORED_PROCEDURE_READ_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_STORED_PROCEDURE_REPLACE_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_TRIGGER_DELETE_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_TRIGGER_READ_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_TRIGGER_REPLACE_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_USER_DEFINED_FUNCTION_DELETE_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_USER_DEFINED_FUNCTION_READ_VALUE; +import static com.azure.cosmos.implementation.sastokens.PermissionScopeValues.SCOPE_USER_DEFINED_FUNCTION_REPLACE_VALUE; + +/** + * Represents permission scope settings applicable to data plane related operations. + */ +public enum DataPlanePermissionScope { + // REQUIRED: This enum must be kept in sync with the DataPlanePermissionScope enum in backend services. + + /** + * Cosmos Container read scope. + */ + SCOPE_CONTAINER_EXECUTE_QUERIES("ContainerExecuteQueriesFeeds", SCOPE_CONTAINER_EXECUTE_QUERIES_VALUE), + SCOPE_CONTAINER_READ_FEEDS("ContainerReadFeeds", SCOPE_CONTAINER_READ_FEEDS_VALUE), + SCOPE_CONTAINER_READ_STORED_PROCEDURES("ContainerReadStoredProcedures", SCOPE_CONTAINER_READ_STORED_PROCEDURES_VALUE), + SCOPE_CONTAINER_READ_USER_DEFINED_FUNCTIONS("ContainerUserDefinedFunctions", SCOPE_CONTAINER_READ_USER_DEFINED_FUNCTIONS_VALUE), + SCOPE_CONTAINER_READ_TRIGGERS("ContainerReadTriggers", SCOPE_CONTAINER_READ_TRIGGERS_VALUE), + SCOPE_CONTAINER_READ_CONFLICTS("ContainerReadConflicts", SCOPE_CONTAINER_READ_CONFLICTS_VALUE), + SCOPE_ITEM_READ("ItemRead", SCOPE_ITEM_READ_VALUE), + SCOPE_STORED_PROCEDURE_READ("StoreProcedureRead", SCOPE_STORED_PROCEDURE_READ_VALUE), + SCOPE_USER_DEFINED_FUNCTION_READ("UserDefinedFunctionRead", SCOPE_USER_DEFINED_FUNCTION_READ_VALUE), + SCOPE_TRIGGER_READ("TriggerRead", SCOPE_TRIGGER_READ_VALUE), + + /** + * Cosmos Container read scope. + */ + SCOPE_CONTAINER_CREATE_ITEMS("ContainerCreateItems", SCOPE_CONTAINER_CREATE_ITEMS_VALUE), + SCOPE_CONTAINER_REPLACE_ITEMS("ContainerReplaceItems", SCOPE_CONTAINER_REPLACE_ITEMS_VALUE), + SCOPE_CONTAINER_UPSERT_ITEMS("ContainerUpsertItems", SCOPE_CONTAINER_UPSERT_ITEMS_VALUE), + SCOPE_CONTAINER_DELETE_ITEMS("ContainerDeleteItems", SCOPE_CONTAINER_DELETE_ITEMS_VALUE), + SCOPE_CONTAINER_CREATE_STORED_PROCEDURES("ContainerCreateStoredProcedures", SCOPE_CONTAINER_CREATE_STORED_PROCEDURES_VALUE), + SCOPE_CONTAINER_REPLACE_STORED_PROCEDURES("ContainerReplaceStoredProcedures", SCOPE_CONTAINER_REPLACE_STORED_PROCEDURES_VALUE), + SCOPE_CONTAINER_DELETE_STORED_PROCEDURES("ContainerDeleteStoredProcedures", SCOPE_CONTAINER_DELETE_STORED_PROCEDURES_VALUE), + SCOPE_CONTAINER_EXECUTE_STORED_PROCEDURES("ContainerDeleteStoredProcedures", SCOPE_CONTAINER_EXECUTE_STORED_PROCEDURES_VALUE), + SCOPE_CONTAINER_CREATE_TRIGGERS("ContainerCreateTriggers", SCOPE_CONTAINER_CREATE_TRIGGERS_VALUE), + SCOPE_CONTAINER_REPLACE_TRIGGERS("ContainerReplaceTriggers", SCOPE_CONTAINER_REPLACE_TRIGGERS_VALUE), + SCOPE_CONTAINER_DELETE_TRIGGERS("ContainerDeleteTriggers", SCOPE_CONTAINER_DELETE_TRIGGERS_VALUE), + SCOPE_CONTAINER_CREATE_USER_DEFINED_FUNCTIONS("ContainerCreateUserDefinedFunctions", SCOPE_CONTAINER_CREATE_USER_DEFINED_FUNCTIONS_VALUE), + SCOPE_CONTAINER_REPLACE_USER_DEFINED_FUNCTIONS("ContainerReplaceUserDefinedFunctions", SCOPE_CONTAINER_REPLACE_USER_DEFINED_FUNCTIONS_VALUE), + SCOPE_CONTAINER_DELETE_USER_DEFINED_FUNCTIONS("ContainerCreateUserDefinedFunctions", SCOPE_CONTAINER_DELETE_USER_DEFINED_FUNCTIONS_VALUE), + SCOPE_CONTAINER_DELETE_CONFLICTS("ContainerDeleteConflics", SCOPE_CONTAINER_DELETE_CONFLICTS_VALUE), + SCOPE_ITEM_REPLACE("ItemReplace", SCOPE_ITEM_REPLACE_VALUE), + SCOPE_ITEM_UPSERT("ItemUpsert", SCOPE_ITEM_UPSERT_VALUE), + SCOPE_ITEM_DELETE("ItemDelete", SCOPE_ITEM_DELETE_VALUE), + SCOPE_STORED_PROCEDURE_REPLACE("StoredProcedureReplace", SCOPE_STORED_PROCEDURE_REPLACE_VALUE), + SCOPE_STORED_PROCEDURE_DELETE("StoredProcedureReplace", SCOPE_STORED_PROCEDURE_DELETE_VALUE), + SCOPE_STORED_PROCEDURE_EXECUTE("StoredProcedureReplace", SCOPE_STORED_PROCEDURE_EXECUTE_VALUE), + SCOPE_USER_DEFINED_FUNCTION_REPLACE("UserDefinedFunctionReplace", SCOPE_USER_DEFINED_FUNCTION_REPLACE_VALUE), + SCOPE_USER_DEFINED_FUNCTION_DELETE("UserDefinedFunctionReplace", SCOPE_USER_DEFINED_FUNCTION_DELETE_VALUE), + SCOPE_TRIGGER_REPLACE("TriggerReplace", SCOPE_TRIGGER_REPLACE_VALUE), + SCOPE_TRIGGER_DELETE("TriggerDelete", SCOPE_TRIGGER_DELETE_VALUE), + + /** + * Composite read scope. + */ + SCOPE_CONTAINER_READ_ALL_ACCESS("ContainerReadAllAccess", SCOPE_CONTAINER_READ_ALL_ACCESS_VALUE), + SCOPE_ITEM_READ_ALL_ACCESS("ItemReadAllAccess", SCOPE_ITEM_READ_ALL_ACCESS_VALUE), + + /** + * Composite write scope. + */ + SCOPE_CONTAINER_WRITE_ALL_ACCESS("ContainerWriteAllAccess", SCOPE_CONTAINER_WRITE_ALL_ACCESS_VALUE), + SCOPE_ITEM_WRITE_ALL_ACCESS("ItemWriteAllAccess", SCOPE_ITEM_WRITE_ALL_ACCESS_VALUE), + + NONE("None", 0x0); + + + private final int value; + private final String stringValue; + private final String toLowerStringValue; + + DataPlanePermissionScope(String stringValue, int scopeBitMask) { + this.stringValue = stringValue; + this.toLowerStringValue = stringValue.toLowerCase(Locale.ROOT); + this.value = scopeBitMask; + } + + @Override + public String toString() { + return this.stringValue; + } + + public String toLowerCase() { + return this.toLowerStringValue; + } + + public int value() { + return this.value; + } +} diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/PermissionScopeValues.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/PermissionScopeValues.java new file mode 100644 index 000000000000..aae381dd053d --- /dev/null +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/PermissionScopeValues.java @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.implementation.sastokens; + +/** + * Represents permission scope values. + */ +class PermissionScopeValues { + /** + * Values which set permission scope applicable to control plane related operations. + */ + static final short SCOPE_ACCOUNT_READ_VALUE = (short) 0x0001; + static final short SCOPE_ACCOUNT_LIST_DATABASES_VALUE = (short) 0x0002; + static final short SCOPE_DATABASE_READ_VALUE = (short) 0x0004; + static final short SCOPE_DATABASE_READ_OFFER_VALUE = (short) 0x0008; + static final short SCOPE_DATABASE_LIST_CONTAINERS_VALUE = (short) 0x0010; + static final short SCOPE_CONTAINER_READ_VALUE = (short) 0x0020; + static final short SCOPE_CONTAINER_READ_OFFER_VALUE = (short) 0x0040; + + static final short SCOPE_ACCOUNT_CREATE_DATABASES_VALUE = (short) 0x0001; + static final short SCOPE_ACCOUNT_DELETE_DATABASES_VALUE = (short) 0x0002; + static final short SCOPE_DATABASE_DELETE_VALUE = (short) 0x0004; + static final short SCOPE_DATABASE_REPLACE_OFFER_VALUE = (short) 0x0008; + static final short SCOPE_DATABASE_CREATE_CONTAINERS_VALUE = (short) 0x0010; + static final short SCOPE_DATABASE_DELETE_CONTAINERS_VALUE = (short) 0x0020; + static final short SCOPE_CONTAINER_REPLACE_VALUE = (short) 0x0040; + static final short SCOPE_CONTAINER_DELETE_VALUE = (short) 0x0080; + static final short SCOPE_CONTAINER_REPLACE_OFFER_VALUE = (short) 0x0100; + + static final short SCOPE_ACCOUNT_READ_ALL_ACCESS_VALUE = (short) 0xFFFF; + static final short SCOPE_DATABASE_READ_ALL_ACCESS_VALUE = + SCOPE_DATABASE_READ_VALUE + | SCOPE_DATABASE_READ_OFFER_VALUE + | SCOPE_DATABASE_LIST_CONTAINERS_VALUE + | SCOPE_CONTAINER_READ_VALUE + | SCOPE_CONTAINER_READ_OFFER_VALUE; + static final short SCOPE_CONTAINERS_READ_ALL_ACCESS_VALUE = + SCOPE_CONTAINER_READ_VALUE + | SCOPE_CONTAINER_READ_OFFER_VALUE; + + static final short SCOPE_ACCOUNT_WRITE_ALL_ACCESS_VALUE = (short) 0xFFFF; + static final short SCOPE_DATABASE_WRITE_ALL_ACCESS_VALUE = + SCOPE_DATABASE_DELETE_VALUE + | SCOPE_DATABASE_REPLACE_OFFER_VALUE + | SCOPE_DATABASE_CREATE_CONTAINERS_VALUE + | SCOPE_DATABASE_DELETE_CONTAINERS_VALUE + | SCOPE_CONTAINER_REPLACE_VALUE + | SCOPE_CONTAINER_DELETE_VALUE + | SCOPE_CONTAINER_REPLACE_OFFER_VALUE; + static final short SCOPE_CONTAINERS_WRITE_ALL_ACCESS_VALUE = + SCOPE_CONTAINER_REPLACE_VALUE + | SCOPE_CONTAINER_DELETE_VALUE + | SCOPE_CONTAINER_REPLACE_OFFER_VALUE; + + /** + * Values which set permission scope applicable to data plane related operations. + */ + static final int SCOPE_CONTAINER_EXECUTE_QUERIES_VALUE = 0x00000001; + static final int SCOPE_CONTAINER_READ_FEEDS_VALUE = 0x00000002; + static final int SCOPE_CONTAINER_READ_STORED_PROCEDURES_VALUE = 0x00000004; + static final int SCOPE_CONTAINER_READ_USER_DEFINED_FUNCTIONS_VALUE = 0x00000008; + static final int SCOPE_CONTAINER_READ_TRIGGERS_VALUE = 0x00000010; + static final int SCOPE_CONTAINER_READ_CONFLICTS_VALUE = 0x00000020; + static final int SCOPE_ITEM_READ_VALUE = 0x00000040; + static final int SCOPE_STORED_PROCEDURE_READ_VALUE = 0x00000080; + static final int SCOPE_USER_DEFINED_FUNCTION_READ_VALUE = 0x00000100; + static final int SCOPE_TRIGGER_READ_VALUE = 0x00000200; + + static final int SCOPE_CONTAINER_CREATE_ITEMS_VALUE = 0x00000001; + static final int SCOPE_CONTAINER_REPLACE_ITEMS_VALUE = 0x00000002; + static final int SCOPE_CONTAINER_UPSERT_ITEMS_VALUE = 0x00000004; + static final int SCOPE_CONTAINER_DELETE_ITEMS_VALUE = 0x00000008; + static final int SCOPE_CONTAINER_CREATE_STORED_PROCEDURES_VALUE = 0x00000010; + static final int SCOPE_CONTAINER_REPLACE_STORED_PROCEDURES_VALUE = 0x00000020; + static final int SCOPE_CONTAINER_DELETE_STORED_PROCEDURES_VALUE = 0x00000040; + static final int SCOPE_CONTAINER_EXECUTE_STORED_PROCEDURES_VALUE = 0x00000080; + static final int SCOPE_CONTAINER_CREATE_TRIGGERS_VALUE = 0x00000100; + static final int SCOPE_CONTAINER_REPLACE_TRIGGERS_VALUE = 0x00000200; + static final int SCOPE_CONTAINER_DELETE_TRIGGERS_VALUE = 0x00000400; + static final int SCOPE_CONTAINER_CREATE_USER_DEFINED_FUNCTIONS_VALUE = 0x00000800; + static final int SCOPE_CONTAINER_REPLACE_USER_DEFINED_FUNCTIONS_VALUE = 0x00001000; + static final int SCOPE_CONTAINER_DELETE_USER_DEFINED_FUNCTIONS_VALUE = 0x00002000; + static final int SCOPE_CONTAINER_DELETE_CONFLICTS_VALUE = 0x00004000; + static final int SCOPE_ITEM_REPLACE_VALUE = 0x00010000; + static final int SCOPE_ITEM_UPSERT_VALUE = 0x00020000; + static final int SCOPE_ITEM_DELETE_VALUE = 0x00040000; + static final int SCOPE_STORED_PROCEDURE_REPLACE_VALUE = 0x00100000; + static final int SCOPE_STORED_PROCEDURE_DELETE_VALUE = 0x00200000; + static final int SCOPE_STORED_PROCEDURE_EXECUTE_VALUE = 0x00400000; + static final int SCOPE_USER_DEFINED_FUNCTION_REPLACE_VALUE = 0x00800000; + static final int SCOPE_USER_DEFINED_FUNCTION_DELETE_VALUE = 0x01000000; + static final int SCOPE_TRIGGER_REPLACE_VALUE = 0x02000000; + static final int SCOPE_TRIGGER_DELETE_VALUE = 0x04000000; + + static final int SCOPE_CONTAINER_READ_ALL_ACCESS_VALUE = 0xFFFFFFFF; + static final int SCOPE_ITEM_READ_ALL_ACCESS_VALUE = + SCOPE_CONTAINER_EXECUTE_QUERIES_VALUE + | SCOPE_ITEM_READ_VALUE; + static final int SCOPE_CONTAINER_WRITE_ALL_ACCESS_VALUE = 0xFFFFFFFF; + static final int SCOPE_ITEM_WRITE_ALL_ACCESS_VALUE = + SCOPE_CONTAINER_CREATE_ITEMS_VALUE + | SCOPE_CONTAINER_REPLACE_ITEMS_VALUE + | SCOPE_CONTAINER_UPSERT_ITEMS_VALUE + | SCOPE_CONTAINER_DELETE_ITEMS_VALUE + | SCOPE_ITEM_REPLACE_VALUE + | SCOPE_ITEM_UPSERT_VALUE + | SCOPE_ITEM_DELETE_VALUE; + + static final int NONE_VALUE = 0; +} diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/SasTokenAuthorizationHelper.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/SasTokenAuthorizationHelper.java new file mode 100644 index 000000000000..e095f4adec8b --- /dev/null +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/SasTokenAuthorizationHelper.java @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.cosmos.implementation.sastokens; + +import com.azure.cosmos.implementation.Constants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is used internally and act as a helper in authorization of + * resources from SAS tokens. + * + */ +public class SasTokenAuthorizationHelper { + private static final Logger logger = LoggerFactory.getLogger(SasTokenAuthorizationHelper.class); + + /** + * This method help to differentiate between master key and SAS token. + * + * @param token SAS token provided. + * @return Whether given token is a SAS token or not. + */ + public static boolean isSasToken(String token) { + int typeSeparatorPosition = token.indexOf('&'); + if (typeSeparatorPosition == -1) { + return false; + } + String authType = token.substring(0, typeSeparatorPosition); + int typeKeyValueSepartorPosition = authType.indexOf('='); + if (typeKeyValueSepartorPosition == -1 || !authType.substring(0, typeKeyValueSepartorPosition) + .equalsIgnoreCase(Constants.Properties.AUTH_SCHEMA_TYPE)) { + return false; + } + + String authTypeValue = authType.substring(typeKeyValueSepartorPosition + 1); + + return authTypeValue.equalsIgnoreCase(Constants.Properties.SAS_TOKEN); + } +} diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/SasTokenImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/SasTokenImpl.java new file mode 100644 index 000000000000..218d50cc31df --- /dev/null +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/SasTokenImpl.java @@ -0,0 +1,498 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.implementation.sastokens; + +import com.azure.cosmos.implementation.Paths; +import com.azure.cosmos.implementation.Utils; +import com.azure.cosmos.models.SasTokenPartitionKeyValueRange; +import com.azure.cosmos.models.SasTokenPermissionKind; +import com.azure.cosmos.models.SasTokenProperties; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +import static com.azure.cosmos.implementation.sastokens.ControlPlanePermissionScope.SCOPE_CONTAINER_READ; +import static com.azure.cosmos.implementation.sastokens.ControlPlanePermissionScope.SCOPE_CONTAINER_READ_OFFER; +import static com.azure.cosmos.implementation.sastokens.ControlPlanePermissionScope.SCOPE_CONTAINER_WRITE_ALL_ACCESS; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_CONTAINER_CREATE_ITEMS; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_CONTAINER_DELETE_CONFLICTS; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_CONTAINER_DELETE_ITEMS; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_CONTAINER_EXECUTE_QUERIES; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_CONTAINER_EXECUTE_STORED_PROCEDURES; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_CONTAINER_READ_ALL_ACCESS; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_CONTAINER_READ_CONFLICTS; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_CONTAINER_READ_FEEDS; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_CONTAINER_REPLACE_ITEMS; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_CONTAINER_UPSERT_ITEMS; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_ITEM_DELETE; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_ITEM_READ; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_ITEM_READ_ALL_ACCESS; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_ITEM_REPLACE; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_ITEM_UPSERT; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_ITEM_WRITE_ALL_ACCESS; +import static com.azure.cosmos.implementation.sastokens.DataPlanePermissionScope.SCOPE_STORED_PROCEDURE_EXECUTE; + +/** + * Represents the implementation of a permission configuration object to be used when creating a Cosmos shared access + * signature token. + */ +public class SasTokenImpl implements SasTokenProperties { + private static final String AUTH_PREFIX = "type=sas&ver=1.0&sig="; + private static final String SAS_TOKEN_SEPARATOR = ";"; + + String user; + String userTag; + String databaseName; + String containerName; + String resourceName; + String resourcePath; + CosmosContainerChildResourceKind resourceKind; + List partitionKeyValueRanges; + Instant startTime; + Instant expiryTime; + + byte keyType; + + short controlPlaneReaderScope; + short controlPlaneWriterScope; + int dataPlaneReaderScope; + int dataPlaneWriterScope; + + public SasTokenImpl() { + this.user = ""; + this.userTag = ""; + this.databaseName = ""; + this.containerName = ""; + this.resourceName = ""; + this.resourcePath = ""; + this.startTime = Instant.now(); + this.expiryTime = null; + this.controlPlaneReaderScope = 0; + this.controlPlaneWriterScope = 0; + this.dataPlaneReaderScope = 0; + this.dataPlaneWriterScope = 0; + this.keyType = 0; + } + + /** + * Generates the payload representing the permission configuration for the sas token. + *

+ * The payload is composed of the following: + * {@literal + * user\n + * userTag\n + * resourcePath\n + * BASE64_ENCODED_PartitionKeyValues_CommaSeparated\n + * epochStartTime\n + * epochExpiryTime\n + * keyKind\n + * shortAsHexControlPlaneReaderScope\n + * shortAsHexControlPlaneWriterScope\n + * intAsHexDataPlaneReaderScope\n + * intAsHexDataPlaneWriterScope\n + * } + * + * @return the permission configuration payload. + */ + private String generatePayload() { + StringBuilder resourcePrefixPath = new StringBuilder(); + + if (!this.databaseName.isEmpty()) { + resourcePrefixPath.append(Paths.ROOT).append(Paths.DATABASES_PATH_SEGMENT) + .append("/").append(this.databaseName); + } + + if (!this.containerName.isEmpty()) { + if (this.databaseName.isEmpty()) { + throw new IllegalArgumentException("databaseName"); + } + + resourcePrefixPath.append(Paths.ROOT).append(Paths.COLLECTIONS_PATH_SEGMENT) + .append(Paths.ROOT).append(this.containerName); + } + + if (!this.resourceName.isEmpty()) { + if (this.containerName.isEmpty()) { + throw new IllegalArgumentException("containerName"); + } + + switch (this.resourceKind) { + case ITEM: + resourcePrefixPath.append(Paths.ROOT).append(Paths.DOCUMENTS_PATH_SEGMENT); + break; + case STORED_PROCEDURE: + resourcePrefixPath.append(Paths.ROOT).append(Paths.STORED_PROCEDURES_PATH_SEGMENT); + break; + case USER_DEFINED_FUNCTION: + resourcePrefixPath.append(Paths.ROOT).append(Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT); + break; + case TRIGGER: + resourcePrefixPath.append(Paths.ROOT).append(Paths.TRIGGERS_PATH_SEGMENT); + break; + default: + throw new IllegalArgumentException("resourceKind"); + } + + resourcePrefixPath.append(Paths.ROOT).append(this.resourceName); + } + + resourcePrefixPath.append(Paths.ROOT); + this.resourcePath = resourcePrefixPath.toString(); + + StringBuilder partitionRanges = new StringBuilder(); + if (this.partitionKeyValueRanges != null && !this.partitionKeyValueRanges.isEmpty()) { + if (this.resourceKind != CosmosContainerChildResourceKind.ITEM) { + throw new IllegalArgumentException("partitionKeyValueRanges"); + } + + this.partitionKeyValueRanges.forEach(range -> partitionRanges.append(range.encode()).append(",")); + } + + if (this.expiryTime == null) { + this.expiryTime = this.startTime.plus(Duration.ofHours(2)); + } + + if (this.controlPlaneReaderScope == 0) { + this.controlPlaneReaderScope |= SCOPE_CONTAINER_READ.value(); + this.controlPlaneReaderScope |= SCOPE_CONTAINER_READ_OFFER.value(); + } + + if (this.dataPlaneReaderScope == 0 && this.dataPlaneWriterScope == 0) { + this.dataPlaneReaderScope |= SCOPE_CONTAINER_READ_ALL_ACCESS.value(); + } + + StringBuilder payload = new StringBuilder(this.user).append("\n") + .append(this.userTag).append("\n") + .append(this.resourcePath).append("\n") + .append(partitionRanges).append("\n") + .append(String.format("%X", this.startTime.getEpochSecond())).append("\n") + .append(String.format("%X", this.expiryTime.getEpochSecond())).append("\n") + .append(String.format("%X", this.keyType)).append("\n") + .append(String.format("%X", this.controlPlaneReaderScope)). append("\n") + .append(String.format("%X", this.controlPlaneWriterScope)). append("\n") + .append(String.format("%X", this.dataPlaneReaderScope)). append("\n") + .append(String.format("%X", this.dataPlaneWriterScope)). append("\n"); + + return Utils.encodeBase64String(payload.toString().getBytes(StandardCharsets.UTF_8)); + } + + private String getSasTokenWithHMACSHA256(String key) { + byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8); + byte[] keyDecodedBytes = Utils.Base64Decoder.decode(keyBytes); + SecretKey signingKey = new SecretKeySpec(keyDecodedBytes, "HMACSHA256"); + try { + Mac macInstance = Mac.getInstance("HMACSHA256"); + macInstance.init(signingKey); + + // Get payload which is Base64 encoded + String payload = this.generatePayload(); + byte[] payloadBytes = payload.getBytes(StandardCharsets.UTF_8); + byte[] digest = macInstance.doFinal(payloadBytes); + String authorizationToken = Utils.encodeBase64String(digest); + + StringBuilder token = new StringBuilder(AUTH_PREFIX) + .append(authorizationToken) + .append(SAS_TOKEN_SEPARATOR) + .append(payload); + + return token.toString(); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new IllegalStateException(e); + } + } + + /** + * Creates a Cosmos shared access signature token using the specified account key and a HMACSHA256 encoder. + * + * @param key the Cosmos key that will be used to generate a shared access signature token. + * @return the shared access signature token. + */ + @Override + public String getSasTokenValueUsingHMAC(String key) { + this.keyType = 0; + if (key == null || key.isEmpty()) { + throw new IllegalArgumentException("key"); + } + + return getSasTokenWithHMACSHA256(key); + } + + /** + * Create a Cosmos shared access signature token using the specified account key and a HMACSHA256 encoder. + *

+ * Providing key type will help expedite the authentication and authorization executed by the Cosmos service. + * + * @param key the Cosmos key that will be used to generate a shared access signature token. + * @param keyType the Cosmos key type that will be used to generate a shared access signature token. + * @return the shared access signature token. + */ + @Override + public String getSasTokenValueUsingHMAC(String key, CosmosKeyType keyType) { + if (key == null || key.isEmpty()) { + throw new IllegalArgumentException("key"); + } + + switch (keyType) { + case PRIMARY_MASTER: + this.keyType = 1; + break; + case SECONDARY_MASTER: + this.keyType = 2; + break; + case PRIMARY_READONLY: + this.keyType = 3; + break; + case SECONDARY_READONLY: + this.keyType = 4; + break; + default: + throw new IllegalArgumentException("keyType"); + } + + return getSasTokenWithHMACSHA256(key); + } + + @Override + public String getDatabaseName() { + return this.databaseName; + } + + @Override + public SasTokenProperties setDatabaseName(String databaseName) { + if (databaseName == null || Utils.trimBeginningAndEndingSlashes(databaseName).isEmpty()) { + throw new IllegalArgumentException("databaseName"); + } + + this.databaseName = Utils.trimBeginningAndEndingSlashes(databaseName); + + return this; + } + + @Override + public String getContainerName() { + return this.containerName; + } + + @Override + public SasTokenProperties setContainerName(String containerName) { + if (containerName == null || Utils.trimBeginningAndEndingSlashes(containerName).isEmpty()) { + throw new IllegalArgumentException("containerName"); + } + + this.containerName = Utils.trimBeginningAndEndingSlashes(containerName); + + return this; + } + + @Override + public CosmosContainerChildResourceKind getResourceKind() { + return this.resourceKind; + } + + @Override + public String getResourceName() { + return this.resourceName; + } + + @Override + public SasTokenProperties setResourceName(CosmosContainerChildResourceKind kind, String resourceName) { + if (resourceName == null) { + throw new IllegalArgumentException("resourceName"); + } + + this.resourceName = Utils.trimBeginningAndEndingSlashes(resourceName); + this.resourceKind = kind; + + return this; + } + + @Override + public String getUser() { + return this.user; + } + + @Override + public SasTokenProperties setUser(String user) { + if (user == null || user.isEmpty()) { + throw new IllegalArgumentException("user"); + } + + this.user = user; + + return this; + } + + @Override + public String getUserTag() { + return this.userTag; + } + + @Override + public SasTokenProperties setUserTag(String userTag) { + if (userTag == null) { + throw new IllegalArgumentException("userTag"); + } + + this.userTag = userTag; + + return this; + } + + @Override + public Instant getExpiryTime() { + return this.expiryTime; + } + + @Override + public SasTokenProperties setExpiryTime(Duration expiryTime) { + if (expiryTime == null) { + throw new IllegalArgumentException("expiryTime"); + } + + this.expiryTime = startTime.plus(expiryTime); + + return this; + } + + @Override + public Instant getStartTime() { + return this.startTime; + } + + @Override + public SasTokenProperties setStartTime(Instant startTime) { + if (startTime == null) { + throw new IllegalArgumentException("startTime"); + } + + this.startTime = startTime; + + return this; + } + + @Override + public Iterable getPartitionKeyValueRanges() { + return this.partitionKeyValueRanges; + } + + @Override + public SasTokenProperties setPartitionKeyValueRanges(Iterable partitionKeyValues) { + if (partitionKeyValues != null) { + this.partitionKeyValueRanges = new ArrayList<>(); + partitionKeyValues.forEach(partitionKey -> this.partitionKeyValueRanges.add(SasTokenPartitionKeyValueRange.create(partitionKey))); + } else { + this.partitionKeyValueRanges = null; + } + + return this; + } + + @Override + public SasTokenProperties addPartitionKeyValue(String partitionKeyValue) { + if (this.partitionKeyValueRanges == null) { + this.partitionKeyValueRanges = new ArrayList<>(); + } + + this.partitionKeyValueRanges.add(SasTokenPartitionKeyValueRange.create(partitionKeyValue)); + + return this; + } + + @Override + public SasTokenProperties addPermission(SasTokenPermissionKind permissionKind) { + switch (permissionKind) { + + // Container data all. + case CONTAINER_CREATE_ITEMS: { + this.dataPlaneWriterScope |= SCOPE_CONTAINER_CREATE_ITEMS.value(); + break; + } + case CONTAINER_REPLACE_ITEMS: { + this.dataPlaneWriterScope |= SCOPE_CONTAINER_REPLACE_ITEMS.value(); + break; + } + case CONTAINER_UPSERT_ITEMS: { + this.dataPlaneWriterScope |= SCOPE_CONTAINER_UPSERT_ITEMS.value(); + break; + } + case CONTAINER_DELETE_ITEMS: { + this.dataPlaneWriterScope |= SCOPE_CONTAINER_DELETE_ITEMS.value(); + break; + } + case CONTAINER_EXECUTE_QUERIES: { + this.dataPlaneWriterScope |= SCOPE_CONTAINER_EXECUTE_QUERIES.value(); + break; + } + case CONTAINER_READ_FEEDS: { + this.dataPlaneReaderScope |= SCOPE_CONTAINER_READ_FEEDS.value(); + break; + } + case CONTAINER_EXECUTE_STORED_PROCEDURES: { + this.dataPlaneWriterScope |= SCOPE_CONTAINER_EXECUTE_STORED_PROCEDURES.value(); + break; + } + case CONTAINER_MANAGE_CONFLICTS: { + this.dataPlaneReaderScope |= SCOPE_CONTAINER_READ_CONFLICTS.value(); + this.dataPlaneWriterScope |= SCOPE_CONTAINER_DELETE_CONFLICTS.value(); + break; + } + case CONTAINER_READ_ANY: { + this.dataPlaneReaderScope |= SCOPE_CONTAINER_READ_ALL_ACCESS.value(); + break; + } + case CONTAINER_FULL_ACCESS: { + this.dataPlaneReaderScope |= SCOPE_CONTAINER_READ_ALL_ACCESS.value(); + this.dataPlaneWriterScope |= SCOPE_CONTAINER_WRITE_ALL_ACCESS.value(); + break; + } + + + // Cosmos container item scope. + case ITEM_FULL_ACCESS: { + this.dataPlaneWriterScope |= SCOPE_ITEM_WRITE_ALL_ACCESS.value(); + this.addPermission(SasTokenPermissionKind.ITEM_READ_ANY); + break; + } + case ITEM_READ_ANY: { + this.dataPlaneReaderScope |= SCOPE_ITEM_READ_ALL_ACCESS.value(); + break; + } + case ITEM_READ: { + this.dataPlaneReaderScope |= SCOPE_ITEM_READ.value(); + break; + } + case ITEM_REPLACE: { + this.dataPlaneWriterScope |= SCOPE_ITEM_REPLACE.value(); + break; + } + case ITEM_UPSERT: { + this.dataPlaneWriterScope |= SCOPE_ITEM_UPSERT.value(); + break; + } + case ITEM_DELETE: { + this.dataPlaneWriterScope |= SCOPE_ITEM_DELETE.value(); + break; + } + + case STORE_PROCEDURE_EXECUTE: { + this.dataPlaneWriterScope |= SCOPE_STORED_PROCEDURE_EXECUTE.value(); + break; + } + + default: + throw new IllegalArgumentException("permissionKind"); + } + + return this; + } +} diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/SasTokenPartitionKeyValueRangeImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/SasTokenPartitionKeyValueRangeImpl.java new file mode 100644 index 000000000000..578eb20b4c56 --- /dev/null +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/sastokens/SasTokenPartitionKeyValueRangeImpl.java @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.implementation.sastokens; + +import com.azure.cosmos.models.SasTokenPartitionKeyValueRange; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * Represents a partition key value range to be used when creating a shared access signature token. + */ +public class SasTokenPartitionKeyValueRangeImpl implements SasTokenPartitionKeyValueRange { + String partitionKeyValue; + + /** + * Constructs the partition key value range for which to grant access. + * + * @param partitionKeyValue the partition key value for which to grant access. + */ + public SasTokenPartitionKeyValueRangeImpl(String partitionKeyValue) { + this.partitionKeyValue = partitionKeyValue; + } + + /** + * Gets the partition key value. + * + * @return the partition key value. + */ + @Override + public String getPartitionKey() { + return this.partitionKeyValue; + } + + /** + * Encodes the current partition key value range to be used when generating a shared access signature token. + * + * + * @return a string formatted as "base64_of_partitionKey". + */ + @Override + public String encode() { + return Base64.getUrlEncoder().withoutPadding().encodeToString(this.partitionKeyValue.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Decodes a representation of a partition key value range used when generating a shared access signature token. + * + * The input string must be in "base64_of_partitionKey" format. + * + * @return an instance of SasTokenPartitionKeyValueRange representing the input string. + */ + public static SasTokenPartitionKeyValueRange decode(String encoding) { + byte[] decodedString = Base64.getDecoder().decode(encoding); + + return new SasTokenPartitionKeyValueRangeImpl(new String(decodedString, StandardCharsets.UTF_8)); + } +} diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/SasTokenPartitionKeyValueRange.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/SasTokenPartitionKeyValueRange.java new file mode 100644 index 000000000000..f98bf1f2ef97 --- /dev/null +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/SasTokenPartitionKeyValueRange.java @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.models; + +import com.azure.cosmos.implementation.sastokens.SasTokenPartitionKeyValueRangeImpl; +import com.azure.cosmos.util.Beta; + +/** + * Represents a partition key value range to be used when creating a shared access signature token. + */ +@Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) +public interface SasTokenPartitionKeyValueRange { + + /** + * Creates a {@link SasTokenPartitionKeyValueRange} representing the partition key value for which to grant access. + * + * @param partitionKeyValue the partition key value for which to grant access. + * @return a {@link SasTokenPartitionKeyValueRange} representing the partition key value for which to grant access. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + static SasTokenPartitionKeyValueRange create(String partitionKeyValue) { + return new SasTokenPartitionKeyValueRangeImpl(partitionKeyValue); + } + + /** + * Gets the partition key value. + * + * @return the partition key value. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + String getPartitionKey(); + + /** + * Encodes the current partition key value range to be used when generating a shared access signature token. + * + * @return a string formatted as "base64_of_partitionKey". + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + String encode(); + + /** + * Decodes a representation of a partition key value range used when generating a shared access signature token. + * + * The input string must be in "base64_of_partitionKey" format. + * + * @param encoding the encoded input string. + * @return an instance of SasTokenPartitionKeyValueRange representing the input string. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + static SasTokenPartitionKeyValueRange decode(String encoding) { + return SasTokenPartitionKeyValueRangeImpl.decode(encoding); + } +} diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/SasTokenPermissionKind.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/SasTokenPermissionKind.java new file mode 100644 index 000000000000..8f63ef469a56 --- /dev/null +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/SasTokenPermissionKind.java @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.models; + +import com.azure.cosmos.util.Beta; + +/** + * Defines permission scopes applicable when generating a Cosmos DB shared access signature token. + */ +@Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) +public enum SasTokenPermissionKind { + /** + * Cosmos DB Container Resources scope. + */ + CONTAINER_CREATE_ITEMS, + CONTAINER_REPLACE_ITEMS, + CONTAINER_UPSERT_ITEMS, + CONTAINER_DELETE_ITEMS, + CONTAINER_EXECUTE_QUERIES, + CONTAINER_READ_FEEDS, + CONTAINER_EXECUTE_STORED_PROCEDURES, + CONTAINER_MANAGE_CONFLICTS, + CONTAINER_READ_ANY, + CONTAINER_FULL_ACCESS, + + /** + * Cosmos DB Item scope. + */ + ITEM_READ_ANY, + ITEM_FULL_ACCESS, + ITEM_READ, + ITEM_REPLACE, + ITEM_UPSERT, + ITEM_DELETE, + + /** + * Cosmos DB Store Procedure scope. + */ + STORE_PROCEDURE_EXECUTE, +} diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/SasTokenProperties.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/SasTokenProperties.java new file mode 100644 index 000000000000..ccf021c4655e --- /dev/null +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/SasTokenProperties.java @@ -0,0 +1,275 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.models; + +import com.azure.cosmos.implementation.sastokens.SasTokenImpl; +import com.azure.cosmos.util.Beta; + +import java.time.Duration; +import java.time.Instant; + +/** + * Represents a permission configuration object to be used when creating a Cosmos DB shared access signature token. + */ +@Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) +public interface SasTokenProperties { + /** + * Gets the name of the Cosmos DB database to grant access to. + * + * If database name is an empty string then the access is granted at account level. + * + * @return the name of the database to grant access to. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + String getDatabaseName(); + + /** + * Sets the name of the Cosmos DB database within which the target resource belongs to and for which to grant access to. + * + * If database name is an empty string then the access is granted at account level. + * + * @param databaseName the name of the database to grant access to. + * @return the current permission configuration object. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + SasTokenProperties setDatabaseName(String databaseName); + + /** + * Gets the name of the Cosmos DB container to grant access to or as the parent resource of the target reosource. + * + * A valid non-empty database name must be set; if the container name is an empty string then + * the access is granted at database level. + * + * @return the name of the container to grant access to. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + String getContainerName(); + + /** + * Gets the name of the Cosmos DB container to grant access to. + * + * A valid non-empty database name must be set; if the container name is an empty string then + * the access is granted at database level. + * + * @param containerName the name of the container to grant access to. + * @return the current permission configuration object. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + SasTokenProperties setContainerName(String containerName); + + /** + * Gets the type of the Cosmos resources to grant access to. + * + * A valid non-empty container name must be set in combination with this setting. + * + * @return the type of the resources to grant access to. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + CosmosContainerChildResourceKind getResourceKind(); + + /** + * Gets the name of the Cosmos DB resources to grant access to. + * + * A valid non-empty container name must be set in combination with this setting. + * + * @return the name of the resources to grant access to. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + String getResourceName(); + + /** + * Sets the name of the Cosmos DB resource to grant access to. + * + * A valid non-empty container name must be set in combination with this setting. + * + * @param kind the type of the resource (item, stored procedure etc) to grant access to. + * @param resourceName the prefix name of the resources to grant access to. + * @return the current permission configuration object. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + SasTokenProperties setResourceName(CosmosContainerChildResourceKind kind, String resourceName); + + /** + * Gets the user name or ID registered with this permission object. + * + * A user name or ID is a unique identifier which will be used for tracing and auditing purposes in combination + * with the permission token when authenticating and authorizing Cosmos operations. + * + * @return the name or ID of the user associated with this token. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + String getUser(); + + /** + * Sets the user name or ID registered with this permission object. + * + * A user name or ID is a unique identifier which will be used for tracing and auditing purposes in combination + * with the permission token when authenticating and authorizing Cosmos operations. A non-empty and maximum of + * 40 characters in length string can be set for the user; if a user name or ID is not specified, a random 10 + * length string will be used instead. + * + * @param user the name or ID of the user associated with this token. + * @return the current permission configuration object. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + SasTokenProperties setUser(String user); + + /** + * Gets the tag for the user name or ID registered with this permission object. + * + * The user tag is a unique identifier which will be used for tracing and auditing purposes in combination + * with the permission token when authenticating and authorizing Cosmos operations. + * + * @return the tag corresponding to the user associated with this token. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + String getUserTag(); + + /** + * Sets the tag for the user name or ID registered with this permission object. + * + * The user tag is a unique identifier which will be used for tracing and auditing purposes in combination + * with the permission token when authenticating and authorizing Cosmos operations. + * + * @param userTag the tag corresponding to the user associated with this token. + * @return the current permission configuration object. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + SasTokenProperties setUserTag(String userTag); + + /** + * Gets the expiry time (GMT time zone) for the shared access signature associated with the permission instance. + * + * @return the expiry time (GMT time zone) for a shared access signature associated with the permission instance. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + Instant getExpiryTime(); + + /** + * Sets the expiry time for the shared access signature associated with the permission instance. + *

+ * Default is 2 hours from start time; the maximum duration allowed to be set as expiry time is 24 hours from + * the specified start time. + * + * @param expiryTime the expiry time for a shared access signature token associated with the permission + * instance, up to 24 hours from the start time. + * @return the current permission configuration object. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + SasTokenProperties setExpiryTime(Duration expiryTime); + + /** + * Gets the start time (GMT time zone) for the shared access signature associated with the permission instance. + * + * @return the start time (GMT time zone) for a shared access signature associated with the permission instance. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + Instant getStartTime(); + + /** + * Sets the start time for the shared access signature associated with the permission instance. + * + * Default is current time (now). + * + * @param startTime the start time for a shared access signature associated with the permission instance. + * @return the current permission configuration object. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + SasTokenProperties setStartTime(Instant startTime); + + /** + * Gets the list of partition key value ranges to be used when creating a shared access signature token. + * + * @return the set of partition key value ranges to be used when creating a shared access signature token. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + Iterable getPartitionKeyValueRanges(); + + /** + * Sets the list of partition key values to be used when creating a shared access signature token. + * + * In the presence of this setting only operation using documents with the partition key value within these ranges + * are allowed; default is empty set, any partition key value is allowed. + * + * @param partitionKeyValues the list of partition key values to be used when creating a shared access signature token. + * @return the current permission configuration object. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + SasTokenProperties setPartitionKeyValueRanges(Iterable partitionKeyValues); + + /** + * Adds a partition key value to be used when creating a shared access signature token. + * + * In the presence of this setting only operation using documents with the partition key value within these ranges + * are allowed; default is empty set, any partition key value is allowed. + * + * @param partitionKeyValue the partition key value to be used when creating a shared access signature token. + * @return the current permission configuration object. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + SasTokenProperties addPartitionKeyValue(String partitionKeyValue); + + /** + * Adds a permission setting to execute specific Cosmos operation or set of operations. + * If no specific permission was set, default is read permissions at the container level. + * + * @param permissionKind the permission setting which allows execution of specific Cosmos operation or set of operations. + * @return the current permission configuration object. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + SasTokenProperties addPermission(SasTokenPermissionKind permissionKind); + + /** + * Creates a permission configuration to be used when creating a Cosmos shared access signature token. + * + * @param user the user that will be associated with this shared access signature token. + * @param databaseName the database name that will be associated with this shared access signature token. + * @param containerName the container name that will be associated with this shared access signature token. + * @return an instance of {@link SasTokenProperties} that will be used to generated the token. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + static SasTokenProperties create(String user, String databaseName, String containerName) { + return new SasTokenImpl() + .setUser(user) + .setDatabaseName(databaseName) + .setContainerName(containerName); + } + + /** + * Creates a Cosmos shared access signature token using the specified account key and a HMACSHA256 encoder. + * + * @param key the Cosmos key that will be used to generate a shared access signature token. + * @return the shared access signature token. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + String getSasTokenValueUsingHMAC(String key); + + /** + * Creates a Cosmos shared access signature token using the specified account key and a HMACSHA256 encoder. + *

+ * Providing key type will help expedite the authentication and authorization executed by the Cosmos service. + * + * @param key the Cosmos key that will be used to generate a shared access signature token. + * @param keyType the Cosmos key type that will be used to generate a shared access signature token. + * @return the shared access signature token. + */ + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + String getSasTokenValueUsingHMAC(String key, CosmosKeyType keyType); + + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + enum CosmosContainerChildResourceKind { + ITEM, + STORED_PROCEDURE, + USER_DEFINED_FUNCTION, + TRIGGER + } + + @Beta(value = Beta.SinceVersion.V4_11_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + enum CosmosKeyType { + PRIMARY_MASTER, + SECONDARY_MASTER, + PRIMARY_READONLY, + SECONDARY_READONLY + } +} diff --git a/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/implementation/sastokens/SasTokenTests.java b/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/implementation/sastokens/SasTokenTests.java new file mode 100644 index 000000000000..2cbe61d55d85 --- /dev/null +++ b/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/implementation/sastokens/SasTokenTests.java @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.cosmos.implementation.sastokens; + +import com.azure.cosmos.models.SasTokenProperties; +import org.testng.annotations.Test; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.util.Base64; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SasTokenTests { + private final static String TEST_KEY = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="; + + private final static String TEST_EXPECTED_SIMPLE_SASTOKEN = "type=sas&ver=1.0&sig=nRJ8Lp6toJT3SVzplvdNud5Z7LnSPxEf2/suT4up0X4=;dXNlcjEKCi9kYnMvZGIxL2NvbGxzL2NvbGwxLwoKMAoxQzIwCjAKNjAKMApGRkZGRkZGRgowCg=="; + private final static String TEST_EXPECTED_SIMPLE_PAYLOAD = "user1\n" + + "\n" + + "/dbs/db1/colls/coll1/\n" + + "\n" + + "0\n" + + "1C20\n" + + "0\n" + + "60\n" + + "0\n" + + "FFFFFFFF\n" + + "0\n"; + + @Test(groups = "unit") + public void createSimpleSasToken() { + SasTokenProperties sasTokenProperties = SasTokenProperties.create("user1", "db1", "coll1") + .setStartTime(Instant.ofEpochMilli(0)); + assertThat("user1").isEqualTo(sasTokenProperties.getUser()); + assertThat("").isEqualTo(sasTokenProperties.getUserTag()); + assertThat("db1").isEqualTo(sasTokenProperties.getDatabaseName()); + assertThat("coll1").isEqualTo(sasTokenProperties.getContainerName()); + + String sasTokenValue = sasTokenProperties.getSasTokenValueUsingHMAC(TEST_KEY); + assertThat(TEST_EXPECTED_SIMPLE_SASTOKEN) + .isEqualTo(sasTokenValue); + + String[] tokenSegments = sasTokenValue.split("&"); + assertThat(3).isEqualTo(tokenSegments.length); + + String[] sasTokenParts = tokenSegments[2].split(";"); + assertThat(2).isEqualTo(sasTokenParts.length); + + byte[] decodedString = Base64.getDecoder().decode(sasTokenParts[1]); + String sasTokenPayload = new String(decodedString, StandardCharsets.UTF_8); + assertThat(TEST_EXPECTED_SIMPLE_PAYLOAD) + .isEqualTo(sasTokenPayload); + } + + private final static String TEST_EXPECTED_EXPIRY_SASTOKEN = "type=sas&ver=1.0&sig=pCgZFxV9JQN1i3vzYNTfQldW1No7I+MSgN628TZcJAI=;dXNlcjEKCi9kYnMvZGIxL2NvbGxzL2NvbGwxLwoKNUZFRTY2MDEKNjIxM0I3MDEKMAo2MAowCkZGRkZGRkZGCjAK"; + private final static String TEST_EXPECTED_EXPIRY_PAYLOAD = "user1\n" + + "\n" + + "/dbs/db1/colls/coll1/\n" + + "\n" + + "5FEE6601\n" + + "6213B701\n" + + "0\n" + + "60\n" + + "0\n" + + "FFFFFFFF\n" + + "0\n"; + @Test(groups = "unit") + public void createSasTokenWithExpiryTime() { + SasTokenProperties sasTokenProperties = SasTokenProperties.create("user1", "db1", "coll1") + .setStartTime(Instant.parse("2021-01-01T00:00:01.00Z")) + .setExpiryTime(Duration.ofHours(10000)); + assertThat("user1").isEqualTo(sasTokenProperties.getUser()); + assertThat("").isEqualTo(sasTokenProperties.getUserTag()); + assertThat("db1").isEqualTo(sasTokenProperties.getDatabaseName()); + assertThat("coll1").isEqualTo(sasTokenProperties.getContainerName()); + assertThat(1609459201L).isEqualTo(sasTokenProperties.getStartTime().getEpochSecond()); + assertThat(1645459201L).isEqualTo(sasTokenProperties.getExpiryTime().getEpochSecond()); + + String sasTokenValue = sasTokenProperties.getSasTokenValueUsingHMAC(TEST_KEY); + assertThat(TEST_EXPECTED_EXPIRY_SASTOKEN) + .isEqualTo(sasTokenValue); + + String[] tokenSegments = sasTokenValue.split("&"); + assertThat(3).isEqualTo(tokenSegments.length); + + String[] sasTokenParts = tokenSegments[2].split(";"); + assertThat(2).isEqualTo(sasTokenParts.length); + + byte[] decodedString = Base64.getDecoder().decode(sasTokenParts[1]); + String sasTokenPayload = new String(decodedString, StandardCharsets.UTF_8); + assertThat(TEST_EXPECTED_EXPIRY_PAYLOAD) + .isEqualTo(sasTokenPayload); + } +}