From aa2e7a0ac120c7642567e413ed5edab5e869ee68 Mon Sep 17 00:00:00 2001 From: pemari-msft Date: Tue, 29 Sep 2015 15:06:34 -0700 Subject: [PATCH] Storage Client Library 6.0.0 --- BreakingChanges.txt | 7 + BuildAspNetK.cmd | 10 +- .../AssemblyInfo.cs | 6 +- .../WindowsAzure.StorageK.nuspec | 96 +- .../project.json | 68 +- .../Analytics/CloudAnalyticsClient.cs | 20 + .../Analytics/LogRecordStreamReader.cs | 2 +- Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs | 2 +- .../Shared/Protocol/HttpWebRequestFactory.cs | 2 +- .../Shared/Protocol/StorageService.cs | 5 + Lib/ClassLibraryCommon/Table/CloudTable.cs | 4 +- .../Table/DataServices/TableServiceContext.cs | 4 - .../TableOperationHttpRequestFactory.cs | 10 +- .../TableOperationHttpResponseParsers.cs | 11 +- .../Table/TableEncryptionPolicy.cs | 3 +- Lib/Common/Auth/StorageCredentials.cs | 24 +- Lib/Common/Blob/CloudBlob.Common.cs | 37 +- Lib/Common/Blob/CloudBlobContainer.Common.cs | 25 +- Lib/Common/Blob/ICloudBlob.Common.cs | 8 +- .../Blob/SharedAccessBlobPermissions.cs | 14 +- Lib/Common/Blob/SharedAccessBlobPolicy.cs | 22 +- Lib/Common/CloudStorageAccount.cs | 59 +- .../Core/Auth/SharedAccessSignatureHelper.cs | 241 ++- .../Core/Executor/StorageCommandBase.cs | 2 +- Lib/Common/Core/SR.cs | 10 +- Lib/Common/Core/Util/CommonUtility.cs | 4 +- Lib/Common/Core/Util/Exceptions.cs | 12 +- Lib/Common/File/CloudFile.Common.cs | 18 +- Lib/Common/File/CloudFileShare.Common.cs | 18 +- .../File/Protocol/FileServiceProperties.cs | 47 +- .../File/SharedAccessFilePermissions.cs | 7 +- Lib/Common/File/SharedAccessFilePolicy.cs | 11 +- Lib/Common/IPAddressOrRange.cs | 163 ++ Lib/Common/Queue/CloudQueue.Common.cs | 54 +- Lib/Common/Shared/Protocol/Constants.cs | 50 +- .../Shared/Protocol/ServiceProperties.cs | 12 +- Lib/Common/SharedAccessAccountPermissions.cs | 73 + Lib/Common/SharedAccessAccountPolicy.cs | 190 +++ .../SharedAccessAccountResourceTypes.cs | 48 + Lib/Common/SharedAccessAccountServices.cs | 53 + Lib/Common/SharedAccessProtocol.cs | 37 + Lib/Common/StorageException.cs | 9 - Lib/Common/StorageExtendedErrorInformation.cs | 15 +- Lib/Common/Table/CloudTable.Common.cs | 72 +- Lib/Common/Table/CloudTableClient.Common.cs | 16 +- .../Table/TableBatchOperation.Common.cs | 2 +- Lib/Common/Table/TableEntity.cs | 15 +- Lib/Common/Table/TableOperation.Common.cs | 4 +- Lib/Common/Table/TablePayloadFormat.cs | 3 + Lib/Common/Table/TableQuery.Common.cs | 7 +- Lib/Common/Table/TableRequestOptions.cs | 27 +- .../Table/TableStorageEntityContainer.cs | 0 .../Table/TableStorageModel.cs | 0 .../Microsoft.WindowsAzure.Storage.csproj | 34 +- Lib/Portable/Properties/AssemblyInfo.cs | 6 +- Lib/Portable/packages.config | 1 + Lib/WindowsDesktop/GlobalSuppressions.cs | 46 + Lib/WindowsDesktop/Properties/AssemblyInfo.cs | 12 +- .../WindowsAzure.Storage.nuspec | 4 +- Lib/WindowsPhone/Properties/AssemblyInfo.cs | 4 +- .../Microsoft.WindowsAzure.Storage.csproj | 10 +- Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs | 4 +- Lib/WindowsPhoneRT/packages.config | 1 + Lib/WindowsRuntime/Core/Executor/Executor.cs | 10 +- .../Microsoft.WindowsAzure.Storage.csproj | 10 +- Lib/WindowsRuntime/Properties/AssemblyInfo.cs | 4 +- Lib/WindowsRuntime/Table/CloudTable.cs | 20 +- Lib/WindowsRuntime/Table/CloudTableClient.cs | 2 + .../Protocol/HttpResponseAdapterMessage.cs | 9 +- .../TableOperationHttpRequestFactory.cs | 145 +- .../TableOperationHttpResponseParsers.cs | 660 ++++++-- .../Table/TableBatchOperation.cs | 5 +- Lib/WindowsRuntime/Table/TableOperation.cs | 19 +- Lib/WindowsRuntime/Table/TableQuery.cs | 16 +- Lib/WindowsRuntime/Table/TableQueryGeneric.cs | 5 +- Lib/WindowsRuntime/packages.config | 1 + README.md | 2 +- .../project.json | 2 +- Test/ClassLibraryCommon/AccountSASTests.cs | 1454 +++++++++++++++++ .../Analytics/CloudAnalyticsClientTests.cs | 10 + Test/ClassLibraryCommon/Blob/SASTests.cs | 591 ++++--- .../File/FileAnalyticsUnitTests.cs | 16 + Test/ClassLibraryCommon/File/FileSASTests.cs | 306 +++- .../Queue/CloudQueueTest.cs | 293 +++- .../Table/CloudTableCRUDUnitTests.cs | 74 +- .../Table/CloudTableClientTests.cs | 48 +- .../Table/SAS/TableSasUnitTests.cs | 402 +++-- .../Table/TableBatchOperationTest.cs | 258 +-- .../Table/TableEntityEncryptionTests.cs | 2 + .../Table/TableOperationUnitTests.cs | 173 +- .../Table/TableQueryGenericTests.cs | 218 ++- .../Table/TableQueryTests.cs | 101 +- Test/ClassLibraryCommon/TestHelper.cs | 2 +- Test/Common/Table/Entities/ComplexEntity.cs | 9 +- Test/Common/TestBase.Common.cs | 20 +- .../TestConfigProcess/TenantConfiguration.cs | 5 + .../TestConfigProcess/TestConfigurations.cs | 5 + Test/Common/TestHelper.Common.cs | 50 +- ...Microsoft.WindowsAzure.Storage.Test.csproj | 63 +- .../WindowsDesktop/Properties/AssemblyInfo.cs | 4 +- .../DataServices/CancellationUnitTests.cs | 2 +- .../DataServices/SAS/TableSasUnitTests.cs | 78 +- .../DataServices/TableEntityUnitTests.cs | 2 + .../TableServiceQueryUnitTests.cs | 2 + .../WindowsDesktop/Table/TableManglerTests.cs | 2 +- Test/WindowsPhone/Properties/AssemblyInfo.cs | 4 +- .../WindowsPhone81/Properties/AssemblyInfo.cs | 4 +- .../Table/CloudTableCRUDUnitTaskTests.cs | 2 + .../Table/CloudTableClientTaskTest.cs | 2 + .../Table/TableBatchOperationTaskTest.cs | 2 + .../Table/TableOperationUnitTaskTests.cs | 2 + .../Table/TableQueryGenericTaskTests.cs | 2 + .../Table/TableQueryTaskTests.cs | 2 + .../Properties/AssemblyInfo.cs | 4 +- Test/WindowsRuntime/AccountSasTests.cs | 1439 ++++++++++++++++ ...crosoft.WindowsAzure.StorageRT.Test.csproj | 4 +- .../WindowsRuntime/Properties/AssemblyInfo.cs | 4 +- .../Table/CloudTableCRUDUnitTests.cs | 95 +- .../Table/CloudTableClientTest.cs | 58 +- .../Table/TableBatchOperationTest.cs | 286 +++- .../Table/TableOperationUnitTests.cs | 268 ++- .../Table/TableQueryGenericTests.cs | 41 +- Test/WindowsRuntime/Table/TableQueryTests.cs | 97 +- changelog.txt | 15 + global.json | 2 +- 125 files changed, 7485 insertions(+), 1729 deletions(-) create mode 100644 Lib/Common/IPAddressOrRange.cs create mode 100644 Lib/Common/SharedAccessAccountPermissions.cs create mode 100644 Lib/Common/SharedAccessAccountPolicy.cs create mode 100644 Lib/Common/SharedAccessAccountResourceTypes.cs create mode 100644 Lib/Common/SharedAccessAccountServices.cs create mode 100644 Lib/Common/SharedAccessProtocol.cs rename Lib/{ClassLibraryCommon => Common}/Table/TableStorageEntityContainer.cs (100%) rename Lib/{ClassLibraryCommon => Common}/Table/TableStorageModel.cs (100%) create mode 100644 Test/ClassLibraryCommon/AccountSASTests.cs create mode 100644 Test/WindowsRuntime/AccountSasTests.cs diff --git a/BreakingChanges.txt b/BreakingChanges.txt index a97e25fbd..7173bc76c 100644 --- a/BreakingChanges.txt +++ b/BreakingChanges.txt @@ -1,3 +1,10 @@ +Tracking Breaking Changes since 5.0 + +- All: Certain error messages returned by the Azure Storage Services have changed in the 2015-04-05 service version. +- All: Removed deprecated Shared Access Signature methods that take version as a parameter. +- Tables: Altered default settings for echoContent and payloadFormat. AtomPub payloadFormat is now obsolete. +- Tables: Changed the client error message for batch transactions to be more clear. + Tracking Breaking Changes since 4.0 - All: Removed dependency on Microsoft.WindowsAzure.ConfigurationManager. diff --git a/BuildAspNetK.cmd b/BuildAspNetK.cmd index 6c5e45807..290022b6b 100644 --- a/BuildAspNetK.cmd +++ b/BuildAspNetK.cmd @@ -1,8 +1,8 @@ pushd %~dp0 -call Tools\nuget.exe install dnx-coreclr-win-x86 -Version 1.0.0-beta5 -Prerelease -call Tools\nuget.exe install dnx-clr-win-x86 -Version 1.0.0-beta5 -Prerelease -call dnx-coreclr-win-x86.1.0.0-beta5\bin\dnu restore -call dnx-clr-win-x86.1.0.0-beta5\bin\dnu restore +call Tools\nuget.exe install dnx-coreclr-win-x86 -Version 1.0.0-beta7 -Prerelease +call Tools\nuget.exe install dnx-clr-win-x86 -Version 1.0.0-beta7 -Prerelease +call dnx-coreclr-win-x86.1.0.0-beta7\bin\dnu restore +call dnx-clr-win-x86.1.0.0-beta7\bin\dnu restore cd Lib\AspNet\Microsoft.WindowsAzure.Storage -call ..\..\..\dnx-coreclr-win-x86.1.0.0-beta5\bin\dnu build --configuration release +call ..\..\..\dnx-coreclr-win-x86.1.0.0-beta7\bin\dnu build --configuration release popd diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs index 7786b150f..5d7c5f856 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs @@ -33,8 +33,8 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("5.0.3.0")] -[assembly: AssemblyFileVersion("5.0.3.0")] -[assembly: AssemblyInformationalVersion("5.0.3.0-preview")] +[assembly: AssemblyVersion("6.0.1.0")] +[assembly: AssemblyFileVersion("6.0.1.0")] +[assembly: AssemblyInformationalVersion("6.0.1.0-preview")] [assembly: InternalsVisibleTo("Microsoft.WindowsAzure.Storage.Test")] diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec b/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec index 714eff202..1efe416de 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage - 5.0.3-preview + 6.0.1-preview Windows Azure Storage Microsoft Microsoft @@ -16,55 +16,56 @@ Microsoft Azure Storage team's blog - http://blogs.msdn.com/b/windowsazurestorag A client library for working with Microsoft Azure storage services including blobs, files, tables, and queues. Microsoft, Azure, Storage, Table, Blob, File, Queue, Scalable, windowsazureofficial - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + @@ -74,9 +75,11 @@ Microsoft Azure Storage team's blog - http://blogs.msdn.com/b/windowsazurestorag + + @@ -85,6 +88,7 @@ Microsoft Azure Storage team's blog - http://blogs.msdn.com/b/windowsazurestorag + diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json index b955b3742..e2da38cfa 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json @@ -1,54 +1,54 @@ { - "version": "5.0.3.0", + "version": "6.0.1.0", "authors": [ "Microsoft Corporation" ], "description": "Azure Storage SDK for ASP.NET 5 Preview", "dependencies": { - "Microsoft.Data.OData": "5.6.3", - "Microsoft.Data.Services.Client": "5.6.3", - "System.Spatial": "5.6.3" + "Microsoft.Data.OData": "5.6.4", + "Microsoft.Data.Services.Client": "5.6.4", + "System.Spatial": "5.6.4", + "Newtonsoft.Json": "6.0.8" }, "frameworks": { "dnxcore50": { "dependencies": { - "System.Collections": "4.0.10-beta-*", - "System.Collections.Concurrent": "4.0.0-beta-*", + "System.Collections": "4.0.11-beta-*", + "System.Collections.Concurrent": "4.0.11-beta-*", "System.Collections.Specialized": "4.0.0-beta-*", - "System.Diagnostics.Debug": "4.0.10-beta-*", - "System.Diagnostics.Tools": "4.0.0-beta-*", + "System.Diagnostics.Debug": "4.0.11-beta-*", + "System.Diagnostics.Tools": "4.0.1-beta-*", "System.Diagnostics.TraceSource": "4.0.0-beta-*", - "System.Diagnostics.Tracing": "4.0.10-beta-*", - "System.Dynamic.Runtime": "4.0.0-beta-*", - "System.Globalization": "4.0.10-beta-*", - "System.IO": "4.0.10-beta-*", - "System.IO.FileSystem": "4.0.0-beta-*", - "System.IO.FileSystem.Primitives": "4.0.0-beta-*", - "System.Linq": "4.0.0-beta-*", - "System.Linq.Expressions": "4.0.0-beta-*", + "System.Diagnostics.Tracing": "4.0.21-beta-*", + "System.Dynamic.Runtime": "4.0.11-beta-*", + "System.Globalization": "4.0.11-beta-*", + "System.IO": "4.0.11-beta-*", + "System.IO.FileSystem": "4.0.1-beta-*", + "System.IO.FileSystem.Primitives": "4.0.1-beta-*", + "System.Linq": "4.0.1-beta-*", + "System.Linq.Expressions": "4.0.11-beta-*", "System.Linq.Queryable": "4.0.0-beta-*", - "System.Net.Http": "4.0.0-beta-*", - "System.Net.Primitives": "4.0.10-beta-*", - "System.Reflection": "4.0.10-beta-*", - "System.Reflection.Extensions": "4.0.0-beta-*", - "System.Reflection.TypeExtensions": "4.0.0-beta-*", + "System.Net.Http": "4.0.1-beta-*", + "System.Net.Primitives": "4.0.11-beta-*", + "System.Reflection": "4.1.0-beta-*", + "System.Reflection.Extensions": "4.0.1-beta-*", + "System.Reflection.TypeExtensions": "4.0.1-beta-*", "System.Runtime": "4.0.20-beta-*", - "System.Runtime.Extensions": "4.0.10-beta-*", - "System.Runtime.InteropServices": "4.0.20-beta-*", + "System.Runtime.Extensions": "4.0.11-beta-*", + "System.Runtime.InteropServices": "4.0.21-beta-*", "System.Runtime.Serialization.Primitives": "4.0.0-beta-*", "System.Runtime.Serialization.Xml": "4.0.10-beta-*", "System.Security.Cryptography.Encoding": "4.0.0-beta-*", - "System.Security.Cryptography.Encryption": "4.0.0-beta-*", - "System.Security.Cryptography.Hashing": "4.0.0-beta-*", - "System.Security.Cryptography.Hashing.Algorithms": "4.0.0-beta-*", - "System.Text.Encoding": "4.0.10-beta-*", - "System.Text.Encoding.Extensions": "4.0.10-beta-*", - "System.Text.RegularExpressions": "4.0.10-beta-*", - "System.Threading": "4.0.0-beta-*", - "System.Threading.Tasks": "4.0.10-beta-*", + "System.Security.Cryptography.Primitives": "4.0.0-beta-*", + "System.Security.Cryptography.Algorithms": "4.0.0-beta-*", + "System.Text.Encoding": "4.0.11-beta-*", + "System.Text.Encoding.Extensions": "4.0.11-beta-*", + "System.Text.RegularExpressions": "4.0.11-beta-*", + "System.Threading": "4.0.11-beta-*", + "System.Threading.Tasks": "4.0.11-beta-*", "System.Threading.Thread": "4.0.0-beta-*", "System.Threading.ThreadPool": "4.0.10-beta-*", - "System.Threading.Timer": "4.0.0-beta-*", - "System.Xml.ReaderWriter": "4.0.10-beta-*", - "System.Xml.XDocument": "4.0.0-beta-*", + "System.Threading.Timer": "4.0.1-beta-*", + "System.Xml.ReaderWriter": "4.0.11-beta-*", + "System.Xml.XDocument": "4.0.11-beta-*", "System.Xml.XmlSerializer": "4.0.0-beta-*" } }, diff --git a/Lib/ClassLibraryCommon/Analytics/CloudAnalyticsClient.cs b/Lib/ClassLibraryCommon/Analytics/CloudAnalyticsClient.cs index ebe70052b..5a39dc6cd 100644 --- a/Lib/ClassLibraryCommon/Analytics/CloudAnalyticsClient.cs +++ b/Lib/ClassLibraryCommon/Analytics/CloudAnalyticsClient.cs @@ -121,6 +121,16 @@ public CloudTable GetHourMetricsTable(StorageService service, StorageLocation lo return this.tableClient.GetTableReference(Constants.AnalyticsConstants.MetricsHourSecondaryTransactionsTable); } + case StorageService.File: + if (location == StorageLocation.Primary) + { + return this.tableClient.GetTableReference(Constants.AnalyticsConstants.MetricsHourPrimaryTransactionsFile); + } + else + { + return this.tableClient.GetTableReference(Constants.AnalyticsConstants.MetricsHourSecondaryTransactionsFile); + } + default: throw new ArgumentException(SR.InvalidStorageService); } @@ -176,6 +186,16 @@ public CloudTable GetMinuteMetricsTable(StorageService service, StorageLocation return this.tableClient.GetTableReference(Constants.AnalyticsConstants.MetricsMinuteSecondaryTransactionsTable); } + case StorageService.File: + if (location == StorageLocation.Primary) + { + return this.tableClient.GetTableReference(Constants.AnalyticsConstants.MetricsMinutePrimaryTransactionsFile); + } + else + { + return this.tableClient.GetTableReference(Constants.AnalyticsConstants.MetricsMinuteSecondaryTransactionsFile); + } + default: throw new ArgumentException(SR.InvalidStorageService); } diff --git a/Lib/ClassLibraryCommon/Analytics/LogRecordStreamReader.cs b/Lib/ClassLibraryCommon/Analytics/LogRecordStreamReader.cs index c4a6d2e9e..ed477b706 100644 --- a/Lib/ClassLibraryCommon/Analytics/LogRecordStreamReader.cs +++ b/Lib/ClassLibraryCommon/Analytics/LogRecordStreamReader.cs @@ -180,7 +180,7 @@ public void EndCurrentRecord() bool parsed = DateTimeOffset.TryParseExact( temp, format, - null /* provider */, + CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out tempDateTime); if (parsed) diff --git a/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs index 269c8a5c7..a5f08d6f3 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs @@ -66,7 +66,7 @@ public CloudBlobStream OpenWrite(long? size, AccessCondition accessCondition = n if (modifiedOptions.EncryptionPolicy != null) { - transform = options.EncryptionPolicy.CreateAndSetEncryptionContext(this.Metadata, true /* noPadding */); + transform = modifiedOptions.EncryptionPolicy.CreateAndSetEncryptionContext(this.Metadata, true /* noPadding */); } #endif diff --git a/Lib/ClassLibraryCommon/Shared/Protocol/HttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/Shared/Protocol/HttpWebRequestFactory.cs index 0055af920..901d28f14 100644 --- a/Lib/ClassLibraryCommon/Shared/Protocol/HttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/Shared/Protocol/HttpWebRequestFactory.cs @@ -52,7 +52,7 @@ internal static HttpWebRequest CreateWebRequest(string method, Uri uri, int? tim builder.Add("timeout", timeout.Value.ToString(CultureInfo.InvariantCulture)); } -#if WINDOWS_PHONE +#if !WINDOWS_DESKTOP // Windows Phone does not allow the caller to disable caching, so a random GUID // is added to every URI to make it look a different request. builder.Add("randomguid", Guid.NewGuid().ToString("N")); diff --git a/Lib/ClassLibraryCommon/Shared/Protocol/StorageService.cs b/Lib/ClassLibraryCommon/Shared/Protocol/StorageService.cs index 845a554dc..dc20146ca 100644 --- a/Lib/ClassLibraryCommon/Shared/Protocol/StorageService.cs +++ b/Lib/ClassLibraryCommon/Shared/Protocol/StorageService.cs @@ -36,5 +36,10 @@ public enum StorageService /// Table Service. /// Table, + + /// + /// File Service. + /// + File, } } diff --git a/Lib/ClassLibraryCommon/Table/CloudTable.cs b/Lib/ClassLibraryCommon/Table/CloudTable.cs index 94c568824..0cf09c036 100644 --- a/Lib/ClassLibraryCommon/Table/CloudTable.cs +++ b/Lib/ClassLibraryCommon/Table/CloudTable.cs @@ -906,7 +906,7 @@ public void Create(TableRequestOptions requestOptions = null, OperationContext o DynamicTableEntity tblEntity = new DynamicTableEntity(); tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - TableOperation operation = new TableOperation(tblEntity, TableOperationType.Insert); + TableOperation operation = new TableOperation(tblEntity, TableOperationType.Insert, false); operation.IsTableEntity = true; CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); @@ -941,7 +941,7 @@ public ICancellableAsyncResult BeginCreate(TableRequestOptions requestOptions, O DynamicTableEntity tblEntity = new DynamicTableEntity(); tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - TableOperation operation = new TableOperation(tblEntity, TableOperationType.Insert); + TableOperation operation = new TableOperation(tblEntity, TableOperationType.Insert, false); operation.IsTableEntity = true; CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); diff --git a/Lib/ClassLibraryCommon/Table/DataServices/TableServiceContext.cs b/Lib/ClassLibraryCommon/Table/DataServices/TableServiceContext.cs index 502dd5b37..f0d8815c3 100644 --- a/Lib/ClassLibraryCommon/Table/DataServices/TableServiceContext.cs +++ b/Lib/ClassLibraryCommon/Table/DataServices/TableServiceContext.cs @@ -41,11 +41,7 @@ public class TableServiceContext : DataServiceContext, IDisposable { private IAuthenticationHandler authenticationHandler; -#if WINDOWS_DESKTOP private TablePayloadFormat payloadFormat = TablePayloadFormat.Json; -#else - private PayloadFormat payloadFormat = PayloadFormat.AtomPub; -#endif /// /// Initializes a new instance of the class. diff --git a/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpRequestFactory.cs b/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpRequestFactory.cs index 23daf652d..05b17024c 100644 --- a/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpRequestFactory.cs +++ b/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpRequestFactory.cs @@ -42,6 +42,8 @@ internal static HttpWebRequest BuildRequestCore(Uri uri, UriQueryBuilder builder internal static HttpWebRequest BuildRequestForTableQuery(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext ctx, TablePayloadFormat payloadFormat) { HttpWebRequest msg = BuildRequestCore(uri, builder, "GET", timeout, useVersionHeader, ctx); + + // Set Accept and Content-Type based on the payload format. SetAcceptHeaderForHttpWebRequest(msg, payloadFormat); Logger.LogInformational(ctx, SR.PayloadFormat, payloadFormat); return msg; @@ -81,7 +83,7 @@ internal static Tuple BuildRequestForTableOperation(Uri } } - // prefer header + // Prefer header if (operation.OperationType == TableOperationType.Insert) { msg.Headers.Add("Prefer", operation.EchoContent ? "return-content" : "return-no-content"); @@ -101,7 +103,6 @@ internal static Tuple BuildRequestForTableOperation(Uri }; HttpWebRequestAdapterMessage adapterMsg = new HttpWebRequestAdapterMessage(msg, bufferManager); - if (operation.HttpMethod != "HEAD" && operation.HttpMethod != "GET") { SetContentTypeForAdapterMessage(adapterMsg, payloadFormat); @@ -132,8 +133,8 @@ internal static Tuple BuildRequestForTableBatchOperation HttpWebRequestAdapterMessage adapterMsg = new HttpWebRequestAdapterMessage(msg, bufferManager); + // Start Batch ODataMessageWriter odataWriter = new ODataMessageWriter(adapterMsg, writerSettings); - ODataBatchWriter batchWriter = odataWriter.CreateODataBatchWriter(); batchWriter.WriteStartBatch(); @@ -252,7 +253,7 @@ internal static IEnumerable GetPropertiesWithKeys(ITableEntity en #endregion #region Set Headers - + #pragma warning disable 0618 private static void SetAcceptHeaderForHttpWebRequest(HttpWebRequest msg, TablePayloadFormat payloadFormat) { if (payloadFormat == TablePayloadFormat.AtomPub) @@ -320,6 +321,7 @@ private static void SetAcceptAndContentTypeForODataBatchMessage(ODataBatchOperat mimePartMsg.SetHeader(Constants.HeaderConstants.PayloadContentTypeHeader, Constants.JsonContentTypeHeaderValue); } } + #pragma warning restore 0618 #endregion } } diff --git a/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpResponseParsers.cs b/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpResponseParsers.cs index 7935485fb..2278d5f46 100644 --- a/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpResponseParsers.cs +++ b/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpResponseParsers.cs @@ -100,9 +100,12 @@ internal static TableResult TableOperationPostProcess(TableResult result, TableO } else if (operation.OperationType == TableOperationType.Insert && (!operation.EchoContent)) { - result.Etag = HttpResponseParsers.GetETag(resp); - operation.Entity.ETag = result.Etag; - operation.Entity.Timestamp = ParseETagForTimestamp(result.Etag); + if (HttpResponseParsers.GetETag(resp) != null) + { + result.Etag = HttpResponseParsers.GetETag(resp); + operation.Entity.ETag = result.Etag; + operation.Entity.Timestamp = ParseETagForTimestamp(result.Etag); + } } else { @@ -235,7 +238,7 @@ internal static IList TableBatchOperationPostProcess(IList EncryptEntity(IDictionary + /// A value indicating whether or not this StorageCredentials object is meant for HTTPS access only. + /// + private bool isHttpsOnly; + /// /// Gets the associated shared access signature token for the credentials. /// @@ -84,7 +89,7 @@ public bool IsSAS { get { - return (this.SASToken != null) && (this.AccountName == null); + return (this.SASToken != null); } } @@ -287,6 +292,16 @@ public byte[] ExportKey() #endif public Uri TransformUri(Uri resourceUri) { + CommonUtility.AssertNotNull("resourceUri", resourceUri); +#if WINDOWS_RT || PORTABLE || ASPNET_K + if (this.isHttpsOnly && (string.CompareOrdinal(resourceUri.Scheme, "https") != 0)) +#else + if (this.isHttpsOnly && (string.CompareOrdinal(resourceUri.Scheme, Uri.UriSchemeHttps) != 0)) +#endif + { + throw new ArgumentException(SR.CannotTransformNonHttpsUriWithHttpsOnlyCredentials); + } + if (this.IsSAS) { return this.queryBuilder.AddToUri(resourceUri); @@ -365,13 +380,20 @@ public bool Equals(StorageCredentials other) private void UpdateQueryBuilder() { this.queryBuilder = new UriQueryBuilder(); + this.isHttpsOnly = false; IDictionary parameters = HttpWebUtility.ParseQueryString(this.SASToken); foreach (KeyValuePair parameter in parameters) { + if (string.CompareOrdinal(parameter.Key, Constants.QueryConstants.SignedProtocols) == 0 && string.CompareOrdinal(parameter.Value, "https") == 0) + { + this.isHttpsOnly = true; + } + this.queryBuilder.Add(parameter.Key, parameter.Value); } this.queryBuilder.Add(Constants.QueryConstants.ApiVersion, Constants.HeaderConstants.TargetStorageVersion); + } } } diff --git a/Lib/Common/Blob/CloudBlob.Common.cs b/Lib/Common/Blob/CloudBlob.Common.cs index ee727d521..d8d04300e 100644 --- a/Lib/Common/Blob/CloudBlob.Common.cs +++ b/Lib/Common/Blob/CloudBlob.Common.cs @@ -413,20 +413,7 @@ public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, SharedAcce /// A shared access signature, as a URI query string. public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, SharedAccessBlobHeaders headers, string groupPolicyIdentifier) { - if (!this.ServiceClient.Credentials.IsSharedKey) - { - string errorMessage = string.Format(CultureInfo.InvariantCulture, SR.CannotCreateSASWithoutAccountKey); - throw new InvalidOperationException(errorMessage); - } - - string resourceName = this.GetCanonicalName(true /* ignoreSnapshotTime */, Constants.HeaderConstants.TargetStorageVersion); - StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; - string signature = SharedAccessSignatureHelper.GetHash(policy, headers, groupPolicyIdentifier, resourceName, Constants.HeaderConstants.TargetStorageVersion, accountKey.KeyValue); - - // Future resource type changes from "c" => "container" - UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature(policy, headers, groupPolicyIdentifier, "b", signature, accountKey.KeyName, Constants.HeaderConstants.TargetStorageVersion); - - return builder.ToString(); + return this.GetSharedAccessSignature(policy, headers, groupPolicyIdentifier, null, null); } /// @@ -435,10 +422,10 @@ public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, SharedAcce /// A object specifying the access policy for the shared access signature. /// A object specifying optional header values to set for a blob accessed with this SAS. /// A string identifying a stored access policy. - /// A string indicating the desired SAS version to use, in storage service version format. Value must be 2012-02-12 or 2013-08-15. + /// The allowed protocols (https only, or http and https). Null if you don't want to restrict protocol. + /// The allowed IP address or IP address range. Null if you don't want to restrict based on IP address. /// A shared access signature, as a URI query string. - [Obsolete("This overload has been deprecated because the SAS tokens generated using the current version work fine with old libraries. Please use the other overloads.")] - public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, SharedAccessBlobHeaders headers, string groupPolicyIdentifier, string sasVersion) + public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, SharedAccessBlobHeaders headers, string groupPolicyIdentifier, SharedAccessProtocol? protocols, IPAddressOrRange ipAddressOrRange) { if (!this.ServiceClient.Credentials.IsSharedKey) { @@ -446,13 +433,12 @@ public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, SharedAcce throw new InvalidOperationException(errorMessage); } - string validatedSASVersion = SharedAccessSignatureHelper.ValidateSASVersionString(sasVersion); - string resourceName = this.GetCanonicalName(true /* ignoreSnapshotTime */, validatedSASVersion); + string resourceName = this.GetCanonicalName(true /* ignoreSnapshotTime */); StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; - string signature = SharedAccessSignatureHelper.GetHash(policy, headers, groupPolicyIdentifier, resourceName, validatedSASVersion, accountKey.KeyValue); + string signature = SharedAccessSignatureHelper.GetHash(policy, headers, groupPolicyIdentifier, resourceName, Constants.HeaderConstants.TargetStorageVersion, protocols, ipAddressOrRange, accountKey.KeyValue); // Future resource type changes from "c" => "container" - UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature(policy, headers, groupPolicyIdentifier, "b", signature, accountKey.KeyName, validatedSASVersion); + UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature(policy, headers, groupPolicyIdentifier, "b", signature, accountKey.KeyName, Constants.HeaderConstants.TargetStorageVersion, protocols, ipAddressOrRange); return builder.ToString(); } @@ -465,9 +451,8 @@ public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, SharedAcce /// This is used by both Shared Access and Copy blob operations. /// /// Indicates if the snapshot time is ignored. - /// A string indicating the desired SAS version to use, in storage service version format. Value must be 2012-02-12 or 2013-08-15. /// The canonical name of the blob. - private string GetCanonicalName(bool ignoreSnapshotTime, string sasVersion) + private string GetCanonicalName(bool ignoreSnapshotTime) { string accountName = this.ServiceClient.Credentials.AccountName; string containerName = this.Container.Name; @@ -475,12 +460,6 @@ private string GetCanonicalName(bool ignoreSnapshotTime, string sasVersion) // Replace \ with / for uri compatibility when running under .net 4.5. string blobName = this.Name.Replace('\\', '/'); string canonicalNameFormat = "/{0}/{1}/{2}/{3}"; - if (sasVersion == Constants.VersionConstants.February2012 || sasVersion == Constants.VersionConstants.August2013) - { - // Do not prepend service name for older versions - canonicalNameFormat = "/{1}/{2}/{3}"; - } - string canonicalName = string.Format(CultureInfo.InvariantCulture, canonicalNameFormat, SR.Blob, accountName, containerName, blobName); if (!ignoreSnapshotTime && this.SnapshotTime != null) diff --git a/Lib/Common/Blob/CloudBlobContainer.Common.cs b/Lib/Common/Blob/CloudBlobContainer.Common.cs index a7310012f..6d1b5c044 100644 --- a/Lib/Common/Blob/CloudBlobContainer.Common.cs +++ b/Lib/Common/Blob/CloudBlobContainer.Common.cs @@ -167,19 +167,13 @@ private void ParseQueryAndVerify(StorageUri address, StorageCredentials credenti /// /// Returns the canonical name for shared access. /// - /// A string indicating the desired SAS version to get canonical name for, in storage service version format. /// The canonical name. - private string GetSharedAccessCanonicalName(string sasVersion) + private string GetSharedAccessCanonicalName() { string accountName = this.ServiceClient.Credentials.AccountName; string containerName = this.Name; string canonicalNameFormat = "/{0}/{1}/{2}"; - if (sasVersion == Constants.VersionConstants.February2012 || sasVersion == Constants.VersionConstants.August2013) - { - // Do not prepend service name for older versions - canonicalNameFormat = "/{1}/{2}"; - } return string.Format(CultureInfo.InvariantCulture, canonicalNameFormat, SR.Blob, accountName, containerName); } @@ -193,7 +187,7 @@ private string GetSharedAccessCanonicalName(string sasVersion) /// The query string returned includes the leading question mark. public string GetSharedAccessSignature(SharedAccessBlobPolicy policy) { - return this.GetSharedAccessSignature(policy, null /* groupPolicyIdentifier */, null /* sasVersion */); + return this.GetSharedAccessSignature(policy, null /* groupPolicyIdentifier */); } /// @@ -205,7 +199,7 @@ public string GetSharedAccessSignature(SharedAccessBlobPolicy policy) /// The query string returned includes the leading question mark. public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, string groupPolicyIdentifier) { - return this.GetSharedAccessSignature(policy, groupPolicyIdentifier, null /* sasVersion */); + return this.GetSharedAccessSignature(policy, groupPolicyIdentifier, null /* protocols */, null /* ipAddressOrRange */); } /// @@ -213,10 +207,11 @@ public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, string gro /// /// A object specifying the access policy for the shared access signature. /// A container-level access policy. - /// A string indicating the desired SAS version to use, in storage service version format. Value must be 2012-02-12 or 2013-08-15. + /// The allowed protocols (https only, or http and https). Null if you don't want to restrict protocol. + /// The allowed IP address or IP address range. Null if you don't want to restrict based on IP address. /// A shared access signature, as a URI query string. /// The query string returned includes the leading question mark. - public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, string groupPolicyIdentifier, string sasVersion) + public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, string groupPolicyIdentifier, SharedAccessProtocol? protocols, IPAddressOrRange ipAddressOrRange) { if (!this.ServiceClient.Credentials.IsSharedKey) { @@ -224,14 +219,14 @@ public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, string gro throw new InvalidOperationException(errorMessage); } - string validatedSASVersion = SharedAccessSignatureHelper.ValidateSASVersionString(sasVersion); - string resourceName = this.GetSharedAccessCanonicalName(validatedSASVersion); + string resourceName = this.GetSharedAccessCanonicalName(); + StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; - string signature = SharedAccessSignatureHelper.GetHash(policy, null /* headers */, groupPolicyIdentifier, resourceName, validatedSASVersion, accountKey.KeyValue); + string signature = SharedAccessSignatureHelper.GetHash(policy, null /* headers */, groupPolicyIdentifier, resourceName, Constants.HeaderConstants.TargetStorageVersion, protocols, ipAddressOrRange, accountKey.KeyValue); string accountKeyName = accountKey.KeyName; // Future resource type changes from "c" => "container" - UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature(policy, null /* headers */, groupPolicyIdentifier, "c", signature, accountKeyName, validatedSASVersion); + UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature(policy, null /* headers */, groupPolicyIdentifier, "c", signature, accountKeyName, Constants.HeaderConstants.TargetStorageVersion, protocols, ipAddressOrRange); return builder.ToString(); } diff --git a/Lib/Common/Blob/ICloudBlob.Common.cs b/Lib/Common/Blob/ICloudBlob.Common.cs index 43844119f..3cffcab8b 100644 --- a/Lib/Common/Blob/ICloudBlob.Common.cs +++ b/Lib/Common/Blob/ICloudBlob.Common.cs @@ -19,6 +19,7 @@ namespace Microsoft.WindowsAzure.Storage.Blob { using System; using System.Collections.Generic; + using Microsoft.WindowsAzure.Storage; /// /// An interface required for Windows Azure blob types. The and classes implement the interface. @@ -146,9 +147,10 @@ public partial interface ICloudBlob : IListBlobItem /// A object specifying the access policy for the shared access signature. /// A object specifying optional header values to set for a blob accessed with this SAS. /// A string identifying a stored access policy. - /// A string indicating the desired SAS version to use, in storage service version format. Value must be 2012-02-12 or later. - /// A shared access signature. - string GetSharedAccessSignature(SharedAccessBlobPolicy policy, SharedAccessBlobHeaders headers, string groupPolicyIdentifier, string sasVersion); + /// The allowed protocols (https only, or http and https). Null if you don't want to restrict protocol. + /// The allowed IP address or IP address range. Null if you don't want to restrict based on IP address. + /// A shared access signature, as a URI query string. + string GetSharedAccessSignature(SharedAccessBlobPolicy policy, SharedAccessBlobHeaders headers, string groupPolicyIdentifier, SharedAccessProtocol? protocols, IPAddressOrRange ipAddressOrRange); #endif } } diff --git a/Lib/Common/Blob/SharedAccessBlobPermissions.cs b/Lib/Common/Blob/SharedAccessBlobPermissions.cs index 8403018f5..93ee7b8eb 100644 --- a/Lib/Common/Blob/SharedAccessBlobPermissions.cs +++ b/Lib/Common/Blob/SharedAccessBlobPermissions.cs @@ -41,13 +41,23 @@ public enum SharedAccessBlobPermissions Write = 0x2, /// - /// Delete access granted for blobs. + /// Delete access granted. /// Delete = 0x4, /// /// List access granted. /// - List = 0x8 + List = 0x8, + + /// + /// Add access granted. + /// + Add = 0x10, + + /// + /// Create access granted. + /// + Create = 0x20 } } \ No newline at end of file diff --git a/Lib/Common/Blob/SharedAccessBlobPolicy.cs b/Lib/Common/Blob/SharedAccessBlobPolicy.cs index 5b6db3d8c..8c3fc2a53 100644 --- a/Lib/Common/Blob/SharedAccessBlobPolicy.cs +++ b/Lib/Common/Blob/SharedAccessBlobPolicy.cs @@ -59,7 +59,7 @@ public SharedAccessBlobPolicy() /// The shared access permissions, in string format. public static string PermissionsToString(SharedAccessBlobPermissions permissions) { - // The service supports a fixed order => rwdl + // The service supports a fixed order => racwdl StringBuilder builder = new StringBuilder(); if ((permissions & SharedAccessBlobPermissions.Read) == SharedAccessBlobPermissions.Read) @@ -67,6 +67,16 @@ public static string PermissionsToString(SharedAccessBlobPermissions permissions builder.Append("r"); } + if ((permissions & SharedAccessBlobPermissions.Add) == SharedAccessBlobPermissions.Add) + { + builder.Append("a"); + } + + if ((permissions & SharedAccessBlobPermissions.Create) == SharedAccessBlobPermissions.Create) + { + builder.Append("c"); + } + if ((permissions & SharedAccessBlobPermissions.Write) == SharedAccessBlobPermissions.Write) { builder.Append("w"); @@ -116,12 +126,20 @@ public static SharedAccessBlobPermissions PermissionsFromString(string input) permissions |= SharedAccessBlobPermissions.List; break; + case 'a': + permissions |= SharedAccessBlobPermissions.Add; + break; + + case 'c': + permissions |= SharedAccessBlobPermissions.Create; + break; + default: throw new ArgumentOutOfRangeException("input"); } } - // Incase we ever change none to be something other than 0 + // In case we ever change none to be something other than 0 if (permissions == 0) { permissions |= SharedAccessBlobPermissions.None; diff --git a/Lib/Common/CloudStorageAccount.cs b/Lib/Common/CloudStorageAccount.cs index 66d5b95de..d3b8d993c 100644 --- a/Lib/Common/CloudStorageAccount.cs +++ b/Lib/Common/CloudStorageAccount.cs @@ -23,6 +23,7 @@ namespace Microsoft.WindowsAzure.Storage using Microsoft.WindowsAzure.Storage.Auth; using Microsoft.WindowsAzure.Storage.Blob; using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Core.Auth; using Microsoft.WindowsAzure.Storage.Core.Util; #if !PORTABLE using Microsoft.WindowsAzure.Storage.File; @@ -35,6 +36,7 @@ namespace Microsoft.WindowsAzure.Storage using System.Globalization; using System.Linq; using AccountSetting = System.Collections.Generic.KeyValuePair>; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; /// /// Represents a Windows Azure Storage account. @@ -292,14 +294,44 @@ public CloudStorageAccount(StorageCredentials storageCredentials, bool useHttps) /// true to use HTTPS to connect to storage service endpoints; otherwise, false. /// Using HTTPS to connect to the storage services is recommended. public CloudStorageAccount(StorageCredentials storageCredentials, string endpointSuffix, bool useHttps) + : this(storageCredentials, storageCredentials == null ? null : storageCredentials.AccountName, endpointSuffix, useHttps) + { + } + + /// + /// Initializes a new instance of the class using the specified + /// credentials and endpoint suffix, and specifies whether to use HTTP or HTTPS to connect to the storage services. + /// + /// A object. + /// The name of the account. + /// The DNS endpoint suffix for all storage services, e.g. "core.windows.net". + /// true to use HTTPS to connect to storage service endpoints; otherwise, false. + /// Using HTTPS to connect to the storage services is recommended. + public CloudStorageAccount(StorageCredentials storageCredentials, string accountName, string endpointSuffix, bool useHttps) { CommonUtility.AssertNotNull("storageCredentials", storageCredentials); + if (!string.IsNullOrEmpty(storageCredentials.AccountName)) + { + if (string.IsNullOrEmpty(accountName)) + { + accountName = storageCredentials.AccountName; + } + else + { + if (string.Compare(storageCredentials.AccountName, accountName, System.StringComparison.Ordinal) != 0) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, SR.AccountNameMismatch, storageCredentials.AccountName, accountName)); + } + } + } + + CommonUtility.AssertNotNull("AccountName", accountName); string protocol = useHttps ? "https" : "http"; - this.BlobStorageUri = ConstructBlobEndpoint(protocol, storageCredentials.AccountName, endpointSuffix); - this.QueueStorageUri = ConstructQueueEndpoint(protocol, storageCredentials.AccountName, endpointSuffix); - this.TableStorageUri = ConstructTableEndpoint(protocol, storageCredentials.AccountName, endpointSuffix); - this.FileStorageUri = ConstructFileEndpoint(protocol, storageCredentials.AccountName, endpointSuffix); + this.BlobStorageUri = ConstructBlobEndpoint(protocol, accountName, endpointSuffix); + this.QueueStorageUri = ConstructQueueEndpoint(protocol, accountName, endpointSuffix); + this.TableStorageUri = ConstructTableEndpoint(protocol, accountName, endpointSuffix); + this.FileStorageUri = ConstructFileEndpoint(protocol, accountName, endpointSuffix); this.Credentials = storageCredentials; this.EndpointSuffix = endpointSuffix; this.DefaultEndpoints = true; @@ -591,6 +623,25 @@ public CloudFileClient CreateCloudFileClient() return new CloudFileClient(this.FileStorageUri, this.Credentials); } + + /// + /// Returns a shared access signature for the account. + /// + /// A object specifying the access policy for the shared access signature. + /// A shared access signature, as a URI query string. + /// The query string returned includes the leading question mark. + public string GetSharedAccessSignature(SharedAccessAccountPolicy policy) + { + if (!this.Credentials.IsSharedKey) + { + string errorMessage = string.Format(CultureInfo.CurrentCulture, SR.CannotCreateSASWithoutAccountKey); + throw new InvalidOperationException(errorMessage); + } + + string signature = SharedAccessSignatureHelper.GetHash(policy, this.Credentials.AccountName, Constants.HeaderConstants.TargetStorageVersion, this.Credentials.Key.KeyValue); + UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature(policy, signature, this.Credentials.Key.KeyName, Constants.HeaderConstants.TargetStorageVersion); + return builder.ToString(); + } #endif /// diff --git a/Lib/Common/Core/Auth/SharedAccessSignatureHelper.cs b/Lib/Common/Core/Auth/SharedAccessSignatureHelper.cs index 12b2752f3..ab04b8652 100644 --- a/Lib/Common/Core/Auth/SharedAccessSignatureHelper.cs +++ b/Lib/Common/Core/Auth/SharedAccessSignatureHelper.cs @@ -17,6 +17,7 @@ namespace Microsoft.WindowsAzure.Storage.Core.Auth { + using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Auth; using Microsoft.WindowsAzure.Storage.Blob; using Microsoft.WindowsAzure.Storage.Core.Util; @@ -42,12 +43,14 @@ internal static class SharedAccessSignatureHelper /// Get the complete query builder for creating the Shared Access Signature query. /// /// The shared access policy to hash. - /// The optional header values to set for a blob returned with this SAS. Not valid for the 2012-02-12 version. + /// The optional header values to set for a blob returned with this SAS. /// An optional identifier for the policy. /// Either "b" for blobs or "c" for containers. /// The signature to use. /// The name of the key used to create the signature, or null if the key is implicit. /// A string indicating the desired SAS version to use, in storage service version format. + /// The HTTP/HTTPS protocols for Account SAS. + /// The IP range for IPSAS. /// The finished query builder. internal static UriQueryBuilder GetSignature( SharedAccessBlobPolicy policy, @@ -56,7 +59,10 @@ internal static UriQueryBuilder GetSignature( string resourceType, string signature, string accountKeyName, - string sasVersion) + string sasVersion, + SharedAccessProtocol? protocols, + IPAddressOrRange ipAddressOrRange + ) { CommonUtility.AssertNotNullOrEmpty("resourceType", resourceType); @@ -67,6 +73,8 @@ internal static UriQueryBuilder GetSignature( AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedIdentifier, accessPolicyIdentifier); AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedKey, accountKeyName); AddEscapedIfNotNull(builder, Constants.QueryConstants.Signature, signature); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedProtocols, GetProtocolString(protocols)); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedIP, ipAddressOrRange == null ? null : ipAddressOrRange.ToString()); if (policy != null) { @@ -102,6 +110,8 @@ internal static UriQueryBuilder GetSignature( /// The signature to use. /// The name of the key used to create the signature, or null if the key is implicit. /// A string indicating the desired SAS version to use, in storage service version format. + /// The HTTP/HTTPS protocols for Account SAS. + /// The IP range for IPSAS. /// The finished query builder. internal static UriQueryBuilder GetSignature( SharedAccessFilePolicy policy, @@ -110,7 +120,9 @@ internal static UriQueryBuilder GetSignature( string resourceType, string signature, string accountKeyName, - string sasVersion) + string sasVersion, + SharedAccessProtocol? protocols, + IPAddressOrRange ipAddressOrRange) { CommonUtility.AssertNotNullOrEmpty("resourceType", resourceType); @@ -121,6 +133,8 @@ internal static UriQueryBuilder GetSignature( AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedIdentifier, accessPolicyIdentifier); AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedKey, accountKeyName); AddEscapedIfNotNull(builder, Constants.QueryConstants.Signature, signature); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedProtocols, GetProtocolString(protocols)); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedIP, ipAddressOrRange == null ? null : ipAddressOrRange.ToString()); if (policy != null) { @@ -154,13 +168,17 @@ internal static UriQueryBuilder GetSignature( /// The signature to use. /// The name of the key used to create the signature, or null if the key is implicit. /// A string indicating the desired SAS version to use, in storage service version format. + /// The HTTP/HTTPS protocols for Account SAS. + /// The IP range for IPSAS. /// The finished query builder. internal static UriQueryBuilder GetSignature( SharedAccessQueuePolicy policy, string accessPolicyIdentifier, string signature, string accountKeyName, - string sasVersion) + string sasVersion, + SharedAccessProtocol? protocols, + IPAddressOrRange ipAddressOrRange) { CommonUtility.AssertNotNull("signature", signature); @@ -170,6 +188,8 @@ internal static UriQueryBuilder GetSignature( AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedIdentifier, accessPolicyIdentifier); AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedKey, accountKeyName); AddEscapedIfNotNull(builder, Constants.QueryConstants.Signature, signature); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedProtocols, GetProtocolString(protocols)); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedIP, ipAddressOrRange == null ? null : ipAddressOrRange.ToString()); if (policy != null) { @@ -199,6 +219,8 @@ internal static UriQueryBuilder GetSignature( /// The signature to use. /// The name of the key used to create the signature, or null if the key is implicit. /// A string indicating the desired SAS version to use, in storage service version format. + /// The HTTP/HTTPS protocols for Account SAS. + /// The IP range for IPSAS. /// The finished query builder. internal static UriQueryBuilder GetSignature( SharedAccessTablePolicy policy, @@ -210,7 +232,9 @@ internal static UriQueryBuilder GetSignature( string endRowKey, string signature, string accountKeyName, - string sasVersion) + string sasVersion, + SharedAccessProtocol? protocols, + IPAddressOrRange ipAddressOrRange) { CommonUtility.AssertNotNull("signature", signature); @@ -225,6 +249,8 @@ internal static UriQueryBuilder GetSignature( AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedIdentifier, accessPolicyIdentifier); AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedKey, accountKeyName); AddEscapedIfNotNull(builder, Constants.QueryConstants.Signature, signature); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedProtocols, GetProtocolString(protocols)); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedIP, ipAddressOrRange == null ? null : ipAddressOrRange.ToString()); if (policy != null) { @@ -240,6 +266,45 @@ internal static UriQueryBuilder GetSignature( return builder; } + + internal static UriQueryBuilder GetSignature( + SharedAccessAccountPolicy policy, + string signature, + string accountKeyName, + string sasVersion) + { + CommonUtility.AssertNotNull("signature", signature); + CommonUtility.AssertNotNull("policy", policy); + + UriQueryBuilder builder = new UriQueryBuilder(); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedVersion, sasVersion); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedKey, accountKeyName); + AddEscapedIfNotNull(builder, Constants.QueryConstants.Signature, signature); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedProtocols, policy.Protocols == null ? null : GetProtocolString(policy.Protocols.Value)); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedIP, policy.IPAddressOrRange == null ? null : policy.IPAddressOrRange.ToString()); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedStart, GetDateTimeOrNull(policy.SharedAccessStartTime)); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedExpiry, GetDateTimeOrNull(policy.SharedAccessExpiryTime)); + + string resourceTypes = SharedAccessAccountPolicy.ResourceTypesToString(policy.ResourceTypes); + if (!string.IsNullOrEmpty(resourceTypes)) + { + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedResourceTypes, resourceTypes); + } + + string services = SharedAccessAccountPolicy.ServicesToString(policy.Services); + if (!string.IsNullOrEmpty(services)) + { + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedServices, services); + } + + string permissions = SharedAccessAccountPolicy.PermissionsToString(policy.Permissions); + if (!string.IsNullOrEmpty(permissions)) + { + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedPermissions, permissions); + } + + return builder; + } #endif /// @@ -264,6 +329,23 @@ internal static string GetDateTimeOrNull(DateTimeOffset? value) return result; } +#if !PORTABLE + /// + /// Converts the specified value to either a string representation or null. + /// + /// The protocols to convert + /// A string representing the specified value. + internal static string GetProtocolString(SharedAccessProtocol? protocols) + { + if (!protocols.HasValue) + { + return null; + } + + return protocols.Value == SharedAccessProtocol.HttpsOnly ? "https" : "https,http"; + } +#endif + /// /// Escapes and adds the specified name/value pair to the query builder if it is not null. /// @@ -334,6 +416,8 @@ internal static StorageCredentials ParseQuery(IDictionary queryP /// An optional identifier for the policy. /// The canonical resource string, unescaped. /// A string indicating the desired SAS version to use, in storage service version format. + /// The HTTP/HTTPS protocols for Account SAS. + /// The IP range for IPSAS. /// The key value retrieved as an atomic operation used for signing. /// The signed hash. internal static string GetHash( @@ -341,6 +425,8 @@ internal static string GetHash( string accessPolicyIdentifier, string resourceName, string sasVersion, + SharedAccessProtocol? protocols, + IPAddressOrRange ipAddressOrRange, byte[] keyValue) { CommonUtility.AssertNotNullOrEmpty("resourceName", resourceName); @@ -362,18 +448,23 @@ internal static string GetHash( //// signedexpiry + "\n" + //// canonicalizedresource + "\n" + //// signedidentifier + "\n" + + //// signedIP + "\n" + + //// signedProtocol + "\n" + //// signedversion //// //// HMAC-SHA256(UTF8.Encode(StringToSign)) + //// string stringToSign = string.Format( CultureInfo.InvariantCulture, - "{0}\n{1}\n{2}\n{3}\n{4}\n{5}", + "{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}\n{7}", permissions, GetDateTimeOrEmpty(startTime), GetDateTimeOrEmpty(expiryTime), resourceName, accessPolicyIdentifier, + ipAddressOrRange == null ? string.Empty : ipAddressOrRange.ToString(), + GetProtocolString(protocols), sasVersion); Logger.LogVerbose(null /* operationContext */, SR.TraceStringToSign, stringToSign); @@ -392,6 +483,8 @@ internal static string GetHash( /// The end row key, or null. /// The canonical resource string, unescaped. /// A string indicating the desired SAS version to use, in storage service version format. + /// The HTTP/HTTPS protocols for Account SAS. + /// The IP range for IPSAS. /// The key value retrieved as an atomic operation used for signing. /// The signed hash. internal static string GetHash( @@ -403,6 +496,8 @@ internal static string GetHash( string endRowKey, string resourceName, string sasVersion, + SharedAccessProtocol? protocols, + IPAddressOrRange ipAddressOrRange, byte[] keyValue) { CommonUtility.AssertNotNullOrEmpty("resourceName", resourceName); @@ -424,6 +519,8 @@ internal static string GetHash( //// signedexpiry + "\n" + //// canonicalizedresource + "\n" + //// signedidentifier + "\n" + + //// signedIP + "\n" + + //// signedProtocol + "\n" + //// signedversion + "\n" + //// startpk + "\n" + //// startrk + "\n" + @@ -431,15 +528,18 @@ internal static string GetHash( //// endrk //// //// HMAC-SHA256(UTF8.Encode(StringToSign)) + //// string stringToSign = string.Format( CultureInfo.InvariantCulture, - "{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}\n{7}\n{8}\n{9}", + "{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}\n{7}\n{8}\n{9}\n{10}\n{11}", permissions, GetDateTimeOrEmpty(startTime), GetDateTimeOrEmpty(expiryTime), resourceName, accessPolicyIdentifier, + ipAddressOrRange == null ? string.Empty : ipAddressOrRange.ToString(), + GetProtocolString(protocols), sasVersion, startPartitionKey, startRowKey, @@ -455,10 +555,12 @@ internal static string GetHash( /// Get the signature hash embedded inside the Shared Access Signature. /// /// The shared access policy to hash. - /// The optional header values to set for a blob returned with this SAS. Not valid for the 2012-02-12 version. + /// The optional header values to set for a blob returned with this SAS. /// An optional identifier for the policy. /// The canonical resource string, unescaped. /// A string indicating the desired SAS version to use, in storage service version format. + /// The HTTP/HTTPS protocols for Account SAS. + /// The IP range for IPSAS. /// The key value retrieved as an atomic operation used for signing. /// The signed hash. internal static string GetHash( @@ -467,6 +569,8 @@ internal static string GetHash( string accessPolicyIdentifier, string resourceName, string sasVersion, + SharedAccessProtocol? protocols, + IPAddressOrRange ipAddressOrRange, byte[] keyValue) { CommonUtility.AssertNotNullOrEmpty("resourceName", resourceName); @@ -482,12 +586,14 @@ internal static string GetHash( startTime = policy.SharedAccessStartTime; expiryTime = policy.SharedAccessExpiryTime; } - + //// StringToSign = signedpermissions + "\n" + //// signedstart + "\n" + //// signedexpiry + "\n" + //// canonicalizedresource + "\n" + //// signedidentifier + "\n" + + //// signedIP + "\n" + + //// signedProtocol + "\n" + //// signedversion + "\n" + //// cachecontrol + "\n" + //// contentdisposition + "\n" + @@ -497,51 +603,37 @@ internal static string GetHash( //// //// HMAC-SHA256(UTF8.Encode(StringToSign)) //// - //// Note that the final five headers are invalid for the 2012-02-12 version. + + string cacheControl = null; + string contentDisposition = null; + string contentEncoding = null; + string contentLanguage = null; + string contentType = null; + if (headers != null) + { + cacheControl = headers.CacheControl; + contentDisposition = headers.ContentDisposition; + contentEncoding = headers.ContentEncoding; + contentLanguage = headers.ContentLanguage; + contentType = headers.ContentType; + } string stringToSign = string.Format( CultureInfo.InvariantCulture, - "{0}\n{1}\n{2}\n{3}\n{4}\n{5}", + "{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}\n{7}\n{8}\n{9}\n{10}\n{11}\n{12}", permissions, GetDateTimeOrEmpty(startTime), GetDateTimeOrEmpty(expiryTime), resourceName, accessPolicyIdentifier, - sasVersion); - - if (string.Equals(sasVersion, Constants.VersionConstants.February2012)) - { - if (headers != null) - { - string errorString = string.Format(CultureInfo.CurrentCulture, SR.InvalidHeaders); - throw new ArgumentException(errorString); - } - } - else - { - string cacheControl = null; - string contentDisposition = null; - string contentEncoding = null; - string contentLanguage = null; - string contentType = null; - if (headers != null) - { - cacheControl = headers.CacheControl; - contentDisposition = headers.ContentDisposition; - contentEncoding = headers.ContentEncoding; - contentLanguage = headers.ContentLanguage; - contentType = headers.ContentType; - } - - stringToSign = stringToSign + string.Format( - CultureInfo.InvariantCulture, - "\n{0}\n{1}\n{2}\n{3}\n{4}", - cacheControl, - contentDisposition, - contentEncoding, - contentLanguage, - contentType); - } + ipAddressOrRange == null ? string.Empty : ipAddressOrRange.ToString(), + GetProtocolString(protocols), + sasVersion, + cacheControl, + contentDisposition, + contentEncoding, + contentLanguage, + contentType); Logger.LogVerbose(null /* operationContext */, SR.TraceStringToSign, stringToSign); @@ -556,6 +648,8 @@ internal static string GetHash( /// An optional identifier for the policy. /// The canonical resource string, unescaped. /// A string indicating the desired SAS version to use, in storage service version format. + /// The HTTP/HTTPS protocols for Account SAS. + /// The IP range for IPSAS. /// The key value retrieved as an atomic operation used for signing. /// The signed hash. internal static string GetHash( @@ -564,6 +658,8 @@ internal static string GetHash( string accessPolicyIdentifier, string resourceName, string sasVersion, + SharedAccessProtocol? protocols, + IPAddressOrRange ipAddressOrRange, byte[] keyValue) { CommonUtility.AssertNotNullOrEmpty("resourceName", resourceName); @@ -585,6 +681,8 @@ internal static string GetHash( //// signedexpiry + "\n" + //// canonicalizedresource + "\n" + //// signedidentifier + "\n" + + //// signedIP + "\n" + + //// signedProtocol + "\n" + //// signedversion + "\n" + //// cachecontrol + "\n" + //// contentdisposition + "\n" + @@ -611,12 +709,14 @@ internal static string GetHash( string stringToSign = string.Format( CultureInfo.InvariantCulture, - "{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}\n{7}\n{8}\n{9}\n{10}", + "{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}\n{7}\n{8}\n{9}\n{10}\n{11}\n{12}", permissions, GetDateTimeOrEmpty(startTime), GetDateTimeOrEmpty(expiryTime), resourceName, accessPolicyIdentifier, + ipAddressOrRange == null ? string.Empty : ipAddressOrRange.ToString(), + GetProtocolString(protocols), sasVersion, cacheControl, contentDisposition, @@ -628,32 +728,31 @@ internal static string GetHash( return CryptoUtility.ComputeHmac256(keyValue, stringToSign); } -#endif - /// - /// Check to see if the SAS Version string provided is valid. - /// - /// A string indicating the desired SAS version to use, in storage service version format. Value must be 2012-02-12 or 2013-08-15 - /// The valid SAS version string or the default (storage version) if given was null/invalid. - internal static string ValidateSASVersionString(string sasVersion) + internal static string GetHash( + SharedAccessAccountPolicy policy, + string accountName, + string sasVersion, + byte[] keyValue) { - if (sasVersion == null) - { - return Constants.HeaderConstants.TargetStorageVersion; - } - else if (string.Equals(sasVersion, Constants.VersionConstants.August2013)) - { - return Constants.VersionConstants.August2013; - } - else if (string.Equals(sasVersion, Constants.VersionConstants.February2012)) - { - return Constants.VersionConstants.February2012; - } - else - { - string errorString = string.Format(CultureInfo.CurrentCulture, SR.InvalidSASVersion); - throw new ArgumentException(errorString); - } + string stringToSign = string.Format( + CultureInfo.InvariantCulture, + "{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}\n{7}\n{8}\n{9}", + accountName, + SharedAccessAccountPolicy.PermissionsToString(policy.Permissions), + SharedAccessAccountPolicy.ServicesToString(policy.Services), + SharedAccessAccountPolicy.ResourceTypesToString(policy.ResourceTypes), + GetDateTimeOrEmpty(policy.SharedAccessStartTime), + GetDateTimeOrEmpty(policy.SharedAccessExpiryTime), + policy.IPAddressOrRange == null ? string.Empty : policy.IPAddressOrRange.ToString(), + GetProtocolString(policy.Protocols), + sasVersion, + string.Empty); + + Logger.LogVerbose(null /* operationContext */, SR.TraceStringToSign, stringToSign); + + return CryptoUtility.ComputeHmac256(keyValue, stringToSign); } +#endif } } diff --git a/Lib/Common/Core/Executor/StorageCommandBase.cs b/Lib/Common/Core/Executor/StorageCommandBase.cs index 6863395fe..f2ab400b7 100644 --- a/Lib/Common/Core/Executor/StorageCommandBase.cs +++ b/Lib/Common/Core/Executor/StorageCommandBase.cs @@ -72,7 +72,7 @@ internal IList RequestResults // Delegate that will be executed in the event of an Exception after signing. public Action, Exception, OperationContext> RecoveryAction = null; -#if ASPNET_K || PORTABLE +#if WINDOWS_RT || ASPNET_K || PORTABLE public Func ParseError = null; #else // Delegate that will be executed in the event of a failure. diff --git a/Lib/Common/Core/SR.cs b/Lib/Common/Core/SR.cs index f08b9e58b..ef5617512 100644 --- a/Lib/Common/Core/SR.cs +++ b/Lib/Common/Core/SR.cs @@ -23,12 +23,14 @@ namespace Microsoft.WindowsAzure.Storage.Core internal class SR { public const string AbsoluteAddressNotPermitted = "Address '{0}' is an absolute address. Only relative addresses are permitted."; + public const string AccountNameMismatch = "Account names do not match. First account name is {0}, second is {1}."; public const string ArgumentEmptyError = "The argument must not be empty string."; public const string ArgumentOutOfRangeError = "The argument is out of range. Value passed: {0}"; public const string ArgumentTooLargeError = "The argument '{0}' is larger than maximum of '{1}'"; public const string ArgumentTooSmallError = "The argument '{0}' is smaller than minimum of '{1}'"; public const string AttemptedEdmTypeForTheProperty = "Attempting to deserialize '{0}' as type '{1}'"; public const string BatchWithRetreiveContainsOtherOperations = "A batch transaction with a retrieve operation cannot contain any other operations."; + public const string BatchErrorInOperation = "Element {0} in the batch returned an unexpected response code."; public const string BinaryMessageShouldUseBase64Encoding = "EncodeMessage should be true for binary message."; public const string Blob = "blob"; public const string BlobDataCorrupted = "Blob data corrupted (integrity check failed), Expected value is '{0}', retrieved '{1}'"; @@ -43,6 +45,7 @@ internal class SR public const string CannotCreateSASSignatureForGivenCred = "Cannot create Shared Access Signature as the credentials does not have account name information. Please check that the credentials used support creating Shared Access Signature."; public const string CannotCreateSASWithoutAccountKey = "Cannot create Shared Access Signature unless Account Key credentials are used."; public const string CannotModifySnapshot = "Cannot perform this operation on a blob representing a snapshot."; + public const string CannotTransformNonHttpsUriWithHttpsOnlyCredentials = "Cannot transform a Uri object using a StorageCredentials object that is marked HTTPS only."; public const string CannotUpdateKeyWithoutAccountKeyCreds = "Cannot update key unless Account Key credentials are used."; public const string CannotUpdateSasWithoutSasCreds = "Cannot update Shared Access Signature unless Sas credentials are used."; public const string ConcurrentOperationsNotSupported = "Could not acquire exclusive use of the TableServiceContext, Concurrent operations are not supported."; @@ -92,6 +95,7 @@ internal class SR public const string InvalidFileListItem = "Invalid file list item returned"; public const string InvalidGeoReplicationStatus = "Invalid geo-replication status in response: '{0}'"; public const string InvalidHeaders = "Headers are not supported in the 2012-02-12 version."; + public const string InvalidIPAddress = "Error when parsing IP address: IP address is invalid."; public const string InvalidLeaseStatus = "Invalid lease status in response: '{0}'"; public const string InvalidLeaseState = "Invalid lease state in response: '{0}'"; public const string InvalidLeaseDuration = "Invalid lease duration in response: '{0}'"; @@ -105,6 +109,8 @@ internal class SR public const string InvalidResourceReservedName = "Invalid {0} name. This {0} name is reserved."; public const string InvalidSASVersion = "SAS Version invalid. Valid versions include 2012-02-12 and 2013-08-15."; public const string InvalidStorageService = "Invalid storage service specified."; + public const string IPAddressOrRangeMustBeNullInOldVersion = "IPAddressOrRange must be null when creating a SAS token with an older service version parameter."; + public const string IPMustBeIPV4InSAS = "When specifying an IP Address in a SAS token, it must be an IPv4 address. Input address was {0}."; public const string IQueryableExtensionObjectMustBeTableQuery = "Query must be a TableQuery"; public const string JsonNotSupportedOnRT = "JSON payloads are not supported in Windows Runtime."; public const string JsonReaderNotInCompletedState = "The JSON reader has not yet reached the completed state."; @@ -150,7 +156,8 @@ internal class SR public const string PreconditionFailureIgnored = "Pre-condition failure on a retry is being ignored since the request should have succeeded in the first attempt."; public const string PrimaryOnlyCommand = "This operation can only be executed against the primary storage location."; public const string PropertyResolverCacheDisabled = "Property resolver cache is disabled."; - public const string PropertyResolverThrewError = "The custom property resolver delegate threw an exception. Check the inner exception for more details"; + public const string PropertyResolverThrewError = "The custom property resolver delegate threw an exception. Check the inner exception for more details."; + public const string ProtocolsMustBeNullInOldVersion = "SharedAccessProtocol must be null when creating a SAS token with an older service version parameter."; public const string PutBlobNeedsStoreBlobContentMD5 = "When uploading a blob in a single request, StoreBlobContentMD5 must be set to true if UseTransactionalMD5 is true, because the MD5 calculated for the transaction will be stored in the blob."; public const string QueryBuilderKeyNotFound = "'{0}' key not found in the query builder."; public const string Queue = "queue"; @@ -221,7 +228,6 @@ internal class SR public const string UnexpectedContinuationType = "Unexpected Continuation Type"; public const string UnexpectedLocation = "Unexpected Location '{0}'"; public const string UnexpectedResponseCode = "Unexpected response code, Expected:{0}, Received:{1}"; - public const string UnexpectedResponseCodeForOperation = "Unexpected response code for operation : "; public const string UnsupportedPropertyTypeForEncryption = "Unsupported type : {0} encountered during encryption. Only string properties can be encrypted on the client side."; public const string UpdateMessageVisibilityRequired = "Calls to UpdateMessage must include the Visibility flag."; public const string UsingDefaultPropertyResolver = "Using the default property resolver to deserialize the entity."; diff --git a/Lib/Common/Core/Util/CommonUtility.cs b/Lib/Common/Core/Util/CommonUtility.cs index 0c3b34e2d..379e45497 100644 --- a/Lib/Common/Core/Util/CommonUtility.cs +++ b/Lib/Common/Core/Util/CommonUtility.cs @@ -179,7 +179,7 @@ internal static void ArgumentOutOfRange(string paramName, object value) /// The value of the parameter. /// The minimum value for the parameter. /// The maximum value for the parameter. -#if WINDOWS_PHONE && WINDOWS_DESKTOP +#if !WINDOWS_DESKTOP [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] #endif internal static void AssertInBounds(string paramName, T val, T min, T max) @@ -203,7 +203,7 @@ internal static void AssertInBounds(string paramName, T val, T min, T max) /// The name of the parameter. /// The value of the parameter. /// The minimum value for the parameter. -#if WINDOWS_PHONE && WINDOWS_DESKTOP +#if !WINDOWS_DESKTOP [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] #endif internal static void AssertInBounds(string paramName, T val, T min) diff --git a/Lib/Common/Core/Util/Exceptions.cs b/Lib/Common/Core/Util/Exceptions.cs index 8823b6354..10d3273d9 100644 --- a/Lib/Common/Core/Util/Exceptions.cs +++ b/Lib/Common/Core/Util/Exceptions.cs @@ -22,6 +22,7 @@ namespace Microsoft.WindowsAzure.Storage.Core.Util #if WINDOWS_RT || ASPNET_K || PORTABLE using System.Globalization; using System.IO; + using System.Net; using System.Net.Http; using System.Threading.Tasks; using Microsoft.WindowsAzure.Storage.Shared.Protocol; @@ -30,7 +31,7 @@ namespace Microsoft.WindowsAzure.Storage.Core.Util internal class Exceptions { #if WINDOWS_RT || ASPNET_K || PORTABLE - internal async static Task PopulateStorageExceptionFromHttpResponseMessage(HttpResponseMessage response, RequestResult currentResult) + internal async static Task PopulateStorageExceptionFromHttpResponseMessage(HttpResponseMessage response, RequestResult currentResult, Func parseError) { if (!response.IsSuccessStatusCode) { @@ -61,7 +62,14 @@ internal async static Task PopulateStorageExceptionFromHttpRes try { Stream errStream = await response.Content.ReadAsStreamAsync(); - currentResult.ExtendedErrorInformation = StorageExtendedErrorInformation.ReadFromStream(errStream.AsInputStream()); + if (parseError != null) + { + currentResult.ExtendedErrorInformation = parseError(errStream, response, response.Content.Headers.ContentType.ToString()); + } + else + { + currentResult.ExtendedErrorInformation = StorageExtendedErrorInformation.ReadFromStream(errStream.AsInputStream()); + } } catch (Exception) { diff --git a/Lib/Common/File/CloudFile.Common.cs b/Lib/Common/File/CloudFile.Common.cs index c8f3a8583..9ad8a4ea1 100644 --- a/Lib/Common/File/CloudFile.Common.cs +++ b/Lib/Common/File/CloudFile.Common.cs @@ -325,6 +325,20 @@ public string GetSharedAccessSignature(SharedAccessFilePolicy policy, SharedAcce /// A string identifying a stored access policy. /// A shared access signature, as a URI query string. public string GetSharedAccessSignature(SharedAccessFilePolicy policy, SharedAccessFileHeaders headers, string groupPolicyIdentifier) + { + return GetSharedAccessSignature(policy, headers, groupPolicyIdentifier, null, null); + } + + /// + /// Returns a shared access signature for the file. + /// + /// A object specifying the access policy for the shared access signature. + /// A object specifying optional header values to set for a file accessed with this SAS. + /// A string identifying a stored access policy. + /// The allowed protocols (https only, or http and https). Null if you don't want to restrict protocol. + /// The allowed IP address or IP address range. Null if you don't want to restrict based on IP address. + /// A shared access signature, as a URI query string. + public string GetSharedAccessSignature(SharedAccessFilePolicy policy, SharedAccessFileHeaders headers, string groupPolicyIdentifier, SharedAccessProtocol? protocols, IPAddressOrRange ipAddressOrRange) { if (!this.ServiceClient.Credentials.IsSharedKey) { @@ -334,9 +348,9 @@ public string GetSharedAccessSignature(SharedAccessFilePolicy policy, SharedAcce string resourceName = this.GetCanonicalName(); StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; - string signature = SharedAccessSignatureHelper.GetHash(policy, headers, groupPolicyIdentifier, resourceName, Constants.HeaderConstants.TargetStorageVersion, accountKey.KeyValue); + string signature = SharedAccessSignatureHelper.GetHash(policy, headers, groupPolicyIdentifier, resourceName, Constants.HeaderConstants.TargetStorageVersion, protocols, ipAddressOrRange, accountKey.KeyValue); - UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature(policy, headers, groupPolicyIdentifier, "f", signature, accountKey.KeyName, Constants.HeaderConstants.TargetStorageVersion); + UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature(policy, headers, groupPolicyIdentifier, "f", signature, accountKey.KeyName, Constants.HeaderConstants.TargetStorageVersion, protocols, ipAddressOrRange); return builder.ToString(); } diff --git a/Lib/Common/File/CloudFileShare.Common.cs b/Lib/Common/File/CloudFileShare.Common.cs index 6566bc4f0..f8930638e 100644 --- a/Lib/Common/File/CloudFileShare.Common.cs +++ b/Lib/Common/File/CloudFileShare.Common.cs @@ -175,6 +175,20 @@ public string GetSharedAccessSignature(SharedAccessFilePolicy policy) /// A shared access signature, as a URI query string. /// The query string returned includes the leading question mark. public string GetSharedAccessSignature(SharedAccessFilePolicy policy, string groupPolicyIdentifier) + { + return this.GetSharedAccessSignature(policy, groupPolicyIdentifier, null, null); + } + + /// + /// Returns a shared access signature for the share. + /// + /// A object specifying the access policy for the shared access signature. + /// A share-level access policy. + /// The allowed protocols (https only, or http and https). Null if you don't want to restrict protocol. + /// The allowed IP address or IP address range. Null if you don't want to restrict based on IP address. + /// A shared access signature, as a URI query string. + /// The query string returned includes the leading question mark. + public string GetSharedAccessSignature(SharedAccessFilePolicy policy, string groupPolicyIdentifier, SharedAccessProtocol? protocols, IPAddressOrRange ipAddressOrRange) { if (!this.ServiceClient.Credentials.IsSharedKey) { @@ -184,10 +198,10 @@ public string GetSharedAccessSignature(SharedAccessFilePolicy policy, string gro string resourceName = this.GetSharedAccessCanonicalName(); StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; - string signature = SharedAccessSignatureHelper.GetHash(policy, null /* headers */, groupPolicyIdentifier, resourceName, Constants.HeaderConstants.TargetStorageVersion, accountKey.KeyValue); + string signature = SharedAccessSignatureHelper.GetHash(policy, null /* headers */, groupPolicyIdentifier, resourceName, Constants.HeaderConstants.TargetStorageVersion, protocols, ipAddressOrRange, accountKey.KeyValue); string accountKeyName = accountKey.KeyName; - UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature(policy, null /* headers */, groupPolicyIdentifier, "s", signature, accountKeyName, Constants.HeaderConstants.TargetStorageVersion); + UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature(policy, null /* headers */, groupPolicyIdentifier, "s", signature, accountKeyName, Constants.HeaderConstants.TargetStorageVersion, protocols, ipAddressOrRange); return builder.ToString(); } diff --git a/Lib/Common/File/Protocol/FileServiceProperties.cs b/Lib/Common/File/Protocol/FileServiceProperties.cs index 20245769b..584194f6b 100644 --- a/Lib/Common/File/Protocol/FileServiceProperties.cs +++ b/Lib/Common/File/Protocol/FileServiceProperties.cs @@ -34,7 +34,7 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol /// public sealed class FileServiceProperties { - private ServiceProperties serviceProperties; + internal ServiceProperties serviceProperties; /// /// Initializes a new instance of the class. @@ -42,8 +42,6 @@ public sealed class FileServiceProperties public FileServiceProperties() { this.serviceProperties = new ServiceProperties(); - this.serviceProperties.HourMetrics = null; - this.serviceProperties.MinuteMetrics = null; this.serviceProperties.Logging = null; } @@ -55,7 +53,7 @@ public CorsProperties Cors { get { - return this.serviceProperties.Cors; + return this.serviceProperties.Cors; } set @@ -64,6 +62,38 @@ public CorsProperties Cors } } + /// + /// Gets or sets the hour metrics properties. + /// + /// The metrics properties. + public MetricsProperties HourMetrics + { + get + { + return this.serviceProperties.HourMetrics; + } + set + { + this.serviceProperties.HourMetrics = value; + } + } + + /// + /// Gets or sets the minutes metrics properties. + /// + /// The metrics properties. + public MetricsProperties MinuteMetrics + { + get + { + return this.serviceProperties.MinuteMetrics; + } + set + { + this.serviceProperties.MinuteMetrics = value; + } + } + /// /// Constructs a ServiceProperties object from an XML document received from the service. /// @@ -71,11 +101,16 @@ public CorsProperties Cors /// A ServiceProperties object containing the properties in the XML document. internal static FileServiceProperties FromServiceXml(XDocument servicePropertiesDocument) { - XElement servicePropertiesElement = servicePropertiesDocument.Element(ServiceProperties.StorageServicePropertiesName); + XElement servicePropertiesElement = + servicePropertiesDocument.Element(ServiceProperties.StorageServicePropertiesName); FileServiceProperties properties = new FileServiceProperties { - Cors = ServiceProperties.ReadCorsPropertiesFromXml(servicePropertiesElement.Element(ServiceProperties.CorsName)) + Cors = ServiceProperties.ReadCorsPropertiesFromXml(servicePropertiesElement.Element(ServiceProperties.CorsName)), + HourMetrics = ServiceProperties.ReadMetricsPropertiesFromXml( + servicePropertiesElement.Element(ServiceProperties.HourMetricsName)), + MinuteMetrics = ServiceProperties.ReadMetricsPropertiesFromXml( + servicePropertiesElement.Element(ServiceProperties.MinuteMetricsName)) }; return properties; diff --git a/Lib/Common/File/SharedAccessFilePermissions.cs b/Lib/Common/File/SharedAccessFilePermissions.cs index 0eddc7a6c..bc10139b4 100644 --- a/Lib/Common/File/SharedAccessFilePermissions.cs +++ b/Lib/Common/File/SharedAccessFilePermissions.cs @@ -48,6 +48,11 @@ public enum SharedAccessFilePermissions /// /// List access granted. /// - List = 0x8 + List = 0x8, + + /// + /// Create access granted. + /// + Create = 0x10 } } \ No newline at end of file diff --git a/Lib/Common/File/SharedAccessFilePolicy.cs b/Lib/Common/File/SharedAccessFilePolicy.cs index 742d959f4..7496eb2fc 100644 --- a/Lib/Common/File/SharedAccessFilePolicy.cs +++ b/Lib/Common/File/SharedAccessFilePolicy.cs @@ -59,7 +59,7 @@ public SharedAccessFilePolicy() /// The shared access permissions, in string format. public static string PermissionsToString(SharedAccessFilePermissions permissions) { - // The service supports a fixed order => rwdl + // The service supports a fixed order => rcwdl StringBuilder builder = new StringBuilder(); if ((permissions & SharedAccessFilePermissions.Read) == SharedAccessFilePermissions.Read) @@ -67,6 +67,11 @@ public static string PermissionsToString(SharedAccessFilePermissions permissions builder.Append("r"); } + if ((permissions & SharedAccessFilePermissions.Create) == SharedAccessFilePermissions.Create) + { + builder.Append("c"); + } + if ((permissions & SharedAccessFilePermissions.Write) == SharedAccessFilePermissions.Write) { builder.Append("w"); @@ -116,6 +121,10 @@ public static SharedAccessFilePermissions PermissionsFromString(string input) permissions |= SharedAccessFilePermissions.List; break; + case 'c': + permissions |= SharedAccessFilePermissions.Create; + break; + default: throw new ArgumentOutOfRangeException("input"); } diff --git a/Lib/Common/IPAddressOrRange.cs b/Lib/Common/IPAddressOrRange.cs new file mode 100644 index 000000000..003cbdd33 --- /dev/null +++ b/Lib/Common/IPAddressOrRange.cs @@ -0,0 +1,163 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage +{ + using System; + using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Core.Util; +#if WINDOWS_DESKTOP + using System.Net; + using System.Net.Sockets; + using System.Globalization; +#elif WINDOWS_RT + using Windows.Networking; + using System.Globalization; +#endif + + /// + /// Specifies either a single IP Address or a single range of IP Addresses (a minimum and a maximum, inclusive.) + /// + public class IPAddressOrRange + { + /// + /// Initializes a new instance of the IPAddressOrRange class from a single IPAddress. + /// + /// The IP Address that the IPAddressOrRange object will represent. + public IPAddressOrRange(string address) + { + CommonUtility.AssertNotNull("address", address); + + // Validate that the address is IPv4 + IPAddressOrRange.AssertIPv4(address); + + this.Address = address; + this.IsSingleAddress = true; + } + + /// + /// Initializes a new instance of the IPAddressOrRange class from two IPAddress objects, a minimum and a maximum. + /// + /// The minimum IP Address that the IPAddressOrRange object will use as a range boundary, inclusive. + /// The maximum IP Address that the IPAddressOrRange object will use as a range boundary, inclusive. + public IPAddressOrRange(string minimum, string maximum) + { + CommonUtility.AssertNotNull("minimum", minimum); + CommonUtility.AssertNotNull("maximum", maximum); + + // Validate that the addresses are IPv4 + IPAddressOrRange.AssertIPv4(minimum); + IPAddressOrRange.AssertIPv4(maximum); + + this.MinimumAddress = minimum; + this.MaximumAddress = maximum; + this.IsSingleAddress = false; + } + + /// + /// The IP Address. + /// Returns null if this object represents a range of IP addresses. + /// + public string Address + { + get; + private set; + } + + /// + /// The minimum IP Address for the range, inclusive. + /// Returns null if this object represents a single IP address. + /// + public string MinimumAddress + { + get; + private set; + } + + /// + /// The maximum IP Address for the range, inclusive. + /// Returns null if this object represents a single IP address. + /// + public string MaximumAddress + { + get; + private set; + } + + /// + /// True if this object represents a single IP Address, false if it represents a range. + /// + public bool IsSingleAddress + { + get; + private set; + } + + /// + /// Provides a string representation of this IPAddressOrRange object. + /// + /// The string representation of this IPAddressOrRange object. + public override string ToString() + { + if (this.IsSingleAddress) + { + return Address; + } + else + { + return MinimumAddress + "-" + MaximumAddress; + } + } + + /// + /// Assert that an IP address is in IPv4 format. + /// + /// The IP address to assert. + private static void AssertIPv4(string address) + { +#if WINDOWS_DESKTOP + IPAddress parsedAddress; + + if (IPAddress.TryParse(address, out parsedAddress) == false) + { + throw new ArgumentException(SR.InvalidIPAddress); + } + + if (parsedAddress.AddressFamily != AddressFamily.InterNetwork) + { + throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, SR.IPMustBeIPV4InSAS, address)); + } +#elif WINDOWS_RT + HostName parsedAddress; + + try + { + parsedAddress = new HostName(address); + } + catch (ArgumentException) + { + throw new ArgumentException(SR.InvalidIPAddress); + } + + if (parsedAddress.Type != HostNameType.Ipv4) + { + throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, SR.IPMustBeIPV4InSAS, address)); + } +#endif + } + } +} diff --git a/Lib/Common/Queue/CloudQueue.Common.cs b/Lib/Common/Queue/CloudQueue.Common.cs index 6c3cdaf66..e4e0eedc7 100644 --- a/Lib/Common/Queue/CloudQueue.Common.cs +++ b/Lib/Common/Queue/CloudQueue.Common.cs @@ -199,19 +199,13 @@ private void ParseQueryAndVerify(StorageUri address, StorageCredentials credenti /// /// Returns the canonical name for shared access. /// - /// A string indicating the desired SAS version to use, in storage service version format. /// The canonical name. - private string GetCanonicalName(string sasVersion) + private string GetCanonicalName() { string accountName = this.ServiceClient.Credentials.AccountName; string queueName = this.Name; string canonicalNameFormat = "/{0}/{1}/{2}"; - if (sasVersion == Constants.VersionConstants.February2012 || sasVersion == Constants.VersionConstants.August2013) - { - // Do not prepend service name for older versions - canonicalNameFormat = "/{1}/{2}"; - } - + return string.Format(CultureInfo.InvariantCulture, canonicalNameFormat, SR.Queue, accountName, queueName); } @@ -308,32 +302,7 @@ public string GetSharedAccessSignature(SharedAccessQueuePolicy policy) /// The query string returned includes the leading question mark. public string GetSharedAccessSignature(SharedAccessQueuePolicy policy, string accessPolicyIdentifier) { - if (!this.ServiceClient.Credentials.IsSharedKey) - { - string errorMessage = string.Format(CultureInfo.CurrentCulture, SR.CannotCreateSASWithoutAccountKey); - throw new InvalidOperationException(errorMessage); - } - - string resourceName = this.GetCanonicalName(Constants.HeaderConstants.TargetStorageVersion); - StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; - - string signature = SharedAccessSignatureHelper.GetHash( - policy, - accessPolicyIdentifier, - resourceName, - Constants.HeaderConstants.TargetStorageVersion, - accountKey.KeyValue); - - string accountKeyName = accountKey.KeyName; - - UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature( - policy, - accessPolicyIdentifier, - signature, - accountKeyName, - Constants.HeaderConstants.TargetStorageVersion); - - return builder.ToString(); + return this.GetSharedAccessSignature(policy, accessPolicyIdentifier, null, null); } /// @@ -341,11 +310,11 @@ public string GetSharedAccessSignature(SharedAccessQueuePolicy policy, string ac /// /// A object specifying the access policy for the shared access signature. /// A string identifying a stored access policy. - /// A string indicating the desired SAS version to use, in storage service version format. Value must be 2012-02-12 or 2013-08-15. + /// The allowed protocols (https only, or http and https). Null if you don't want to restrict protocol. + /// The allowed IP address or IP address range. Null if you don't want to restrict based on IP address. /// A shared access signature, as a URI query string. /// The query string returned includes the leading question mark. - [Obsolete("This overload has been deprecated because the SAS tokens generated using the current version work fine with old libraries. Please use the other overloads.")] - public string GetSharedAccessSignature(SharedAccessQueuePolicy policy, string accessPolicyIdentifier, string sasVersion) + public string GetSharedAccessSignature(SharedAccessQueuePolicy policy, string accessPolicyIdentifier, SharedAccessProtocol? protocols, IPAddressOrRange ipAddressOrRange) { if (!this.ServiceClient.Credentials.IsSharedKey) { @@ -353,15 +322,16 @@ public string GetSharedAccessSignature(SharedAccessQueuePolicy policy, string ac throw new InvalidOperationException(errorMessage); } - string validatedSASVersion = SharedAccessSignatureHelper.ValidateSASVersionString(sasVersion); - string resourceName = this.GetCanonicalName(validatedSASVersion); + string resourceName = this.GetCanonicalName(); StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; string signature = SharedAccessSignatureHelper.GetHash( policy, accessPolicyIdentifier, resourceName, - validatedSASVersion, + Constants.HeaderConstants.TargetStorageVersion, + protocols, + ipAddressOrRange, accountKey.KeyValue); string accountKeyName = accountKey.KeyName; @@ -371,7 +341,9 @@ public string GetSharedAccessSignature(SharedAccessQueuePolicy policy, string ac accessPolicyIdentifier, signature, accountKeyName, - validatedSASVersion); + Constants.HeaderConstants.TargetStorageVersion, + protocols, + ipAddressOrRange); return builder.ToString(); } diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index 3c9f2d596..c39f826fc 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -718,6 +718,10 @@ static class Constants /// internal const string JsonNoMetadataAcceptHeaderValue = "application/json;odata=nometadata"; + /// + /// Header value argument to set JSON no metadata. + /// + internal const string NoMetadata = "odata=nometadata"; /// /// Header value to set Content-Type to AtomPub. /// @@ -782,9 +786,9 @@ static HeaderConstants() /// Specifies the value to use for UserAgent header. /// #if ASPNET_K || PORTABLE - public const string UserAgentProductVersion = "5.0.3-preview"; + public const string UserAgentProductVersion = "6.0.1-preview"; #else - public const string UserAgentProductVersion = "5.0.2"; + public const string UserAgentProductVersion = "6.0.0"; #endif /// @@ -1066,7 +1070,7 @@ static HeaderConstants() /// Current storage version header value. /// Every time this version changes, assembly version needs to be updated as well. /// - public const string TargetStorageVersion = "2015-02-21"; + public const string TargetStorageVersion = "2015-04-05"; /// /// Specifies the file type. @@ -1231,6 +1235,26 @@ public static class QueryConstants /// public const string SignedResource = "sr"; + /// + /// Query component for the signed SAS resource types. + /// + public const string SignedResourceTypes = "srt"; + + /// + /// Query component for the signed SAS service. + /// + public const string SignedServices = "ss"; + + /// + /// Query component for the signed SAS protocol. + /// + public const string SignedProtocols = "spr"; + + /// + /// Query component for the signed SAS IP. + /// + public const string SignedIP = "sip"; + /// /// Query component for the SAS table name. /// @@ -1468,6 +1492,11 @@ public static class AnalyticsConstants /// public const string MetricsHourPrimaryTransactionsQueue = "$MetricsHourPrimaryTransactionsQueue"; + /// + /// Constant for the file service primary location hourly metrics table. + /// + public const string MetricsHourPrimaryTransactionsFile = "$MetricsHourPrimaryTransactionsFile"; + /// /// Constant for the blob service primary location minute metrics table. /// @@ -1483,6 +1512,11 @@ public static class AnalyticsConstants /// public const string MetricsMinutePrimaryTransactionsQueue = "$MetricsMinutePrimaryTransactionsQueue"; + /// + /// Constant for the file service primary location minute metrics table. + /// + public const string MetricsMinutePrimaryTransactionsFile = "$MetricsMinutePrimaryTransactionsFile"; + /// /// Constant for the blob service secondary location hourly metrics table. /// @@ -1498,6 +1532,11 @@ public static class AnalyticsConstants /// public const string MetricsHourSecondaryTransactionsQueue = "$MetricsHourSecondaryTransactionsQueue"; + /// + /// Constant for the file service secondary location hourly metrics table. + /// + public const string MetricsHourSecondaryTransactionsFile = "$MetricsHourSecondaryTransactionsFile"; + /// /// Constant for the blob service secondary location minute metrics table. /// @@ -1513,6 +1552,11 @@ public static class AnalyticsConstants /// public const string MetricsMinuteSecondaryTransactionsQueue = "$MetricsMinuteSecondaryTransactionsQueue"; + /// + /// Constant for the file service secondary location minute metrics table. + /// + public const string MetricsMinuteSecondaryTransactionsFile = "$MetricsMinuteSecondaryTransactionsFile"; + /// /// Constant for default logging version. /// diff --git a/Lib/Common/Shared/Protocol/ServiceProperties.cs b/Lib/Common/Shared/Protocol/ServiceProperties.cs index 6fafa4055..7f6e3ca8f 100644 --- a/Lib/Common/Shared/Protocol/ServiceProperties.cs +++ b/Lib/Common/Shared/Protocol/ServiceProperties.cs @@ -392,6 +392,11 @@ private static XElement GenerateCorsXml(CorsProperties cors) /// A LoggingProperties object containing the properties in the element. private static LoggingProperties ReadLoggingPropertiesFromXml(XElement element) { + if (element == null) + { + return new LoggingProperties(); + } + LoggingOperations state = LoggingOperations.None; if (bool.Parse(element.Element(DeleteName).Value)) @@ -422,8 +427,13 @@ private static LoggingProperties ReadLoggingPropertiesFromXml(XElement element) /// /// The XML element. /// A MetricsProperties object containing the properties in the element. - private static MetricsProperties ReadMetricsPropertiesFromXml(XElement element) + internal static MetricsProperties ReadMetricsPropertiesFromXml(XElement element) { + if (element == null) + { + return new MetricsProperties(); + } + MetricsLevel state = MetricsLevel.None; if (bool.Parse(element.Element(EnabledName).Value)) diff --git a/Lib/Common/SharedAccessAccountPermissions.cs b/Lib/Common/SharedAccessAccountPermissions.cs new file mode 100644 index 000000000..2fa76a718 --- /dev/null +++ b/Lib/Common/SharedAccessAccountPermissions.cs @@ -0,0 +1,73 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage +{ + using System; + + /// + /// Specifies the set of possible permissions for a shared access account policy. + /// + [Flags] + public enum SharedAccessAccountPermissions + { + /// + /// No shared access granted. + /// + None = 0x0, + + /// + /// Permission to read resources and list queues and tables granted. + /// + Read = 0x1, + + /// + /// Permission to add messages, table entities, blobs, and files granted. + /// + Add = 0x2, + + /// + /// Permission to create containers, blobs, shares, directories, and files granted. + /// + Create = 0x4, + + /// + /// Permissions to update messages and table entities granted. + /// + Update = 0x8, + + /// + /// Permission to get and delete messages granted. + /// + ProcessMessages = 0x10, + + /// + /// Permission to write resources granted. + /// + Write = 0x20, + + /// + /// Permission to delete resources granted. + /// + Delete = 0x40, + + /// + /// Permission to list blob containers, blobs, shares, directories, and files granted. + /// + List = 0x80 + } +} diff --git a/Lib/Common/SharedAccessAccountPolicy.cs b/Lib/Common/SharedAccessAccountPolicy.cs new file mode 100644 index 000000000..342c4a525 --- /dev/null +++ b/Lib/Common/SharedAccessAccountPolicy.cs @@ -0,0 +1,190 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage +{ + using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.Shared; + using System; + using System.Net; + using System.Text; + + /// + /// Represents a shared access policy for a account, which specifies the start time, expiry time, + /// permissions, signed service, signed resource type, signed protocol, and signed IP addresses for a shared access signature. + /// + public sealed class SharedAccessAccountPolicy + { + /// + /// Initializes a new instance of the SharedAccessAccountPolicy class. + /// + public SharedAccessAccountPolicy() + { + } + + /// + /// Gets or sets the start time for a shared access signature associated with this shared access policy. + /// + /// A specifying the shared access start time. + public DateTimeOffset? SharedAccessStartTime { get; set; } + + /// + /// Gets or sets the expiry time for a shared access signature associated with this shared access policy. + /// + /// A specifying the shared access expiry time. + public DateTimeOffset? SharedAccessExpiryTime { get; set; } + + /// + /// Gets or sets the permissions for a shared access signature associated with this shared access policy. + /// + /// A object. + public SharedAccessAccountPermissions Permissions { get; set; } + + /// + /// Gets or sets the services (blob, file, queue, table) for a shared access signature associated with this shared access policy. + /// + public SharedAccessAccountServices Services { get; set; } + + /// + /// Gets or sets the resource type for a shared access signature associated with this shared access policy. + /// + public SharedAccessAccountResourceTypes ResourceTypes { get; set; } + + /// + /// Gets or sets the allowed protocols for a shared access signature associated with this shared access policy. + /// + public SharedAccessProtocol? Protocols { get; set; } + + /// + /// Gets or sets the allowed IP address or IP address range for a shared access signature associated with this shared access policy. + /// + public IPAddressOrRange IPAddressOrRange { get; set; } + + /// + /// Converts the permissions specified for the shared access policy to a string. + /// + /// A object. + /// The shared access permissions in string format. + public static string PermissionsToString(SharedAccessAccountPermissions permissions) + { + StringBuilder builder = new StringBuilder(); + + if ((permissions & SharedAccessAccountPermissions.Read) == SharedAccessAccountPermissions.Read) + { + builder.Append("r"); + } + + if ((permissions & SharedAccessAccountPermissions.Add) == SharedAccessAccountPermissions.Add) + { + builder.Append("a"); + } + + if ((permissions & SharedAccessAccountPermissions.Create) == SharedAccessAccountPermissions.Create) + { + builder.Append("c"); + } + + if ((permissions & SharedAccessAccountPermissions.Update) == SharedAccessAccountPermissions.Update) + { + builder.Append("u"); + } + + if ((permissions & SharedAccessAccountPermissions.ProcessMessages) == SharedAccessAccountPermissions.ProcessMessages) + { + builder.Append("p"); + } + + if ((permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write) + { + builder.Append("w"); + } + + if ((permissions & SharedAccessAccountPermissions.Delete) == SharedAccessAccountPermissions.Delete) + { + builder.Append("d"); + } + + if ((permissions & SharedAccessAccountPermissions.List) == SharedAccessAccountPermissions.List) + { + builder.Append("l"); + } + + return builder.ToString(); + } + + /// + /// Converts the services specified for the shared access policy to a string. + /// + /// A object. + /// The shared access services in string format. + public static string ServicesToString(SharedAccessAccountServices services) + { + StringBuilder builder = new StringBuilder(); + + if ((services & SharedAccessAccountServices.Blob) == SharedAccessAccountServices.Blob) + { + builder.Append("b"); + } + + if ((services & SharedAccessAccountServices.File) == SharedAccessAccountServices.File) + { + builder.Append("f"); + } + + if ((services & SharedAccessAccountServices.Queue) == SharedAccessAccountServices.Queue) + { + builder.Append("q"); + } + + if ((services & SharedAccessAccountServices.Table) == SharedAccessAccountServices.Table) + { + builder.Append("t"); + } + + return builder.ToString(); + } + + /// + /// Converts the ResourceTypes specified for the shared access policy to a string. + /// + /// A object. + /// The shared access resource types in string format. + public static string ResourceTypesToString(SharedAccessAccountResourceTypes resourceTypes) + { + StringBuilder builder = new StringBuilder(); + + if ((resourceTypes & SharedAccessAccountResourceTypes.Service) == SharedAccessAccountResourceTypes.Service) + { + builder.Append("s"); + } + + if ((resourceTypes & SharedAccessAccountResourceTypes.Container) == SharedAccessAccountResourceTypes.Container) + { + builder.Append("c"); + } + + if ((resourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object) + { + builder.Append("o"); + } + + return builder.ToString(); + } + } +} + + diff --git a/Lib/Common/SharedAccessAccountResourceTypes.cs b/Lib/Common/SharedAccessAccountResourceTypes.cs new file mode 100644 index 000000000..5a0e3adc5 --- /dev/null +++ b/Lib/Common/SharedAccessAccountResourceTypes.cs @@ -0,0 +1,48 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage +{ + using System; + + /// + /// Specifies the set of possible signed resource types for a shared access account policy. + /// + [Flags] + public enum SharedAccessAccountResourceTypes + { + /// + /// No shared access granted. + /// + None = 0x0, + + /// + /// Permission to access service level APIs granted. + /// + Service = 0x1, + + /// + /// Permission to access container level APIs (Blob Containers, Tables, Queues, File Shares) granted. + /// + Container = 0x2, + + /// + /// Permission to access object level APIs (Blobs, Table Entities, Queue Messages, Files) granted + /// + Object = 0x4 + } +} diff --git a/Lib/Common/SharedAccessAccountServices.cs b/Lib/Common/SharedAccessAccountServices.cs new file mode 100644 index 000000000..baa5d60be --- /dev/null +++ b/Lib/Common/SharedAccessAccountServices.cs @@ -0,0 +1,53 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage +{ + using System; + + /// + /// Specifies the set of possible signed services for a shared access account policy. + /// + [Flags] + public enum SharedAccessAccountServices + { + /// + /// No shared access granted. + /// + None = 0x0, + + /// + /// Permission to access blob resources granted. + /// + Blob = 0x1, + + /// + /// Permission to access file resources granted. + /// + File = 0x2, + + /// + /// Permission to access queue resources granted. + /// + Queue = 0x4, + + /// + /// Permission to access table resources granted. + /// + Table = 0x8 + } +} diff --git a/Lib/Common/SharedAccessProtocol.cs b/Lib/Common/SharedAccessProtocol.cs new file mode 100644 index 000000000..e25d40488 --- /dev/null +++ b/Lib/Common/SharedAccessProtocol.cs @@ -0,0 +1,37 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage +{ + using System; + + /// + /// Specifies the set of possible signed protocols for a shared access account policy. + /// + public enum SharedAccessProtocol + { + /// + /// Permission to use SAS only through https granted. + /// + HttpsOnly = 0x1, + + /// + /// Permission to use SAS through https or http granted. Equivalent to not specifying any permission at all. + /// + HttpsOrHttp = 0x2 + } +} diff --git a/Lib/Common/StorageException.cs b/Lib/Common/StorageException.cs index 1b04f5984..1f25ada33 100644 --- a/Lib/Common/StorageException.cs +++ b/Lib/Common/StorageException.cs @@ -169,11 +169,7 @@ public static StorageException TranslateException(Exception ex, RequestResult re if (response != null) { StorageException.PopulateRequestResult(reqResult, response); -#if WINDOWS_RT - reqResult.ExtendedErrorInformation = StorageExtendedErrorInformation.ReadFromStream(response.GetResponseStream().AsInputStream()); -#else reqResult.ExtendedErrorInformation = parseError(response.GetResponseStream()); -#endif } } #endif @@ -252,12 +248,7 @@ internal static StorageException TranslateExceptionWithPreBufferedStream(Excepti if (response != null) { PopulateRequestResult(reqResult, response); - -#if WINDOWS_RT - reqResult.ExtendedErrorInformation = StorageExtendedErrorInformation.ReadFromStream(responseStream); -#else reqResult.ExtendedErrorInformation = parseError(responseStream); -#endif } } #endif diff --git a/Lib/Common/StorageExtendedErrorInformation.cs b/Lib/Common/StorageExtendedErrorInformation.cs index 94104e477..1c7320953 100644 --- a/Lib/Common/StorageExtendedErrorInformation.cs +++ b/Lib/Common/StorageExtendedErrorInformation.cs @@ -25,6 +25,9 @@ namespace Microsoft.WindowsAzure.Storage using System.Collections.Generic; using System.IO; using System.Net; +#if WINDOWS_RT || PORTABLE || ASPNET_K + using System.Net.Http; +#endif using System.Xml; #if WINDOWS_DESKTOP && !WINDOWS_PHONE @@ -61,7 +64,7 @@ public StorageExtendedErrorInformation() public string ErrorMessage { get; internal set; } /// - /// Gets additional error details. + /// Gets additional error details from XML-formatted input stream. /// /// An containing the additional error details. public IDictionary AdditionalDetails { get; internal set; } @@ -74,7 +77,7 @@ public static StorageExtendedErrorInformation ReadFromStream(IInputStream inputS #endif /// - /// Gets the error details from stream. + /// Gets the error details from an XML-formatted error stream. /// /// The input stream. /// The error details. @@ -117,8 +120,11 @@ static StorageExtendedErrorInformation ReadFromStream(Stream inputStream) /// The web response. /// The response Content-Type. /// The error details. -#if !(WINDOWS_RT || ASPNET_K || PORTABLE) +#if WINDOWS_RT || ASPNET_K || PORTABLE + public static StorageExtendedErrorInformation ReadFromStreamUsingODataLib(Stream inputStream, HttpResponseMessage response, string contentType) +#else public static StorageExtendedErrorInformation ReadFromStreamUsingODataLib(Stream inputStream, HttpWebResponse response, string contentType) +#endif { CommonUtility.AssertNotNull("inputStream", inputStream); CommonUtility.AssertNotNull("response", response); @@ -131,7 +137,6 @@ public static StorageExtendedErrorInformation ReadFromStreamUsingODataLib(Stream HttpResponseAdapterMessage responseMessage = new HttpResponseAdapterMessage(response, inputStream, contentType); return ReadAndParseExtendedError(responseMessage); } -#endif /// /// Gets the error details from the stream using OData library. @@ -160,7 +165,6 @@ public static StorageExtendedErrorInformation ReadDataServiceResponseFromStream( /// /// The IODataResponseMessage to parse. /// The error details. -#if !(WINDOWS_RT || ASPNET_K || PORTABLE) public static StorageExtendedErrorInformation ReadAndParseExtendedError(IODataResponseMessage responseMessage) { StorageExtendedErrorInformation storageExtendedError = null; @@ -201,7 +205,6 @@ public static StorageExtendedErrorInformation ReadAndParseExtendedError(IODataRe return storageExtendedError; } -#endif #region IXmlSerializable diff --git a/Lib/Common/Table/CloudTable.Common.cs b/Lib/Common/Table/CloudTable.Common.cs index 1d277d83f..d13fd81ea 100644 --- a/Lib/Common/Table/CloudTable.Common.cs +++ b/Lib/Common/Table/CloudTable.Common.cs @@ -17,6 +17,7 @@ namespace Microsoft.WindowsAzure.Storage.Table { + using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Auth; using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Auth; @@ -172,39 +173,7 @@ public string GetSharedAccessSignature( string endPartitionKey, string endRowKey) { - if (!this.ServiceClient.Credentials.IsSharedKey) - { - string errorMessage = string.Format(CultureInfo.CurrentCulture, SR.CannotCreateSASWithoutAccountKey); - throw new InvalidOperationException(errorMessage); - } - - string resourceName = this.GetCanonicalName(Constants.HeaderConstants.TargetStorageVersion); - StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; - - string signature = SharedAccessSignatureHelper.GetHash( - policy, - accessPolicyIdentifier, - startPartitionKey, - startRowKey, - endPartitionKey, - endRowKey, - resourceName, - Constants.HeaderConstants.TargetStorageVersion, - accountKey.KeyValue); - - UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature( - policy, - this.Name, - accessPolicyIdentifier, - startPartitionKey, - startRowKey, - endPartitionKey, - endRowKey, - signature, - accountKey.KeyName, - Constants.HeaderConstants.TargetStorageVersion); - - return builder.ToString(); + return this.GetSharedAccessSignature(policy, accessPolicyIdentifier, startPartitionKey, startRowKey, endPartitionKey, endRowKey, null, null); } /// @@ -216,11 +185,11 @@ public string GetSharedAccessSignature( /// A string specifying the start row key, or null. /// A string specifying the end partition key, or null. /// A string specifying the end row key, or null. - /// A string indicating the desired SAS version to use, in storage service version format. Value must be 2012-02-12 or 2013-08-15. + /// The allowed protocols (https only, or http and https). Null if you don't want to restrict protocol. + /// The allowed IP address or IP address range. Null if you don't want to restrict based on IP address. /// A shared access signature, as a URI query string. /// The query string returned includes the leading question mark. /// Thrown if the current credentials don't support creating a shared access signature. - [Obsolete("This overload has been deprecated because the SAS tokens generated using the current version work fine with old libraries. Please use the other overloads.")] public string GetSharedAccessSignature( SharedAccessTablePolicy policy, string accessPolicyIdentifier, @@ -228,16 +197,16 @@ public string GetSharedAccessSignature( string startRowKey, string endPartitionKey, string endRowKey, - string sasVersion) + SharedAccessProtocol? protocols, + IPAddressOrRange ipAddressOrRange) { if (!this.ServiceClient.Credentials.IsSharedKey) - { - string errorMessage = string.Format(CultureInfo.CurrentCulture, SR.CannotCreateSASWithoutAccountKey); - throw new InvalidOperationException(errorMessage); - } + { + string errorMessage = string.Format(CultureInfo.CurrentCulture, SR.CannotCreateSASWithoutAccountKey); + throw new InvalidOperationException(errorMessage); + } - string validatedSASVersion = SharedAccessSignatureHelper.ValidateSASVersionString(sasVersion); - string resourceName = this.GetCanonicalName(validatedSASVersion); + string resourceName = this.GetCanonicalName(); StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; string signature = SharedAccessSignatureHelper.GetHash( @@ -248,7 +217,9 @@ public string GetSharedAccessSignature( endPartitionKey, endRowKey, resourceName, - validatedSASVersion, + Constants.HeaderConstants.TargetStorageVersion, + protocols, + ipAddressOrRange, accountKey.KeyValue); UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature( @@ -261,12 +232,14 @@ public string GetSharedAccessSignature( endRowKey, signature, accountKey.KeyName, - validatedSASVersion); + Constants.HeaderConstants.TargetStorageVersion, + protocols, + ipAddressOrRange); return builder.ToString(); } -#endif +#endif /// /// Returns the name of the table. /// @@ -299,20 +272,13 @@ private void ParseQueryAndVerify(StorageUri address, StorageCredentials credenti /// /// Gets the canonical name of the table, formatted as table/<account-name>/<table-name>. /// - /// A string indicating the desired SAS version to use, in storage service version format. /// The canonical name of the table. [SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.String.ToLower", Justification = "ToLower(CultureInfo) is not present in RT and ToLowerInvariant() also violates FxCop")] - private string GetCanonicalName(string sasVersion) + private string GetCanonicalName() { string accountName = this.ServiceClient.Credentials.AccountName; string tableNameLowerCase = this.Name.ToLower(); string canonicalNameFormat = "/{0}/{1}/{2}"; - if (sasVersion == Constants.VersionConstants.February2012 || sasVersion == Constants.VersionConstants.August2013) - { - // Do not prepend service name for older versions - canonicalNameFormat = "/{1}/{2}"; - } - return string.Format(CultureInfo.InvariantCulture, canonicalNameFormat, SR.Table, accountName, tableNameLowerCase); } } diff --git a/Lib/Common/Table/CloudTableClient.Common.cs b/Lib/Common/Table/CloudTableClient.Common.cs index b9494c216..9b7018047 100644 --- a/Lib/Common/Table/CloudTableClient.Common.cs +++ b/Lib/Common/Table/CloudTableClient.Common.cs @@ -69,11 +69,7 @@ public CloudTableClient(StorageUri storageUri, StorageCredentials credentials) this.DefaultRequestOptions = new TableRequestOptions(); this.DefaultRequestOptions.RetryPolicy = new ExponentialRetry(); this.DefaultRequestOptions.LocationMode = RetryPolicies.LocationMode.PrimaryOnly; -#if !WINDOWS_RT this.DefaultRequestOptions.PayloadFormat = TablePayloadFormat.Json; -#else - this.DefaultRequestOptions.PayloadFormat = TablePayloadFormat.AtomPub; -#endif this.AuthenticationScheme = AuthenticationScheme.SharedKey; this.UsePathStyleUris = CommonUtility.UsePathStyleAddressing(this.BaseUri); @@ -196,8 +192,7 @@ public TimeSpan? MaximumExecutionTime /// Gets and sets the table payload format for requests against any table accessed with this object. /// /// A enumeration value. - /// By default, this property is set to for .NET and Windows Phone. For Windows Runtime, - /// it is set to . + /// By default, this property is set to . [Obsolete("Use DefaultRequestOptions.PayloadFormat.")] public TablePayloadFormat? PayloadFormat { @@ -208,15 +203,6 @@ public TablePayloadFormat? PayloadFormat set { -#if WINDOWS_RT - if (value.HasValue) - { - if (value.Value == TablePayloadFormat.Json || value.Value == TablePayloadFormat.JsonNoMetadata || value.Value == TablePayloadFormat.JsonFullMetadata) - { - throw new ArgumentException(SR.JsonNotSupportedOnRT, "value"); - } - } -#endif this.DefaultRequestOptions.PayloadFormat = value; } } diff --git a/Lib/Common/Table/TableBatchOperation.Common.cs b/Lib/Common/Table/TableBatchOperation.Common.cs index 86b5cb46f..82aa9c1ad 100644 --- a/Lib/Common/Table/TableBatchOperation.Common.cs +++ b/Lib/Common/Table/TableBatchOperation.Common.cs @@ -71,7 +71,7 @@ public void Delete(ITableEntity entity) /// The entity to be inserted into the table. public void Insert(ITableEntity entity) { - this.Insert(entity, true); + this.Insert(entity, false); } /// diff --git a/Lib/Common/Table/TableEntity.cs b/Lib/Common/Table/TableEntity.cs index a784c088a..23d44bda6 100644 --- a/Lib/Common/Table/TableEntity.cs +++ b/Lib/Common/Table/TableEntity.cs @@ -522,19 +522,8 @@ private static ReadAction CompileReadAction(Type type) exprs.Add(Expression.Assign(propName, Expression.Constant(prop.Name))); exprs.Add(Expression.Assign(tempProp, Expression.Call(GetKeyOrNullFromDictionaryMethodInfo, propName, dictVariable, ctxParam))); - exprs.Add( - - // If property is not null - Expression.IfThen(Expression.NotEqual(tempProp, Expression.Constant(null)), - - // then if prop.Isnull, objProp.SetValue(null) - Expression.IfThenElse(Expression.Call(tempProp, EntityPropertyIsNullInfo), - - // then - Expression.Call(Expression.Convert(instanceParam, type), prop.FindSetProp(), Expression.Convert(Expression.Constant(null), prop.PropertyType)), - - // else set entity property based on type - readExpr))); + // If property is not null + exprs.Add(Expression.IfThen(Expression.NotEqual(tempProp, Expression.Constant(null)), readExpr)); } } diff --git a/Lib/Common/Table/TableOperation.Common.cs b/Lib/Common/Table/TableOperation.Common.cs index 1ac1322c7..b8f9c8ca1 100644 --- a/Lib/Common/Table/TableOperation.Common.cs +++ b/Lib/Common/Table/TableOperation.Common.cs @@ -291,8 +291,8 @@ public static TableOperation Retrieve(string partitionKey, string rowke /// Creates a new table operation that retrieves the contents of /// the given entity in a table. /// - /// A string containing the partition key of the entity to be replaced. - /// A string containing the row key of the entity to be replaced. + /// A string containing the partition key of the entity to be retrieved. + /// A string containing the row key of the entity to be retrieved. /// The object. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "rowkey", Justification = "Reviewed : rowkey is allowed.")] public static TableOperation Retrieve(string partitionKey, string rowkey) diff --git a/Lib/Common/Table/TablePayloadFormat.cs b/Lib/Common/Table/TablePayloadFormat.cs index 9f19cedd6..2d281a518 100644 --- a/Lib/Common/Table/TablePayloadFormat.cs +++ b/Lib/Common/Table/TablePayloadFormat.cs @@ -17,6 +17,8 @@ namespace Microsoft.WindowsAzure.Storage.Table { + using System; + /// /// Describes the payload formats supported for Tables. /// @@ -25,6 +27,7 @@ public enum TablePayloadFormat /// /// Use AtomPub. /// + [Obsolete("Please use a JSON payload format for table operations.")] AtomPub, /// diff --git a/Lib/Common/Table/TableQuery.Common.cs b/Lib/Common/Table/TableQuery.Common.cs index 76c0d3c3d..260f9c0fb 100644 --- a/Lib/Common/Table/TableQuery.Common.cs +++ b/Lib/Common/Table/TableQuery.Common.cs @@ -166,10 +166,15 @@ private static string GenerateFilterCondition(string propertyName, string operat { string valueOperand = null; - if (edmType == EdmType.Boolean || edmType == EdmType.Double || edmType == EdmType.Int32) + if (edmType == EdmType.Boolean || edmType == EdmType.Int32) { valueOperand = givenValue; } + else if (edmType == EdmType.Double) + { + bool decimalExists = givenValue.Contains("."); + valueOperand = decimalExists ? givenValue : string.Format(CultureInfo.InvariantCulture, "{0}.0", givenValue); + } else if (edmType == EdmType.Int64) { valueOperand = string.Format(CultureInfo.InvariantCulture, "{0}L", givenValue); diff --git a/Lib/Common/Table/TableRequestOptions.cs b/Lib/Common/Table/TableRequestOptions.cs index 34b317613..52aca36f9 100644 --- a/Lib/Common/Table/TableRequestOptions.cs +++ b/Lib/Common/Table/TableRequestOptions.cs @@ -57,8 +57,8 @@ public TableRequestOptions(TableRequestOptions other) this.MaximumExecutionTime = other.MaximumExecutionTime; this.OperationExpiryTime = other.OperationExpiryTime; this.PayloadFormat = other.PayloadFormat; -#if !(WINDOWS_RT || ASPNET_K || PORTABLE) this.PropertyResolver = other.PropertyResolver; +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) this.EncryptionPolicy = other.EncryptionPolicy; this.RequireEncryption = other.RequireEncryption; this.EncryptionResolver = other.EncryptionResolver; @@ -76,23 +76,18 @@ internal static TableRequestOptions ApplyDefaults(TableRequestOptions requestOpt ?? RetryPolicies.LocationMode.PrimaryOnly; modifiedOptions.ServerTimeout = modifiedOptions.ServerTimeout ?? serviceClient.DefaultRequestOptions.ServerTimeout; modifiedOptions.MaximumExecutionTime = modifiedOptions.MaximumExecutionTime ?? serviceClient.DefaultRequestOptions.MaximumExecutionTime; -#if !(WINDOWS_RT || ASPNET_K || PORTABLE) - modifiedOptions.PayloadFormat = (modifiedOptions.PayloadFormat - ?? serviceClient.DefaultRequestOptions.PayloadFormat) - ?? TablePayloadFormat.Json; -#else - modifiedOptions.PayloadFormat = (modifiedOptions.PayloadFormat - ?? serviceClient.DefaultRequestOptions.PayloadFormat) - ?? TablePayloadFormat.AtomPub; -#endif + modifiedOptions.PayloadFormat = ( + modifiedOptions.PayloadFormat + ?? serviceClient.DefaultRequestOptions.PayloadFormat) + ?? TablePayloadFormat.Json; if (!modifiedOptions.OperationExpiryTime.HasValue && modifiedOptions.MaximumExecutionTime.HasValue) { modifiedOptions.OperationExpiryTime = DateTime.Now + modifiedOptions.MaximumExecutionTime.Value; } -#if !(WINDOWS_RT || ASPNET_K || PORTABLE) modifiedOptions.PropertyResolver = modifiedOptions.PropertyResolver ?? serviceClient.DefaultRequestOptions.PropertyResolver; +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) modifiedOptions.EncryptionPolicy = modifiedOptions.EncryptionPolicy ?? serviceClient.DefaultRequestOptions.EncryptionPolicy; modifiedOptions.RequireEncryption = modifiedOptions.RequireEncryption ?? serviceClient.DefaultRequestOptions.RequireEncryption; modifiedOptions.EncryptionResolver = modifiedOptions.EncryptionResolver ?? serviceClient.DefaultRequestOptions.EncryptionResolver; @@ -236,20 +231,11 @@ public TablePayloadFormat? PayloadFormat { if (value.HasValue) { -#if WINDOWS_RT - if (value.Value == TablePayloadFormat.Json || value.Value == TablePayloadFormat.JsonNoMetadata || value.Value == TablePayloadFormat.JsonFullMetadata) - { - throw new ArgumentException(SR.JsonNotSupportedOnRT, "value"); - } -#endif this.payloadFormat = value.Value; } } } - // For now, this is ok since we don't have RT support. Has to change to internal once we do. In that case, we can add overloads to Table operation - // methods to take in EdmTypeResolver. -#if !(WINDOWS_RT || ASPNET_K || PORTABLE) private Func propertyResolver = null; /// @@ -261,6 +247,7 @@ public Func PropertyResolver set { this.propertyResolver = value; } } +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) private Func encryptionResolver = null; /// diff --git a/Lib/ClassLibraryCommon/Table/TableStorageEntityContainer.cs b/Lib/Common/Table/TableStorageEntityContainer.cs similarity index 100% rename from Lib/ClassLibraryCommon/Table/TableStorageEntityContainer.cs rename to Lib/Common/Table/TableStorageEntityContainer.cs diff --git a/Lib/ClassLibraryCommon/Table/TableStorageModel.cs b/Lib/Common/Table/TableStorageModel.cs similarity index 100% rename from Lib/ClassLibraryCommon/Table/TableStorageModel.cs rename to Lib/Common/Table/TableStorageModel.cs diff --git a/Lib/Portable/Microsoft.WindowsAzure.Storage.csproj b/Lib/Portable/Microsoft.WindowsAzure.Storage.csproj index e6b8e1645..914742d6e 100644 --- a/Lib/Portable/Microsoft.WindowsAzure.Storage.csproj +++ b/Lib/Portable/Microsoft.WindowsAzure.Storage.csproj @@ -307,6 +307,9 @@ IContinuationToken.cs + + IPAddressOrRange.cs + IRequestOptions.cs @@ -418,6 +421,21 @@ RetryPolicies\RetryInfo.cs + + SharedAccessAccountPermissions.cs + + + SharedAccessAccountPolicy.cs + + + SharedAccessAccountResourceTypes.cs + + + SharedAccessAccountServices.cs + + + SharedAccessProtocol.cs + Shared\Protocol\AccessPolicyResponseBase.cs @@ -547,6 +565,12 @@ Table\TableContinuationToken.cs + + Table\TableStorageModel.cs + + + Table\TableStorageEntityContainer.cs + Table\TableEntity.cs @@ -604,12 +628,12 @@ Blob\BlobWriteStreamHelper.cs - - Blob\CloudBlob.cs - Blob\CloudAppendBlob.cs + + Blob\CloudBlob.cs + Blob\CloudBlobClient.cs @@ -763,6 +787,10 @@ ..\..\packages\Microsoft.Data.OData.5.6.4\lib\portable-net45+wp8+win8+wpa\Microsoft.Data.OData.dll + + False + ..\..\packages\Newtonsoft.Json.6.0.8\lib\portable-net45+wp80+win8+wpa81+aspnetcore50\Newtonsoft.Json.dll + ..\..\packages\System.Spatial.5.6.4\lib\portable-net45+wp8+win8+wpa\System.Spatial.dll diff --git a/Lib/Portable/Properties/AssemblyInfo.cs b/Lib/Portable/Properties/AssemblyInfo.cs index 8142bd0a1..113fe16fa 100644 --- a/Lib/Portable/Properties/AssemblyInfo.cs +++ b/Lib/Portable/Properties/AssemblyInfo.cs @@ -26,6 +26,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("5.0.3.0")] -[assembly: AssemblyFileVersion("5.0.3.0")] -[assembly: AssemblyInformationalVersion("5.0.3.0-preview")] +[assembly: AssemblyVersion("6.0.1.0")] +[assembly: AssemblyFileVersion("6.0.1.0")] +[assembly: AssemblyInformationalVersion("6.0.1.0-preview")] diff --git a/Lib/Portable/packages.config b/Lib/Portable/packages.config index 88e91017a..af311dffe 100644 --- a/Lib/Portable/packages.config +++ b/Lib/Portable/packages.config @@ -3,4 +3,5 @@ + \ No newline at end of file diff --git a/Lib/WindowsDesktop/GlobalSuppressions.cs b/Lib/WindowsDesktop/GlobalSuppressions.cs index b2af84a08..ede31cc4a 100644 --- a/Lib/WindowsDesktop/GlobalSuppressions.cs +++ b/Lib/WindowsDesktop/GlobalSuppressions.cs @@ -28,6 +28,9 @@ [assembly: SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableQuery`1.#GenerateFilterConditionForInt(System.String,System.String,System.Int32)", Justification = "Backward compatibility")] [assembly: SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableQuery`1.#GenerateFilterConditionForDouble(System.String,System.String,System.Double)", Justification = "Backward compatibility")] +// CA 1008 +[assembly: SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Scope = "type", Target = "Microsoft.WindowsAzure.Storage.SharedAccessProtocol")] + // CA1011 [assembly: SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#StartCopyFromBlob(Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#BeginStartCopyFromBlob(Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] @@ -417,6 +420,8 @@ [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobWriteStream.#WriteAppendBlock(System.IO.Stream,System.Int64,System.String,Microsoft.WindowsAzure.Storage.Core.Util.StorageAsyncResult`1)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.TruncatingNonCloseableStream.#BeginRead(System.Byte[],System.Int32,System.Int32,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.BlobDecryptStream.#WriteStreamCallback(System.IAsyncResult)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.LengthLimitingStream.#WriteStreamCallback(System.IAsyncResult)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Executor.ExecutionState`1.#CheckDisposeAction()", Justification = "Reviewed.")] // CA1040 [assembly: SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces", Scope = "type", Target = "Microsoft.WindowsAzure.Storage.IContinuationToken", Justification = "Reviewed : Specifies a common base type a continuation token can be cast to")] @@ -457,6 +462,9 @@ [assembly: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Executor.TableExecutor.#ExecuteSync`2(Microsoft.WindowsAzure.Storage.Core.Executor.TableCommand`2,Microsoft.WindowsAzure.Storage.RetryPolicies.IRetryPolicy,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Executor.Executor.#EndOperation`1(Microsoft.WindowsAzure.Storage.Core.Executor.ExecutionState`1)", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Auth.SharedAccessSignatureHelper.#ParseQuery(System.Collections.Generic.IDictionary`2,System.Boolean)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.Protocol.TableOperationHttpResponseParsers.#TableBatchOperationPostProcess(System.Collections.Generic.IList`1,Microsoft.WindowsAzure.Storage.Table.TableBatchOperation,Microsoft.WindowsAzure.Storage.Core.Executor.RESTCommand`1>,System.Net.HttpWebResponse,Microsoft.WindowsAzure.Storage.OperationContext,Microsoft.WindowsAzure.Storage.Table.TableRequestOptions,System.String)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#BeginUploadFromStreamHelper(System.IO.Stream,System.Nullable`1,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#GetBlobImpl(Microsoft.WindowsAzure.Storage.Blob.BlobAttributes,System.IO.Stream,System.Nullable`1,System.Nullable`1,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions)", Justification = "Reviewed")] // CA1505 [assembly: SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.Queryable.ReflectionUtil.#.cctor()", Justification = "Reviewed")] @@ -505,11 +513,28 @@ [assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "etag", Scope = "type", Target = "Microsoft.WindowsAzure.Storage.Table.EntityResolver`1", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Etag", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.RequestResult.#Etag", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Etag", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableResult.#Etag", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "AddressOr", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.SharedAccessAccountPolicy.#IPAddressOrRange", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "AddressOr", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#GetSharedAccessSignature(Microsoft.WindowsAzure.Storage.Blob.SharedAccessBlobPolicy,Microsoft.WindowsAzure.Storage.Blob.SharedAccessBlobHeaders,System.String,System.Nullable`1,Microsoft.WindowsAzure.Storage.IPAddressOrRange)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "AddressOr", Scope = "type", Target = "Microsoft.WindowsAzure.Storage.IPAddressOrRange", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "AddressOr", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Queue.CloudQueue.#GetSharedAccessSignature(Microsoft.WindowsAzure.Storage.Queue.SharedAccessQueuePolicy,System.String,System.Nullable`1,Microsoft.WindowsAzure.Storage.IPAddressOrRange)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "AddressOr", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.File.CloudFile.#GetSharedAccessSignature(Microsoft.WindowsAzure.Storage.File.SharedAccessFilePolicy,Microsoft.WindowsAzure.Storage.File.SharedAccessFileHeaders,System.String,System.Nullable`1,Microsoft.WindowsAzure.Storage.IPAddressOrRange)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "AddressOr", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.File.CloudFileShare.#GetSharedAccessSignature(Microsoft.WindowsAzure.Storage.File.SharedAccessFilePolicy,System.String,System.Nullable`1,Microsoft.WindowsAzure.Storage.IPAddressOrRange)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "AddressOr", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.CloudTable.#GetSharedAccessSignature(Microsoft.WindowsAzure.Storage.Table.SharedAccessTablePolicy,System.String,System.String,System.String,System.String,System.String,System.Nullable`1,Microsoft.WindowsAzure.Storage.IPAddressOrRange)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "AddressOr", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlobContainer.#GetSharedAccessSignature(Microsoft.WindowsAzure.Storage.Blob.SharedAccessBlobPolicy,System.String,System.Nullable`1,Microsoft.WindowsAzure.Storage.IPAddressOrRange)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "AddressOr", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlobContainer.#GetSharedAccessSignature(Microsoft.WindowsAzure.Storage.Blob.SharedAccessBlobPolicy,System.String,System.Nullable`1,Microsoft.WindowsAzure.Storage.IPAddressOrRange)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "AddressOr", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.ICloudBlob.#GetSharedAccessSignature(Microsoft.WindowsAzure.Storage.Blob.SharedAccessBlobPolicy,Microsoft.WindowsAzure.Storage.Blob.SharedAccessBlobHeaders,System.String,System.Nullable`1,Microsoft.WindowsAzure.Storage.IPAddressOrRange)", Justification = "Reviewed")] // CA1704 [assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "req", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageException.#TranslateException(System.Exception,Microsoft.WindowsAzure.Storage.RequestResult)", Justification = "req is allowed")] [assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "req", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageException.#TranslateException(System.Exception,Microsoft.WindowsAzure.Storage.RequestResult,System.Func`2)", Justification = "req is allowed")] [assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "type", Target = "Microsoft.WindowsAzure.Storage.Core.MultiBufferMemoryStream", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.ICloudBlob.#GetSharedAccessSignature(Microsoft.WindowsAzure.Storage.Blob.SharedAccessBlobPolicy,Microsoft.WindowsAzure.Storage.Blob.SharedAccessBlobHeaders,System.String,System.Nullable`1,Microsoft.WindowsAzure.Storage.IPAddressOrRange)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#GetSharedAccessSignature(Microsoft.WindowsAzure.Storage.Blob.SharedAccessBlobPolicy,Microsoft.WindowsAzure.Storage.Blob.SharedAccessBlobHeaders,System.String,System.Nullable`1,Microsoft.WindowsAzure.Storage.IPAddressOrRange)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlobContainer.#GetSharedAccessSignature(Microsoft.WindowsAzure.Storage.Blob.SharedAccessBlobPolicy,System.String,System.Nullable`1,Microsoft.WindowsAzure.Storage.IPAddressOrRange)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.CloudTable.#GetSharedAccessSignature(Microsoft.WindowsAzure.Storage.Table.SharedAccessTablePolicy,System.String,System.String,System.String,System.String,System.String,System.Nullable`1,Microsoft.WindowsAzure.Storage.IPAddressOrRange)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Queue.CloudQueue.#GetSharedAccessSignature(Microsoft.WindowsAzure.Storage.Queue.SharedAccessQueuePolicy,System.String,System.Nullable`1,Microsoft.WindowsAzure.Storage.IPAddressOrRange)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.File.CloudFileShare.#GetSharedAccessSignature(Microsoft.WindowsAzure.Storage.File.SharedAccessFilePolicy,System.String,System.Nullable`1,Microsoft.WindowsAzure.Storage.IPAddressOrRange)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.File.CloudFile.#GetSharedAccessSignature(Microsoft.WindowsAzure.Storage.File.SharedAccessFilePolicy,Microsoft.WindowsAzure.Storage.File.SharedAccessFileHeaders,System.String,System.Nullable`1,Microsoft.WindowsAzure.Storage.IPAddressOrRange)", Justification = "Reviewed.")] // CA1709 [assembly: SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SAS", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Analytics.MetricsEntity.#SASSuccess", Justification = "Reviewed")] @@ -558,6 +583,7 @@ [assembly: SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.File.CloudFileClient.#LocationMode", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#EndStartCopyFromBlob(System.IAsyncResult)", Justification = "APM end methods should not be static")] [assembly: SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#BlobType", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableEncryptionPolicy.#DecryptEntity(System.Collections.Generic.IDictionary`2,System.Collections.Generic.HashSet`1,System.String,System.String,System.Byte[],Microsoft.WindowsAzure.Storage.EncryptionData)", Justification = "Reviewed")] // CA1823 [assembly: SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.Queryable.ResourceBinder+PropertyInfoEqualityComparer.#Instance", Justification = "Reviewed")] @@ -669,6 +695,13 @@ [assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#BeginAppendFromFile(System.String,System.IO.FileMode,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#BeginAppendFromByteArray(System.Byte[],System.Int32,System.Int32,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.TruncatingNonCloseableStream.#BeginRead(System.Byte[],System.Int32,System.Int32,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptedWriteStream.#.ctor(Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.Security.Cryptography.ICryptoTransform)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptedWriteStream.#.ctor(Microsoft.WindowsAzure.Storage.Blob.CloudPageBlob,System.Int64,System.Boolean,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.Security.Cryptography.ICryptoTransform)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptedWriteStream.#.ctor(Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.Security.Cryptography.ICryptoTransform)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.BlobDecryptStream.#BeginWrite(System.Byte[],System.Int32,System.Int32,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.LengthLimitingStream.#BeginWrite(System.Byte[],System.Int32,System.Int32,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.Protocol.TableOperationHttpWebRequestFactory.#BuildRequestForTableBatchOperation(System.Uri,Microsoft.WindowsAzure.Storage.Core.UriQueryBuilder,Microsoft.WindowsAzure.Storage.IBufferManager,System.Nullable`1,System.String,Microsoft.WindowsAzure.Storage.Table.TableBatchOperation,System.Boolean,Microsoft.WindowsAzure.Storage.OperationContext,Microsoft.WindowsAzure.Storage.Table.TableRequestOptions,System.String)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.Protocol.TableOperationHttpWebRequestFactory.#BuildRequestForTableOperation(System.Uri,Microsoft.WindowsAzure.Storage.Core.UriQueryBuilder,Microsoft.WindowsAzure.Storage.IBufferManager,System.Nullable`1,Microsoft.WindowsAzure.Storage.Table.TableOperation,System.Boolean,Microsoft.WindowsAzure.Storage.OperationContext,Microsoft.WindowsAzure.Storage.Table.TableRequestOptions,System.String)", Justification = "Reviewed.")] // CA2204 [assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "deleteSnapshotsOption", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.Protocol.BlobHttpWebRequestFactory.#Delete(System.Uri,System.Nullable`1,System.Nullable`1,Microsoft.WindowsAzure.Storage.Blob.DeleteSnapshotsOption,Microsoft.WindowsAzure.Storage.AccessCondition,System.Boolean,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Reviewed.")] @@ -692,6 +725,14 @@ [assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "RequireEncryption", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.Protocol.TableOperationHttpResponseParsers.#ReadAndResolve`1(Microsoft.Data.OData.ODataEntry,System.Func`6,System.String,!!0>,Microsoft.WindowsAzure.Storage.Table.TableRequestOptions)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "RequireEncryption", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Queue.QueueRequestOptions.#AssertPolicyIfRequired()", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "RequireEncryption", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Queue.QueueEncryptionPolicy.#DecryptMessage(System.String,System.Nullable`1)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "UploadFromStream", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptedWriteStream.#.ctor(Microsoft.WindowsAzure.Storage.Blob.CloudPageBlob,System.Int64,System.Boolean,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.Security.Cryptography.ICryptoTransform)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "FullBlob", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptedWriteStream.#.ctor(Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.Security.Cryptography.ICryptoTransform)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "UploadFromStream", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptedWriteStream.#.ctor(Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.Security.Cryptography.ICryptoTransform)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "BlobEncryptionMode", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptedWriteStream.#.ctor(Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.Security.Cryptography.ICryptoTransform)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "EncryptionPolicy", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions.#AssertNoEncryptionPolicyOrStrictMode()", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "RequestOptions", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions.#AssertNoEncryptionPolicyOrStrictMode()", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "RequireEncryption", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptionPolicy.#DecryptBlob(System.IO.Stream,System.Collections.Generic.IDictionary`2,System.Security.Cryptography.ICryptoTransform&,System.Nullable`1,System.Byte[],System.Boolean)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "RequireEncryption", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions.#AssertPolicyIfRequired()", Justification = "Reviewed.")] // CA2208 [assembly: SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Util.AsyncStreamCopier`1.#EndOperation(System.IAsyncResult)", Justification = "Reviewed")] @@ -699,6 +740,11 @@ // CA2214 [assembly: SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableStorageEntityContainer.#.ctor(Microsoft.WindowsAzure.Storage.Table.TableStorageModel)", Justification = "Reviewed - The constructor for the instance calling the method has been executed")] +// CA2215 +[assembly: SuppressMessage("Microsoft.Usage", "CA2215:Dispose methods should call base class dispose", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.LengthLimitingStream.#Dispose(System.Boolean)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Usage", "CA2215:Dispose methods should call base class dispose", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.NonCloseableStream.#Dispose(System.Boolean)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Usage", "CA2215:Dispose methods should call base class dispose", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptedWriteStream.#Dispose(System.Boolean)", Justification = "Reviewed")] + // CA2227 [assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableQuery`1.#SelectColumns", Justification = "Back compatibility")] [assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableQuery.#SelectColumns", Justification = "Back compatibility")] diff --git a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs index 0a15424b5..153587305 100644 --- a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs @@ -34,8 +34,8 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("5.0.2.0")] -[assembly: AssemblyFileVersion("5.0.2.0")] +[assembly: AssemblyVersion("6.0.0.0")] +[assembly: AssemblyFileVersion("6.0.0.0")] #if SIGN [assembly: InternalsVisibleTo( @@ -45,7 +45,15 @@ "d333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307" + "e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c3" + "08055da9")] +[assembly: InternalsVisibleTo( + "Microsoft.WindowsAzure.Storage.Extensions, PublicKey=" + + "0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67" + + "871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0b" + + "d333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307" + + "e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c3" + + "08055da9")] #else [assembly: InternalsVisibleTo("Microsoft.WindowsAzure.Storage.Test")] +[assembly: InternalsVisibleTo("Microsoft.WindowsAzure.Storage.Extensions")] #endif [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec index c76326219..b7ba52029 100644 --- a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec +++ b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage - 5.0.2 + 6.0.0 Windows Azure Storage Microsoft Microsoft @@ -24,9 +24,11 @@ Microsoft Azure Storage team's blog - http://blogs.msdn.com/b/windowsazurestorag + + diff --git a/Lib/WindowsPhone/Properties/AssemblyInfo.cs b/Lib/WindowsPhone/Properties/AssemblyInfo.cs index 7ac3c78d6..57cab4632 100644 --- a/Lib/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Lib/WindowsPhone/Properties/AssemblyInfo.cs @@ -32,8 +32,8 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("5.0.2.0")] -[assembly: AssemblyFileVersion("5.0.2.0")] +[assembly: AssemblyVersion("6.0.0.0")] +[assembly: AssemblyFileVersion("6.0.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] #if SIGN diff --git a/Lib/WindowsPhoneRT/Microsoft.WindowsAzure.Storage.csproj b/Lib/WindowsPhoneRT/Microsoft.WindowsAzure.Storage.csproj index a2480058f..914196638 100644 --- a/Lib/WindowsPhoneRT/Microsoft.WindowsAzure.Storage.csproj +++ b/Lib/WindowsPhoneRT/Microsoft.WindowsAzure.Storage.csproj @@ -41,16 +41,20 @@ False - ..\..\packages\Microsoft.Data.Edm.5.6.4\lib\portable-net40+sl5+wp8+win8+wpa\Microsoft.Data.Edm.dll + ..\..\packages\Microsoft.Data.Edm.5.6.4\lib\portable-net45+wp8+win8+wpa\Microsoft.Data.Edm.dll False - ..\..\packages\Microsoft.Data.OData.5.6.4\lib\portable-net40+sl5+wp8+win8+wpa\Microsoft.Data.OData.dll + ..\..\packages\Microsoft.Data.OData.5.6.4\lib\portable-net45+wp8+win8+wpa\Microsoft.Data.OData.dll + + + False + ..\..\packages\Newtonsoft.Json.6.0.8\lib\portable-net45+wp80+win8+wpa81+aspnetcore50\Newtonsoft.Json.dll False - ..\..\packages\System.Spatial.5.6.4\lib\portable-net40+sl5+wp8+win8+wpa\System.Spatial.dll + ..\..\packages\System.Spatial.5.6.4\lib\portable-net45+wp8+win8+wpa\System.Spatial.dll diff --git a/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs b/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs index cec038533..041c6092a 100644 --- a/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs +++ b/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs @@ -24,8 +24,8 @@ // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("5.0.2.0")] -[assembly: AssemblyFileVersion("5.0.2.0")] +[assembly: AssemblyVersion("6.0.0.0")] +[assembly: AssemblyFileVersion("6.0.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] [assembly: ComVisible(false)] diff --git a/Lib/WindowsPhoneRT/packages.config b/Lib/WindowsPhoneRT/packages.config index 47ccc6ac6..9f62ef926 100644 --- a/Lib/WindowsPhoneRT/packages.config +++ b/Lib/WindowsPhoneRT/packages.config @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/Lib/WindowsRuntime/Core/Executor/Executor.cs b/Lib/WindowsRuntime/Core/Executor/Executor.cs index 566ce1191..2fe326605 100644 --- a/Lib/WindowsRuntime/Core/Executor/Executor.cs +++ b/Lib/WindowsRuntime/Core/Executor/Executor.cs @@ -132,7 +132,7 @@ private async static Task ExecuteAsyncInternal(RESTCommand cmd, IRetryP if (!executionState.Resp.IsSuccessStatusCode) { // At this point, don't try to read the stream to parse the error - executionState.ExceptionRef = await Exceptions.PopulateStorageExceptionFromHttpResponseMessage(executionState.Resp, executionState.Cmd.CurrentResult); + executionState.ExceptionRef = await Exceptions.PopulateStorageExceptionFromHttpResponseMessage(executionState.Resp, executionState.Cmd.CurrentResult, executionState.Cmd.ParseError); } Logger.LogInformational(executionState.OperationContext, SR.TraceResponse, executionState.Cmd.CurrentResult.HttpStatusCode, executionState.Cmd.CurrentResult.ServiceRequestID, executionState.Cmd.CurrentResult.ContentMd5, executionState.Cmd.CurrentResult.Etag); @@ -174,9 +174,9 @@ private async static Task ExecuteAsyncInternal(RESTCommand cmd, IRetryP await cmd.ResponseStream.WriteToAsync(cmd.ErrorStream, null /* copyLength */, null /* maxLength */, false, executionState, new StreamDescriptor(), token); cmd.ErrorStream.Seek(0, SeekOrigin.Begin); #if ASPNET_K || PORTABLE - executionState.ExceptionRef = StorageException.TranslateExceptionWithPreBufferedStream(executionState.ExceptionRef, executionState.Cmd.CurrentResult, null /* parseError */, cmd.ErrorStream, executionState.Resp); + executionState.ExceptionRef = StorageException.TranslateExceptionWithPreBufferedStream(executionState.ExceptionRef, executionState.Cmd.CurrentResult, stream => executionState.Cmd.ParseError(stream, executionState.Resp, null), cmd.ErrorStream, executionState.Resp); #else - executionState.ExceptionRef = StorageException.TranslateExceptionWithPreBufferedStream(executionState.ExceptionRef, executionState.Cmd.CurrentResult, null /* parseError */, cmd.ErrorStream); + executionState.ExceptionRef = StorageException.TranslateExceptionWithPreBufferedStream(executionState.ExceptionRef, executionState.Cmd.CurrentResult, stream => executionState.Cmd.ParseError(stream, executionState.Resp, null), cmd.ErrorStream); #endif throw executionState.ExceptionRef; } @@ -242,9 +242,9 @@ private async static Task ExecuteAsyncInternal(RESTCommand cmd, IRetryP } #if ASPNET_K || PORTABLE - StorageException translatedException = StorageException.TranslateException(e, executionState.Cmd.CurrentResult, null, executionState.Resp); + StorageException translatedException = StorageException.TranslateException(e, executionState.Cmd.CurrentResult, stream => executionState.Cmd.ParseError(stream, executionState.Resp, null), executionState.Resp); #else - StorageException translatedException = StorageException.TranslateException(e, executionState.Cmd.CurrentResult, null); + StorageException translatedException = StorageException.TranslateException(e, executionState.Cmd.CurrentResult, stream => executionState.Cmd.ParseError(stream, executionState.Resp, null)); #endif executionState.ExceptionRef = translatedException; Logger.LogInformational(executionState.OperationContext, SR.TraceRetryCheck, executionState.RetryCount, executionState.Cmd.CurrentResult.HttpStatusCode, translatedException.IsRetryable ? "yes" : "no", translatedException.Message); diff --git a/Lib/WindowsRuntime/Microsoft.WindowsAzure.Storage.csproj b/Lib/WindowsRuntime/Microsoft.WindowsAzure.Storage.csproj index 9d4296da1..ccb6ff004 100644 --- a/Lib/WindowsRuntime/Microsoft.WindowsAzure.Storage.csproj +++ b/Lib/WindowsRuntime/Microsoft.WindowsAzure.Storage.csproj @@ -41,16 +41,20 @@ False - ..\..\packages\Microsoft.Data.Edm.5.6.4\lib\portable-net40+sl5+wp8+win8+wpa\Microsoft.Data.Edm.dll + ..\..\packages\Microsoft.Data.Edm.5.6.4\lib\portable-net45+wp8+win8+wpa\Microsoft.Data.Edm.dll False - ..\..\packages\Microsoft.Data.OData.5.6.4\lib\portable-net40+sl5+wp8+win8+wpa\Microsoft.Data.OData.dll + ..\..\packages\Microsoft.Data.OData.5.6.4\lib\portable-net45+wp8+win8+wpa\Microsoft.Data.OData.dll + + + False + ..\..\packages\Newtonsoft.Json.6.0.8\lib\portable-net45+wp80+win8+wpa81+aspnetcore50\Newtonsoft.Json.dll False - ..\..\packages\System.Spatial.5.6.4\lib\portable-net40+sl5+wp8+win8+wpa\System.Spatial.dll + ..\..\packages\System.Spatial.5.6.4\lib\portable-net45+wp8+win8+wpa\System.Spatial.dll diff --git a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs index ff085b322..d9af188a5 100644 --- a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs @@ -25,8 +25,8 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("5.0.2.0")] -[assembly: AssemblyFileVersion("5.0.2.0")] +[assembly: AssemblyVersion("6.0.0.0")] +[assembly: AssemblyFileVersion("6.0.0.0")] [assembly: ComVisible(false)] #if SIGN diff --git a/Lib/WindowsRuntime/Table/CloudTable.cs b/Lib/WindowsRuntime/Table/CloudTable.cs index e70f1ffce..b94650c02 100644 --- a/Lib/WindowsRuntime/Table/CloudTable.cs +++ b/Lib/WindowsRuntime/Table/CloudTable.cs @@ -259,7 +259,7 @@ public IAsyncAction CreateAsync(TableRequestOptions requestOptions, OperationCon DynamicTableEntity tblEntity = new DynamicTableEntity(); tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - TableOperation operation = new TableOperation(tblEntity, TableOperationType.Insert); + TableOperation operation = new TableOperation(tblEntity, TableOperationType.Insert, false); operation.IsTableEntity = true; #if ASPNET_K @@ -520,9 +520,9 @@ public IAsyncOperation DeleteIfExistsAsync(TableRequestOptions requestOpti #region Exists /// - /// Checks existence of the queue. + /// Checks existence of the table. /// - /// true if the queue exists. + /// true if the table exists. #if ASPNET_K || PORTABLE public Task ExistsAsync() #else @@ -533,11 +533,11 @@ public IAsyncOperation ExistsAsync() } /// - /// Checks existence of the queue. + /// Checks existence of the table. /// /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. /// An object for tracking the current operation. - /// true if the queue exists. + /// true if the table exists. #if ASPNET_K || PORTABLE public Task ExistsAsync(TableRequestOptions requestOptions, OperationContext operationContext) { @@ -552,12 +552,12 @@ public IAsyncOperation ExistsAsync(TableRequestOptions requestOptions, Ope #if ASPNET_K || PORTABLE /// - /// Checks existence of the queue. + /// Checks existence of the table. /// /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. /// An object for tracking the current operation. /// A to observe while waiting for a task to complete. - /// true if the queue exists. + /// true if the table exists. public Task ExistsAsync(TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) { return this.ExistsAsync(false, requestOptions, operationContext, cancellationToken); @@ -565,13 +565,13 @@ public Task ExistsAsync(TableRequestOptions requestOptions, OperationConte #endif /// - /// Checks existence of the queue. + /// Checks existence of the table. /// /// If true, the command will be executed against the primary location. /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. /// An object for tracking the current operation. /// A to observe while waiting for a task to complete. - /// true if the queue exists. + /// true if the table exists. #if ASPNET_K || PORTABLE private Task ExistsAsync(bool primaryOnly, TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) #else @@ -688,6 +688,7 @@ private RESTCommand SetPermissionsImpl(TablePermissions acl, TableRequ requestOptions.ApplyToStorageCommand(putCmd); putCmd.Handler = this.ServiceClient.AuthenticationHandler; putCmd.BuildClient = HttpClientFactory.BuildHttpClient; + putCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableHttpRequestMessageFactory.SetAcl(uri, serverTimeout, cnt, ctx); putCmd.BuildContent = (cmd, ctx) => HttpContentFactory.BuildContentFromStream(memoryStream, 0, memoryStream.Length, null /* md5 */, cmd, ctx); putCmd.StreamToDispose = memoryStream; @@ -772,6 +773,7 @@ private RESTCommand GetPermissionsImpl(TableRequestOptions req getCmd.CommandLocationMode = CommandLocationMode.PrimaryOrSecondary; getCmd.Handler = this.ServiceClient.AuthenticationHandler; getCmd.BuildClient = HttpClientFactory.BuildHttpClient; + getCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; getCmd.RetrieveResponseStream = true; getCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableHttpRequestMessageFactory.GetAcl(uri, serverTimeout, cnt, ctx); getCmd.PreProcessResponse = (cmd, resp, ex, ctx) => HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, null /* retVal */, cmd, ex); diff --git a/Lib/WindowsRuntime/Table/CloudTableClient.cs b/Lib/WindowsRuntime/Table/CloudTableClient.cs index cbb5246cd..c8d68fda0 100644 --- a/Lib/WindowsRuntime/Table/CloudTableClient.cs +++ b/Lib/WindowsRuntime/Table/CloudTableClient.cs @@ -331,6 +331,7 @@ private RESTCommand GetServicePropertiesImpl(TableRequestOpti retCmd.CommandLocationMode = CommandLocationMode.PrimaryOrSecondary; retCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableHttpRequestMessageFactory.GetServiceProperties(uri, serverTimeout, ctx); retCmd.RetrieveResponseStream = true; + retCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; retCmd.Handler = this.AuthenticationHandler; retCmd.BuildClient = HttpClientFactory.BuildHttpClient; retCmd.PreProcessResponse = @@ -511,6 +512,7 @@ private RESTCommand GetServiceStatsImpl(TableRequestOptions reques retCmd.CommandLocationMode = CommandLocationMode.PrimaryOrSecondary; retCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableHttpRequestMessageFactory.GetServiceStats(uri, serverTimeout, ctx); retCmd.RetrieveResponseStream = true; + retCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; retCmd.Handler = this.AuthenticationHandler; retCmd.BuildClient = HttpClientFactory.BuildHttpClient; retCmd.PreProcessResponse = (cmd, resp, ex, ctx) => HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, null /* retVal */, cmd, ex); diff --git a/Lib/WindowsRuntime/Table/Protocol/HttpResponseAdapterMessage.cs b/Lib/WindowsRuntime/Table/Protocol/HttpResponseAdapterMessage.cs index 9d42341f2..3deee80f5 100644 --- a/Lib/WindowsRuntime/Table/Protocol/HttpResponseAdapterMessage.cs +++ b/Lib/WindowsRuntime/Table/Protocol/HttpResponseAdapterMessage.cs @@ -29,11 +29,18 @@ internal class HttpResponseAdapterMessage : IODataResponseMessage { private HttpResponseMessage resp = null; private Stream str = null; + private string responseContentType = null; public HttpResponseAdapterMessage(HttpResponseMessage resp, Stream str) + : this(resp, str, null /* responseContentType */) + { + } + + public HttpResponseAdapterMessage(HttpResponseMessage resp, Stream str, string responseContentType) { this.resp = resp; this.str = str; + this.responseContentType = responseContentType; } public Task GetStreamAsync() @@ -46,7 +53,7 @@ public string GetHeader(string headerName) switch (headerName) { case "Content-Type": - return this.resp.Content.Headers.ContentType != null ? this.resp.Content.Headers.ContentType.ToString() : null; + return this.responseContentType != null ? this.responseContentType : (this.resp.Content.Headers.ContentType != null ? this.resp.Content.Headers.ContentType.ToString() : null); case "Content-Range": return this.resp.Content.Headers.ContentRange != null ? this.resp.Content.Headers.ContentRange.ToString() : null; case "Content-MD5": diff --git a/Lib/WindowsRuntime/Table/Protocol/TableOperationHttpRequestFactory.cs b/Lib/WindowsRuntime/Table/Protocol/TableOperationHttpRequestFactory.cs index 1bf49be2c..2a14de074 100644 --- a/Lib/WindowsRuntime/Table/Protocol/TableOperationHttpRequestFactory.cs +++ b/Lib/WindowsRuntime/Table/Protocol/TableOperationHttpRequestFactory.cs @@ -33,23 +33,34 @@ internal static HttpRequestMessage BuildRequestCore(Uri uri, UriQueryBuilder bui { HttpRequestMessage msg = HttpRequestMessageFactory.CreateRequestMessage(method, uri, timeout, builder, content, ctx); - msg.Headers.Add("Accept", "application/atom+xml,application/xml"); msg.Headers.Add("Accept-Charset", "UTF-8"); - msg.Headers.Add("MaxDataServiceVersion", "2.0;NetFx"); + msg.Headers.Add("MaxDataServiceVersion", "3.0;NetFx"); return msg; } - internal static HttpRequestMessage BuildRequestForTableQuery(Uri uri, UriQueryBuilder builder, int? timeout, HttpContent content, OperationContext ctx) + internal static HttpRequestMessage BuildRequestForTableQuery(Uri uri, UriQueryBuilder builder, int? timeout, HttpContent content, OperationContext ctx, TablePayloadFormat payloadFormat) { HttpRequestMessage msg = BuildRequestCore(uri, builder, HttpMethod.Get, timeout, content, ctx); - + + // Set Accept and Content-Type based on the payload format. + SetAcceptHeaderForHttpWebRequest(msg, payloadFormat); + Logger.LogInformational(ctx, SR.PayloadFormat, payloadFormat); return msg; } - internal static HttpRequestMessage BuildRequestForTableOperation(RESTCommand cmd, Uri uri, UriQueryBuilder builder, int? timeout, TableOperation operation, CloudTableClient client, HttpContent content, OperationContext ctx) + internal static HttpRequestMessage BuildRequestForTableOperation(RESTCommand cmd, Uri uri, UriQueryBuilder builder, int? timeout, TableOperation operation, CloudTableClient client, HttpContent content, OperationContext ctx, TablePayloadFormat payloadFormat) { HttpRequestMessage msg = BuildRequestCore(uri, builder, operation.HttpMethod, timeout, content, ctx); + + // Set Accept and Content-Type based on the payload format. + SetAcceptHeaderForHttpWebRequest(msg, payloadFormat); + Logger.LogInformational(ctx, SR.PayloadFormat, payloadFormat); + + if (!operation.HttpMethod.Equals("HEAD") && !operation.HttpMethod.Equals("GET")) + { + SetContentTypeForHttpWebRequest(msg, payloadFormat); + } if (operation.OperationType == TableOperationType.InsertOrMerge || operation.OperationType == TableOperationType.Merge) { @@ -65,6 +76,12 @@ internal static HttpRequestMessage BuildRequestForTableOperation(RESTCommand< msg.Headers.Add("If-Match", operation.Entity.ETag); } + // Prefer header + if (operation.OperationType == TableOperationType.Insert) + { + msg.Headers.Add("Prefer", operation.EchoContent ? "return-content" : "return-no-content"); + } + if (operation.OperationType == TableOperationType.Insert || operation.OperationType == TableOperationType.Merge || operation.OperationType == TableOperationType.InsertOrMerge || @@ -75,13 +92,18 @@ internal static HttpRequestMessage BuildRequestForTableOperation(RESTCommand< ODataMessageWriterSettings writerSettings = new ODataMessageWriterSettings() { CheckCharacters = false, // sets this flag on the XmlWriter for ATOM - Version = ODataVersion.V2 // set the Odata version to use when writing the entry + Version = TableConstants.ODataProtocolVersion // set the Odata version to use when writing the entry }; HttpRequestAdapterMessage adapterMsg = new HttpRequestAdapterMessage(msg, client.BufferManager, (int)Constants.KB); + if (!operation.HttpMethod.Equals("HEAD") && !operation.HttpMethod.Equals("GET")) + { + SetContentTypeForAdapterMessage(adapterMsg, payloadFormat); + } + cmd.StreamToDispose = adapterMsg.GetStream(); - ODataMessageWriter odataWriter = new ODataMessageWriter(adapterMsg, writerSettings); + ODataMessageWriter odataWriter = new ODataMessageWriter(adapterMsg, writerSettings, new TableStorageModel(client.AccountName)); ODataWriter writer = odataWriter.CreateODataEntryWriter(); WriteOdataEntity(operation.Entity, operation.OperationType, ctx, writer); @@ -91,15 +113,16 @@ internal static HttpRequestMessage BuildRequestForTableOperation(RESTCommand< return msg; } - internal static HttpRequestMessage BuildRequestForTableBatchOperation(RESTCommand cmd, Uri uri, UriQueryBuilder builder, int? timeout, string tableName, TableBatchOperation batch, CloudTableClient client, HttpContent content, OperationContext ctx) + internal static HttpRequestMessage BuildRequestForTableBatchOperation(RESTCommand cmd, Uri uri, UriQueryBuilder builder, int? timeout, string tableName, TableBatchOperation batch, CloudTableClient client, HttpContent content, OperationContext ctx, TablePayloadFormat payloadFormat) { HttpRequestMessage msg = BuildRequestCore(NavigationHelper.AppendPathToSingleUri(uri, "$batch"), builder, HttpMethod.Post, timeout, content, ctx); + Logger.LogInformational(ctx, SR.PayloadFormat, payloadFormat); // create the writer, indent for readability of the examples. ODataMessageWriterSettings writerSettings = new ODataMessageWriterSettings() { CheckCharacters = false, // sets this flag on the XmlWriter for ATOM - Version = ODataVersion.V2 // set the Odata version to use when writing the entry + Version = TableConstants.ODataProtocolVersion // set the Odata version to use when writing the entry }; HttpRequestAdapterMessage adapterMsg = new HttpRequestAdapterMessage(msg, client.BufferManager, 64 * (int)Constants.KB); @@ -125,6 +148,7 @@ internal static HttpRequestMessage BuildRequestForTableBatchOperation(RESTCom string httpMethod = operation.OperationType == TableOperationType.Merge || operation.OperationType == TableOperationType.InsertOrMerge ? "MERGE" : operation.HttpMethod.Method; ODataBatchOperationRequestMessage mimePartMsg = batchWriter.CreateOperationRequestMessage(httpMethod, operation.GenerateRequestURI(uri, tableName)); + SetAcceptAndContentTypeForODataBatchMessage(mimePartMsg, payloadFormat); // etag if (operation.OperationType == TableOperationType.Delete || @@ -134,9 +158,15 @@ internal static HttpRequestMessage BuildRequestForTableBatchOperation(RESTCom mimePartMsg.SetHeader("If-Match", operation.Entity.ETag); } + // Prefer header + if (operation.OperationType == TableOperationType.Insert) + { + mimePartMsg.SetHeader("Prefer", operation.EchoContent ? "return-content" : "return-no-content"); + } + if (operation.OperationType != TableOperationType.Delete && operation.OperationType != TableOperationType.Retrieve) { - using (ODataMessageWriter batchEntryWriter = new ODataMessageWriter(mimePartMsg, writerSettings)) + using (ODataMessageWriter batchEntryWriter = new ODataMessageWriter(mimePartMsg, writerSettings, new TableStorageModel(client.AccountName))) { // Write entity ODataWriter entryWriter = batchEntryWriter.CreateODataEntryWriter(); @@ -162,7 +192,8 @@ private static void WriteOdataEntity(ITableEntity entity, TableOperationType ope { ODataEntry entry = new ODataEntry() { - Properties = GetPropertiesWithKeys(entity, ctx) + Properties = GetPropertiesWithKeys(entity, ctx, operationType), + TypeName = "account.sometype" }; if (operationType != TableOperationType.Insert && operationType != TableOperationType.Retrieve) @@ -170,6 +201,7 @@ private static void WriteOdataEntity(ITableEntity entity, TableOperationType ope entry.ETag = entity.ETag; } + entry.SetAnnotation(new SerializationTypeNameAnnotation { TypeName = null }); writer.WriteStart(entry); writer.WriteEnd(); writer.Flush(); @@ -182,22 +214,101 @@ internal static List GetPropertiesFromDictionary(IDictionary new ODataProperty() { Name = kvp.Key, Value = kvp.Value.PropertyAsObject }).ToList(); } - internal static List GetPropertiesWithKeys(ITableEntity entity, OperationContext operationContext) + internal static List GetPropertiesWithKeys(ITableEntity entity, OperationContext operationContext, TableOperationType operationType) { List retProps = GetPropertiesFromDictionary(entity.WriteEntity(operationContext)); + if (operationType == TableOperationType.Insert) + { + if (entity.PartitionKey != null) + { + retProps.Add(new ODataProperty() { Name = TableConstants.PartitionKey, Value = entity.PartitionKey }); + } + + if (entity.RowKey != null) + { + retProps.Add(new ODataProperty() { Name = TableConstants.RowKey, Value = entity.RowKey }); + } + } + + return retProps; + } + #endregion + + #region Set Headers + #pragma warning disable 0618 + private static void SetAcceptHeaderForHttpWebRequest(HttpRequestMessage msg, TablePayloadFormat payloadFormat) + { + if (payloadFormat == TablePayloadFormat.AtomPub) + { + msg.Headers.Add(Constants.HeaderConstants.PayloadAcceptHeader, Constants.AtomAcceptHeaderValue); + } + else if (payloadFormat == TablePayloadFormat.JsonFullMetadata) + { + msg.Headers.Add(Constants.HeaderConstants.PayloadAcceptHeader, Constants.JsonFullMetadataAcceptHeaderValue); + } + else if (payloadFormat == TablePayloadFormat.Json) + { + msg.Headers.Add(Constants.HeaderConstants.PayloadAcceptHeader, Constants.JsonLightAcceptHeaderValue); + } + else if (payloadFormat == TablePayloadFormat.JsonNoMetadata) + { + msg.Headers.Add(Constants.HeaderConstants.PayloadAcceptHeader, Constants.JsonNoMetadataAcceptHeaderValue); + } + } - if (entity.PartitionKey != null) + private static void SetContentTypeForHttpWebRequest(HttpRequestMessage msg, TablePayloadFormat payloadFormat) + { + if (msg.Content == null || msg.Content.Headers == null) { - retProps.Add(new ODataProperty() { Name = TableConstants.PartitionKey, Value = entity.PartitionKey }); + return; } - if (entity.RowKey != null) + if (payloadFormat == TablePayloadFormat.AtomPub) + { + msg.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(Constants.AtomContentTypeHeaderValue); + } + else { - retProps.Add(new ODataProperty() { Name = TableConstants.RowKey, Value = entity.RowKey }); + msg.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(Constants.JsonContentTypeHeaderValue); } + } - return retProps; + private static void SetContentTypeForAdapterMessage(HttpRequestAdapterMessage adapterMsg, TablePayloadFormat payloadFormat) + { + if (payloadFormat == TablePayloadFormat.AtomPub) + { + adapterMsg.SetHeader(Constants.HeaderConstants.PayloadContentTypeHeader, Constants.AtomContentTypeHeaderValue); + } + else + { + adapterMsg.SetHeader(Constants.HeaderConstants.PayloadContentTypeHeader, Constants.JsonContentTypeHeaderValue); + } + } + + private static void SetAcceptAndContentTypeForODataBatchMessage(ODataBatchOperationRequestMessage mimePartMsg, TablePayloadFormat payloadFormat) + { + if (payloadFormat == TablePayloadFormat.AtomPub) + { + mimePartMsg.SetHeader(Constants.HeaderConstants.PayloadAcceptHeader, Constants.AtomAcceptHeaderValue); + mimePartMsg.SetHeader(Constants.HeaderConstants.PayloadContentTypeHeader, Constants.AtomContentTypeHeaderValue); + } + else if (payloadFormat == TablePayloadFormat.JsonFullMetadata) + { + mimePartMsg.SetHeader(Constants.HeaderConstants.PayloadAcceptHeader, Constants.JsonFullMetadataAcceptHeaderValue); + mimePartMsg.SetHeader(Constants.HeaderConstants.PayloadContentTypeHeader, Constants.JsonContentTypeHeaderValue); + } + else if (payloadFormat == TablePayloadFormat.Json) + { + mimePartMsg.SetHeader(Constants.HeaderConstants.PayloadAcceptHeader, Constants.JsonLightAcceptHeaderValue); + mimePartMsg.SetHeader(Constants.HeaderConstants.PayloadContentTypeHeader, Constants.JsonContentTypeHeaderValue); + } + else + { + mimePartMsg.SetHeader(Constants.HeaderConstants.PayloadAcceptHeader, Constants.JsonNoMetadataAcceptHeaderValue); + mimePartMsg.SetHeader(Constants.HeaderConstants.PayloadContentTypeHeader, Constants.JsonContentTypeHeaderValue); + } } + #pragma warning restore 0618 #endregion } } diff --git a/Lib/WindowsRuntime/Table/Protocol/TableOperationHttpResponseParsers.cs b/Lib/WindowsRuntime/Table/Protocol/TableOperationHttpResponseParsers.cs index f5f9363d3..f02e5cc40 100644 --- a/Lib/WindowsRuntime/Table/Protocol/TableOperationHttpResponseParsers.cs +++ b/Lib/WindowsRuntime/Table/Protocol/TableOperationHttpResponseParsers.cs @@ -18,8 +18,12 @@ namespace Microsoft.WindowsAzure.Storage.Table.Protocol { using Microsoft.Data.OData; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + using System.Globalization; using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Executor; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; using System.Collections.Generic; using System.IO; @@ -27,6 +31,7 @@ namespace Microsoft.WindowsAzure.Storage.Table.Protocol using System.Net; using System.Net.Http; using System.Threading.Tasks; + using System.Reflection; internal static class TableOperationHttpResponseParsers { @@ -49,9 +54,19 @@ internal static TableResult TableOperationPreProcess(TableResult result, Tabl } else if (operation.OperationType == TableOperationType.Insert) { - if (resp.StatusCode != HttpStatusCode.Created) + if (operation.EchoContent) { - throw ex; + if (resp.StatusCode != HttpStatusCode.Created) + { + throw ex; + } + } + else + { + if (resp.StatusCode != HttpStatusCode.NoContent) + { + throw ex; + } } } else @@ -75,242 +90,268 @@ internal static TableResult TableOperationPreProcess(TableResult result, Tabl return result; } - internal static Task TableOperationPostProcess(TableResult result, TableOperation operation, RESTCommand cmd, HttpResponseMessage resp, OperationContext ctx) + internal static Task TableOperationPostProcess(TableResult result, TableOperation operation, RESTCommand cmd, HttpResponseMessage resp, OperationContext ctx, TableRequestOptions options, string accountName) { return Task.Run(() => + { + if (operation.OperationType != TableOperationType.Retrieve && operation.OperationType != TableOperationType.Insert) + { + result.Etag = resp.Headers.ETag.ToString(); + operation.Entity.ETag = result.Etag; + } + else if (operation.OperationType == TableOperationType.Insert && (!operation.EchoContent)) + { + if (resp.Headers.ETag != null) + { + result.Etag = resp.Headers.ETag.ToString(); + operation.Entity.ETag = result.Etag; + operation.Entity.Timestamp = ParseETagForTimestamp(result.Etag); + } + } + else + { + // Parse entity + ODataMessageReaderSettings readerSettings = new ODataMessageReaderSettings(); + readerSettings.MessageQuotas = new ODataMessageQuotas() { MaxPartsPerBatch = TableConstants.TableServiceMaxResults, MaxReceivedMessageSize = TableConstants.TableServiceMaxPayload }; + IEnumerable contentType; + resp.Content.Headers.TryGetValues(Constants.HeaderConstants.PayloadContentTypeHeader, out contentType); + + if (contentType != null && contentType.FirstOrDefault() != null && contentType.FirstOrDefault().Contains(Constants.JsonContentTypeHeaderValue) && + contentType.FirstOrDefault().Contains(Constants.NoMetadata)) { - if (operation.OperationType != TableOperationType.Retrieve && operation.OperationType != TableOperationType.Insert) + IEnumerable etag; + resp.Headers.TryGetValues(Constants.HeaderConstants.EtagHeader, out etag); + if (etag != null) { - result.Etag = resp.Headers.ETag.ToString(); - operation.Entity.ETag = result.Etag; + result.Etag = etag.FirstOrDefault(); } - else - { - // Parse entity - ODataMessageReaderSettings readerSettings = new ODataMessageReaderSettings(); - readerSettings.MessageQuotas = new ODataMessageQuotas() { MaxPartsPerBatch = TableConstants.TableServiceMaxResults, MaxReceivedMessageSize = TableConstants.TableServiceMaxPayload }; - ReadOdataEntity(result, operation, new HttpResponseAdapterMessage(resp, cmd.ResponseStream), ctx, readerSettings); - } + ReadEntityUsingJsonParser(result, operation, cmd.ResponseStream, ctx, options); + } + else + { + ReadOdataEntity(result, operation, new HttpResponseAdapterMessage(resp, cmd.ResponseStream), ctx, readerSettings, accountName); + } + } - return result; - }); + return result; + }); } - internal static Task> TableBatchOperationPostProcess(IList result, TableBatchOperation batch, RESTCommand> cmd, HttpResponseMessage resp, OperationContext ctx) + internal static Task> TableBatchOperationPostProcess(IList result, TableBatchOperation batch, RESTCommand> cmd, HttpResponseMessage resp, OperationContext ctx, TableRequestOptions options, string accountName) { return Task.Run(() => + { + ODataMessageReaderSettings readerSettings = new ODataMessageReaderSettings(); + readerSettings.MessageQuotas = new ODataMessageQuotas() { MaxPartsPerBatch = TableConstants.TableServiceMaxResults, MaxReceivedMessageSize = TableConstants.TableServiceMaxPayload }; + + using (ODataMessageReader responseReader = new ODataMessageReader(new HttpResponseAdapterMessage(resp, cmd.ResponseStream), readerSettings)) { - ODataMessageReaderSettings readerSettings = new ODataMessageReaderSettings(); - readerSettings.MessageQuotas = new ODataMessageQuotas() { MaxPartsPerBatch = TableConstants.TableServiceMaxResults, MaxReceivedMessageSize = TableConstants.TableServiceMaxPayload }; + // create a reader + ODataBatchReader reader = responseReader.CreateODataBatchReader(); - using (ODataMessageReader responseReader = new ODataMessageReader(new HttpResponseAdapterMessage(resp, cmd.ResponseStream), readerSettings)) + // Initial => changesetstart + if (reader.State == ODataBatchReaderState.Initial) { - // create a reader - ODataBatchReader reader = responseReader.CreateODataBatchReader(); + reader.Read(); + } - // Initial => changesetstart - if (reader.State == ODataBatchReaderState.Initial) - { - reader.Read(); - } + if (reader.State == ODataBatchReaderState.ChangesetStart) + { + // ChangeSetStart => Operation + reader.Read(); + } - if (reader.State == ODataBatchReaderState.ChangesetStart) - { - // ChangeSetStart => Operation - reader.Read(); - } + int index = 0; + bool failError = false; + bool failUnexpected = false; - int index = 0; - bool failError = false; - bool failUnexpected = false; + while (reader.State == ODataBatchReaderState.Operation) + { + TableOperation currentOperation = batch[index]; + TableResult currentResult = new TableResult() { Result = currentOperation.Entity }; + result.Add(currentResult); - while (reader.State == ODataBatchReaderState.Operation) - { - TableOperation currentOperation = batch[index]; - TableResult currentResult = new TableResult() { Result = currentOperation.Entity }; - result.Add(currentResult); + ODataBatchOperationResponseMessage mimePartResponseMessage = reader.CreateOperationResponseMessage(); + string contentType = mimePartResponseMessage.GetHeader(Constants.ContentTypeElement); - ODataBatchOperationResponseMessage mimePartResponseMessage = reader.CreateOperationResponseMessage(); - currentResult.HttpStatusCode = mimePartResponseMessage.StatusCode; + currentResult.HttpStatusCode = mimePartResponseMessage.StatusCode; - // Validate Status Code - if (currentOperation.OperationType == TableOperationType.Insert) + // Validate Status Code + if (currentOperation.OperationType == TableOperationType.Insert) + { + failError = mimePartResponseMessage.StatusCode == (int)HttpStatusCode.Conflict; + if (currentOperation.EchoContent) { - failError = mimePartResponseMessage.StatusCode == (int)HttpStatusCode.Conflict; failUnexpected = mimePartResponseMessage.StatusCode != (int)HttpStatusCode.Created; } - else if (currentOperation.OperationType == TableOperationType.Retrieve) + else { - if (mimePartResponseMessage.StatusCode == (int)HttpStatusCode.NotFound) - { - index++; + failUnexpected = mimePartResponseMessage.StatusCode != (int)HttpStatusCode.NoContent; + } + } + else if (currentOperation.OperationType == TableOperationType.Retrieve) + { + if (mimePartResponseMessage.StatusCode == (int)HttpStatusCode.NotFound) + { + index++; - // Operation => next - reader.Read(); - continue; - } + // Operation => next + reader.Read(); + continue; + } - failUnexpected = mimePartResponseMessage.StatusCode != (int)HttpStatusCode.OK; + failUnexpected = mimePartResponseMessage.StatusCode != (int)HttpStatusCode.OK; + } + else + { + failError = mimePartResponseMessage.StatusCode == (int)HttpStatusCode.NotFound; + failUnexpected = mimePartResponseMessage.StatusCode != (int)HttpStatusCode.NoContent; + } + + if (failError) + { + // If the parse error is null, then don't get the extended error information and the StorageException will contain SR.ExtendedErrorUnavailable message. + if (cmd.ParseError != null) + { + cmd.CurrentResult.ExtendedErrorInformation = cmd.ParseError(mimePartResponseMessage.GetStream(), resp, contentType); } else { - failError = mimePartResponseMessage.StatusCode == (int)HttpStatusCode.NotFound; - failUnexpected = mimePartResponseMessage.StatusCode != (int)HttpStatusCode.NoContent; + cmd.CurrentResult.ExtendedErrorInformation = StorageExtendedErrorInformation.ReadFromStream(mimePartResponseMessage.GetStream()); } + + cmd.CurrentResult.HttpStatusCode = mimePartResponseMessage.StatusCode; + throw new StorageException( + cmd.CurrentResult, + cmd.CurrentResult.ExtendedErrorInformation != null ? cmd.CurrentResult.ExtendedErrorInformation.ErrorMessage : SR.ExtendedErrorUnavailable, + null) + { + IsRetryable = false + }; + } - if (failError) + if (failUnexpected) + { + // If the parse error is null, then don't get the extended error information and the StorageException will contain SR.ExtendedErrorUnavailable message. + if (cmd.ParseError != null) { - cmd.CurrentResult.ExtendedErrorInformation = StorageExtendedErrorInformation.ReadFromStream(mimePartResponseMessage.GetStream()); - cmd.CurrentResult.HttpStatusCode = mimePartResponseMessage.StatusCode; - throw new StorageException( - cmd.CurrentResult, - cmd.CurrentResult.ExtendedErrorInformation != null ? cmd.CurrentResult.ExtendedErrorInformation.ErrorMessage : SR.ExtendedErrorUnavailable, - null) - { - IsRetryable = false - }; + cmd.CurrentResult.ExtendedErrorInformation = cmd.ParseError(mimePartResponseMessage.GetStream(), resp, contentType); } - if (failUnexpected) - { - cmd.CurrentResult.ExtendedErrorInformation = StorageExtendedErrorInformation.ReadFromStream(mimePartResponseMessage.GetStream()); - cmd.CurrentResult.HttpStatusCode = mimePartResponseMessage.StatusCode; + cmd.CurrentResult.HttpStatusCode = mimePartResponseMessage.StatusCode; - string indexString = Convert.ToString(index); + string indexString = Convert.ToString(index); - // Attempt to extract index of failing entity from extended error info - if (cmd.CurrentResult.ExtendedErrorInformation != null && - !string.IsNullOrEmpty(cmd.CurrentResult.ExtendedErrorInformation.ErrorMessage)) + // Attempt to extract index of failing entity from extended error info + if (cmd.CurrentResult.ExtendedErrorInformation != null && + !string.IsNullOrEmpty(cmd.CurrentResult.ExtendedErrorInformation.ErrorMessage)) + { + string tempIndex = TableRequest.ExtractEntityIndexFromExtendedErrorInformation(cmd.CurrentResult); + if (!string.IsNullOrEmpty(tempIndex)) { - string tempIndex = TableRequest.ExtractEntityIndexFromExtendedErrorInformation(cmd.CurrentResult); - if (!string.IsNullOrEmpty(tempIndex)) - { - indexString = tempIndex; - } + indexString = tempIndex; } - - throw new StorageException(cmd.CurrentResult, SR.UnexpectedResponseCodeForOperation + indexString, null) { IsRetryable = true }; } - // Update etag - if (!string.IsNullOrEmpty(mimePartResponseMessage.GetHeader("ETag"))) - { - currentResult.Etag = mimePartResponseMessage.GetHeader("ETag"); + throw new StorageException(cmd.CurrentResult, string.Format(SR.BatchErrorInOperation, indexString), null) { IsRetryable = true }; + } - if (currentOperation.Entity != null) - { - currentOperation.Entity.ETag = currentResult.Etag; - } - } + // Update etag + if (!string.IsNullOrEmpty(mimePartResponseMessage.GetHeader("ETag"))) + { + currentResult.Etag = mimePartResponseMessage.GetHeader("ETag"); - // Parse Entity if needed - if (currentOperation.OperationType == TableOperationType.Retrieve || currentOperation.OperationType == TableOperationType.Insert) + if (currentOperation.Entity != null) { - ReadOdataEntity(currentResult, currentOperation, mimePartResponseMessage, ctx, readerSettings); + currentOperation.Entity.ETag = currentResult.Etag; } - - index++; - - // Operation => - reader.Read(); } - } - - return result; - }); - } - - internal static Task TableQueryPostProcess(Stream responseStream, HttpResponseMessage resp, OperationContext ctx) - { - return Task.Run(() => - { - TableQuerySegment retSeg = new TableQuerySegment(); - retSeg.ContinuationToken = ContinuationFromResponse(resp); - ODataMessageReaderSettings readerSettings = new ODataMessageReaderSettings(); - readerSettings.MessageQuotas = new ODataMessageQuotas() { MaxPartsPerBatch = TableConstants.TableServiceMaxResults, MaxReceivedMessageSize = TableConstants.TableServiceMaxPayload }; - - using (ODataMessageReader responseReader = new ODataMessageReader(new HttpResponseAdapterMessage(resp, responseStream), readerSettings)) - { - // create a reader - ODataReader reader = responseReader.CreateODataFeedReader(); - - // Start => FeedStart - if (reader.State == ODataReaderState.Start) - { - reader.Read(); - } - - // Feedstart - if (reader.State == ODataReaderState.FeedStart) - { - reader.Read(); - } - - while (reader.State == ODataReaderState.EntryStart) - { - // EntryStart => EntryEnd - reader.Read(); - - ODataEntry entry = (ODataEntry)reader.Item; + // Parse Entity if needed + if (currentOperation.OperationType == TableOperationType.Retrieve || (currentOperation.OperationType == TableOperationType.Insert && currentOperation.EchoContent)) + { + if (mimePartResponseMessage.GetHeader(Constants.ContentTypeElement).Contains(Constants.JsonContentTypeHeaderValue) && + mimePartResponseMessage.GetHeader(Constants.ContentTypeElement).Contains(Constants.NoMetadata)) + { + ReadEntityUsingJsonParser(currentResult, currentOperation, mimePartResponseMessage.GetStream(), ctx, options); + } + else + { + ReadOdataEntity(currentResult, currentOperation, mimePartResponseMessage, ctx, readerSettings, accountName); + } + } + else if (currentOperation.OperationType == TableOperationType.Insert) + { + currentOperation.Entity.Timestamp = ParseETagForTimestamp(currentResult.Etag); + } - DynamicTableEntity retEntity = new DynamicTableEntity(); - ReadAndUpdateTableEntity(retEntity, entry, EntityReadFlags.All, ctx); - retSeg.Results.Add(retEntity); + index++; - // Entry End => ? + // Operation => reader.Read(); } - - DrainODataReader(reader); } - return retSeg; + return result; }); } - internal static Task> TableQueryPostProcessGeneric(Stream responseStream, Func, string, TElement> resolver, HttpResponseMessage resp, OperationContext ctx) + internal static Task> TableQueryPostProcessGeneric(Stream responseStream, Func, string, TElement> resolver, HttpResponseMessage resp, TableRequestOptions options, OperationContext ctx, string accountName) { return Task.Run(() => { ResultSegment retSeg = new ResultSegment(new List()); retSeg.ContinuationToken = ContinuationFromResponse(resp); + IEnumerable contentType; + IEnumerable eTag; - ODataMessageReaderSettings readerSettings = new ODataMessageReaderSettings(); - readerSettings.MessageQuotas = new ODataMessageQuotas() { MaxPartsPerBatch = TableConstants.TableServiceMaxResults, MaxReceivedMessageSize = TableConstants.TableServiceMaxPayload }; + resp.Content.Headers.TryGetValues(Constants.ContentTypeElement, out contentType); + resp.Headers.TryGetValues(Constants.EtagElement, out eTag); - using (ODataMessageReader responseReader = new ODataMessageReader(new HttpResponseAdapterMessage(resp, responseStream), readerSettings)) + string ContentType = contentType != null ? contentType.FirstOrDefault() : null; + string ETag = eTag != null ? eTag.FirstOrDefault() : null; + if (ContentType.Contains(Constants.JsonContentTypeHeaderValue) && ContentType.Contains(Constants.NoMetadata)) { - // create a reader - ODataReader reader = responseReader.CreateODataFeedReader(); + ReadQueryResponseUsingJsonParser(retSeg, responseStream, ETag, resolver, options.PropertyResolver, typeof(TElement), null); + } + else + { + ODataMessageReaderSettings readerSettings = new ODataMessageReaderSettings(); + readerSettings.MessageQuotas = new ODataMessageQuotas() { MaxPartsPerBatch = TableConstants.TableServiceMaxResults, MaxReceivedMessageSize = TableConstants.TableServiceMaxPayload }; - // Start => FeedStart - if (reader.State == ODataReaderState.Start) + using (ODataMessageReader responseReader = new ODataMessageReader(new HttpResponseAdapterMessage(resp, responseStream), readerSettings, new TableStorageModel(accountName))) { - reader.Read(); - } + // create a reader + ODataReader reader = responseReader.CreateODataFeedReader(); - // Feedstart - if (reader.State == ODataReaderState.FeedStart) - { - reader.Read(); - } + // Start => FeedStart + if (reader.State == ODataReaderState.Start) + { + reader.Read(); + } - while (reader.State == ODataReaderState.EntryStart) - { - // EntryStart => EntryEnd - reader.Read(); + // Feedstart + if (reader.State == ODataReaderState.FeedStart) + { + reader.Read(); + } - ODataEntry entry = (ODataEntry)reader.Item; + while (reader.State == ODataReaderState.EntryStart) + { + // EntryStart => EntryEnd + reader.Read(); - retSeg.Results.Add(ReadAndResolve(entry, resolver)); + ODataEntry entry = (ODataEntry)reader.Item; - // Entry End => ? - reader.Read(); - } + retSeg.Results.Add(ReadAndResolve(entry, resolver)); - DrainODataReader(reader); + // Entry End => ? + reader.Read(); + } + + DrainODataReader(reader); + } } return retSeg; @@ -366,11 +407,11 @@ internal static TableContinuationToken ContinuationFromResponse(HttpResponseMess return newContinuationToken; } - private static void ReadOdataEntity(TableResult result, TableOperation operation, IODataResponseMessage respMsg, OperationContext ctx, ODataMessageReaderSettings readerSettings) + private static void ReadOdataEntity(TableResult result, TableOperation operation, IODataResponseMessage respMsg, OperationContext ctx, ODataMessageReaderSettings readerSettings, string accountName) { - using (ODataMessageReader messageReader = new ODataMessageReader(respMsg, readerSettings)) + using (ODataMessageReader messageReader = new ODataMessageReader(respMsg, readerSettings, new TableStorageModel(accountName))) { - // create a reader + // Create a reader. ODataReader reader = messageReader.CreateODataEntryReader(); while (reader.Read()) @@ -386,11 +427,7 @@ private static void ReadOdataEntity(TableResult result, TableOperation operation } else { - result.Etag = ReadAndUpdateTableEntity( - operation.Entity, - entry, - EntityReadFlags.Timestamp | EntityReadFlags.Etag, - ctx); + result.Etag = ReadAndUpdateTableEntity(operation.Entity, entry, EntityReadFlags.Timestamp | EntityReadFlags.Etag, ctx); } } } @@ -399,6 +436,136 @@ private static void ReadOdataEntity(TableResult result, TableOperation operation } } + private static void ReadQueryResponseUsingJsonParser(ResultSegment retSeg, Stream responseStream, string etag, Func, string, TElement> resolver, Func propertyResolver, Type type, OperationContext ctx) + { + StreamReader streamReader = new StreamReader(responseStream); + using (JsonReader reader = new JsonTextReader(streamReader)) + { + reader.DateParseHandling = DateParseHandling.None; + JObject dataSet = JObject.Load(reader); + JToken dataTable = dataSet["value"]; + + foreach (JToken token in dataTable) + { + Dictionary properties = token.ToObject>(); + retSeg.Results.Add(ReadAndResolveWithEdmTypeResolver(properties, resolver, propertyResolver, etag, type, ctx)); + } + + if (reader.Read()) + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, SR.JsonReaderNotInCompletedState)); + } + } + } + + private static void ReadEntityUsingJsonParser(TableResult result, TableOperation operation, Stream stream, OperationContext ctx, TableRequestOptions options) + { + StreamReader streamReader = new StreamReader(stream); + using (JsonReader reader = new JsonTextReader(streamReader)) + { + JsonSerializer serializer = new JsonSerializer(); + Dictionary properties = serializer.Deserialize>(reader); + if (operation.OperationType == TableOperationType.Retrieve) + { + result.Result = ReadAndResolveWithEdmTypeResolver(properties, operation.RetrieveResolver, options.PropertyResolver, result.Etag, operation.PropertyResolverType, ctx); + } + else + { + ReadAndUpdateTableEntityWithEdmTypeResolver(operation.Entity, properties, EntityReadFlags.Timestamp | EntityReadFlags.Etag, options.PropertyResolver, ctx); + } + + if (reader.Read()) + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, SR.JsonReaderNotInCompletedState)); + } + } + } + + internal static void ReadAndUpdateTableEntityWithEdmTypeResolver(ITableEntity entity, Dictionary entityAttributes, EntityReadFlags flags, Func propertyResolver, OperationContext ctx) + { + Dictionary entityProperties = (flags & EntityReadFlags.Properties) > 0 ? new Dictionary() : null; + Dictionary propertyResolverDictionary = null; + + if (entity.GetType() != typeof(DynamicTableEntity)) + { + propertyResolverDictionary = TableOperationHttpResponseParsers.CreatePropertyResolverDictionary(entity.GetType()); + } + + if (flags > 0) + { + foreach (KeyValuePair prop in entityAttributes) + { + if (prop.Key == TableConstants.PartitionKey) + { + entity.PartitionKey = (string)prop.Value; + } + else if (prop.Key == TableConstants.RowKey) + { + entity.RowKey = (string)prop.Value; + } + else if (prop.Key == TableConstants.Timestamp) + { + if ((flags & EntityReadFlags.Timestamp) == 0) + { + continue; + } + + entity.Timestamp = DateTime.Parse(prop.Value, CultureInfo.InvariantCulture); + } + else if ((flags & EntityReadFlags.Properties) > 0) + { + if (propertyResolver != null) + { + Logger.LogVerbose(ctx, SR.UsingUserProvidedPropertyResolver); + try + { + EdmType type = propertyResolver(entity.PartitionKey, entity.RowKey, prop.Key, prop.Value); + Logger.LogVerbose(ctx, SR.AttemptedEdmTypeForTheProperty, prop.Key, type.GetType().ToString()); + try + { + entityProperties.Add(prop.Key, EntityProperty.CreateEntityPropertyFromObject(prop.Value, type.GetType())); + } + catch (FormatException ex) + { + throw new StorageException(string.Format(CultureInfo.InvariantCulture, SR.FailParseProperty, prop.Key, prop.Value, type.ToString()), ex) { IsRetryable = false }; + } + } + catch (StorageException) + { + throw; + } + catch (Exception ex) + { + throw new StorageException(SR.PropertyResolverThrewError, ex) { IsRetryable = false }; + } + } + else if (entity.GetType() != typeof(DynamicTableEntity)) + { + EdmType edmType; + Logger.LogVerbose(ctx, SR.UsingDefaultPropertyResolver); + + if (propertyResolverDictionary != null) + { + propertyResolverDictionary.TryGetValue(prop.Key, out edmType); + Logger.LogVerbose(ctx, SR.AttemptedEdmTypeForTheProperty, prop.Key, edmType); + entityProperties.Add(prop.Key, EntityProperty.CreateEntityPropertyFromObject(prop.Value, edmType)); + } + } + else + { + Logger.LogVerbose(ctx, SR.NoPropertyResolverAvailable); + entityProperties.Add(prop.Key, EntityProperty.CreateEntityPropertyFromObject(prop.Value, typeof(string))); + } + } + } + + if ((flags & EntityReadFlags.Properties) > 0) + { + entity.ReadEntity(entityProperties, ctx); + } + } + } + private static T ReadAndResolve(ODataEntry entry, Func, string, T> resolver) { string pk = null; @@ -429,6 +596,86 @@ private static T ReadAndResolve(ODataEntry entry, Func(Dictionary entityAttributes, Func, string, T> resolver, Func propertyResolver, string etag, Type type, OperationContext ctx) + { + string pk = null; + string rk = null; + DateTimeOffset ts = new DateTimeOffset(); + Dictionary properties = new Dictionary(); + Dictionary propertyResolverDictionary = null; + + if (type != null) + { + propertyResolverDictionary = TableOperationHttpResponseParsers.CreatePropertyResolverDictionary(type); + } + + foreach (KeyValuePair prop in entityAttributes) + { + if (prop.Key == TableConstants.PartitionKey) + { + pk = (string)prop.Value; + } + else if (prop.Key == TableConstants.RowKey) + { + rk = (string)prop.Value; + } + else if (prop.Key == TableConstants.Timestamp) + { + ts = DateTimeOffset.Parse(prop.Value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + if (etag == null) + { + etag = GetETagFromTimestamp(prop.Value); + } + } + else + { + if (propertyResolver != null) + { + Logger.LogVerbose(ctx, SR.UsingUserProvidedPropertyResolver); + try + { + EdmType edmType = propertyResolver(pk, rk, prop.Key, prop.Value); + Logger.LogVerbose(ctx, SR.AttemptedEdmTypeForTheProperty, prop.Key, edmType); + try + { + properties.Add(prop.Key, EntityProperty.CreateEntityPropertyFromObject(prop.Value, edmType)); + } + catch (FormatException ex) + { + throw new StorageException(string.Format(CultureInfo.InvariantCulture, SR.FailParseProperty, prop.Key, prop.Value, edmType), ex) { IsRetryable = false }; + } + } + catch (StorageException) + { + throw; + } + catch (Exception ex) + { + throw new StorageException(SR.PropertyResolverThrewError, ex) { IsRetryable = false }; + } + } + else if (type != null) + { + Logger.LogVerbose(ctx, SR.UsingDefaultPropertyResolver); + EdmType edmType; + if (propertyResolverDictionary != null) + { + propertyResolverDictionary.TryGetValue(prop.Key, out edmType); + Logger.LogVerbose(ctx, SR.AttemptedEdmTypeForTheProperty, prop.Key, edmType); + properties.Add(prop.Key, EntityProperty.CreateEntityPropertyFromObject(prop.Value, edmType)); + } + } + else + { + Logger.LogVerbose(ctx, SR.NoPropertyResolverAvailable); + properties.Add(prop.Key, EntityProperty.CreateEntityPropertyFromObject(prop.Value, EdmType.String)); + } + } + } + + return resolver(pk, rk, ts, properties, etag); + } + // returns etag internal static string ReadAndUpdateTableEntity(ITableEntity entity, ODataEntry entry, EntityReadFlags flags, OperationContext ctx) { @@ -484,5 +731,68 @@ internal static string ReadAndUpdateTableEntity(ITableEntity entity, ODataEntry return entry.ETag; } + + private static DateTimeOffset ParseETagForTimestamp(string etag) + { + // Handle strong ETags as well. + if (etag.StartsWith("W/", StringComparison.Ordinal)) + { + etag = etag.Substring(2); + } + + // DateTimeOffset.ParseExact can't be used because the decimal part after seconds may not be present for rounded times. + etag = etag.Substring(Constants.ETagPrefix.Length, etag.Length - 2 - Constants.ETagPrefix.Length); + return DateTimeOffset.Parse(Uri.UnescapeDataString(etag), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + } + + private static string GetETagFromTimestamp(string timeStampString) + { + timeStampString = Uri.EscapeDataString(timeStampString); + return "W/\"datetime'" + timeStampString + "'\""; + } + + private static Dictionary CreatePropertyResolverDictionary(Type type) + { + Dictionary propertyResolverDictionary = new Dictionary(); + IEnumerable objectProperties = type.GetRuntimeProperties(); + + foreach (PropertyInfo property in objectProperties) + { + if (property.PropertyType == typeof(byte[])) + { + propertyResolverDictionary.Add(property.Name, EdmType.Binary); + } + else if (property.PropertyType == typeof(bool) || property.PropertyType == typeof(bool?)) + { + propertyResolverDictionary.Add(property.Name, EdmType.Boolean); + } + else if (property.PropertyType == typeof(DateTime) || property.PropertyType == typeof(DateTime?) || property.PropertyType == typeof(DateTimeOffset) || property.PropertyType == typeof(DateTimeOffset?)) + { + propertyResolverDictionary.Add(property.Name, EdmType.DateTime); + } + else if (property.PropertyType == typeof(double) || property.PropertyType == typeof(double?)) + { + propertyResolverDictionary.Add(property.Name, EdmType.Double); + } + else if (property.PropertyType == typeof(Guid) || property.PropertyType == typeof(Guid?)) + { + propertyResolverDictionary.Add(property.Name, EdmType.Guid); + } + else if (property.PropertyType == typeof(int) || property.PropertyType == typeof(int?)) + { + propertyResolverDictionary.Add(property.Name, EdmType.Int32); + } + else if (property.PropertyType == typeof(long) || property.PropertyType == typeof(long?)) + { + propertyResolverDictionary.Add(property.Name, EdmType.Int64); + } + else + { + propertyResolverDictionary.Add(property.Name, EdmType.String); + } + } + + return propertyResolverDictionary; + } } } diff --git a/Lib/WindowsRuntime/Table/TableBatchOperation.cs b/Lib/WindowsRuntime/Table/TableBatchOperation.cs index 55b6060bf..4a7d29dc2 100644 --- a/Lib/WindowsRuntime/Table/TableBatchOperation.cs +++ b/Lib/WindowsRuntime/Table/TableBatchOperation.cs @@ -81,9 +81,10 @@ private static RESTCommand> BatchImpl(TableBatchOperation bat batchCmd.RetrieveResponseStream = true; batchCmd.Handler = client.AuthenticationHandler; batchCmd.BuildClient = HttpClientFactory.BuildHttpClient; - batchCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableBatchOperation(cmd, uri, builder, serverTimeout, tableName, batch, client, cnt, ctx); + batchCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; + batchCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableBatchOperation(cmd, uri, builder, serverTimeout, tableName, batch, client, cnt, ctx, requestOptions.PayloadFormat.Value); batchCmd.PreProcessResponse = (cmd, resp, ex, ctx) => HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Accepted, resp.StatusCode, results, cmd, ex); - batchCmd.PostProcessResponse = (cmd, resp, ctx) => TableOperationHttpResponseParsers.TableBatchOperationPostProcess(results, batch, cmd, resp, ctx); + batchCmd.PostProcessResponse = (cmd, resp, ctx) => TableOperationHttpResponseParsers.TableBatchOperationPostProcess(results, batch, cmd, resp, ctx, requestOptions, client.AccountName); batchCmd.RecoveryAction = (cmd, ex, ctx) => results.Clear(); return batchCmd; diff --git a/Lib/WindowsRuntime/Table/TableOperation.cs b/Lib/WindowsRuntime/Table/TableOperation.cs index 670fe9592..2d438a475 100644 --- a/Lib/WindowsRuntime/Table/TableOperation.cs +++ b/Lib/WindowsRuntime/Table/TableOperation.cs @@ -122,10 +122,11 @@ private static RESTCommand InsertImpl(TableOperation operation, Clo insertCmd.RetrieveResponseStream = true; insertCmd.Handler = client.AuthenticationHandler; insertCmd.BuildClient = HttpClientFactory.BuildHttpClient; - insertCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableOperation(cmd, uri, builder, serverTimeout, operation, client, cnt, ctx); + insertCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; + insertCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableOperation(cmd, uri, builder, serverTimeout, operation, client, cnt, ctx, requestOptions.PayloadFormat.Value); insertCmd.PreProcessResponse = (cmd, resp, ex, ctx) => TableOperationHttpResponseParsers.TableOperationPreProcess(result, operation, resp, ex, cmd, ctx); - insertCmd.PostProcessResponse = (cmd, resp, ctx) => TableOperationHttpResponseParsers.TableOperationPostProcess(result, operation, cmd, resp, ctx); + insertCmd.PostProcessResponse = (cmd, resp, ctx) => TableOperationHttpResponseParsers.TableOperationPostProcess(result, operation, cmd, resp, ctx, requestOptions, client.AccountName); return insertCmd; } @@ -138,8 +139,9 @@ private static RESTCommand DeleteImpl(TableOperation operation, Clo TableResult result = new TableResult() { Result = operation.Entity }; deleteCmd.RetrieveResponseStream = false; deleteCmd.Handler = client.AuthenticationHandler; + deleteCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; deleteCmd.BuildClient = HttpClientFactory.BuildHttpClient; - deleteCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableOperation(cmd, uri, builder, serverTimeout, operation, client, cnt, ctx); + deleteCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableOperation(cmd, uri, builder, serverTimeout, operation, client, cnt, ctx, requestOptions.PayloadFormat.Value); deleteCmd.PreProcessResponse = (cmd, resp, ex, ctx) => TableOperationHttpResponseParsers.TableOperationPreProcess(result, operation, resp, ex, cmd, ctx); return deleteCmd; @@ -153,8 +155,9 @@ private static RESTCommand MergeImpl(TableOperation operation, Clou TableResult result = new TableResult() { Result = operation.Entity }; mergeCmd.RetrieveResponseStream = false; mergeCmd.Handler = client.AuthenticationHandler; + mergeCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; mergeCmd.BuildClient = HttpClientFactory.BuildHttpClient; - mergeCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableOperation(cmd, uri, builder, serverTimeout, operation, client, cnt, ctx); + mergeCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableOperation(cmd, uri, builder, serverTimeout, operation, client, cnt, ctx, requestOptions.PayloadFormat.Value); mergeCmd.PreProcessResponse = (cmd, resp, ex, ctx) => TableOperationHttpResponseParsers.TableOperationPreProcess(result, operation, resp, ex, cmd, ctx); return mergeCmd; @@ -169,7 +172,8 @@ private static RESTCommand ReplaceImpl(TableOperation operation, Cl replaceCmd.RetrieveResponseStream = false; replaceCmd.Handler = client.AuthenticationHandler; replaceCmd.BuildClient = HttpClientFactory.BuildHttpClient; - replaceCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableOperation(cmd, uri, builder, serverTimeout, operation, client, cnt, ctx); + replaceCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; + replaceCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableOperation(cmd, uri, builder, serverTimeout, operation, client, cnt, ctx, requestOptions.PayloadFormat.Value); replaceCmd.PreProcessResponse = (cmd, resp, ex, ctx) => TableOperationHttpResponseParsers.TableOperationPreProcess(result, operation, resp, ex, cmd, ctx); return replaceCmd; @@ -185,7 +189,8 @@ private static RESTCommand RetrieveImpl(TableOperation operation, C retrieveCmd.RetrieveResponseStream = true; retrieveCmd.Handler = client.AuthenticationHandler; retrieveCmd.BuildClient = HttpClientFactory.BuildHttpClient; - retrieveCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableOperation(cmd, uri, builder, serverTimeout, operation, client, cnt, ctx); + retrieveCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; + retrieveCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableOperation(cmd, uri, builder, serverTimeout, operation, client, cnt, ctx, requestOptions.PayloadFormat.Value); retrieveCmd.PreProcessResponse = (cmd, resp, ex, ctx) => TableOperationHttpResponseParsers.TableOperationPreProcess(result, operation, resp, ex, cmd, ctx); retrieveCmd.PostProcessResponse = (cmd, resp, ctx) => Task.Run(async () => @@ -195,7 +200,7 @@ private static RESTCommand RetrieveImpl(TableOperation operation, C return result; } - result = await TableOperationHttpResponseParsers.TableOperationPostProcess(result, operation, cmd, resp, ctx); + result = await TableOperationHttpResponseParsers.TableOperationPostProcess(result, operation, cmd, resp, ctx, requestOptions, client.AccountName); return result; }); return retrieveCmd; diff --git a/Lib/WindowsRuntime/Table/TableQuery.cs b/Lib/WindowsRuntime/Table/TableQuery.cs index a65466f47..bf2bae7e4 100644 --- a/Lib/WindowsRuntime/Table/TableQuery.cs +++ b/Lib/WindowsRuntime/Table/TableQuery.cs @@ -76,7 +76,7 @@ internal IAsyncOperation ExecuteQuerySegmentedAsync(TableCont TableRequestOptions modifiedOptions = TableRequestOptions.ApplyDefaults(requestOptions, client); operationContext = operationContext ?? new OperationContext(); - RESTCommand cmdToExecute = QueryImpl(this, continuationToken, client, tableName, modifiedOptions); + RESTCommand cmdToExecute = QueryImpl(this, continuationToken, client, tableName, EntityUtilities.ResolveEntityByType, modifiedOptions); #if ASPNET_K || PORTABLE return Task.Run(async () => await Executor.ExecuteAsync( @@ -93,7 +93,7 @@ internal IAsyncOperation ExecuteQuerySegmentedAsync(TableCont #endif } - private static RESTCommand QueryImpl(TableQuery query, TableContinuationToken token, CloudTableClient client, string tableName, TableRequestOptions requestOptions) + private static RESTCommand QueryImpl(TableQuery query, TableContinuationToken token, CloudTableClient client, string tableName, EntityResolver resolver, TableRequestOptions requestOptions) { UriQueryBuilder builder = query.GenerateQueryBuilder(); @@ -111,17 +111,18 @@ private static RESTCommand QueryImpl(TableQuery query, TableC queryCmd.Handler = client.AuthenticationHandler; queryCmd.BuildClient = HttpClientFactory.BuildHttpClient; queryCmd.Builder = builder; - queryCmd.BuildRequest = (cmd, uri, queryBuilder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableQuery(uri, builder, serverTimeout, cnt, ctx); + queryCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; + queryCmd.BuildRequest = (cmd, uri, queryBuilder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableQuery(uri, builder, serverTimeout, cnt, ctx, requestOptions.PayloadFormat.Value); queryCmd.PreProcessResponse = (cmd, resp, ex, ctx) => HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp.StatusCode, null /* retVal */, cmd, ex); queryCmd.PostProcessResponse = async (cmd, resp, ctx) => { - TableQuerySegment resSeg = await TableOperationHttpResponseParsers.TableQueryPostProcess(cmd.ResponseStream, resp, ctx); + ResultSegment resSeg = await TableOperationHttpResponseParsers.TableQueryPostProcessGeneric(cmd.ResponseStream, resolver.Invoke, resp, requestOptions, ctx, client.AccountName); if (resSeg.ContinuationToken != null) { resSeg.ContinuationToken.TargetLocation = cmd.CurrentResult.TargetLocation; } - return resSeg; + return new TableQuerySegment(resSeg); }; return queryCmd; @@ -171,12 +172,13 @@ private RESTCommand> QueryImpl(Table queryCmd.RetrieveResponseStream = true; queryCmd.Handler = client.AuthenticationHandler; queryCmd.BuildClient = HttpClientFactory.BuildHttpClient; + queryCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; queryCmd.Builder = builder; - queryCmd.BuildRequest = (cmd, uri, queryBuilder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableQuery(uri, builder, serverTimeout, cnt, ctx); + queryCmd.BuildRequest = (cmd, uri, queryBuilder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableQuery(uri, builder, serverTimeout, cnt, ctx, requestOptions.PayloadFormat.Value); queryCmd.PreProcessResponse = (cmd, resp, ex, ctx) => HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp.StatusCode, null /* retVal */, cmd, ex); queryCmd.PostProcessResponse = async (cmd, resp, ctx) => { - ResultSegment resSeg = await TableOperationHttpResponseParsers.TableQueryPostProcessGeneric(cmd.ResponseStream, resolver.Invoke, resp, ctx); + ResultSegment resSeg = await TableOperationHttpResponseParsers.TableQueryPostProcessGeneric(cmd.ResponseStream, resolver.Invoke, resp, requestOptions, ctx, client.AccountName); if (resSeg.ContinuationToken != null) { resSeg.ContinuationToken.TargetLocation = cmd.CurrentResult.TargetLocation; diff --git a/Lib/WindowsRuntime/Table/TableQueryGeneric.cs b/Lib/WindowsRuntime/Table/TableQueryGeneric.cs index 1c5d09594..9396e001e 100644 --- a/Lib/WindowsRuntime/Table/TableQueryGeneric.cs +++ b/Lib/WindowsRuntime/Table/TableQueryGeneric.cs @@ -177,12 +177,13 @@ internal IAsyncOperation> ExecuteQuerySegmentedAsync< queryCmd.RetrieveResponseStream = true; queryCmd.Handler = client.AuthenticationHandler; queryCmd.BuildClient = HttpClientFactory.BuildHttpClient; + queryCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; queryCmd.Builder = builder; - queryCmd.BuildRequest = (cmd, uri, queryBuilder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableQuery(uri, builder, serverTimeout, cnt, ctx); + queryCmd.BuildRequest = (cmd, uri, queryBuilder, cnt, serverTimeout, ctx) => TableOperationHttpRequestMessageFactory.BuildRequestForTableQuery(uri, builder, serverTimeout, cnt, ctx, requestOptions.PayloadFormat.Value); queryCmd.PreProcessResponse = (cmd, resp, ex, ctx) => HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp.StatusCode, null /* retVal */, cmd, ex); queryCmd.PostProcessResponse = async (cmd, resp, ctx) => { - ResultSegment resSeg = await TableOperationHttpResponseParsers.TableQueryPostProcessGeneric(cmd.ResponseStream, resolver.Invoke, resp, ctx); + ResultSegment resSeg = await TableOperationHttpResponseParsers.TableQueryPostProcessGeneric(cmd.ResponseStream, resolver.Invoke, resp, requestOptions, ctx, client.AccountName); if (resSeg.ContinuationToken != null) { resSeg.ContinuationToken.TargetLocation = cmd.CurrentResult.TargetLocation; diff --git a/Lib/WindowsRuntime/packages.config b/Lib/WindowsRuntime/packages.config index 39fae7f18..f8e7d3166 100644 --- a/Lib/WindowsRuntime/packages.config +++ b/Lib/WindowsRuntime/packages.config @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/README.md b/README.md index 9ec3c3adb..6e8cdd8ab 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Microsoft Azure Storage SDK for .NET (5.0.2) +# Microsoft Azure Storage SDK for .NET (6.0.0) The Microsoft Azure Storage SDK for .NET allows you to build Azure applications that take advantage of scalable cloud computing resources. diff --git a/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json b/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json index 461862a1a..251912e5e 100644 --- a/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json +++ b/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json @@ -1,5 +1,5 @@ { - "version": "5.0.3.0", + "version": "6.0.1.0", "dependencies": { "xunit": "2.1.0-*", "xunit.runner.dnx": "2.1.0-*", diff --git a/Test/ClassLibraryCommon/AccountSASTests.cs b/Test/ClassLibraryCommon/AccountSASTests.cs new file mode 100644 index 000000000..71da582a5 --- /dev/null +++ b/Test/ClassLibraryCommon/AccountSASTests.cs @@ -0,0 +1,1454 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage +{ + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.WindowsAzure.Storage; + using Microsoft.WindowsAzure.Storage.Auth; + using Microsoft.WindowsAzure.Storage.Blob; + using Microsoft.WindowsAzure.Storage.File; + using Microsoft.WindowsAzure.Storage.Queue; + using Microsoft.WindowsAzure.Storage.Queue.Protocol; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using Microsoft.WindowsAzure.Storage.Table; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Security.Cryptography; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using System.Xml; + using System.Xml.Linq; + + [TestClass] + public class AccountSASTests : TestBase + { + public void RunPermissionsTestBlobs(SharedAccessAccountPolicy policy) + { + CloudBlobClient blobClient = GenerateCloudBlobClient(); + string containerName = "c" + Guid.NewGuid().ToString("N"); + try + { + CloudStorageAccount account = new CloudStorageAccount(blobClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = new CloudStorageAccount(accountSAS, blobClient.StorageUri, null, null, null); + CloudBlobClient blobClientWithSAS = accountWithSAS.CreateCloudBlobClient(); + CloudBlobContainer containerWithSAS = blobClientWithSAS.GetContainerReference(containerName); + CloudBlobContainer container = blobClient.GetContainerReference(containerName); + + // General pattern - If current perms support doing a thing with SAS, do the thing with SAS and validate with shared + // Otherwise, make sure SAS fails and then do the thing with shared key. + + // Things to do: + // Create the container (Create / Write perms, Container RT) + // List containers with prefix (List perms, Service RT) + // Create an append blob (Create / Write perms, Object RT) + // Append a block to append blob (Add / Write perms, Object RT) + // Read the data from the append blob (Read perms, Object RT) + // Delete the blob (Delete perms, Object RT) + + if ((((policy.Permissions & SharedAccessAccountPermissions.Create) == SharedAccessAccountPermissions.Create) || ((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Container) == SharedAccessAccountResourceTypes.Container)) + { + containerWithSAS.Create(); + } + else + { + TestHelper.ExpectedException(() => containerWithSAS.Create(), "Create a container should fail with SAS without Create or Write and Container-level permissions."); + container.Create(); + } + + Assert.IsTrue(container.Exists()); + + if (((policy.Permissions & SharedAccessAccountPermissions.List) == SharedAccessAccountPermissions.List) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Service) == SharedAccessAccountResourceTypes.Service)) + { + Assert.AreEqual(container.Name, blobClientWithSAS.ListContainers(container.Name).First().Name); + } + else + { + TestHelper.ExpectedException(() => blobClientWithSAS.ListContainers(container.Name).First(), "List containers should fail with SAS without List and Service-level permissions."); + } + + string blobName = "blob"; + CloudAppendBlob appendBlob = container.GetAppendBlobReference(blobName); + CloudAppendBlob appendBlobWithSAS = containerWithSAS.GetAppendBlobReference(blobName); + if ((((policy.Permissions & SharedAccessAccountPermissions.Create) == SharedAccessAccountPermissions.Create) || ((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + appendBlobWithSAS.CreateOrReplace(); + } + else + { + TestHelper.ExpectedException(() => appendBlobWithSAS.CreateOrReplace(), "Creating an append blob should fail with SAS without Create or Write and Object-level perms."); + appendBlob.CreateOrReplace(); + } + + Assert.IsTrue(appendBlob.Exists()); + + string blobText = "blobText"; + if ((((policy.Permissions & SharedAccessAccountPermissions.Add) == SharedAccessAccountPermissions.Add) || ((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(blobText))) + { + appendBlobWithSAS.AppendBlock(stream); + } + } + else + { + using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(blobText))) + { + TestHelper.ExpectedException(() => appendBlobWithSAS.AppendBlock(stream), "Append a block to an append blob should fail with SAS without Add or Write and Object-level perms."); + stream.Seek(0, SeekOrigin.Begin); + appendBlob.AppendBlock(stream); + } + } + + Assert.AreEqual(blobText, appendBlob.DownloadText()); + + if (((policy.Permissions & SharedAccessAccountPermissions.Read) == SharedAccessAccountPermissions.Read) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + Assert.AreEqual(blobText, appendBlobWithSAS.DownloadText()); + } + else + { + TestHelper.ExpectedException(() => appendBlobWithSAS.DownloadText(), "Reading a blob's contents with SAS without Read and Object-level permissions should fail."); + } + + if (((policy.Permissions & SharedAccessAccountPermissions.Delete) == SharedAccessAccountPermissions.Delete) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + appendBlobWithSAS.Delete(); + } + else + { + TestHelper.ExpectedException(() => appendBlobWithSAS.Delete(), "Deleting a blob with SAS without Delete and Object-level perms should fail."); + appendBlob.Delete(); + } + + Assert.IsFalse(appendBlob.Exists()); + } + finally + { + blobClient.GetContainerReference(containerName).DeleteIfExists(); + } + } + + public void RunPermissionsTestTables(SharedAccessAccountPolicy policy) + { + CloudTableClient tableClient = GenerateCloudTableClient(); + string tableName = "t" + Guid.NewGuid().ToString("N"); + ServiceProperties initialProperties = tableClient.GetServiceProperties(); + try + { + CloudStorageAccount account = new CloudStorageAccount(tableClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = new CloudStorageAccount(accountSAS, null, null, tableClient.StorageUri, null); + CloudTableClient tableClientWithSAS = accountWithSAS.CreateCloudTableClient(); + CloudTable tableWithSAS = tableClientWithSAS.GetTableReference(tableName); + CloudTable table = tableClient.GetTableReference(tableName); + + // General pattern - If current perms support doing a thing with SAS, do the thing with SAS and validate with shared + // Otherwise, make sure SAS fails and then do the thing with shared key. + + // Things to do: + // Create the table (Create or Write perms, Container RT) + // List tables (List perms, Container RT) + // Set table service properties (Write perms, Service RT) + // Insert an entity (Add perms, Object RT) + // Merge an entity (Update perms, Object RT) + // Insert or merge an entity (Add and update perms, Object RT) (test this twice, once for insert, once for merge.) + // Query the table for the entity (Read perms, Object RT) + // Delete the entity (Delete perms, Object RT) + + if ((((policy.Permissions & SharedAccessAccountPermissions.Create) == SharedAccessAccountPermissions.Create) || ((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Container) == SharedAccessAccountResourceTypes.Container)) + { + tableWithSAS.Create(); + } + else + { + TestHelper.ExpectedException(() => tableWithSAS.Create(), "Creating a table with SAS should fail without Add and Container-level permissions."); + table.Create(); + } + Assert.IsTrue(table.Exists()); + + if (((policy.Permissions & SharedAccessAccountPermissions.List) == SharedAccessAccountPermissions.List) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Container) == SharedAccessAccountResourceTypes.Container)) + { + Assert.AreEqual(tableName, tableClientWithSAS.ListTables(tableName).First().Name); + } + else + { + TestHelper.ExpectedException(() => tableClientWithSAS.ListTables(tableName).First(), "Listing tables with SAS should fail without Read and Container-level permissions."); + } + + ServiceProperties properties = new ServiceProperties(); + properties.Logging = initialProperties.Logging; + properties.HourMetrics = initialProperties.HourMetrics; + properties.MinuteMetrics = initialProperties.MinuteMetrics; + properties.DefaultServiceVersion = initialProperties.DefaultServiceVersion; + CorsRule rule = new CorsRule(); + string sampleOriginText = "sampleOrigin"; + rule.AllowedOrigins.Add(sampleOriginText); + rule.AllowedMethods = CorsHttpMethods.Get; + rule.MaxAgeInSeconds = 100; + properties.Cors.CorsRules.Add(rule); + if (((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Service) == SharedAccessAccountResourceTypes.Service)) + { + tableClientWithSAS.SetServiceProperties(properties); + } + else + { + TestHelper.ExpectedException(() => tableClientWithSAS.SetServiceProperties(properties), "Setting table service properites should fail with SAS without Write and Service-level permissions."); + tableClient.SetServiceProperties(properties); + } + Assert.AreEqual(sampleOriginText, rule.AllowedOrigins.First()); + + string propName = "prop"; + string propName2 = "prop2"; + DynamicTableEntity entity1 = new DynamicTableEntity(); + + string partitionKey = "PK"; + string rowKeyPrefix = "RK"; + entity1.PartitionKey = partitionKey; + entity1.RowKey = rowKeyPrefix + "1"; + entity1.Properties = new Dictionary() { { propName, EntityProperty.GeneratePropertyForInt(4) } }; + entity1.ETag = "*"; + + if (((policy.Permissions & SharedAccessAccountPermissions.Add) == SharedAccessAccountPermissions.Add) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + tableWithSAS.Execute(TableOperation.Insert(entity1)); + } + else + { + TestHelper.ExpectedException(() => tableWithSAS.Execute(TableOperation.Insert(entity1)), "Inserting an entity should fail without Add and Object-level permissions."); + table.Execute(TableOperation.Insert(entity1)); + } + TableQuery query = new TableQuery().Where(string.Format("(PartitionKey eq '{0}') and (RowKey eq '{1}')", entity1.PartitionKey, entity1.RowKey)); + Assert.AreEqual(entity1.Properties[propName].Int32Value, table.ExecuteQuery(query).First().Properties[propName].Int32Value); + entity1.ETag = "*"; + + DynamicTableEntity entity1changed = new DynamicTableEntity(); + entity1changed.PartitionKey = "PK"; + entity1changed.RowKey = "RK1"; + entity1changed.Properties = new Dictionary() { { propName2, EntityProperty.GeneratePropertyForInt(5) } }; + entity1changed.ETag = "*"; + + if (((policy.Permissions & SharedAccessAccountPermissions.Update) == SharedAccessAccountPermissions.Update) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + tableWithSAS.Execute(TableOperation.Merge(entity1changed)); + } + else + { + TestHelper.ExpectedException(() => tableWithSAS.Execute(TableOperation.Merge(entity1changed)), "Merging an entity should fail without Update and Object-level permissions."); + table.Execute(TableOperation.Merge(entity1changed)); + } + + DynamicTableEntity result = table.ExecuteQuery(query).First(); + Assert.AreEqual(entity1.Properties[propName].Int32Value, result.Properties[propName].Int32Value); + Assert.AreEqual(entity1changed.Properties[propName2].Int32Value, result.Properties[propName2].Int32Value); + entity1changed.ETag = "*"; + + DynamicTableEntity entity2 = new DynamicTableEntity(); + entity2.PartitionKey = partitionKey; + entity2.RowKey = rowKeyPrefix + "2"; + entity2.Properties = new Dictionary() { { propName, EntityProperty.GeneratePropertyForInt(4) } }; + entity2.ETag = "*"; + + if ((((policy.Permissions & SharedAccessAccountPermissions.Add) == SharedAccessAccountPermissions.Add) && ((policy.Permissions & SharedAccessAccountPermissions.Update) == SharedAccessAccountPermissions.Update)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + tableWithSAS.Execute(TableOperation.InsertOrMerge(entity2)); + } + else + { + TestHelper.ExpectedException(() => tableWithSAS.Execute(TableOperation.InsertOrMerge(entity2)), "Inserting or merging an entity should fail without Add and Update and Object-level permissions."); + table.Execute(TableOperation.InsertOrMerge(entity2)); + } + query = new TableQuery().Where(string.Format("(PartitionKey eq '{0}') and (RowKey eq '{1}')", entity2.PartitionKey, entity2.RowKey)); + Assert.AreEqual(entity2.Properties[propName].Int32Value, table.ExecuteQuery(query).First().Properties[propName].Int32Value); + entity2.ETag = "*"; + + DynamicTableEntity entity2changed = new DynamicTableEntity(); + entity2changed.PartitionKey = partitionKey; + entity2changed.RowKey = rowKeyPrefix + "2"; + entity2changed.Properties = new Dictionary() { { propName2, EntityProperty.GeneratePropertyForInt(5) } }; + entity2changed.ETag = "*"; + if ((((policy.Permissions & SharedAccessAccountPermissions.Add) == SharedAccessAccountPermissions.Add) && ((policy.Permissions & SharedAccessAccountPermissions.Update) == SharedAccessAccountPermissions.Update)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + tableWithSAS.Execute(TableOperation.InsertOrMerge(entity2changed)); + } + else + { + TestHelper.ExpectedException(() => tableWithSAS.Execute(TableOperation.InsertOrMerge(entity2changed)), "Inserting or merging an entity should fail without Add and Update and Object-level permissions."); + table.Execute(TableOperation.InsertOrMerge(entity2changed)); + } + entity2changed.ETag = "*"; + + result = table.ExecuteQuery(query).First(); + Assert.AreEqual(entity2.Properties[propName].Int32Value, result.Properties[propName].Int32Value); + Assert.AreEqual(entity2changed.Properties[propName2].Int32Value, result.Properties[propName2].Int32Value); + + query = new TableQuery().Where(string.Format("(PartitionKey eq '{0}') and (RowKey ge '{1}') and (RowKey le '{2}')", entity1.PartitionKey, entity1.RowKey, entity2.RowKey)); + if (((policy.Permissions & SharedAccessAccountPermissions.Read) == SharedAccessAccountPermissions.Read) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + List entities = tableWithSAS.ExecuteQuery(query).ToList(); + } + else + { + TestHelper.ExpectedException(() => tableWithSAS.ExecuteQuery(query).ToList(), "Querying tables should fail with SAS without Read and Object-level permissions."); + } + + if (((policy.Permissions & SharedAccessAccountPermissions.Delete) == SharedAccessAccountPermissions.Delete) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + tableWithSAS.Execute(TableOperation.Delete(entity1)); + } + else + { + TestHelper.ExpectedException(() => tableWithSAS.Execute(TableOperation.Delete(entity1)), "Deleting an entity should fail with SAS without Delete and Object-level permissions."); + table.Execute(TableOperation.Delete(entity1)); + } + + query = new TableQuery().Where(string.Format("(PartitionKey eq '{0}') and (RowKey eq '{1}')", entity1.PartitionKey, entity1.RowKey)); + Assert.IsFalse(table.ExecuteQuery(query).Any()); + } + finally + { + tableClient.GetTableReference(tableName).DeleteIfExists(); + if (initialProperties != null) + { + tableClient.SetServiceProperties(initialProperties); + } + } + } + public void RunPermissionsTestQueues(SharedAccessAccountPolicy policy) + { + CloudQueueClient queueClient = GenerateCloudQueueClient(); + string queueName = "q" + Guid.NewGuid().ToString("N"); + try + { + CloudStorageAccount account = new CloudStorageAccount(queueClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = new CloudStorageAccount(accountSAS, null, queueClient.StorageUri, null, null); + CloudQueueClient queueClientWithSAS = accountWithSAS.CreateCloudQueueClient(); + CloudQueue queueWithSAS = queueClientWithSAS.GetQueueReference(queueName); + CloudQueue queue = queueClient.GetQueueReference(queueName); + + // General pattern - If current perms support doing a thing with SAS, do the thing with SAS and validate with shared + // Otherwise, make sure SAS fails and then do the thing with shared key. + + // Things to do: + // Create the queue (Create or Write perms, Container RT) + // List queues (List perms, Service RT) + // Set queue metadata (Write perms, Container RT) + // Insert a message (Add perms, Object RT) + // Peek a message (Read perms, Object RT) + // Get a message (Process perms, Object RT) + // Update a message (Update perms, Object RT) + // Clear all messages (Delete perms, Object RT) + + if ((((policy.Permissions & SharedAccessAccountPermissions.Create) == SharedAccessAccountPermissions.Create) || ((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Container) == SharedAccessAccountResourceTypes.Container)) + { + queueWithSAS.Create(); + } + else + { + TestHelper.ExpectedException(() => queueWithSAS.Create(), "Creating a queue with SAS should fail without Add and Container-level permissions."); + queue.Create(); + } + Assert.IsTrue(queue.Exists()); + + if (((policy.Permissions & SharedAccessAccountPermissions.List) == SharedAccessAccountPermissions.List) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Service) == SharedAccessAccountResourceTypes.Service)) + { + Assert.AreEqual(queueName, queueClientWithSAS.ListQueues(queueName).First().Name); + } + else + { + TestHelper.ExpectedException(() => queueClientWithSAS.ListQueues(queueName).First(), "Listing queues with SAS should fail without Read and Service-level permissions."); + } + + queueWithSAS.Metadata["metadatakey"] = "metadatavalue"; + if (((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Container) == SharedAccessAccountResourceTypes.Container)) + { + queueWithSAS.SetMetadata(); + queue.FetchAttributes(); + Assert.AreEqual("metadatavalue", queue.Metadata["metadatakey"]); + } + else + { + TestHelper.ExpectedException(() => queueWithSAS.SetMetadata(), "Setting a queue's metadata with SAS should fail without Write and Container-level permissions."); + } + + string messageText = "messageText"; + CloudQueueMessage message = new CloudQueueMessage(messageText); + if (((policy.Permissions & SharedAccessAccountPermissions.Add) == SharedAccessAccountPermissions.Add) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + queueWithSAS.AddMessage(message); + } + else + { + TestHelper.ExpectedException(() => queueWithSAS.AddMessage(message), "Adding a queue message should fail with SAS without Add and Object-level permissions."); + queue.AddMessage(message); + } + Assert.AreEqual(messageText, queue.PeekMessage().AsString); + + if (((policy.Permissions & SharedAccessAccountPermissions.Read) == SharedAccessAccountPermissions.Read) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + Assert.AreEqual(messageText, queueWithSAS.PeekMessage().AsString); + } + else + { + TestHelper.ExpectedException(() => queueWithSAS.PeekMessage(), "Peeking a queue message should fail with SAS without Read and Object-level permissions."); + } + + CloudQueueMessage messageResult = null; + if (((policy.Permissions & SharedAccessAccountPermissions.ProcessMessages) == SharedAccessAccountPermissions.ProcessMessages) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + messageResult = queueWithSAS.GetMessage(); + } + else + { + TestHelper.ExpectedException(() => queueWithSAS.GetMessage(), "Getting a message should fail with SAS without Process and Object-level permissions."); + messageResult = queue.GetMessage(); + } + Assert.AreEqual(messageText, messageResult.AsString); + + string newMessageContent = "new content"; + messageResult.SetMessageContent(newMessageContent); + if (((policy.Permissions & SharedAccessAccountPermissions.Update) == SharedAccessAccountPermissions.Update) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + queueWithSAS.UpdateMessage(messageResult, TimeSpan.Zero, MessageUpdateFields.Content | MessageUpdateFields.Visibility); + } + else + { + TestHelper.ExpectedException(() => queueWithSAS.UpdateMessage(messageResult, TimeSpan.Zero, MessageUpdateFields.Content | MessageUpdateFields.Visibility), "Updating a message should fail with SAS without Update and Object-level permissions."); + queue.UpdateMessage(messageResult, TimeSpan.Zero, MessageUpdateFields.Content | MessageUpdateFields.Visibility); + } + messageResult = queue.PeekMessage(); + Assert.AreEqual(newMessageContent, messageResult.AsString); + + if (((policy.Permissions & SharedAccessAccountPermissions.Delete) == SharedAccessAccountPermissions.Delete) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + queueWithSAS.Clear(); + } + else + { + TestHelper.ExpectedException(() => queueWithSAS.Clear(), "Clearing messages should fail with SAS without delete and Object-level permissions."); + } + } + finally + { + queueClient.GetQueueReference(queueName).DeleteIfExists(); + } + } + public void RunPermissionsTestFiles(SharedAccessAccountPolicy policy) + { + CloudFileClient fileClient = GenerateCloudFileClient(); + string shareName = "s" + Guid.NewGuid().ToString("N"); + try + { + CloudStorageAccount account = new CloudStorageAccount(fileClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = new CloudStorageAccount(accountSAS, null, null, null, fileClient.StorageUri); + CloudFileClient fileClientWithSAS = accountWithSAS.CreateCloudFileClient(); + CloudFileShare shareWithSAS = fileClientWithSAS.GetShareReference(shareName); + CloudFileShare share = fileClient.GetShareReference(shareName); + + // General pattern - If current perms support doing a thing with SAS, do the thing with SAS and validate with shared + // Otherwise, make sure SAS fails and then do the thing with shared key. + + // Things to do: + // Create the share (Create / Write perms, Container RT) + // List shares with prefix (List perms, Service RT) + // Create a new file (Create / Write, Object RT) + // Add a range to the file (Write, Object RT) + // Read the data from the file (Read, Object RT) + // Overwrite a file (Write, Object RT) + // Delete the file (Delete perms, Object RT) + + if ((((policy.Permissions & SharedAccessAccountPermissions.Create) == SharedAccessAccountPermissions.Create) || ((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Container) == SharedAccessAccountResourceTypes.Container)) + { + shareWithSAS.Create(); + } + else + { + TestHelper.ExpectedException(() => shareWithSAS.Create(), "Creating a share with SAS should fail without Create or Write and Container-level perms."); + share.Create(); + } + Assert.IsTrue(share.Exists()); + + if (((policy.Permissions & SharedAccessAccountPermissions.List) == SharedAccessAccountPermissions.List) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Service) == SharedAccessAccountResourceTypes.Service)) + { + Assert.AreEqual(shareName, fileClientWithSAS.ListShares(shareName).First().Name); + } + else + { + TestHelper.ExpectedException(() => fileClientWithSAS.ListShares(shareName).First(), "Listing shared with SAS should fail without List and Service-level perms."); + } + + string filename = "fileName"; + CloudFile fileWithSAS = shareWithSAS.GetRootDirectoryReference().GetFileReference(filename); + CloudFile file = share.GetRootDirectoryReference().GetFileReference(filename); + + byte[] content = new byte[] { 0x1, 0x2, 0x3, 0x4 }; + if ((((policy.Permissions & SharedAccessAccountPermissions.Create) == SharedAccessAccountPermissions.Create) || ((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + fileWithSAS.Create(content.Length); + } + else + { + TestHelper.ExpectedException(() => fileWithSAS.Create(content.Length), "Creating a file with SAS should fail without Create or Write and Object-level perms."); + file.Create(content.Length); + } + Assert.IsTrue(file.Exists()); + + using (Stream stream = new MemoryStream(content)) + { + if (((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + fileWithSAS.WriteRange(stream, 0); + } + else + { + TestHelper.ExpectedException(() => fileWithSAS.WriteRange(stream, 0), "Writing a range to a file with SAS should fail without Write and Object-level perms."); + stream.Seek(0, SeekOrigin.Begin); + file.WriteRange(stream, 0); + } + } + + byte[] result = new byte[content.Length]; + file.DownloadRangeToByteArray(result, 0, 0, content.Length); + for (int i = 0; i < content.Length; i++) + { + Assert.AreEqual(content[i], result[i]); + } + + if (((policy.Permissions & SharedAccessAccountPermissions.Read) == SharedAccessAccountPermissions.Read) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + result = new byte[content.Length]; + fileWithSAS.DownloadRangeToByteArray(result, 0, 0, content.Length); + for (int i = 0; i < content.Length; i++) + { + Assert.AreEqual(content[i], result[i]); + } + } + else + { + TestHelper.ExpectedException(() => fileWithSAS.DownloadRangeToByteArray(result, 0, 0, content.Length), "Reading a file with SAS should fail without Read and Object-level perms."); + } + + if (((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + fileWithSAS.Create(2); + } + else + { + TestHelper.ExpectedException(() => fileWithSAS.Create(2), "Overwriting a file with SAS should fail without Write and Object-level perms."); + file.Create(2); + } + + result = new byte[content.Length]; + file.DownloadRangeToByteArray(result, 0, 0, content.Length); + for (int i = 0; i < content.Length; i++) + { + Assert.AreEqual(0, result[i]); + } + + if (((policy.Permissions & SharedAccessAccountPermissions.Delete) == SharedAccessAccountPermissions.Delete) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + fileWithSAS.Delete(); + } + else + { + TestHelper.ExpectedException(() => fileWithSAS.Delete(), "Deleting a file with SAS should fail without Delete and Object-level perms."); + file.Delete(); + } + + Assert.IsFalse(file.Exists()); + } + finally + { + fileClient.GetShareReference(shareName).DeleteIfExists(); + } + } + + [TestMethod] + [Description("Test account SAS all permissions, all services")] + [TestCategory(ComponentCategory.Core)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AccountSASPermissions() + { + // Single-threaded, takes 10 minutes to run + // Parallelized, 1 minute. + + Task[] tasks = new Task[0x100]; + + for (int i = 0; i < 0x100; i++) + { + tasks[i] = Task.Factory.StartNew(() => + { + SharedAccessAccountPermissions permissions = (SharedAccessAccountPermissions)i; + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + policy.Permissions = permissions; + this.RunPermissionsTestBlobs(policy); + this.RunPermissionsTestTables(policy); + this.RunPermissionsTestQueues(policy); + this.RunPermissionsTestFiles(policy); + } + ); + } + + Task.WaitAll(tasks); + } + + [TestMethod] + [Description("Test account SAS various combinations of resource types")] + [TestCategory(ComponentCategory.Core)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AccountSASResourceTypes() + { + for (int i = 0; i < 0x8; i++) + { + SharedAccessAccountResourceTypes resourceTypes = (SharedAccessAccountResourceTypes)i; + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + policy.ResourceTypes = resourceTypes; + this.RunPermissionsTestBlobs(policy); + this.RunPermissionsTestTables(policy); + this.RunPermissionsTestQueues(policy); + this.RunPermissionsTestFiles(policy); + } + } + + public void RunBlobTest(SharedAccessAccountPolicy policy, Action testHandler, int? httpsPort) + { + CloudBlobClient blobClient = GenerateCloudBlobClient(); + string containerName = "c" + Guid.NewGuid().ToString("N"); + try + { + CloudStorageAccount account = new CloudStorageAccount(blobClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + + StorageUri storageUri = blobClient.StorageUri; + if (httpsPort != null) + { + storageUri = new StorageUri(TransformSchemeAndPort(storageUri.PrimaryUri, "https", httpsPort.Value), TransformSchemeAndPort(storageUri.SecondaryUri, "https", httpsPort.Value)); + } + + CloudStorageAccount accountWithSAS = new CloudStorageAccount(accountSAS, storageUri, null, null, null); + CloudBlobClient blobClientWithSAS = accountWithSAS.CreateCloudBlobClient(); + CloudBlobContainer containerWithSAS = blobClientWithSAS.GetContainerReference(containerName); + CloudBlobContainer container = blobClient.GetContainerReference(containerName); + container.Create(); + string blobName = "blob"; + CloudBlockBlob blob = container.GetBlockBlobReference(blobName); + string blobText = "blobText"; + blob.UploadText(blobText); + + CloudBlockBlob blobWithSAS = containerWithSAS.GetBlockBlobReference(blobName); + + testHandler(() => Assert.AreEqual(blobText, blobWithSAS.DownloadText())); + } + finally + { + blobClient.GetContainerReference(containerName).DeleteIfExists(); + } + } + + public void RunTableTest(SharedAccessAccountPolicy policy, Action testHandler, int? httpsPort) + { + CloudTableClient tableClient = GenerateCloudTableClient(); + string tableName = "t" + Guid.NewGuid().ToString("N"); + try + { + CloudStorageAccount account = new CloudStorageAccount(tableClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + + StorageUri storageUri = tableClient.StorageUri; + if (httpsPort != null) + { + storageUri = new StorageUri(TransformSchemeAndPort(storageUri.PrimaryUri, "https", httpsPort.Value), TransformSchemeAndPort(storageUri.SecondaryUri, "https", httpsPort.Value)); + } + + CloudStorageAccount accountWithSAS = new CloudStorageAccount(accountSAS, null, null, storageUri, null); + CloudTableClient tableClientWithSAS = accountWithSAS.CreateCloudTableClient(); + CloudTable tableWithSAS = tableClientWithSAS.GetTableReference(tableName); + CloudTable table = tableClient.GetTableReference(tableName); + table.Create(); + + string propName = "prop"; + int propValue = 4; + DynamicTableEntity entity1 = new DynamicTableEntity(); + + string partitionKey = "PK"; + string rowKeyPrefix = "RK"; + entity1.PartitionKey = partitionKey; + entity1.RowKey = rowKeyPrefix + "1"; + entity1.Properties = new Dictionary() { { propName, EntityProperty.GeneratePropertyForInt(propValue) } }; + + table.Execute(TableOperation.Insert(entity1)); + + TableQuery query = new TableQuery().Where(string.Format("(PartitionKey eq '{0}') and (RowKey eq '{1}')", entity1.PartitionKey, entity1.RowKey)); + + testHandler(() => Assert.AreEqual(propValue, tableWithSAS.ExecuteQuery(query).First().Properties[propName].Int32Value)); + } + finally + { + tableClient.GetTableReference(tableName).DeleteIfExists(); + } + } + + public void RunQueueTest(SharedAccessAccountPolicy policy, Action testHandler, int? httpsPort) + { + CloudQueueClient queueClient = GenerateCloudQueueClient(); + string queueName = "q" + Guid.NewGuid().ToString("N"); + try + { + CloudStorageAccount account = new CloudStorageAccount(queueClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + + StorageUri storageUri = queueClient.StorageUri; + if (httpsPort != null) + { + storageUri = new StorageUri(TransformSchemeAndPort(storageUri.PrimaryUri, "https", httpsPort.Value), TransformSchemeAndPort(storageUri.SecondaryUri, "https", httpsPort.Value)); + } + + CloudStorageAccount accountWithSAS = new CloudStorageAccount(accountSAS, null, storageUri, null, null); + CloudQueueClient queueClientWithSAS = accountWithSAS.CreateCloudQueueClient(); + CloudQueue queueWithSAS = queueClientWithSAS.GetQueueReference(queueName); + CloudQueue queue = queueClient.GetQueueReference(queueName); + queue.Create(); + string messageText = "message text"; + CloudQueueMessage message = new CloudQueueMessage(messageText); + queue.AddMessage(message); + + testHandler(() => Assert.AreEqual(messageText, queueWithSAS.GetMessage().AsString)); + } + finally + { + queueClient.GetQueueReference(queueName).DeleteIfExists(); + } + } + + public void RunFileTest(SharedAccessAccountPolicy policy, Action testHandler, int? httpsPort) + { + CloudFileClient fileClient = GenerateCloudFileClient(); + string shareName = "s" + Guid.NewGuid().ToString("N"); + try + { + CloudStorageAccount account = new CloudStorageAccount(fileClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + + StorageUri storageUri = fileClient.StorageUri; + if (httpsPort != null) + { + storageUri = new StorageUri(TransformSchemeAndPort(storageUri.PrimaryUri, "https", httpsPort.Value), TransformSchemeAndPort(storageUri.SecondaryUri, "https", httpsPort.Value)); + } + + CloudStorageAccount accountWithSAS = new CloudStorageAccount(accountSAS, null, null, null, storageUri); + CloudFileClient fileClientWithSAS = accountWithSAS.CreateCloudFileClient(); + CloudFileShare shareWithSAS = fileClientWithSAS.GetShareReference(shareName); + CloudFileShare share = fileClient.GetShareReference(shareName); + share.Create(); + string fileName = "file"; + CloudFile file = share.GetRootDirectoryReference().GetFileReference(fileName); + CloudFile fileWithSAS = shareWithSAS.GetRootDirectoryReference().GetFileReference(fileName); + byte[] content = new byte[] { 0x1, 0x2, 0x3, 0x4 }; + file.Create(content.Length); + using (Stream stream = new MemoryStream(content)) + { + file.WriteRange(stream, 0); + } + + testHandler(() => + { + byte[] result = new byte[content.Length]; + fileWithSAS.DownloadRangeToByteArray(result, 0, 0, content.Length); + for (int i = 0; i < content.Length; i++) + { + Assert.AreEqual(content[i], result[i]); + } + }); + } + finally + { + fileClient.GetShareReference(shareName).DeleteIfExists(); + } + } + + [TestMethod] + [Description("Test account SAS various combinations of services")] + [TestCategory(ComponentCategory.Core)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AccountSASServices() + { + for (int i = 0; i < 0x10; i++ ) + { + SharedAccessAccountPolicy policy = this.GetPolicyWithFullPermissions(); + policy.Services = (SharedAccessAccountServices)i; + RunBlobTest(policy, action => + { + if ((policy.Services & SharedAccessAccountServices.Blob) == SharedAccessAccountServices.Blob) + { + action(); + } + else + { + TestHelper.ExpectedException(() => action(), "Operation should have failed without Blob access."); + } + }, null); + RunTableTest(policy, action => + { + if ((policy.Services & SharedAccessAccountServices.Table) == SharedAccessAccountServices.Table) + { + action(); + } + else + { + TestHelper.ExpectedException(() => action(), "Operation should have failed without Table access."); + } + }, null); + RunQueueTest(policy, action => + { + if ((policy.Services & SharedAccessAccountServices.Queue) == SharedAccessAccountServices.Queue) + { + action(); + } + else + { + TestHelper.ExpectedException(() => action(), "Operation should have failed without Queue access."); + } + }, null); + RunFileTest(policy, action => + { + if ((policy.Services & SharedAccessAccountServices.File) == SharedAccessAccountServices.File) + { + action(); + } + else + { + TestHelper.ExpectedException(() => action(), "Operation should have failed without File access."); + } + }, null); + } + } + + [TestMethod] + [Description("Test account SAS various combinations of start and expiry times")] + [TestCategory(ComponentCategory.Core)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AccountSASStartExpiryTimes() + { + int?[] startOffsets = new int?[] { null, -5, 5 }; + int[] endOffsets = new int[] { -5, 5 }; + + foreach (int? startOffset in startOffsets) + { + foreach (int endOffset in endOffsets) + { + SharedAccessAccountPolicy policy = this.GetPolicyWithFullPermissions(); + + if (startOffset.HasValue) + { + policy.SharedAccessStartTime = DateTime.Now + TimeSpan.FromMinutes(startOffset.Value); + } + else + { + policy.SharedAccessStartTime = null; + } + + policy.SharedAccessExpiryTime = DateTime.Now + TimeSpan.FromMinutes(endOffset); + + bool pass = (!policy.SharedAccessStartTime.HasValue || (policy.SharedAccessStartTime.Value < DateTime.Now)) && (policy.SharedAccessExpiryTime > DateTime.Now); + + RunBlobTest(policy, action => + { + if (pass) + { + action(); + } + else + { + TestHelper.ExpectedException(() => action(), "Operation should have failed with invalid start/expiry times."); + } + }, null); + + RunTableTest(policy, action => + { + if (pass) + { + action(); + } + else + { + TestHelper.ExpectedException(() => action(), "Operation should have failed with invalid start/expiry times."); + } + }, null); + + RunQueueTest(policy, action => + { + if (pass) + { + action(); + } + else + { + TestHelper.ExpectedException(() => action(), "Operation should have failed with invalid start/expiry times."); + } + }, null); + + RunFileTest(policy, action => + { + if (pass) + { + action(); + } + else + { + TestHelper.ExpectedException(() => action(), "Operation should have failed with invalid start/expiry times."); + } + }, null); + } + } + } + + [TestMethod] + [Description("Test account SAS various combinations of signedIPs")] + [TestCategory(ComponentCategory.Core)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AccountSASSignedIPs() + { + IPAddress invalidIP = IPAddress.Parse("255.255.255.255"); + IPAddress myBlobIP = GetMyBlobIPAddressFromService(); + IPAddress myQueueIP = GetMyQueueIPAddressFromService(); + IPAddress myTableIP = GetMyTableIPAddressFromService(); + IPAddress myFileIP = GetMyFileIPAddressFromService(); + + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + policy.IPAddressOrRange = new IPAddressOrRange(invalidIP.ToString()); + + RunBlobTest(policy, action => TestHelper.ExpectedException(() => action(), "Operation should have failed with invalid IP access."), null); + RunTableTest(policy, action => TestHelper.ExpectedException(() => action(), "Operation should have failed with invalid IP access."), null); + RunQueueTest(policy, action => TestHelper.ExpectedException(() => action(), "Operation should have failed with invalid IP access."), null); + RunFileTest(policy, action => TestHelper.ExpectedException(() => action(), "Operation should have failed with invalid IP access."), null); + + policy.IPAddressOrRange = new IPAddressOrRange(myBlobIP.ToString()); + RunBlobTest(policy, action => action(), null); + policy.IPAddressOrRange = new IPAddressOrRange(myTableIP.ToString()); + RunTableTest(policy, action => action(), null); + policy.IPAddressOrRange = new IPAddressOrRange(myQueueIP.ToString()); + RunQueueTest(policy, action => action(), null); + policy.IPAddressOrRange = new IPAddressOrRange(myFileIP.ToString()); + RunFileTest(policy, action => action(), null); + + policy.IPAddressOrRange = new IPAddressOrRange(IPAddress.Parse("255.255.255.0").ToString(), invalidIP.ToString()); + + RunBlobTest(policy, action => TestHelper.ExpectedException(() => action(), "Operation should have failed with invalid IP access."), null); + RunTableTest(policy, action => TestHelper.ExpectedException(() => action(), "Operation should have failed with invalid IP access."), null); + RunQueueTest(policy, action => TestHelper.ExpectedException(() => action(), "Operation should have failed with invalid IP access."), null); + RunFileTest(policy, action => TestHelper.ExpectedException(() => action(), "Operation should have failed with invalid IP access."), null); + + byte[] actualBlobAddressBytes = myBlobIP.GetAddressBytes(); + byte[] initialBlobAddressBytes = actualBlobAddressBytes.ToArray(); + initialBlobAddressBytes[0]--; + byte[] finalBlobAddressBytes = actualBlobAddressBytes.ToArray(); + finalBlobAddressBytes[0]++; + policy.IPAddressOrRange = new IPAddressOrRange(new IPAddress(initialBlobAddressBytes).ToString(), new IPAddress(finalBlobAddressBytes).ToString()); + RunBlobTest(policy, action => action(), null); + + byte[] actualTableAddressBytes = myTableIP.GetAddressBytes(); + byte[] initialTableAddressBytes = actualTableAddressBytes.ToArray(); + initialTableAddressBytes[0]--; + byte[] finalTableAddressBytes = actualTableAddressBytes.ToArray(); + finalTableAddressBytes[0]++; + policy.IPAddressOrRange = new IPAddressOrRange(new IPAddress(initialTableAddressBytes).ToString(), new IPAddress(finalTableAddressBytes).ToString()); + RunTableTest(policy, action => action(), null); + + byte[] actualQueueAddressBytes = myQueueIP.GetAddressBytes(); + byte[] initialQueueAddressBytes = actualQueueAddressBytes.ToArray(); + initialQueueAddressBytes[0]--; + byte[] finalQueueAddressBytes = actualQueueAddressBytes.ToArray(); + finalQueueAddressBytes[0]++; + policy.IPAddressOrRange = new IPAddressOrRange(new IPAddress(initialQueueAddressBytes).ToString(), new IPAddress(finalQueueAddressBytes).ToString()); + RunQueueTest(policy, action => action(), null); + + byte[] actualFileAddressBytes = myFileIP.GetAddressBytes(); + byte[] initialFileAddressBytes = actualFileAddressBytes.ToArray(); + initialFileAddressBytes[0]--; + byte[] finalFileAddressBytes = actualFileAddressBytes.ToArray(); + finalFileAddressBytes[0]++; + policy.IPAddressOrRange = new IPAddressOrRange(new IPAddress(initialFileAddressBytes).ToString(), new IPAddress(finalFileAddressBytes).ToString()); + RunFileTest(policy, action => action(), null); + } + + [TestMethod] + [Description("Test account SAS various combinations of signed protocols")] + [TestCategory(ComponentCategory.Core)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AccountSASSignedProtocols() + { + int blobHttpsPort = 443; + int tableHttpsPort = 443; + int queueHttpsPort = 443; + int fileHttpsPort = 443; + + if (!string.IsNullOrEmpty(TestBase.TargetTenantConfig.BlobSecurePortOverride)) + { + blobHttpsPort = Int32.Parse(TestBase.TargetTenantConfig.BlobSecurePortOverride); + } + + if (!string.IsNullOrEmpty(TestBase.TargetTenantConfig.TableSecurePortOverride)) + { + tableHttpsPort = Int32.Parse(TestBase.TargetTenantConfig.TableSecurePortOverride); + } + + if (!string.IsNullOrEmpty(TestBase.TargetTenantConfig.QueueSecurePortOverride)) + { + queueHttpsPort = Int32.Parse(TestBase.TargetTenantConfig.QueueSecurePortOverride); + } + + if (!string.IsNullOrEmpty(TestBase.TargetTenantConfig.FileSecurePortOverride)) + { + fileHttpsPort = Int32.Parse(TestBase.TargetTenantConfig.FileSecurePortOverride); + } + + for (int i = 0; i < 3; i++) + { + SharedAccessAccountPolicy policy = this.GetPolicyWithFullPermissions(); + policy.Protocols = i == 0 ? (SharedAccessProtocol?) null : (SharedAccessProtocol)i; + + RunBlobTest(policy, action => + { + if (!policy.Protocols.HasValue || (policy.Protocols == SharedAccessProtocol.HttpsOrHttp)) + { + action(); + } + else + { + TestHelper.ExpectedException(() => action(), "Operation should have failed without using Https.", HttpStatusCode.Unused); + } + }, null); + + RunBlobTest(policy, action => + { + action(); + }, blobHttpsPort); + + RunTableTest(policy, action => + { + if (!policy.Protocols.HasValue || (policy.Protocols == SharedAccessProtocol.HttpsOrHttp)) + { + action(); + } + else + { + TestHelper.ExpectedException(() => action(), "Operation should have failed without using Https.", HttpStatusCode.Unused); + } + }, null); + + RunTableTest(policy, action => + { + action(); + }, tableHttpsPort); + + RunQueueTest(policy, action => + { + if (!policy.Protocols.HasValue || (policy.Protocols == SharedAccessProtocol.HttpsOrHttp)) + { + action(); + } + else + { + TestHelper.ExpectedException(() => action(), "Operation should have failed without using Https.", HttpStatusCode.Unused); + } + }, null); + + RunQueueTest(policy, action => + { + action(); + }, queueHttpsPort); + + RunFileTest(policy, action => + { + if (!policy.Protocols.HasValue || (policy.Protocols == SharedAccessProtocol.HttpsOrHttp)) + { + action(); + } + else + { + TestHelper.ExpectedException(() => action(), "Operation should have failed without using Https.", HttpStatusCode.Unused); + } + }, null); + + RunFileTest(policy, action => + { + action(); + }, fileHttpsPort); + } + } + + [TestMethod] + [Description("Test account SAS all parameters blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AccountSASSample() + { + CloudBlobClient blobClient = GenerateCloudBlobClient(); + string containerName = "c" + Guid.NewGuid().ToString("N"); + CloudBlobContainer container = blobClient.GetContainerReference(containerName); + try + { + container.Create(); + string blobName = "blob"; + CloudBlockBlob blockBlob = container.GetBlockBlobReference(blobName); + byte[] data = new byte[] { 0x1, 0x2, 0x3, 0x4 }; + blockBlob.UploadFromByteArray(data, 0, 4); + + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + IPAddress invalidIP = IPAddress.Parse("255.255.255.255"); + policy.IPAddressOrRange = new IPAddressOrRange(invalidIP.ToString()); + + CloudStorageAccount account = new CloudStorageAccount(blobClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = new CloudStorageAccount(accountSAS, blobClient.StorageUri, null, null, null); + CloudBlobClient blobClientWithSAS = accountWithSAS.CreateCloudBlobClient(); + CloudBlobContainer containerWithSAS = blobClientWithSAS.GetContainerReference(containerName); + CloudBlockBlob blockblobWithSAS = containerWithSAS.GetBlockBlobReference(blobName); + + byte[] target = new byte[4]; + OperationContext opContext = new OperationContext(); + IPAddress actualIP = null; + opContext.ResponseReceived += (sender, e) => + { + Stream stream = e.Response.GetResponseStream(); + stream.Seek(0, SeekOrigin.Begin); + using (StreamReader reader = new StreamReader(stream)) + { + string text = reader.ReadToEnd(); + XDocument xdocument = XDocument.Parse(text); + actualIP = IPAddress.Parse(xdocument.Descendants("SourceIP").First().Value); + } + }; + + bool exceptionThrown = false; + try + { + blockblobWithSAS.DownloadRangeToByteArray(target, 0, 0, 4, null, null, opContext); + } + catch (StorageException) + { + exceptionThrown = true; + Assert.IsNotNull(actualIP); + } + + Assert.IsTrue(exceptionThrown); + + policy.IPAddressOrRange = new IPAddressOrRange(actualIP.ToString()); + accountSASToken = account.GetSharedAccessSignature(policy); + accountSAS = new StorageCredentials(accountSASToken); + accountWithSAS = new CloudStorageAccount(accountSAS, blobClient.StorageUri, null, null, null); + blobClientWithSAS = accountWithSAS.CreateCloudBlobClient(); + containerWithSAS = blobClientWithSAS.GetContainerReference(containerName); + blockblobWithSAS = containerWithSAS.GetBlockBlobReference(blobName); + + blockblobWithSAS.DownloadRangeToByteArray(target, 0, 0, 4, null, null, null); + for (int i = 0; i < 4; i++) + { + Assert.AreEqual(data[i], target[i]); + } + Assert.IsTrue(blockblobWithSAS.StorageUri.PrimaryUri.Equals(blockBlob.Uri)); + } + finally + { + container.DeleteIfExists(); + } + } + + private IPAddress GetMyBlobIPAddressFromService() + { + CloudBlobClient blobClient = GenerateCloudBlobClient(); + string containerName = "c" + Guid.NewGuid().ToString("N"); + CloudBlobContainer container = blobClient.GetContainerReference(containerName); + try + { + container.Create(); + string blobName = "blob"; + CloudBlockBlob blockBlob = container.GetBlockBlobReference(blobName); + byte[] data = new byte[] { 0x1, 0x2, 0x3, 0x4 }; + blockBlob.UploadFromByteArray(data, 0, 4); + + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + IPAddress invalidIP = IPAddress.Parse("255.255.255.255"); + policy.IPAddressOrRange = new IPAddressOrRange(invalidIP.ToString()); + + CloudStorageAccount account = new CloudStorageAccount(blobClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = new CloudStorageAccount(accountSAS, blobClient.StorageUri, null, null, null); + CloudBlobClient blobClientWithSAS = accountWithSAS.CreateCloudBlobClient(); + CloudBlobContainer containerWithSAS = blobClientWithSAS.GetContainerReference(containerName); + CloudBlockBlob blockblobWithSAS = containerWithSAS.GetBlockBlobReference(blobName); + + byte[] target = new byte[4]; + IPAddress actualIP = null; + bool exceptionThrown = false; + try + { + blockblobWithSAS.DownloadRangeToByteArray(target, 0, 0, 4); + } + catch (StorageException e) + { + string[] parts = e.RequestInformation.HttpStatusMessage.Split(' '); + actualIP = IPAddress.Parse(parts[parts.Length - 1].Trim('.')); + exceptionThrown = true; + Assert.IsNotNull(actualIP); + } + + Assert.IsTrue(exceptionThrown); + return actualIP; + } + finally + { + container.DeleteIfExists(); + } + } + + private IPAddress GetMyQueueIPAddressFromService() + { + CloudQueueClient queueClient = GenerateCloudQueueClient(); + string queueName = "c" + Guid.NewGuid().ToString("N"); + CloudQueue queue = queueClient.GetQueueReference(queueName); + try + { + queue.Create(); + CloudQueueMessage message = new CloudQueueMessage("content"); + queue.AddMessage(message); + + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + IPAddress invalidIP = IPAddress.Parse("255.255.255.255"); + policy.IPAddressOrRange = new IPAddressOrRange(invalidIP.ToString()); + + CloudStorageAccount account = new CloudStorageAccount(queueClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = new CloudStorageAccount(accountSAS, null, queueClient.StorageUri, null, null); + CloudQueueClient queueClientWithSAS = accountWithSAS.CreateCloudQueueClient(); + CloudQueue queueWithSAS = queueClientWithSAS.GetQueueReference(queueName); + + IPAddress actualIP = null; + bool exceptionThrown = false; + try + { + queueWithSAS.GetMessage(); + } + catch (StorageException e) + { + string[] parts = e.RequestInformation.HttpStatusMessage.Split(' '); + actualIP = IPAddress.Parse(parts[parts.Length - 1].Trim('.')); + exceptionThrown = true; + Assert.IsNotNull(actualIP); + } + + Assert.IsTrue(exceptionThrown); + return actualIP; + } + finally + { + queue.DeleteIfExists(); + } + } + + private IPAddress GetMyTableIPAddressFromService() + { + CloudTableClient tableClient = GenerateCloudTableClient(); + string tableName = "c" + Guid.NewGuid().ToString("N"); + CloudTable table = tableClient.GetTableReference(tableName); + try + { + table.Create(); + + string propName = "prop"; + int propValue = 4; + DynamicTableEntity entity1 = new DynamicTableEntity(); + + string partitionKey = "PK"; + string rowKeyPrefix = "RK"; + entity1.PartitionKey = partitionKey; + entity1.RowKey = rowKeyPrefix + "1"; + entity1.Properties = new Dictionary() { { propName, EntityProperty.GeneratePropertyForInt(propValue) } }; + + table.Execute(TableOperation.Insert(entity1)); + + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + IPAddress invalidIP = IPAddress.Parse("255.255.255.255"); + policy.IPAddressOrRange = new IPAddressOrRange(invalidIP.ToString()); + + CloudStorageAccount account = new CloudStorageAccount(tableClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = new CloudStorageAccount(accountSAS, null, null, tableClient.StorageUri, null); + CloudTableClient tableClientWithSAS = accountWithSAS.CreateCloudTableClient(); + CloudTable tableWithSAS = tableClientWithSAS.GetTableReference(tableName); + + IPAddress actualIP = null; + bool exceptionThrown = false; + + try + { + TableQuery query = new TableQuery().Where(string.Format("(PartitionKey eq '{0}') and (RowKey eq '{1}')", entity1.PartitionKey, entity1.RowKey)); + tableWithSAS.ExecuteQuery(query).First(); + } + catch (StorageException e) + { + string[] parts = e.RequestInformation.HttpStatusMessage.Split(' '); + actualIP = IPAddress.Parse(parts[parts.Length - 1].Trim('.')); + exceptionThrown = true; + Assert.IsNotNull(actualIP); + } + + Assert.IsTrue(exceptionThrown); + return actualIP; + } + finally + { + table.DeleteIfExists(); + } + } + + private IPAddress GetMyFileIPAddressFromService() + { + CloudFileClient fileClient = GenerateCloudFileClient(); + string shareName = "c" + Guid.NewGuid().ToString("N"); + CloudFileShare share = fileClient.GetShareReference(shareName); + try + { + share.Create(); + string fileName = "file"; + share.GetRootDirectoryReference().CreateIfNotExists(); + CloudFile file = share.GetRootDirectoryReference().GetFileReference(fileName); + file.Create(1024); + byte[] data = new byte[] { 0x1, 0x2, 0x3, 0x4 }; + file.UploadFromByteArray(data, 0, 4); + + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + IPAddress invalidIP = IPAddress.Parse("255.255.255.255"); + policy.IPAddressOrRange = new IPAddressOrRange(invalidIP.ToString()); + + CloudStorageAccount account = new CloudStorageAccount(fileClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = new CloudStorageAccount(accountSAS, null, null, null, fileClient.StorageUri); + CloudFileClient fileClientWithSAS = accountWithSAS.CreateCloudFileClient(); + CloudFileShare shareWithSAS = fileClientWithSAS.GetShareReference(shareName); + CloudFile fileWithSAS = shareWithSAS.GetRootDirectoryReference().GetFileReference(fileName); + + byte[] target = new byte[4]; + IPAddress actualIP = null; + bool exceptionThrown = false; + try + { + fileWithSAS.DownloadRangeToByteArray(target, 0, 0, 4); + } + catch (StorageException e) + { + string[] parts = e.RequestInformation.HttpStatusMessage.Split(' '); + actualIP = IPAddress.Parse(parts[parts.Length - 1].Trim('.')); + exceptionThrown = true; + Assert.IsNotNull(actualIP); + } + + Assert.IsTrue(exceptionThrown); + return actualIP; + } + finally + { + share.DeleteIfExists(); + } + } + + private SharedAccessAccountPolicy GetPolicyWithFullPermissions() + { + SharedAccessAccountPolicy policy = new SharedAccessAccountPolicy(); + policy.SharedAccessStartTime = DateTime.Now - TimeSpan.FromMinutes(5); + policy.SharedAccessExpiryTime = DateTime.Now + TimeSpan.FromMinutes(30); + policy.Permissions = (SharedAccessAccountPermissions)(0x100 - 0x1); + policy.Services = (SharedAccessAccountServices)(0x10 - 0x1); + policy.ResourceTypes = (SharedAccessAccountResourceTypes)(0x8 - 0x1); + policy.Protocols = SharedAccessProtocol.HttpsOrHttp; + policy.IPAddressOrRange = null; + return policy; + } + + private static Uri TransformSchemeAndPort(Uri input, string scheme, int port) + { + UriBuilder builder = new UriBuilder(input); + builder.Scheme = scheme; + builder.Port = port; + return builder.Uri; + } + } +} diff --git a/Test/ClassLibraryCommon/Analytics/CloudAnalyticsClientTests.cs b/Test/ClassLibraryCommon/Analytics/CloudAnalyticsClientTests.cs index 89f63e48d..b257541d3 100644 --- a/Test/ClassLibraryCommon/Analytics/CloudAnalyticsClientTests.cs +++ b/Test/ClassLibraryCommon/Analytics/CloudAnalyticsClientTests.cs @@ -105,6 +105,11 @@ public void CloudAnalyticsClientGetMetricsTables() CloudTable tableHourSecondaryTable = analyticsClient.GetHourMetricsTable(StorageService.Table, StorageLocation.Secondary); CloudTable tableMinuteSecondaryTable = analyticsClient.GetMinuteMetricsTable(StorageService.Table, StorageLocation.Secondary); + CloudTable fileHourPrimaryTable = analyticsClient.GetHourMetricsTable(StorageService.File); + CloudTable fileMinutePrimaryTable = analyticsClient.GetMinuteMetricsTable(StorageService.File); + CloudTable fileHourSecondaryTable = analyticsClient.GetHourMetricsTable(StorageService.File, StorageLocation.Secondary); + CloudTable fileMinuteSecondaryTable = analyticsClient.GetMinuteMetricsTable(StorageService.File, StorageLocation.Secondary); + Assert.AreEqual("$MetricsCapacityBlob", capacityTable.Name); Assert.AreEqual("$MetricsHourPrimaryTransactionsBlob", blobHourPrimaryTable.Name); @@ -121,6 +126,11 @@ public void CloudAnalyticsClientGetMetricsTables() Assert.AreEqual("$MetricsMinutePrimaryTransactionsTable", tableMinutePrimaryTable.Name); Assert.AreEqual("$MetricsHourSecondaryTransactionsTable", tableHourSecondaryTable.Name); Assert.AreEqual("$MetricsMinuteSecondaryTransactionsTable", tableMinuteSecondaryTable.Name); + + Assert.AreEqual("$MetricsHourPrimaryTransactionsFile", fileHourPrimaryTable.Name); + Assert.AreEqual("$MetricsMinutePrimaryTransactionsFile", fileMinutePrimaryTable.Name); + Assert.AreEqual("$MetricsHourSecondaryTransactionsFile", fileHourSecondaryTable.Name); + Assert.AreEqual("$MetricsMinuteSecondaryTransactionsFile", fileMinuteSecondaryTable.Name); } #if SYNC diff --git a/Test/ClassLibraryCommon/Blob/SASTests.cs b/Test/ClassLibraryCommon/Blob/SASTests.cs index 57568d262..659aafb5a 100644 --- a/Test/ClassLibraryCommon/Blob/SASTests.cs +++ b/Test/ClassLibraryCommon/Blob/SASTests.cs @@ -16,6 +16,7 @@ // ----------------------------------------------------------------------------------------- using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Auth; using Microsoft.WindowsAzure.Storage.Shared.Protocol; using Microsoft.WindowsAzure.Storage.Core; @@ -24,6 +25,8 @@ using System.Net; using System.Text; using System.Threading; +using System.IO; +using System.Xml.Linq; namespace Microsoft.WindowsAzure.Storage.Blob { @@ -57,6 +60,7 @@ public void TestCleanup() private static void TestAccess(string sasToken, SharedAccessBlobPermissions permissions, SharedAccessBlobHeaders headers, CloudBlobContainer container, CloudBlob blob) { + CloudBlob SASblob; StorageCredentials credentials = string.IsNullOrEmpty(sasToken) ? new StorageCredentials() : new StorageCredentials(sasToken); @@ -66,33 +70,116 @@ private static void TestAccess(string sasToken, SharedAccessBlobPermissions perm container = new CloudBlobContainer(credentials.TransformUri(container.Uri)); if (blob.BlobType == BlobType.BlockBlob) { - blob = container.GetBlockBlobReference(blob.Name); + SASblob = container.GetBlockBlobReference(blob.Name); } else if (blob.BlobType == BlobType.PageBlob) { - blob = container.GetPageBlobReference(blob.Name); + SASblob = container.GetPageBlobReference(blob.Name); } else { - blob = container.GetAppendBlobReference(blob.Name); + SASblob = container.GetAppendBlobReference(blob.Name); } } else { if (blob.BlobType == BlobType.BlockBlob) { - blob = new CloudBlockBlob(credentials.TransformUri(blob.Uri)); + SASblob = new CloudBlockBlob(credentials.TransformUri(blob.Uri)); } else if (blob.BlobType == BlobType.PageBlob) { - blob = new CloudPageBlob(credentials.TransformUri(blob.Uri)); + SASblob = new CloudPageBlob(credentials.TransformUri(blob.Uri)); } else { - blob = new CloudAppendBlob(credentials.TransformUri(blob.Uri)); + SASblob = new CloudAppendBlob(credentials.TransformUri(blob.Uri)); } } + // We want to ensure that 'create', 'add', and 'write' permissions all allow for correct writing of blobs, as is reasonable. + if (((permissions & SharedAccessBlobPermissions.Create) == SharedAccessBlobPermissions.Create) || ((permissions & SharedAccessBlobPermissions.Write) == SharedAccessBlobPermissions.Write)) + { + if (blob.BlobType == BlobType.PageBlob) + { + CloudPageBlob SASpageBlob = (CloudPageBlob)SASblob; + SASpageBlob.Create(512); + CloudPageBlob pageBlob = (CloudPageBlob)blob; + byte[] buffer = new byte[512]; + buffer[0] = 2; // random data + + if (((permissions & SharedAccessBlobPermissions.Write) == SharedAccessBlobPermissions.Write)) + { + SASpageBlob.UploadFromByteArray(buffer, 0, 512); + } + else + { + TestHelper.ExpectedException( + () => SASpageBlob.UploadFromByteArray(buffer, 0, 512), + "pageBlob SAS token without Write perms should not allow for writing/adding", + HttpStatusCode.Forbidden); + pageBlob.UploadFromByteArray(buffer, 0, 512); + } + } + else if (blob.BlobType == BlobType.BlockBlob) + { + if ((permissions & SharedAccessBlobPermissions.Write) == SharedAccessBlobPermissions.Write) + { + UploadText(SASblob, "blob", Encoding.UTF8); + } + else + { + TestHelper.ExpectedException( + () => UploadText(SASblob, "blob", Encoding.UTF8), + "Block blob SAS token without Write or perms should not allow for writing", + HttpStatusCode.Forbidden); + UploadText(blob, "blob", Encoding.UTF8); + } + } + else // append blob + { + // If the sas token contains Feb 2012, append won't be accepted + if (sasToken.Contains(Constants.VersionConstants.February2012)) + { + UploadText(blob, "blob", Encoding.UTF8); + } + else + { + CloudAppendBlob SASAppendBlob = SASblob as CloudAppendBlob; + SASAppendBlob.CreateOrReplace(); + + byte[] textAsBytes = Encoding.UTF8.GetBytes("blob"); + using (MemoryStream stream = new MemoryStream()) + { + stream.Write(textAsBytes, 0, textAsBytes.Length); + stream.Seek(0, SeekOrigin.Begin); + + if (((permissions & SharedAccessBlobPermissions.Add) == SharedAccessBlobPermissions.Add) || ((permissions & SharedAccessBlobPermissions.Write) == SharedAccessBlobPermissions.Write)) + { + SASAppendBlob.AppendBlock(stream, null); + } + else + { + TestHelper.ExpectedException( + () => SASAppendBlob.AppendBlock(stream, null), + "Append blob SAS token without Write or Add perms should not allow for writing/adding", + HttpStatusCode.Forbidden); + stream.Seek(0, SeekOrigin.Begin); + ((CloudAppendBlob)blob).AppendBlock(stream, null); + } + } + } + } + } + else + { + TestHelper.ExpectedException( + () => UploadText(SASblob, "blob", Encoding.UTF8), + "UploadText SAS does not allow for writing/adding", + ((blob.BlobType == BlobType.AppendBlob) && (sasToken != null) && (sasToken.Contains(Constants.VersionConstants.February2012))) ? HttpStatusCode.BadRequest : HttpStatusCode.Forbidden); + UploadText(blob, "blob", Encoding.UTF8); + } + if (container != null) { if ((permissions & SharedAccessBlobPermissions.List) == SharedAccessBlobPermissions.List) @@ -104,88 +191,79 @@ private static void TestAccess(string sasToken, SharedAccessBlobPermissions perm TestHelper.ExpectedException( () => container.ListBlobs().ToArray(), "List blobs while SAS does not allow for listing", - HttpStatusCode.NotFound); + HttpStatusCode.Forbidden); } } - if ((permissions & SharedAccessBlobPermissions.Read) == SharedAccessBlobPermissions.Read) + // need to have written to the blob to read from it. + if (((permissions & SharedAccessBlobPermissions.Read) == SharedAccessBlobPermissions.Read)) { - blob.FetchAttributes(); + SASblob.FetchAttributes(); // Test headers if (headers != null) { if (headers.CacheControl != null) { - Assert.AreEqual(headers.CacheControl, blob.Properties.CacheControl); + Assert.AreEqual(headers.CacheControl, SASblob.Properties.CacheControl); } if (headers.ContentDisposition != null) { - Assert.AreEqual(headers.ContentDisposition, blob.Properties.ContentDisposition); + Assert.AreEqual(headers.ContentDisposition, SASblob.Properties.ContentDisposition); } if (headers.ContentEncoding != null) { - Assert.AreEqual(headers.ContentEncoding, blob.Properties.ContentEncoding); + Assert.AreEqual(headers.ContentEncoding, SASblob.Properties.ContentEncoding); } if (headers.ContentLanguage != null) { - Assert.AreEqual(headers.ContentLanguage, blob.Properties.ContentLanguage); + Assert.AreEqual(headers.ContentLanguage, SASblob.Properties.ContentLanguage); } if (headers.ContentType != null) { - Assert.AreEqual(headers.ContentType, blob.Properties.ContentType); + Assert.AreEqual(headers.ContentType, SASblob.Properties.ContentType); } } } else { TestHelper.ExpectedException( - () => blob.FetchAttributes(), + () => SASblob.FetchAttributes(), "Fetch blob attributes while SAS does not allow for reading", - HttpStatusCode.NotFound); + HttpStatusCode.Forbidden); } if ((permissions & SharedAccessBlobPermissions.Write) == SharedAccessBlobPermissions.Write) { - blob.SetMetadata(); + SASblob.SetMetadata(); } else { TestHelper.ExpectedException( - () => blob.SetMetadata(), + () => SASblob.SetMetadata(), "Set blob metadata while SAS does not allow for writing", - HttpStatusCode.NotFound); + HttpStatusCode.Forbidden); } if ((permissions & SharedAccessBlobPermissions.Delete) == SharedAccessBlobPermissions.Delete) { - blob.Delete(); + SASblob.Delete(); } else { TestHelper.ExpectedException( - () => blob.Delete(), + () => SASblob.Delete(), "Delete blob while SAS does not allow for deleting", - HttpStatusCode.NotFound); + HttpStatusCode.Forbidden); } } -#pragma warning disable 0618 private static void TestBlobSAS(CloudBlob testBlob, SharedAccessBlobPermissions permissions, SharedAccessBlobHeaders headers) { - TestBlobSAS(testBlob, permissions, headers, null); - } -#pragma warning restore 0618 - - [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] - private static void TestBlobSAS(CloudBlob testBlob, SharedAccessBlobPermissions permissions, SharedAccessBlobHeaders headers, string sasVersion) - { - UploadText(testBlob, "blob", Encoding.UTF8); - SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy() { SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), @@ -193,7 +271,7 @@ private static void TestBlobSAS(CloudBlob testBlob, SharedAccessBlobPermissions Permissions = permissions, }; - string sasToken = testBlob.GetSharedAccessSignature(policy, headers, null, sasVersion); + string sasToken = testBlob.GetSharedAccessSignature(policy, headers, null); TestAccess(sasToken, permissions, headers, null, testBlob); } @@ -215,7 +293,6 @@ public void CloudBlobContainerUpdateSASToken() string sasToken = this.testContainer.GetSharedAccessSignature(policy); //Thread.Sleep(35000); CloudBlockBlob testBlockBlob = this.testContainer.GetBlockBlobReference("blockblob"); - UploadText(testBlockBlob, "blob", Encoding.UTF8); TestAccess(sasToken, SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.Write, null, this.testContainer, testBlockBlob); StorageCredentials creds = new StorageCredentials(sasToken); @@ -237,10 +314,9 @@ public void CloudBlobContainerUpdateSASToken() TestHelper.ExpectedException( () => UploadText(blob, "blob", Encoding.UTF8), "Writing to a blob while SAS does not allow for writing", - HttpStatusCode.NotFound); + HttpStatusCode.Forbidden); CloudPageBlob testPageBlob = this.testContainer.GetPageBlobReference("pageblob"); - testPageBlob.Create(0); TestAccess(sasToken2, SharedAccessBlobPermissions.Read, null, this.testContainer, testPageBlob); } @@ -252,7 +328,7 @@ public void CloudBlobContainerUpdateSASToken() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudBlobContainerSASCombinations() { - for (int i = 1; i < 16; i++) + for (int i = 1; i < 0x40; i++) { SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)i; SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy() @@ -264,15 +340,12 @@ public void CloudBlobContainerSASCombinations() string sasToken = this.testContainer.GetSharedAccessSignature(policy); CloudBlockBlob testBlockBlob = this.testContainer.GetBlockBlobReference("blockblob" + i); - UploadText(testBlockBlob, "blob", Encoding.UTF8); SASTests.TestAccess(sasToken, permissions, null, this.testContainer, testBlockBlob); CloudPageBlob testPageBlob = this.testContainer.GetPageBlobReference("pageblob" + i); - UploadText(testPageBlob, "blob", Encoding.UTF8); SASTests.TestAccess(sasToken, permissions, null, this.testContainer, testPageBlob); CloudAppendBlob testAppendBlob = this.testContainer.GetAppendBlobReference("appendblob" + i); - UploadText(testAppendBlob, "blob", Encoding.UTF8); SASTests.TestAccess(sasToken, permissions, null, this.testContainer, testAppendBlob); } } @@ -286,13 +359,10 @@ public void CloudBlobContainerSASCombinations() public void CloudBlobContainerPublicAccess() { CloudBlockBlob testBlockBlob = this.testContainer.GetBlockBlobReference("blockblob"); - UploadText(testBlockBlob, "blob", Encoding.UTF8); CloudPageBlob testPageBlob = this.testContainer.GetPageBlobReference("pageblob"); - UploadText(testPageBlob, "blob", Encoding.UTF8); CloudAppendBlob testAppendBlob = this.testContainer.GetAppendBlobReference("appendblob"); - UploadText(testAppendBlob, "blob", Encoding.UTF8); BlobContainerPermissions permissions = new BlobContainerPermissions(); @@ -346,13 +416,10 @@ public void CloudBlobCreateClientWithAnonymousCreds() public void CloudBlobContainerPolicy() { CloudBlockBlob testBlockBlob = this.testContainer.GetBlockBlobReference("blockblob"); - UploadText(testBlockBlob, "blob", Encoding.UTF8); CloudPageBlob testPageBlob = this.testContainer.GetPageBlobReference("pageblob"); - UploadText(testPageBlob, "blob", Encoding.UTF8); CloudAppendBlob testAppendBlob = this.testContainer.GetAppendBlobReference("appendblob"); - UploadText(testAppendBlob, "blob", Encoding.UTF8); SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy() { @@ -389,7 +456,7 @@ public void CloudBlobContainerPolicy() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudBlockBlobSASCombinations() { - for (int i = 1; i < 8; i++) + for (int i = 1; i < 0x40; i++) { CloudBlockBlob testBlob = this.testContainer.GetBlockBlobReference("blob" + i); SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)i; @@ -405,7 +472,7 @@ public void CloudBlockBlobSASCombinations() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudPageBlobSASCombinations() { - for (int i = 1; i < 8; i++) + for (int i = 1; i < 0x40; i++) { CloudPageBlob testBlob = this.testContainer.GetPageBlobReference("blob" + i); SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)i; @@ -421,7 +488,7 @@ public void CloudPageBlobSASCombinations() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudAppendBlobSASCombinations() { - for (int i = 1; i < 8; i++) + for (int i = 1; i < 0x40; i++) { CloudAppendBlob testBlob = this.testContainer.GetAppendBlobReference("blob" + i); SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)i; @@ -437,7 +504,7 @@ public void CloudAppendBlobSASCombinations() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudBlockBlobSASHeaders() { - for (int i = 1; i < 8; i++) + for (int i = 1; i < 0x40; i++) { CloudBlockBlob testBlob = this.testContainer.GetBlockBlobReference("blob" + i); SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)i; @@ -462,7 +529,7 @@ public void CloudBlockBlobSASHeaders() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudPageBlobSASHeaders() { - for (int i = 1; i < 8; i++) + for (int i = 1; i < 0x40; i++) { CloudPageBlob testBlob = this.testContainer.GetPageBlobReference("blob" + i); SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)i; @@ -487,7 +554,7 @@ public void CloudPageBlobSASHeaders() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudAppendBlobSASHeaders() { - for (int i = 1; i < 8; i++) + for (int i = 1; i < 0x40; i++) { CloudAppendBlob testBlob = this.testContainer.GetAppendBlobReference("blob" + i); SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)i; @@ -503,200 +570,7 @@ public void CloudAppendBlobSASHeaders() TestBlobSAS(testBlob, permissions, headers); } } - - [TestMethod] - [Description("Test access of container with generation of 2012-02-12 SAS token.")] - [TestCategory(ComponentCategory.Blob)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] - public void CloudBlobContainer20120212SASVersion() - { - // Create a policy with read/write access and get SAS. - SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy() - { - SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), - SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), - Permissions = SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.List, - }; - - string sasToken = this.testContainer.GetSharedAccessSignature(policy, null, Constants.VersionConstants.February2012); - CloudBlockBlob testBlockBlob = this.testContainer.GetBlockBlobReference("blockblob"); - UploadText(testBlockBlob, "blob", Encoding.UTF8); - TestAccess(sasToken, SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.List, null, this.testContainer, testBlockBlob); - } - - [TestMethod] - [Description("Test all combinations of permissions using generation of 2012-02-12 SAS tokens for block blobs.")] - [TestCategory(ComponentCategory.Blob)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] - public void CloudBlockBlob20120212SASVersion() - { - for (int i = 1; i < 8; i++) - { - CloudBlockBlob testBlob = this.testContainer.GetBlockBlobReference("blob" + i); - SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)i; - TestBlobSAS(testBlob, permissions, null, Constants.VersionConstants.February2012); - } - } - - [TestMethod] - [Description("Test all combinations of permissions using generation of 2012-02-12 SAS tokens for page blobs.")] - [TestCategory(ComponentCategory.Blob)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] - public void CloudPageBlob20120212SASVersion() - { - for (int i = 1; i < 8; i++) - { - CloudPageBlob testBlob = this.testContainer.GetPageBlobReference("blob" + i); - SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)i; - TestBlobSAS(testBlob, permissions, null, Constants.VersionConstants.February2012); - } - } - - [TestMethod] - [Description("Test all combinations of permissions using generation of 2012-02-12 SAS tokens for append blobs.")] - [TestCategory(ComponentCategory.Blob)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] - public void CloudAppendBlob20120212SASVersion() - { - for (int i = 1; i < 8; i++) - { - CloudAppendBlob testBlob = this.testContainer.GetAppendBlobReference("blob" + i); - SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)i; - TestBlobSAS(testBlob, permissions, null, Constants.VersionConstants.February2012); - } - } - - [TestMethod] - [Description("Test invalid SAS Version for page blobs.")] - [TestCategory(ComponentCategory.Blob)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] - public void CloudPageBlobInvalidSASVersion() - { - try - { - CloudPageBlob testBlob = this.testContainer.GetPageBlobReference("blob" + 1); - SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)1; - TestBlobSAS(testBlob, permissions, null, "2012-02-29"); - Assert.Fail(); - } - catch (ArgumentException e) - { - Assert.AreEqual(SR.InvalidSASVersion, e.Message); - } - } - - [TestMethod] - [Description("Test invalid SAS Version for block blobs.")] - [TestCategory(ComponentCategory.Blob)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] - public void CloudBlockBlobInvalidSASVersion() - { - try - { - CloudBlockBlob testBlob = this.testContainer.GetBlockBlobReference("blob" + 1); - SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)1; - TestBlobSAS(testBlob, permissions, null, "2012-02-29"); - Assert.Fail(); - } - catch (ArgumentException e) - { - Assert.AreEqual(SR.InvalidSASVersion, e.Message); - } - } - - [TestMethod] - [Description("Test invalid SAS Version for append blobs.")] - [TestCategory(ComponentCategory.Blob)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] - public void CloudAppendBlobInvalidSASVersion() - { - try - { - CloudAppendBlob testBlob = this.testContainer.GetAppendBlobReference("blob" + 1); - SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)1; - TestBlobSAS(testBlob, permissions, null, "2012-02-29"); - Assert.Fail(); - } - catch (ArgumentException e) - { - Assert.AreEqual(SR.InvalidSASVersion, e.Message); - } - } - - [TestMethod] - [Description("Negative test for empty SAS Version.")] - [TestCategory(ComponentCategory.Blob)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] - public void CloudPageBlobEmptySASVersion() - { - try - { - CloudPageBlob testBlob = this.testContainer.GetPageBlobReference("blob" + 1); - SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)1; - TestBlobSAS(testBlob, permissions, null, ""); - Assert.Fail(); - } - catch (ArgumentException e) - { - Assert.AreEqual(SR.InvalidSASVersion, e.Message); - } - } - - [TestMethod] - [Description("Test 2012-02-12 SAS Version with header values. Should fail.")] - [TestCategory(ComponentCategory.Blob)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] - public void CloudPageBlobHeaders20120212SASVersion() - { - SharedAccessBlobHeaders headers = new SharedAccessBlobHeaders() - { - CacheControl = "no-transform", - ContentDisposition = "attachment", - ContentEncoding = "gzip", - ContentLanguage = "tr,en", - ContentType = "text/html" - }; - - try - { - CloudPageBlob testBlob = this.testContainer.GetPageBlobReference("blob" + 1); - SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)1; - TestBlobSAS(testBlob, permissions, headers, Constants.VersionConstants.February2012); - Assert.Fail(); - } - catch (ArgumentException e) - { - Assert.AreEqual(SR.InvalidHeaders, e.Message); - } - } - + [TestMethod] [Description("Perform a SAS request and ensure that the api-version query param exists and the x-ms-version header does not.")] [TestCategory(ComponentCategory.Blob)] @@ -776,5 +650,230 @@ public void CloudBlobSASApiVersionQueryParam() container.DeleteIfExists(); } } + + /// + /// Helper function for testing the IPAddressOrRange funcitonality for blobs + /// + /// Function that generates an initial IPAddressOrRange object to use. This is expected to fail on the service. + /// Function that takes in the correct IP address (according to the service) and returns the IPAddressOrRange object + /// that should be accepted by the service + public void CloudBlobSASIPAddressHelper(Func generateInitialIPAddressOrRange, Func generateFinalIPAddressOrRange) + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + CloudBlob blob; + SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy() + { + Permissions = SharedAccessBlobPermissions.Read, + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + }; + + CloudBlockBlob blockBlob = container.GetBlockBlobReference("bb"); + byte[] data = new byte[] { 0x1, 0x2, 0x3, 0x4 }; + blockBlob.UploadFromByteArray(data, 0, 4); + + // The plan then is to use an incorrect IP address to make a call to the service + // ensure that we get an error message + // parse the error message to get my actual IP (as far as the service sees) + // then finally test the success case to ensure we can actually make requests + + IPAddressOrRange ipAddressOrRange = generateInitialIPAddressOrRange(); + string blockBlobToken = blockBlob.GetSharedAccessSignature(policy, null, null, null, ipAddressOrRange); + StorageCredentials blockBlobSAS = new StorageCredentials(blockBlobToken); + Uri blockBlobSASUri = blockBlobSAS.TransformUri(blockBlob.Uri); + StorageUri blockBlobSASStorageUri = blockBlobSAS.TransformUri(blockBlob.StorageUri); + + blob = new CloudBlob(blockBlobSASUri); + byte[] target = new byte[4]; + OperationContext opContext = new OperationContext(); + IPAddress actualIP = null; + opContext.ResponseReceived += (sender, e) => + { + Stream stream = e.Response.GetResponseStream(); + stream.Seek(0, SeekOrigin.Begin); + using (StreamReader reader = new StreamReader(stream)) + { + string text = reader.ReadToEnd(); + XDocument xdocument = XDocument.Parse(text); + actualIP = IPAddress.Parse(xdocument.Descendants("SourceIP").First().Value); + } + }; + + bool exceptionThrown = false; + try + { + blob.DownloadRangeToByteArray(target, 0, 0, 4, null, null, opContext); + } + catch (StorageException) + { + exceptionThrown = true; + Assert.IsNotNull(actualIP); + } + + Assert.IsTrue(exceptionThrown); + ipAddressOrRange = generateFinalIPAddressOrRange(actualIP); + blockBlobToken = blockBlob.GetSharedAccessSignature(policy, null, null, null, ipAddressOrRange); + blockBlobSAS = new StorageCredentials(blockBlobToken); + blockBlobSASUri = blockBlobSAS.TransformUri(blockBlob.Uri); + blockBlobSASStorageUri = blockBlobSAS.TransformUri(blockBlob.StorageUri); + + + blob = new CloudBlob(blockBlobSASUri); + blob.DownloadRangeToByteArray(target, 0, 0, 4, null, null, null); + for (int i = 0; i < 4; i++) + { + Assert.AreEqual(data[i], target[i]); + } + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(blockBlob.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + + blob = new CloudBlob(blockBlobSASStorageUri, null, null); + blob.DownloadRangeToByteArray(target, 0, 0, 4, null, null, null); + for (int i = 0; i < 4; i++) + { + Assert.AreEqual(data[i], target[i]); + } + Assert.IsTrue(blob.StorageUri.Equals(blockBlob.StorageUri)); + + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Perform a SAS request specifying an IP address or range and ensure that everything works properly.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudBlobSASIPAddressQueryParam() + { + CloudBlobSASIPAddressHelper(() => + { + // We need an IP address that will never be a valid source + IPAddress invalidIP = IPAddress.Parse("255.255.255.255"); + return new IPAddressOrRange(invalidIP.ToString()); + }, + (IPAddress actualIP) => + { + return new IPAddressOrRange(actualIP.ToString()); + }); + } + + [TestMethod] + [Description("Perform a SAS request specifying an IP address or range and ensure that everything works properly.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudBlobSASIPRangeQueryParam() + { + CloudBlobSASIPAddressHelper(() => + { + // We need an IP address that will never be a valid source + IPAddress invalidIPBegin = IPAddress.Parse("255.255.255.0"); + IPAddress invalidIPEnd = IPAddress.Parse("255.255.255.255"); + + return new IPAddressOrRange(invalidIPBegin.ToString(), invalidIPEnd.ToString()); + }, + (IPAddress actualIP) => + { + byte[] actualAddressBytes = actualIP.GetAddressBytes(); + byte[] initialAddressBytes = actualAddressBytes.ToArray(); + initialAddressBytes[0]--; + byte[] finalAddressBytes = actualAddressBytes.ToArray(); + finalAddressBytes[0]++; + + return new IPAddressOrRange(new IPAddress(initialAddressBytes).ToString(), new IPAddress(finalAddressBytes).ToString()); + }); + } + + [TestMethod] + [Description("Perform a SAS request specifying a shared protocol and ensure that everything works properly.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudBlobSASSharedProtocolsQueryParam() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + CloudBlob blob; + SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy() + { + Permissions = SharedAccessBlobPermissions.Read, + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + }; + + CloudBlockBlob blockBlob = container.GetBlockBlobReference("bb"); + blockBlob.PutBlockList(new string[] { }); + + foreach (SharedAccessProtocol? protocol in new SharedAccessProtocol?[] { null, SharedAccessProtocol.HttpsOrHttp, SharedAccessProtocol.HttpsOnly }) + { + string blockBlobToken = blockBlob.GetSharedAccessSignature(policy, null, null, protocol, null); + StorageCredentials blockBlobSAS = new StorageCredentials(blockBlobToken); + Uri blockBlobSASUri = new Uri(blockBlob.Uri + blockBlobSAS.SASToken); + StorageUri blockBlobSASStorageUri = new StorageUri(new Uri(blockBlob.StorageUri.PrimaryUri + blockBlobSAS.SASToken), new Uri(blockBlob.StorageUri.SecondaryUri + blockBlobSAS.SASToken)); + + int httpPort = blockBlobSASUri.Port; + int securePort = 443; + + if (!string.IsNullOrEmpty(TestBase.TargetTenantConfig.BlobSecurePortOverride)) + { + securePort = Int32.Parse(TestBase.TargetTenantConfig.BlobSecurePortOverride); + } + + var schemesAndPorts = new[] { + new { scheme = Uri.UriSchemeHttp, port = httpPort}, + new { scheme = Uri.UriSchemeHttps, port = securePort} + }; + + foreach (var item in schemesAndPorts) + { + blockBlobSASUri = TransformSchemeAndPort(blockBlobSASUri, item.scheme, item.port); + blockBlobSASStorageUri = new StorageUri(TransformSchemeAndPort(blockBlobSASStorageUri.PrimaryUri, item.scheme, item.port), TransformSchemeAndPort(blockBlobSASStorageUri.SecondaryUri, item.scheme, item.port)); + + if (protocol.HasValue && protocol.Value == SharedAccessProtocol.HttpsOnly && string.CompareOrdinal(item.scheme, Uri.UriSchemeHttp) == 0) + { + blob = new CloudBlob(blockBlobSASUri); + TestHelper.ExpectedException(() => blob.FetchAttributes(), "Access a blob using SAS with a shared protocols that does not match", HttpStatusCode.Unused); + + blob = new CloudBlob(blockBlobSASStorageUri, null, null); + TestHelper.ExpectedException(() => blob.FetchAttributes(), "Access a blob using SAS with a shared protocols that does not match", HttpStatusCode.Unused); + } + else + { + blob = new CloudBlob(blockBlobSASUri); + blob.FetchAttributes(); + Assert.AreEqual(blob.BlobType, BlobType.BlockBlob); + + blob = new CloudBlob(blockBlobSASStorageUri, null, null); + blob.FetchAttributes(); + Assert.AreEqual(blob.BlobType, BlobType.BlockBlob); + } + } + } + } + finally + { + container.DeleteIfExists(); + } + } + + private static Uri TransformSchemeAndPort(Uri input, string scheme, int port) + { + UriBuilder builder = new UriBuilder(input); + builder.Scheme = scheme; + builder.Port = port; + return builder.Uri; + } } } diff --git a/Test/ClassLibraryCommon/File/FileAnalyticsUnitTests.cs b/Test/ClassLibraryCommon/File/FileAnalyticsUnitTests.cs index 5336e6259..3ea16e0d5 100644 --- a/Test/ClassLibraryCommon/File/FileAnalyticsUnitTests.cs +++ b/Test/ClassLibraryCommon/File/FileAnalyticsUnitTests.cs @@ -87,6 +87,8 @@ public void MyTestCleanup() { Assert.AreEqual(0, TestBase.FileBufferManager.OutstandingBufferCount); } + + client.SetServiceProperties(startProperties); } #endregion @@ -127,6 +129,12 @@ public void CloudFileTestAnalyticsRoundTripSync() } }); + props.MinuteMetrics.MetricsLevel = MetricsLevel.Service; + props.MinuteMetrics.RetentionDays = 8; + + props.HourMetrics.MetricsLevel = MetricsLevel.None; + props.HourMetrics.RetentionDays = 1; + client.SetServiceProperties(props); TestHelper.AssertFileServicePropertiesAreEqual(props, client.GetServiceProperties()); @@ -168,6 +176,12 @@ public void CloudFileTestAnalyticsRoundTripAPM() } }); + props.MinuteMetrics.MetricsLevel = MetricsLevel.Service; + props.MinuteMetrics.RetentionDays = 8; + + props.HourMetrics.MetricsLevel = MetricsLevel.None; + props.HourMetrics.RetentionDays = 1; + using (ManualResetEvent evt = new ManualResetEvent(false)) { IAsyncResult result = null; @@ -520,6 +534,8 @@ private void TestCorsRules(CloudFileClient client, IList corsProps) private static FileServiceProperties DefaultServiceProperties() { FileServiceProperties props = new FileServiceProperties(); + props.MinuteMetrics.Version = Constants.AnalyticsConstants.MetricsVersionV1; + props.HourMetrics.Version = Constants.AnalyticsConstants.MetricsVersionV1; props.Cors.CorsRules = new List(); diff --git a/Test/ClassLibraryCommon/File/FileSASTests.cs b/Test/ClassLibraryCommon/File/FileSASTests.cs index 8f1ba215e..d97683b99 100644 --- a/Test/ClassLibraryCommon/File/FileSASTests.cs +++ b/Test/ClassLibraryCommon/File/FileSASTests.cs @@ -15,6 +15,7 @@ // ----------------------------------------------------------------------------------------- using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Auth; using Microsoft.WindowsAzure.Storage.Shared.Protocol; using Microsoft.WindowsAzure.Storage.Core; @@ -23,6 +24,8 @@ using System.Net; using System.Text; using System.Threading; +using System.Xml.Linq; +using System.IO; namespace Microsoft.WindowsAzure.Storage.File { @@ -56,105 +59,133 @@ public void TestCleanup() private static void TestAccess(string sasToken, SharedAccessFilePermissions permissions, SharedAccessFileHeaders headers, CloudFileShare share, CloudFile file) { + CloudFileShare SASshare = null; + CloudFile SASfile; StorageCredentials credentials = string.IsNullOrEmpty(sasToken) ? new StorageCredentials() : new StorageCredentials(sasToken); + string fileText = "file"; if (share != null) { - share = new CloudFileShare(credentials.TransformUri(share.Uri)); - file = share.GetRootDirectoryReference().GetFileReference(file.Name); + SASshare = new CloudFileShare(credentials.TransformUri(share.Uri)); + SASfile = SASshare.GetRootDirectoryReference().GetFileReference(file.Name); } else { - file = new CloudFile(credentials.TransformUri(file.Uri)); + SASfile = new CloudFile(credentials.TransformUri(file.Uri)); } - if (share != null) + if ((permissions & SharedAccessFilePermissions.Write) == SharedAccessFilePermissions.Write) + { + UploadText(SASfile, fileText, Encoding.UTF8); + } + else if ((permissions & SharedAccessFilePermissions.Create) == SharedAccessFilePermissions.Create) + { + SASfile.Create(Encoding.UTF8.GetBytes(fileText).Length); + TestHelper.ExpectedException( + () => UploadText(SASfile, fileText, Encoding.UTF8), + "UploadText SAS does not allow for writing", + HttpStatusCode.Forbidden); + UploadText(file, fileText, Encoding.UTF8); + + } + else + { + TestHelper.ExpectedException( + () => SASfile.Create(Encoding.UTF8.GetBytes(fileText).Length), + "Create file succeeded but SAS does not allow for writing/creating", + HttpStatusCode.Forbidden); + TestHelper.ExpectedException( + () => UploadText(SASfile, fileText, Encoding.UTF8), + "UploadText SAS does not allow for writing/creating", + HttpStatusCode.Forbidden); + UploadText(file, fileText, Encoding.UTF8); + } + + if (SASshare != null) { if ((permissions & SharedAccessFilePermissions.List) == SharedAccessFilePermissions.List) { - share.GetRootDirectoryReference().ListFilesAndDirectories().ToArray(); + SASshare.GetRootDirectoryReference().ListFilesAndDirectories().ToArray(); } else { TestHelper.ExpectedException( - () => share.GetRootDirectoryReference().ListFilesAndDirectories().ToArray(), + () => SASshare.GetRootDirectoryReference().ListFilesAndDirectories().ToArray(), "List files while SAS does not allow for listing", - HttpStatusCode.NotFound); + HttpStatusCode.Forbidden); } } if ((permissions & SharedAccessFilePermissions.Read) == SharedAccessFilePermissions.Read) { - file.FetchAttributes(); + SASfile.FetchAttributes(); // Test headers if (headers != null) { if (headers.CacheControl != null) { - Assert.AreEqual(headers.CacheControl, file.Properties.CacheControl); + Assert.AreEqual(headers.CacheControl, SASfile.Properties.CacheControl); } if (headers.ContentDisposition != null) { - Assert.AreEqual(headers.ContentDisposition, file.Properties.ContentDisposition); + Assert.AreEqual(headers.ContentDisposition, SASfile.Properties.ContentDisposition); } if (headers.ContentEncoding != null) { - Assert.AreEqual(headers.ContentEncoding, file.Properties.ContentEncoding); + Assert.AreEqual(headers.ContentEncoding, SASfile.Properties.ContentEncoding); } if (headers.ContentLanguage != null) { - Assert.AreEqual(headers.ContentLanguage, file.Properties.ContentLanguage); + Assert.AreEqual(headers.ContentLanguage, SASfile.Properties.ContentLanguage); } if (headers.ContentType != null) { - Assert.AreEqual(headers.ContentType, file.Properties.ContentType); + Assert.AreEqual(headers.ContentType, SASfile.Properties.ContentType); } } } else { TestHelper.ExpectedException( - () => file.FetchAttributes(), + () => SASfile.FetchAttributes(), "Fetch file attributes while SAS does not allow for reading", - HttpStatusCode.NotFound); + HttpStatusCode.Forbidden); } if ((permissions & SharedAccessFilePermissions.Write) == SharedAccessFilePermissions.Write) { - file.SetMetadata(); + SASfile.SetMetadata(); } else { TestHelper.ExpectedException( - () => file.SetMetadata(), + () => SASfile.SetMetadata(), "Set file metadata while SAS does not allow for writing", - HttpStatusCode.NotFound); + HttpStatusCode.Forbidden); } if ((permissions & SharedAccessFilePermissions.Delete) == SharedAccessFilePermissions.Delete) { - file.Delete(); + SASfile.Delete(); } else { TestHelper.ExpectedException( - () => file.Delete(), + () => SASfile.Delete(), "Delete file while SAS does not allow for deleting", - HttpStatusCode.NotFound); + HttpStatusCode.Forbidden); } } private static void TestFileSAS(CloudFile testFile, SharedAccessFilePermissions permissions, SharedAccessFileHeaders headers) { - UploadText(testFile, "file", Encoding.UTF8); - SharedAccessFilePolicy policy = new SharedAccessFilePolicy() { SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), @@ -206,7 +237,7 @@ public void CloudFileShareUpdateSASToken() TestHelper.ExpectedException( () => UploadText(testFile2, "file", Encoding.UTF8), "Writing to a file while SAS does not allow for writing", - HttpStatusCode.NotFound); + HttpStatusCode.Forbidden); CloudFile testFile3 = this.testShare.GetRootDirectoryReference().GetFileReference("file3"); testFile3.Create(0); @@ -221,7 +252,7 @@ public void CloudFileShareUpdateSASToken() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudFileShareSASCombinations() { - for (int i = 1; i < 16; i++) + for (int i = 1; i < 0x20; i++) { SharedAccessFilePermissions permissions = (SharedAccessFilePermissions)i; SharedAccessFilePolicy policy = new SharedAccessFilePolicy() @@ -233,7 +264,6 @@ public void CloudFileShareSASCombinations() string sasToken = this.testShare.GetSharedAccessSignature(policy); CloudFile testFile = this.testShare.GetRootDirectoryReference().GetFileReference("file" + i); - UploadText(testFile, "file", Encoding.UTF8); FileSASTests.TestAccess(sasToken, permissions, null, this.testShare, testFile); } } @@ -246,7 +276,7 @@ public void CloudFileShareSASCombinations() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudFileSASCombinations() { - for (int i = 1; i < 8; i++) + for (int i = 1; i < 0x20; i++) { CloudFile testFile = this.testShare.GetRootDirectoryReference().GetFileReference("file" + i); SharedAccessFilePermissions permissions = (SharedAccessFilePermissions)i; @@ -262,7 +292,7 @@ public void CloudFileSASCombinations() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudFileSASHeaders() { - for (int i = 1; i < 8; i++) + for (int i = 1; i < 0x20; i++) { CloudFile testFile = this.testShare.GetRootDirectoryReference().GetFileReference("file" + i); SharedAccessFilePermissions permissions = (SharedAccessFilePermissions)i; @@ -279,6 +309,90 @@ public void CloudFileSASHeaders() } } + public void CloudFileSASIPAddressHelper(Func generateInitialIPAddressOrRange, Func generateFinalIPAddressOrRange) + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + CloudFile file; + SharedAccessFilePolicy policy = new SharedAccessFilePolicy() + { + Permissions = SharedAccessFilePermissions.Read, + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + }; + + CloudFile fileWithKey = share.GetRootDirectoryReference().GetFileReference("filefile"); + byte[] data = new byte[] { 0x1, 0x2, 0x3, 0x4 }; + fileWithKey.UploadFromByteArray(data, 0, 4); + + // We need an IP address that will never be a valid source + IPAddressOrRange ipAddressOrRange = generateInitialIPAddressOrRange(); + string fileToken = fileWithKey.GetSharedAccessSignature(policy, null, null, null, ipAddressOrRange); + StorageCredentials fileSAS = new StorageCredentials(fileToken); + Uri fileSASUri = fileSAS.TransformUri(fileWithKey.Uri); + StorageUri fileSASStorageUri = fileSAS.TransformUri(fileWithKey.StorageUri); + + file = new CloudFile(fileSASUri); + byte[] target = new byte[4]; + OperationContext opContext = new OperationContext(); + IPAddress actualIP = null; + opContext.ResponseReceived += (sender, e) => + { + Stream stream = e.Response.GetResponseStream(); + stream.Seek(0, SeekOrigin.Begin); + using (StreamReader reader = new StreamReader(stream)) + { + string text = reader.ReadToEnd(); + XDocument xdocument = XDocument.Parse(text); + actualIP = IPAddress.Parse(xdocument.Descendants("SourceIP").First().Value); + } + }; + + bool exceptionThrown = false; + try + { + file.DownloadRangeToByteArray(target, 0, 0, 4, null, null, opContext); + } + catch (StorageException) + { + exceptionThrown = true; + Assert.IsNotNull(actualIP); + } + + Assert.IsTrue(exceptionThrown); + ipAddressOrRange = generateFinalIPAddressOrRange(actualIP); + fileToken = fileWithKey.GetSharedAccessSignature(policy, null, null, null, ipAddressOrRange); + fileSAS = new StorageCredentials(fileToken); + fileSASUri = fileSAS.TransformUri(fileWithKey.Uri); + fileSASStorageUri = fileSAS.TransformUri(fileWithKey.StorageUri); + + file = new CloudFile(fileSASUri); + file.DownloadRangeToByteArray(target, 0, 0, 4, null, null, null); + for (int i = 0; i < 4; i++) + { + Assert.AreEqual(data[i], target[i]); + } + + Assert.IsTrue(file.StorageUri.PrimaryUri.Equals(fileWithKey.Uri)); + Assert.IsNull(file.StorageUri.SecondaryUri); + + file = new CloudFile(fileSASStorageUri, null); + file.DownloadRangeToByteArray(target, 0, 0, 4, null, null, null); + for (int i = 0; i < 4; i++) + { + Assert.AreEqual(data[i], target[i]); + } + + Assert.IsTrue(file.StorageUri.Equals(fileWithKey.StorageUri)); + } + finally + { + share.DeleteIfExists(); + } + } + [TestMethod] [Description("Test SAS against a file directory")] [TestCategory(ComponentCategory.File)] @@ -311,5 +425,141 @@ public void CloudFileDirectorySAS() sasDir = new CloudFileDirectory(new Uri(dir.Uri.AbsoluteUri + sasToken)); sasDir.FetchAttributes(); } + + [TestMethod] + [Description("Perform a SAS request specifying an IP address or range and ensure that everything works properly.")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileSASIPAddressQueryParam() + { + CloudFileSASIPAddressHelper(() => + { + // We need an IP address that will never be a valid source + IPAddress invalidIP = IPAddress.Parse("255.255.255.255"); + return new IPAddressOrRange(invalidIP.ToString()); + }, + (IPAddress actualIP) => + { + return new IPAddressOrRange(actualIP.ToString()); + }); + } + + [TestMethod] + [Description("Perform a SAS request specifying an IP address or range and ensure that everything works properly.")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileSASIPRangeQueryParam() + { + CloudFileSASIPAddressHelper(() => + { + // We need an IP address that will never be a valid source + return new IPAddressOrRange("255.255.255.0", "255.255.255.255"); + }, + (IPAddress actualIP) => + { + byte[] actualAddressBytes = actualIP.GetAddressBytes(); + byte[] initialAddressBytes = actualAddressBytes.ToArray(); + initialAddressBytes[0]--; + byte[] finalAddressBytes = actualAddressBytes.ToArray(); + finalAddressBytes[0]++; + + return new IPAddressOrRange(new IPAddress(initialAddressBytes).ToString(), new IPAddress(finalAddressBytes).ToString()); + }); + } + + [TestMethod] + [Description("Perform a SAS request specifying a shared protocol and ensure that everything works properly.")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileSASSharedProtocolsQueryParam() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + CloudFile file; + SharedAccessFilePolicy policy = new SharedAccessFilePolicy() + { + Permissions = SharedAccessFilePermissions.Read, + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + }; + + CloudFile fileWithKey = share.GetRootDirectoryReference().GetFileReference("filefile"); + byte[] data = new byte[] { 0x1, 0x2, 0x3, 0x4 }; + byte[] target = new byte[4]; + fileWithKey.UploadFromByteArray(data, 0, 4); + + foreach (SharedAccessProtocol? protocol in new SharedAccessProtocol?[] { null, SharedAccessProtocol.HttpsOrHttp, SharedAccessProtocol.HttpsOnly }) + { + string fileToken = fileWithKey.GetSharedAccessSignature(policy, null, null, protocol, null); + StorageCredentials fileSAS = new StorageCredentials(fileToken); + Uri fileSASUri = new Uri(fileWithKey.Uri + fileSAS.SASToken); + StorageUri fileSASStorageUri = new StorageUri(new Uri(fileWithKey.StorageUri.PrimaryUri + fileSAS.SASToken), new Uri(fileWithKey.StorageUri.SecondaryUri + fileSAS.SASToken)); + + int httpPort = fileSASUri.Port; + int securePort = 443; + + if (!string.IsNullOrEmpty(TestBase.TargetTenantConfig.FileSecurePortOverride)) + { + securePort = Int32.Parse(TestBase.TargetTenantConfig.FileSecurePortOverride); + } + + var schemesAndPorts = new[] { + new { scheme = Uri.UriSchemeHttp, port = httpPort}, + new { scheme = Uri.UriSchemeHttps, port = securePort} + }; + + foreach (var item in schemesAndPorts) + { + fileSASUri = TransformSchemeAndPort(fileSASUri, item.scheme, item.port); + fileSASStorageUri = new StorageUri(TransformSchemeAndPort(fileSASStorageUri.PrimaryUri, item.scheme, item.port), TransformSchemeAndPort(fileSASStorageUri.SecondaryUri, item.scheme, item.port)); + + if (protocol.HasValue && protocol == SharedAccessProtocol.HttpsOnly && string.CompareOrdinal(item.scheme, Uri.UriSchemeHttp) == 0) + { + file = new CloudFile(fileSASUri); + TestHelper.ExpectedException(() => file.FetchAttributes(), "Access a file using SAS with a shared protocols that does not match", HttpStatusCode.Unused); + + file = new CloudFile(fileSASStorageUri, null); + TestHelper.ExpectedException(() => file.FetchAttributes(), "Access a file using SAS with a shared protocols that does not match", HttpStatusCode.Unused); + } + else + { + file = new CloudFile(fileSASUri); + file.DownloadRangeToByteArray(target, 0, 0, 4, null, null, null); + for (int i = 0; i < 4; i++) + { + Assert.AreEqual(data[i], target[i]); + } + + file = new CloudFile(fileSASStorageUri, null); + file.DownloadRangeToByteArray(target, 0, 0, 4, null, null, null); + for (int i = 0; i < 4; i++) + { + Assert.AreEqual(data[i], target[i]); + } + } + } + } + } + finally + { + share.DeleteIfExists(); + } + } + + private static Uri TransformSchemeAndPort(Uri input, string scheme, int port) + { + UriBuilder builder = new UriBuilder(input); + builder.Scheme = scheme; + builder.Port = port; + return builder.Uri; + } } } diff --git a/Test/ClassLibraryCommon/Queue/CloudQueueTest.cs b/Test/ClassLibraryCommon/Queue/CloudQueueTest.cs index b8dfa3bf1..3bb9c1a5b 100644 --- a/Test/ClassLibraryCommon/Queue/CloudQueueTest.cs +++ b/Test/ClassLibraryCommon/Queue/CloudQueueTest.cs @@ -22,12 +22,15 @@ using System.Net; using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Queue.Protocol; using Microsoft.WindowsAzure.Storage.Auth; using Microsoft.WindowsAzure.Storage.Shared.Protocol; #if WINDOWS_DESKTOP using System.Threading.Tasks; +using System.IO; +using System.Xml.Linq; #endif namespace Microsoft.WindowsAzure.Storage.Queue @@ -1334,7 +1337,7 @@ public void UpdateQueueSASTest() TestHelper.ExpectedException( () => sasQueue.PeekMessage(), "Peek when Sas does not allow Read access on the queue", - HttpStatusCode.NotFound); + HttpStatusCode.Forbidden); sasQueue.AddMessage(message); @@ -1357,6 +1360,229 @@ public void UpdateQueueSASTest() } } + /// + /// Helper function for testing the IPAddressOrRange funcitonality for queues + /// + /// Function that generates an initial IPAddressOrRange object to use. This is expected to fail on the service. + /// Function that takes in the correct IP address (according to the service) and returns the IPAddressOrRange object + /// that should be accepted by the service + public void CloudQueueSASIPAddressHelper(Func generateInitialIPAddressOrRange, Func generateFinalIPAddressOrRange) + { + CloudQueueClient client = GenerateCloudQueueClient(); + CloudQueue queue = client.GetQueueReference(GenerateNewQueueName()); + + try + { + queue.Create(); + SharedAccessQueuePolicy policy = new SharedAccessQueuePolicy() + { + Permissions = SharedAccessQueuePermissions.Read, + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + }; + + string sampleMessageContent = "sample content"; + CloudQueueMessage message = new CloudQueueMessage(sampleMessageContent); + queue.AddMessage(message); + + // The plan then is to use an incorrect IP address to make a call to the service + // ensure that we get an error message + // parse the error message to get my actual IP (as far as the service sees) + // then finally test the success case to ensure we can actually make requests + + IPAddressOrRange ipAddressOrRange = generateInitialIPAddressOrRange(); + string queueToken = queue.GetSharedAccessSignature(policy, null, null, ipAddressOrRange); + StorageCredentials queueSAS = new StorageCredentials(queueToken); + Uri queueSASUri = queueSAS.TransformUri(queue.Uri); + StorageUri queueSASStorageUri = queueSAS.TransformUri(queue.StorageUri); + + CloudQueue queueWithSAS = new CloudQueue(queueSASUri); + OperationContext opContext = new OperationContext(); + IPAddress actualIP = null; + opContext.ResponseReceived += (sender, e) => + { + Stream stream = e.Response.GetResponseStream(); + stream.Seek(0, SeekOrigin.Begin); + using (StreamReader reader = new StreamReader(stream)) + { + string text = reader.ReadToEnd(); + XDocument xdocument = XDocument.Parse(text); + actualIP = IPAddress.Parse(xdocument.Descendants("SourceIP").First().Value); + } + }; + + bool exceptionThrown = false; + CloudQueueMessage resultMessage; + try + { + resultMessage = queueWithSAS.PeekMessage(null, opContext); + } + catch (StorageException) + { + exceptionThrown = true; + Assert.IsNotNull(actualIP); + } + + Assert.IsTrue(exceptionThrown); + ipAddressOrRange = generateFinalIPAddressOrRange(actualIP); + queueToken = queue.GetSharedAccessSignature(policy, null, null, ipAddressOrRange); + queueSAS = new StorageCredentials(queueToken); + queueSASUri = queueSAS.TransformUri(queue.Uri); + queueSASStorageUri = queueSAS.TransformUri(queue.StorageUri); + + queueWithSAS = new CloudQueue(queueSASUri); + resultMessage = queue.PeekMessage(); + Assert.AreEqual(sampleMessageContent, resultMessage.AsString); + Assert.IsTrue(queueWithSAS.StorageUri.PrimaryUri.Equals(queue.Uri)); + Assert.IsNull(queueWithSAS.StorageUri.SecondaryUri); + + queueWithSAS = new CloudQueue(queueSASStorageUri, null); + resultMessage = queue.PeekMessage(); + Assert.AreEqual(sampleMessageContent, resultMessage.AsString); + Assert.IsTrue(queueWithSAS.StorageUri.Equals(queue.StorageUri)); + + } + finally + { + queue.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Perform a SAS request specifying an IP address or range and ensure that everything works properly.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudQueueSASIPAddressQueryParam() + { + CloudQueueSASIPAddressHelper(() => + { + // We need an IP address that will never be a valid source + IPAddress invalidIP = IPAddress.Parse("255.255.255.255"); + return new IPAddressOrRange(invalidIP.ToString()); + }, + (IPAddress actualIP) => + { + return new IPAddressOrRange(actualIP.ToString()); + }); + } + + [TestMethod] + [Description("Perform a SAS request specifying an IP address or range and ensure that everything works properly.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudQueueSASIPRangeQueryParam() + { + CloudQueueSASIPAddressHelper(() => + { + // We need an IP address that will never be a valid source + IPAddress invalidIPBegin = IPAddress.Parse("255.255.255.0"); + IPAddress invalidIPEnd = IPAddress.Parse("255.255.255.255"); + + return new IPAddressOrRange(invalidIPBegin.ToString(), invalidIPEnd.ToString()); + }, + (IPAddress actualIP) => + { + byte[] actualAddressBytes = actualIP.GetAddressBytes(); + byte[] initialAddressBytes = actualAddressBytes.ToArray(); + initialAddressBytes[0]--; + byte[] finalAddressBytes = actualAddressBytes.ToArray(); + finalAddressBytes[0]++; + + return new IPAddressOrRange(new IPAddress(initialAddressBytes).ToString(), new IPAddress(finalAddressBytes).ToString()); + }); + } + + [TestMethod] + [Description("Perform a SAS request specifying a shared protocol and ensure that everything works properly.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudQueueSASSharedProtocolsQueryParam() + { + CloudQueueClient client = GenerateCloudQueueClient(); + CloudQueue queue = client.GetQueueReference(GenerateNewQueueName()); + try + { + queue.Create(); + SharedAccessQueuePolicy policy = new SharedAccessQueuePolicy() + { + Permissions = SharedAccessQueuePermissions.Read, + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + }; + + string sampleMessageContent = "sample content"; + CloudQueueMessage message = new CloudQueueMessage(sampleMessageContent); + queue.AddMessage(message); + + foreach (SharedAccessProtocol? protocol in new SharedAccessProtocol?[] { null, SharedAccessProtocol.HttpsOrHttp, SharedAccessProtocol.HttpsOnly }) + { + string queueToken = queue.GetSharedAccessSignature(policy, null, protocol, null); + StorageCredentials queueSAS = new StorageCredentials(queueToken); + Uri queueSASUri = new Uri(queue.Uri + queueSAS.SASToken); + StorageUri queueSASStorageUri = new StorageUri(new Uri(queue.StorageUri.PrimaryUri + queueSAS.SASToken), new Uri(queue.StorageUri.SecondaryUri + queueSAS.SASToken)); + + int httpPort = queueSASUri.Port; + int securePort = 443; + + if (!string.IsNullOrEmpty(TestBase.TargetTenantConfig.QueueSecurePortOverride)) + { + securePort = Int32.Parse(TestBase.TargetTenantConfig.QueueSecurePortOverride); + } + + var schemesAndPorts = new[] { + new { scheme = Uri.UriSchemeHttp, port = httpPort}, + new { scheme = Uri.UriSchemeHttps, port = securePort} + }; + + CloudQueue queueWithSAS; + CloudQueueMessage resultMessage; + + foreach (var item in schemesAndPorts) + { + queueSASUri = TransformSchemeAndPort(queueSASUri, item.scheme, item.port); + queueSASStorageUri = new StorageUri(TransformSchemeAndPort(queueSASStorageUri.PrimaryUri, item.scheme, item.port), TransformSchemeAndPort(queueSASStorageUri.SecondaryUri, item.scheme, item.port)); + + if (protocol.HasValue && protocol == SharedAccessProtocol.HttpsOnly && string.CompareOrdinal(item.scheme, Uri.UriSchemeHttp) == 0) + { + queueWithSAS = new CloudQueue(queueSASUri); + TestHelper.ExpectedException(() => queueWithSAS.PeekMessage(), "Access a queue using SAS with a shared protocols that does not match", HttpStatusCode.Unused); + + queueWithSAS = new CloudQueue(queueSASStorageUri, null); + TestHelper.ExpectedException(() => queueWithSAS.PeekMessage(), "Access a queue using SAS with a shared protocols that does not match", HttpStatusCode.Unused); + } + else + { + queueWithSAS = new CloudQueue(queueSASUri); + resultMessage = queueWithSAS.PeekMessage(); + Assert.AreEqual(sampleMessageContent, resultMessage.AsString); + + queueWithSAS = new CloudQueue(queueSASStorageUri, null); + resultMessage = queueWithSAS.PeekMessage(); + Assert.AreEqual(sampleMessageContent, resultMessage.AsString); + } + } + } + } + finally + { + queue.DeleteIfExists(); + } + } + + private static Uri TransformSchemeAndPort(Uri input, string scheme, int port) + { + UriBuilder builder = new UriBuilder(input); + builder.Scheme = scheme; + builder.Port = port; + return builder.Uri; + } + [TestMethod] [Description("Test queue listing")] [TestCategory(ComponentCategory.Queue)] @@ -1428,71 +1654,6 @@ public void QueueEmptyHeaderSigningTest() } } - [TestMethod] - [Description("Test SAS token Generation using the 2012-02-12 version")] - [TestCategory(ComponentCategory.Queue)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] - public void CloudQueueOldSASVersion() - { - CloudQueueClient client = GenerateCloudQueueClient(); - CloudQueue queue = client.GetQueueReference(GenerateNewQueueName()); - - try - { - queue.Create(); - string messageContent = Guid.NewGuid().ToString(); - CloudQueueMessage message = new CloudQueueMessage(messageContent); - queue.AddMessage(message); - - // Prepare SAS authentication with full permissions - string id = Guid.NewGuid().ToString(); - DateTime start = DateTime.UtcNow; - DateTime expiry = start.AddMinutes(30); - QueuePermissions permissions = new QueuePermissions(); - SharedAccessQueuePermissions queuePerm = SharedAccessQueuePermissions.Add | SharedAccessQueuePermissions.ProcessMessages | SharedAccessQueuePermissions.Read | SharedAccessQueuePermissions.Update; - permissions.SharedAccessPolicies.Add(id, new SharedAccessQueuePolicy() - { - SharedAccessStartTime = start, - SharedAccessExpiryTime = expiry, - Permissions = queuePerm - }); - - queue.SetPermissions(permissions); - Thread.Sleep(30 * 1000); - - string sasTokenFromId = queue.GetSharedAccessSignature(null, id, Constants.VersionConstants.February2012); - StorageCredentials sasCredsFromId = new StorageCredentials(sasTokenFromId); - - CloudStorageAccount sasAcc = new CloudStorageAccount(sasCredsFromId, null /* blobEndpoint */, new Uri(TestBase.TargetTenantConfig.QueueServiceEndpoint), null /* tableEndpoint */, null /* fileEndpoint */); - CloudQueueClient sasClient = sasAcc.CreateCloudQueueClient(); - - CloudQueue sasQueueFromSasUri = new CloudQueue(sasClient.Credentials.TransformUri(queue.Uri)); - CloudQueueMessage receivedMessage = sasQueueFromSasUri.PeekMessage(); - Assert.AreEqual(messageContent, receivedMessage.AsString); - - CloudQueue sasQueueFromSasUri1 = new CloudQueue(new Uri(queue.Uri.ToString() + sasTokenFromId)); - CloudQueueMessage receivedMessage1 = sasQueueFromSasUri1.PeekMessage(); - Assert.AreEqual(messageContent, receivedMessage1.AsString); - - CloudQueue sasQueueFromId = new CloudQueue(queue.Uri, sasCredsFromId); - CloudQueueMessage receivedMessage2 = sasQueueFromId.PeekMessage(); - Assert.AreEqual(messageContent, receivedMessage2.AsString); - - string sasTokenFromPolicy = queue.GetSharedAccessSignature(permissions.SharedAccessPolicies[id], null); - StorageCredentials sasCredsFromPolicy = new StorageCredentials(sasTokenFromPolicy); - CloudQueue sasQueueFromPolicy = new CloudQueue(queue.Uri, sasCredsFromPolicy); - CloudQueueMessage receivedMessage3 = sasQueueFromPolicy.PeekMessage(); - Assert.AreEqual(messageContent, receivedMessage3.AsString); - } - finally - { - queue.DeleteIfExists(); - } - } - #region Test Helpers internal static void AssertPermissionsEqual(QueuePermissions permissions1, QueuePermissions permissions2) { diff --git a/Test/ClassLibraryCommon/Table/CloudTableCRUDUnitTests.cs b/Test/ClassLibraryCommon/Table/CloudTableCRUDUnitTests.cs index 72a7acaa6..add0e0e24 100644 --- a/Test/ClassLibraryCommon/Table/CloudTableCRUDUnitTests.cs +++ b/Test/ClassLibraryCommon/Table/CloudTableCRUDUnitTests.cs @@ -144,10 +144,10 @@ private void TestInvalidTableHelper(string tableName, string failMessage, string [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudTableCreateSync() { - DoCloudTableCreateSync(TablePayloadFormat.Json); - DoCloudTableCreateSync(TablePayloadFormat.JsonNoMetadata); - DoCloudTableCreateSync(TablePayloadFormat.JsonFullMetadata); - DoCloudTableCreateSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoCloudTableCreateSync(payloadFormat); + } } private void DoCloudTableCreateSync(TablePayloadFormat format) @@ -177,10 +177,10 @@ private void DoCloudTableCreateSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudTableCreateAlreadyExistsSync() { - DoCloudTableCreateAlreadyExistsSync(TablePayloadFormat.Json); - DoCloudTableCreateAlreadyExistsSync(TablePayloadFormat.JsonNoMetadata); - DoCloudTableCreateAlreadyExistsSync(TablePayloadFormat.JsonFullMetadata); - DoCloudTableCreateAlreadyExistsSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoCloudTableCreateAlreadyExistsSync(payloadFormat); + } } private void DoCloudTableCreateAlreadyExistsSync(TablePayloadFormat format) @@ -417,10 +417,10 @@ public void CloudTableCreateAlreadyExistsTask() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudTableCreateIfNotExistsSync() { - DoCloudTableCreateIfNotExistsSync(TablePayloadFormat.Json); - DoCloudTableCreateIfNotExistsSync(TablePayloadFormat.JsonNoMetadata); - DoCloudTableCreateIfNotExistsSync(TablePayloadFormat.JsonFullMetadata); - DoCloudTableCreateIfNotExistsSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoCloudTableCreateIfNotExistsSync(payloadFormat); + } } private void DoCloudTableCreateIfNotExistsSync(TablePayloadFormat format) @@ -632,10 +632,10 @@ public void CloudTableCreateIfNotExistsRequestOptionsOperationContextCancellatio [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudTableDeleteSync() { - DoCloudTableDeleteSync(TablePayloadFormat.Json); - DoCloudTableDeleteSync(TablePayloadFormat.JsonNoMetadata); - DoCloudTableDeleteSync(TablePayloadFormat.JsonFullMetadata); - DoCloudTableDeleteSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoCloudTableDeleteSync(payloadFormat); + } } private void DoCloudTableDeleteSync(TablePayloadFormat format) @@ -667,10 +667,10 @@ private void DoCloudTableDeleteSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudTableDeleteWhenNotExistSync() { - DoCloudTableDeleteWhenNotExistSync(TablePayloadFormat.Json); - DoCloudTableDeleteWhenNotExistSync(TablePayloadFormat.JsonNoMetadata); - DoCloudTableDeleteWhenNotExistSync(TablePayloadFormat.JsonFullMetadata); - DoCloudTableDeleteWhenNotExistSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoCloudTableDeleteWhenNotExistSync(payloadFormat); + } } private void DoCloudTableDeleteWhenNotExistSync(TablePayloadFormat format) @@ -878,14 +878,11 @@ public void CloudTableDeleteWhenNotExistTask() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudTableDeleteIfExistsSync() { - DoCloudTableDeleteIfExistsSync(TablePayloadFormat.Json, false); - DoCloudTableDeleteIfExistsSync(TablePayloadFormat.JsonNoMetadata, false); - DoCloudTableDeleteIfExistsSync(TablePayloadFormat.JsonFullMetadata, false); - DoCloudTableDeleteIfExistsSync(TablePayloadFormat.AtomPub, false); - DoCloudTableDeleteIfExistsSync(TablePayloadFormat.Json, true); - DoCloudTableDeleteIfExistsSync(TablePayloadFormat.JsonNoMetadata, true); - DoCloudTableDeleteIfExistsSync(TablePayloadFormat.JsonFullMetadata, true); - DoCloudTableDeleteIfExistsSync(TablePayloadFormat.AtomPub, true); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoCloudTableDeleteIfExistsSync(payloadFormat, false); + DoCloudTableDeleteIfExistsSync(payloadFormat, true); + } } private void DoCloudTableDeleteIfExistsSync(TablePayloadFormat format, bool simulateParallelDelete) @@ -937,14 +934,11 @@ private void DoCloudTableDeleteIfExistsSync(TablePayloadFormat format, bool simu [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudTableDeleteIfExistsAPM() { - DoCloudTableDeleteIfExistsAPM(TablePayloadFormat.Json, false); - DoCloudTableDeleteIfExistsAPM(TablePayloadFormat.JsonNoMetadata, false); - DoCloudTableDeleteIfExistsAPM(TablePayloadFormat.JsonFullMetadata, false); - DoCloudTableDeleteIfExistsAPM(TablePayloadFormat.AtomPub, false); - DoCloudTableDeleteIfExistsAPM(TablePayloadFormat.Json, true); - DoCloudTableDeleteIfExistsAPM(TablePayloadFormat.JsonNoMetadata, true); - DoCloudTableDeleteIfExistsAPM(TablePayloadFormat.JsonFullMetadata, true); - DoCloudTableDeleteIfExistsAPM(TablePayloadFormat.AtomPub, true); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoCloudTableDeleteIfExistsAPM(payloadFormat, false); + DoCloudTableDeleteIfExistsAPM(payloadFormat, true); + } } private void DoCloudTableDeleteIfExistsAPM(TablePayloadFormat format, bool simulateParallelDelete) @@ -1175,10 +1169,10 @@ public void CloudTableDeleteIfExistsRequestOptionsOperationContextCancellationTo [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudTableExistsSync() { - DoCloudTableExistsSync(TablePayloadFormat.Json); - DoCloudTableExistsSync(TablePayloadFormat.JsonNoMetadata); - DoCloudTableExistsSync(TablePayloadFormat.JsonFullMetadata); - DoCloudTableExistsSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoCloudTableExistsSync(payloadFormat); + } } private void DoCloudTableExistsSync(TablePayloadFormat format) diff --git a/Test/ClassLibraryCommon/Table/CloudTableClientTests.cs b/Test/ClassLibraryCommon/Table/CloudTableClientTests.cs index 9da78b7d4..121c3e297 100644 --- a/Test/ClassLibraryCommon/Table/CloudTableClientTests.cs +++ b/Test/ClassLibraryCommon/Table/CloudTableClientTests.cs @@ -164,10 +164,10 @@ public void CloudTableClientConstructor() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void ListTablesNoPrefix() { - DoListTablesNoPrefix(TablePayloadFormat.Json); - DoListTablesNoPrefix(TablePayloadFormat.JsonNoMetadata); - DoListTablesNoPrefix(TablePayloadFormat.JsonFullMetadata); - DoListTablesNoPrefix(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoListTablesNoPrefix(payloadFormat); + } } private void DoListTablesNoPrefix(TablePayloadFormat format) @@ -191,10 +191,10 @@ private void DoListTablesNoPrefix(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void ListTablesWithPrefixBasic() { - DoListTablesWithPrefixBasic(TablePayloadFormat.Json); - DoListTablesWithPrefixBasic(TablePayloadFormat.JsonNoMetadata); - DoListTablesWithPrefixBasic(TablePayloadFormat.JsonFullMetadata); - DoListTablesWithPrefixBasic(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoListTablesWithPrefixBasic(payloadFormat); + } } private void DoListTablesWithPrefixBasic(TablePayloadFormat format) @@ -384,10 +384,10 @@ private void DoListTablesWithPrefixExtended(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudTableClientListTablesSharedKeyLite() { - DoCloudTableClientListTablesSharedKeyLite(TablePayloadFormat.Json); - DoCloudTableClientListTablesSharedKeyLite(TablePayloadFormat.JsonNoMetadata); - DoCloudTableClientListTablesSharedKeyLite(TablePayloadFormat.JsonFullMetadata); - DoCloudTableClientListTablesSharedKeyLite(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoCloudTableClientListTablesSharedKeyLite(payloadFormat); + } } private void DoCloudTableClientListTablesSharedKeyLite(TablePayloadFormat format) @@ -587,10 +587,10 @@ public void TableContinuationTokenVerifyXmlWithinXml() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void ListTablesSegmentedBasicSync() { - DoListTablesSegmentedBasicSync(TablePayloadFormat.Json); - DoListTablesSegmentedBasicSync(TablePayloadFormat.JsonNoMetadata); - DoListTablesSegmentedBasicSync(TablePayloadFormat.JsonFullMetadata); - DoListTablesSegmentedBasicSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoListTablesSegmentedBasicSync(payloadFormat); + } } private void DoListTablesSegmentedBasicSync(TablePayloadFormat format) @@ -619,10 +619,10 @@ private void DoListTablesSegmentedBasicSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void ListTablesSegmentedMaxResultsSync() { - DoListTablesSegmentedMaxResultsSync(TablePayloadFormat.Json); - DoListTablesSegmentedMaxResultsSync(TablePayloadFormat.JsonNoMetadata); - DoListTablesSegmentedMaxResultsSync(TablePayloadFormat.JsonFullMetadata); - DoListTablesSegmentedMaxResultsSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoListTablesSegmentedMaxResultsSync(payloadFormat); + } } private void DoListTablesSegmentedMaxResultsSync(TablePayloadFormat format) @@ -654,10 +654,10 @@ private void DoListTablesSegmentedMaxResultsSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void ListTablesSegmentedWithPrefixSync() { - DoListTablesSegmentedWithPrefixSync(TablePayloadFormat.Json); - DoListTablesSegmentedWithPrefixSync(TablePayloadFormat.JsonNoMetadata); - DoListTablesSegmentedWithPrefixSync(TablePayloadFormat.JsonFullMetadata); - DoListTablesSegmentedWithPrefixSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoListTablesSegmentedWithPrefixSync(payloadFormat); + } } private void DoListTablesSegmentedWithPrefixSync(TablePayloadFormat format) diff --git a/Test/ClassLibraryCommon/Table/SAS/TableSasUnitTests.cs b/Test/ClassLibraryCommon/Table/SAS/TableSasUnitTests.cs index 0ff055f78..47d6d630d 100644 --- a/Test/ClassLibraryCommon/Table/SAS/TableSasUnitTests.cs +++ b/Test/ClassLibraryCommon/Table/SAS/TableSasUnitTests.cs @@ -16,17 +16,21 @@ // ----------------------------------------------------------------------------------------- using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Auth; using Microsoft.WindowsAzure.Storage.Shared.Protocol; using Microsoft.WindowsAzure.Storage.Table.Entities; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net; using System.Threading; +using System.Xml.Linq; namespace Microsoft.WindowsAzure.Storage.Table { +#pragma warning disable 0618 [TestClass] public class TableSasUnitTests : TableTestBase { @@ -161,80 +165,6 @@ public void TableSASConstructors() } } - [TestMethod] - [Description("Test TableSas via various constructors using 2012-02-12 version token.")] - [TestCategory(ComponentCategory.Table)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] - public void TableSASConstructorsOldVersion() - { - CloudTableClient tableClient = GenerateCloudTableClient(); - tableClient.DefaultRequestOptions.PayloadFormat = TablePayloadFormat.AtomPub; - CloudTable table = tableClient.GetTableReference("T" + Guid.NewGuid().ToString("N")); - try - { - table.Create(); - - table.Execute(TableOperation.Insert(new BaseEntity("PK", "RK"))); - - // Prepare SAS authentication with full permissions - string sasToken = table.GetSharedAccessSignature( - new SharedAccessTablePolicy - { - Permissions = SharedAccessTablePermissions.Add | SharedAccessTablePermissions.Delete | SharedAccessTablePermissions.Query, - SharedAccessExpiryTime = DateTimeOffset.Now.AddMinutes(30) - }, - null /* accessPolicyIdentifier */, - null /* startPk */, - null /* startRk */, - null /* endPk */, - null /* endRk */, - Constants.VersionConstants.February2012); - - CloudStorageAccount sasAccount; - StorageCredentials sasCreds; - CloudTableClient sasClient; - CloudTable sasTable; - Uri baseUri = new Uri(TestBase.TargetTenantConfig.TableServiceEndpoint); - - // SAS via connection string parse - sasAccount = CloudStorageAccount.Parse(string.Format("TableEndpoint={0};SharedAccessSignature={1}", baseUri.AbsoluteUri, sasToken)); - sasClient = sasAccount.CreateCloudTableClient(); - sasClient.DefaultRequestOptions.PayloadFormat = TablePayloadFormat.AtomPub; - sasTable = sasClient.GetTableReference(table.Name); - - Assert.AreEqual(1, sasTable.ExecuteQuery(new TableQuery()).Count()); - - // SAS via account constructor - sasCreds = new StorageCredentials(sasToken); - sasAccount = new CloudStorageAccount(sasCreds, null, null, baseUri, null); - sasClient = sasAccount.CreateCloudTableClient(); - sasClient.DefaultRequestOptions.PayloadFormat = TablePayloadFormat.AtomPub; - sasTable = sasClient.GetTableReference(table.Name); - Assert.AreEqual(1, sasTable.ExecuteQuery(new TableQuery()).Count()); - - // SAS via client constructor URI + Creds - sasCreds = new StorageCredentials(sasToken); - sasClient = new CloudTableClient(baseUri, sasCreds); - sasClient.DefaultRequestOptions.PayloadFormat = TablePayloadFormat.AtomPub; - sasTable = sasClient.GetTableReference(table.Name); - Assert.AreEqual(1, sasTable.ExecuteQuery(new TableQuery()).Count()); - - // SAS via CloudTable constructor Uri + Client - sasCreds = new StorageCredentials(sasToken); - sasTable = new CloudTable(table.Uri, tableClient.Credentials); - sasClient = sasTable.ServiceClient; - sasClient.DefaultRequestOptions.PayloadFormat = TablePayloadFormat.AtomPub; - Assert.AreEqual(1, sasTable.ExecuteQuery(new TableQuery()).Count()); - } - finally - { - table.DeleteIfExists(); - } - } - #endregion #region Permissions @@ -559,7 +489,7 @@ private void TestPointQuery( string endPk, string endRk) { - bool expectSuccess = (accessPermissions & SharedAccessTablePermissions.Query) != 0; + bool expectSuccess = ((accessPermissions & SharedAccessTablePermissions.Query) != 0); Action queryDelegate = (tableEntity, ctx) => { @@ -576,6 +506,7 @@ private void TestPointQuery( }; + // Perform test TestOperationWithRange( tableName, @@ -586,7 +517,9 @@ private void TestPointQuery( queryDelegate, "point query", expectSuccess, - expectSuccess ? HttpStatusCode.OK : HttpStatusCode.NotFound); + expectSuccess ? HttpStatusCode.OK : HttpStatusCode.NotFound, + false, + expectSuccess); } @@ -630,7 +563,7 @@ private void TestUpdateMerge( updateDelegate, "update merge", expectSuccess, - expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.NotFound); + expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.Forbidden); } /// @@ -673,7 +606,7 @@ private void TestUpdateReplace( updateDelegate, "update replace", expectSuccess, - expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.NotFound); + expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.Forbidden); } /// @@ -715,7 +648,7 @@ private void TestAdd( addDelegate, "add", expectSuccess, - expectSuccess ? HttpStatusCode.Created : HttpStatusCode.NotFound); + expectSuccess ? HttpStatusCode.Created : HttpStatusCode.Forbidden); } /// @@ -758,7 +691,7 @@ private void TestDelete( deleteDelegate, "delete", expectSuccess, - expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.NotFound); + expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.Forbidden); } /// @@ -801,7 +734,7 @@ private void TestUpsertMerge( upsertDelegate, "upsert merge", expectSuccess, - expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.NotFound); + expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.Forbidden); } /// @@ -844,7 +777,7 @@ private void TestUpsertReplace( upsertDelegate, "upsert replace", expectSuccess, - expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.NotFound); + expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.Forbidden); } /// @@ -904,6 +837,44 @@ private void TestOperationWithRange( bool expectSuccess, HttpStatusCode expectedStatusCode, bool isRangeQuery) + { + TestOperationWithRange( + tableName, + startPk, + startRk, + endPk, + endRk, + runOperationDelegate, + opName, + expectSuccess, + expectedStatusCode, + isRangeQuery, + false /* isPointQuery */); + } + + /// + /// Test a table operation on entities inside and outside the given range. + /// + /// The name of the table to test. + /// The start partition key range. + /// The start row key range. + /// The end partition key range. + /// The end row key range. + /// A delegate with the table operation to test. + /// The name of the operation being tested. + /// Whether the operation should succeed on entities within the range. + private void TestOperationWithRange( + string tableName, + string startPk, + string startRk, + string endPk, + string endRk, + Action runOperationDelegate, + string opName, + bool expectSuccess, + HttpStatusCode expectedStatusCode, + bool isRangeQuery, + bool isPointQuery) { CloudTableClient referenceClient = GenerateCloudTableClient(); @@ -938,7 +909,7 @@ private void TestOperationWithRange( TestHelper.ExpectedException( (ctx) => runOperationDelegate(tableEntity, ctx), string.Format("{0} without appropriate permission.", opName), - (int)HttpStatusCode.NotFound); + (int)HttpStatusCode.Forbidden); } if (startPk != null) @@ -952,7 +923,7 @@ private void TestOperationWithRange( TestHelper.ExpectedException( (ctx) => runOperationDelegate(tableEntity, ctx), string.Format("{0} before allowed partition key range", opName), - (int)HttpStatusCode.NotFound); + (int)(isPointQuery ? HttpStatusCode.NotFound : HttpStatusCode.Forbidden)); tableEntity.PartitionKey = partitionKey; } @@ -967,7 +938,7 @@ private void TestOperationWithRange( TestHelper.ExpectedException( (ctx) => runOperationDelegate(tableEntity, ctx), string.Format("{0} after allowed partition key range", opName), - (int)HttpStatusCode.NotFound); + (int)(isPointQuery ? HttpStatusCode.NotFound : HttpStatusCode.Forbidden)); tableEntity.PartitionKey = partitionKey; } @@ -986,7 +957,7 @@ private void TestOperationWithRange( TestHelper.ExpectedException( (ctx) => runOperationDelegate(tableEntity, ctx), string.Format("{0} before allowed row key range", opName), - (int)HttpStatusCode.NotFound); + (int)(isPointQuery ? HttpStatusCode.NotFound : HttpStatusCode.Forbidden)); tableEntity.RowKey = rowKey; } @@ -1006,7 +977,7 @@ private void TestOperationWithRange( TestHelper.ExpectedException( (ctx) => runOperationDelegate(tableEntity, ctx), string.Format("{0} after allowed row key range", opName), - (int)HttpStatusCode.NotFound); + (int)(isPointQuery ? HttpStatusCode.NotFound : HttpStatusCode.Forbidden)); tableEntity.RowKey = rowKey; } @@ -1055,22 +1026,22 @@ public void TableSasInvalidOperations() // Test invalid client operations // BUGBUG: ListTables hides the exception. We should fix this - // TestHelpers.ExpectedException(() => sasClient.ListTablesSegmented(), "List tables with SAS", HttpStatusCode.NotFound); - TestHelper.ExpectedException((ctx) => sasClient.GetServiceProperties(), "Get service properties with SAS", (int)HttpStatusCode.NotFound); - TestHelper.ExpectedException((ctx) => sasClient.SetServiceProperties(properties), "Set service properties with SAS", (int)HttpStatusCode.NotFound); + // TestHelpers.ExpectedException(() => sasClient.ListTablesSegmented(), "List tables with SAS", HttpStatusCode.Forbidden); + TestHelper.ExpectedException((ctx) => sasClient.GetServiceProperties(), "Get service properties with SAS", (int)HttpStatusCode.Forbidden); + TestHelper.ExpectedException((ctx) => sasClient.SetServiceProperties(properties), "Set service properties with SAS", (int)HttpStatusCode.Forbidden); CloudTable sasTable = sasClient.GetTableReference(table.Name); // Verify that creation fails with SAS - TestHelper.ExpectedException((ctx) => sasTable.Create(null, ctx), "Create a table with SAS", (int)HttpStatusCode.NotFound); + TestHelper.ExpectedException((ctx) => sasTable.Create(null, ctx), "Create a table with SAS", (int)HttpStatusCode.Forbidden); // Create the table. table.Create(); // Test invalid table operations - TestHelper.ExpectedException((ctx) => sasTable.Delete(null, ctx), "Delete a table with SAS", (int)HttpStatusCode.NotFound); - TestHelper.ExpectedException((ctx) => sasTable.GetPermissions(null, ctx), "Get ACL with SAS", (int)HttpStatusCode.NotFound); - TestHelper.ExpectedException((ctx) => sasTable.SetPermissions(new TablePermissions(), null, ctx), "Set ACL with SAS", (int)HttpStatusCode.NotFound); + TestHelper.ExpectedException((ctx) => sasTable.Delete(null, ctx), "Delete a table with SAS", (int)HttpStatusCode.Forbidden); + TestHelper.ExpectedException((ctx) => sasTable.GetPermissions(null, ctx), "Get ACL with SAS", (int)HttpStatusCode.Forbidden); + TestHelper.ExpectedException((ctx) => sasTable.SetPermissions(new TablePermissions(), null, ctx), "Set ACL with SAS", (int)HttpStatusCode.Forbidden); } finally { @@ -1112,7 +1083,7 @@ public void TableUpdateSasTestSync() TestHelper.ExpectedException( () => sasTable.Execute(TableOperation.Insert(new BaseEntity("PK", "RK2"))), "Try to insert an entity when SAS doesn't allow inserts", - HttpStatusCode.NotFound); + HttpStatusCode.Forbidden); sasTable.Execute(TableOperation.Delete(entity)); @@ -1230,21 +1201,21 @@ public void TableSasUriPkRkTestSync() TestHelper.ExpectedException( (ctx) => insertDelegate(pkrkEnt, sasTableTransformed, ctx), string.Format("Inserted entity without appropriate SAS permissions."), - (int)HttpStatusCode.NotFound); + (int)HttpStatusCode.Forbidden); TestHelper.ExpectedException( (ctx) => insertDelegate(pkrkEnt, sasTableDirect, ctx), string.Format("Inserted entity without appropriate SAS permissions."), - (int)HttpStatusCode.NotFound); + (int)HttpStatusCode.Forbidden); pkrkEnt = new BaseEntity("tables_batch_1", "05"); TestHelper.ExpectedException( (ctx) => insertDelegate(pkrkEnt, sasTableTransformed, ctx), string.Format("Inserted entity without appropriate SAS permissions."), - (int)HttpStatusCode.NotFound); + (int)HttpStatusCode.Forbidden); TestHelper.ExpectedException( (ctx) => insertDelegate(pkrkEnt, sasTableDirect, ctx), string.Format("Inserted entity without appropriate SAS permissions."), - (int)HttpStatusCode.NotFound); + (int)HttpStatusCode.Forbidden); } finally { @@ -1254,6 +1225,238 @@ public void TableSasUriPkRkTestSync() #endregion + #region SASIPAddressTests + + /// + /// Helper function for testing the IPAddressOrRange funcitonality for tables + /// + /// Function that generates an initial IPAddressOrRange object to use. This is expected to fail on the service. + /// Function that takes in the correct IP address (according to the service) and returns the IPAddressOrRange object + /// that should be accepted by the service + public void CloudTableSASIPAddressHelper(Func generateInitialIPAddressOrRange, Func generateFinalIPAddressOrRange) + { + CloudTableClient tableClient = GenerateCloudTableClient(); + CloudTable table = tableClient.GetTableReference("T" + Guid.NewGuid().ToString("N")); + + try + { + table.Create(); + SharedAccessTablePolicy policy = new SharedAccessTablePolicy() + { + Permissions = SharedAccessTablePermissions.Query, + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + }; + + DynamicTableEntity entity = new DynamicTableEntity("PK", "RK", null, new Dictionary() {{"prop", new EntityProperty(4)}}); + table.Execute(new TableOperation(entity, TableOperationType.Insert)); + + // The plan then is to use an incorrect IP address to make a call to the service + // ensure that we get an error message + // parse the error message to get my actual IP (as far as the service sees) + // then finally test the success case to ensure we can actually make requests + + IPAddressOrRange ipAddressOrRange = generateInitialIPAddressOrRange(); + string tableToken = table.GetSharedAccessSignature(policy, null, null, null, null, null, null, ipAddressOrRange); + StorageCredentials tableSAS = new StorageCredentials(tableToken); + Uri tableSASUri = tableSAS.TransformUri(table.Uri); + StorageUri tableSASStorageUri = tableSAS.TransformUri(table.StorageUri); + + CloudTable tableWithSAS = new CloudTable(tableSASUri); + OperationContext opContext = new OperationContext(); + IPAddress actualIP = null; + + bool exceptionThrown = false; + TableResult result = null; + DynamicTableEntity resultEntity = new DynamicTableEntity(); + TableOperation retrieveOp = new TableOperation(resultEntity, TableOperationType.Retrieve); + retrieveOp.RetrievePartitionKey = entity.PartitionKey; + retrieveOp.RetrieveRowKey = entity.RowKey; + try + { + result = tableWithSAS.Execute(retrieveOp, null, opContext); + } + catch (StorageException e) + { + string[] parts = e.RequestInformation.HttpStatusMessage.Split(' '); + actualIP = IPAddress.Parse(parts[parts.Length - 1].Trim('.')); + exceptionThrown = true; + Assert.IsNotNull(actualIP); + } + + Assert.IsTrue(exceptionThrown); + ipAddressOrRange = generateFinalIPAddressOrRange(actualIP); + tableToken = table.GetSharedAccessSignature(policy, null, null, null, null, null, null, ipAddressOrRange); + tableSAS = new StorageCredentials(tableToken); + tableSASUri = tableSAS.TransformUri(table.Uri); + tableSASStorageUri = tableSAS.TransformUri(table.StorageUri); + + + tableWithSAS = new CloudTable(tableSASUri); + resultEntity = new DynamicTableEntity(); + retrieveOp = new TableOperation(resultEntity, TableOperationType.Retrieve); + retrieveOp.RetrievePartitionKey = entity.PartitionKey; + retrieveOp.RetrieveRowKey = entity.RowKey; + resultEntity = (DynamicTableEntity)tableWithSAS.Execute(retrieveOp).Result; + + Assert.AreEqual(entity.Properties["prop"].PropertyType, resultEntity.Properties["prop"].PropertyType); + Assert.AreEqual(entity.Properties["prop"].Int32Value.Value, resultEntity.Properties["prop"].Int32Value.Value); + Assert.IsTrue(table.StorageUri.PrimaryUri.Equals(tableWithSAS.Uri)); + Assert.IsNull(tableWithSAS.StorageUri.SecondaryUri); + + tableWithSAS = new CloudTable(tableSASStorageUri, null); + resultEntity = new DynamicTableEntity(); + retrieveOp = new TableOperation(resultEntity, TableOperationType.Retrieve); + retrieveOp.RetrievePartitionKey = entity.PartitionKey; + retrieveOp.RetrieveRowKey = entity.RowKey; + resultEntity = (DynamicTableEntity)tableWithSAS.Execute(retrieveOp).Result; + Assert.AreEqual(entity.Properties["prop"].PropertyType, resultEntity.Properties["prop"].PropertyType); + Assert.AreEqual(entity.Properties["prop"].Int32Value.Value, resultEntity.Properties["prop"].Int32Value.Value); + Assert.IsTrue(table.StorageUri.Equals(tableWithSAS.StorageUri)); + } + finally + { + table.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Perform a SAS request specifying an IP address or range and ensure that everything works properly.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudTableSASIPAddressQueryParam() + { + CloudTableSASIPAddressHelper(() => + { + // We need an IP address that will never be a valid source + IPAddress invalidIP = IPAddress.Parse("255.255.255.255"); + return new IPAddressOrRange(invalidIP.ToString()); + }, + (IPAddress actualIP) => + { + return new IPAddressOrRange(actualIP.ToString()); + }); + } + + [TestMethod] + [Description("Perform a SAS request specifying an IP address or range and ensure that everything works properly.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudTableSASIPRangeQueryParam() + { + CloudTableSASIPAddressHelper(() => + { + // We need an IP address that will never be a valid source + IPAddress invalidIPBegin = IPAddress.Parse("255.255.255.0"); + IPAddress invalidIPEnd = IPAddress.Parse("255.255.255.255"); + + return new IPAddressOrRange(invalidIPBegin.ToString(), invalidIPEnd.ToString()); + }, + (IPAddress actualIP) => + { + byte[] actualAddressBytes = actualIP.GetAddressBytes(); + byte[] initialAddressBytes = actualAddressBytes.ToArray(); + initialAddressBytes[0]--; + byte[] finalAddressBytes = actualAddressBytes.ToArray(); + finalAddressBytes[0]++; + + return new IPAddressOrRange(new IPAddress(initialAddressBytes).ToString(), new IPAddress(finalAddressBytes).ToString()); + }); + } + #endregion + + #region SASHttpsTests + [TestMethod] + [Description("Perform a SAS request specifying a shared protocol and ensure that everything works properly.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudTableSASSharedProtocolsQueryParam() + { + CloudTableClient tableClient = GenerateCloudTableClient(); + CloudTable table = tableClient.GetTableReference("T" + Guid.NewGuid().ToString("N")); + try + { + table.Create(); + SharedAccessTablePolicy policy = new SharedAccessTablePolicy() + { + Permissions = SharedAccessTablePermissions.Query, + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + }; + + DynamicTableEntity entity = new DynamicTableEntity("PK", "RK", null, new Dictionary() { { "prop", new EntityProperty(4) } }); + table.Execute(new TableOperation(entity, TableOperationType.Insert)); + + foreach (SharedAccessProtocol? protocol in new SharedAccessProtocol?[] { null, SharedAccessProtocol.HttpsOrHttp, SharedAccessProtocol.HttpsOnly }) + { + string tableToken = table.GetSharedAccessSignature(policy, null, null, null, null, null, protocol, null); + StorageCredentials tableSAS = new StorageCredentials(tableToken); + Uri tableSASUri = new Uri(table.Uri + tableSAS.SASToken); + StorageUri tableSASStorageUri = new StorageUri(new Uri(table.StorageUri.PrimaryUri + tableSAS.SASToken), new Uri(table.StorageUri.SecondaryUri + tableSAS.SASToken)); + + int httpPort = tableSASUri.Port; + int securePort = 443; + + if (!string.IsNullOrEmpty(TestBase.TargetTenantConfig.TableSecurePortOverride)) + { + securePort = Int32.Parse(TestBase.TargetTenantConfig.TableSecurePortOverride); + } + + var schemesAndPorts = new[] { + new { scheme = Uri.UriSchemeHttp, port = httpPort}, + new { scheme = Uri.UriSchemeHttps, port = securePort} + }; + + CloudTable tableWithSAS = null; + TableOperation retrieveOp = TableOperation.Retrieve(entity.PartitionKey, entity.RowKey); + + foreach (var item in schemesAndPorts) + { + tableSASUri = TransformSchemeAndPort(tableSASUri, item.scheme, item.port); + tableSASStorageUri = new StorageUri(TransformSchemeAndPort(tableSASStorageUri.PrimaryUri, item.scheme, item.port), TransformSchemeAndPort(tableSASStorageUri.SecondaryUri, item.scheme, item.port)); + + if (protocol.HasValue && protocol.Value == SharedAccessProtocol.HttpsOnly && string.CompareOrdinal(item.scheme, Uri.UriSchemeHttp) == 0) + { + tableWithSAS = new CloudTable(tableSASUri); + TestHelper.ExpectedException(() => tableWithSAS.Execute(retrieveOp), "Access a table using SAS with a shared protocols that does not match", HttpStatusCode.Unused); + + tableWithSAS = new CloudTable(tableSASStorageUri, null); + TestHelper.ExpectedException(() => tableWithSAS.Execute(retrieveOp), "Access a table using SAS with a shared protocols that does not match", HttpStatusCode.Unused); + } + else + { + tableWithSAS = new CloudTable(tableSASUri); + TableResult result = tableWithSAS.Execute(retrieveOp); + Assert.AreEqual(entity.Properties["prop"], ((DynamicTableEntity)result.Result).Properties["prop"]); + + tableWithSAS = new CloudTable(tableSASStorageUri, null); + result = tableWithSAS.Execute(retrieveOp); + Assert.AreEqual(entity.Properties["prop"], ((DynamicTableEntity)result.Result).Properties["prop"]); + } + } + } + } + finally + { + table.DeleteIfExists(); + } + } + + private static Uri TransformSchemeAndPort(Uri input, string scheme, int port) + { + UriBuilder builder = new UriBuilder(input); + builder.Scheme = scheme; + builder.Port = port; + return builder.Uri; + } + #endregion + #region Test Helpers internal static void AssertPermissionsEqual(TablePermissions permissions1, TablePermissions permissions2) { @@ -1281,4 +1484,5 @@ internal static void AssertPermissionsEqual(TablePermissions permissions1, Table } #endregion } +#pragma warning restore 0618 } diff --git a/Test/ClassLibraryCommon/Table/TableBatchOperationTest.cs b/Test/ClassLibraryCommon/Table/TableBatchOperationTest.cs index a349d1135..b2d6110cd 100644 --- a/Test/ClassLibraryCommon/Table/TableBatchOperationTest.cs +++ b/Test/ClassLibraryCommon/Table/TableBatchOperationTest.cs @@ -224,10 +224,10 @@ public void TableEntityPropertySetter() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchInsertSync() { - DoTableBatchInsertSync(TablePayloadFormat.Json); - DoTableBatchInsertSync(TablePayloadFormat.JsonNoMetadata); - DoTableBatchInsertSync(TablePayloadFormat.JsonFullMetadata); - DoTableBatchInsertSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchInsertSync(payloadFormat); + } } private void DoTableBatchInsertSync(TablePayloadFormat format) @@ -273,10 +273,10 @@ private void DoTableBatchInsertSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchBasicOperationsCheck() { - DoTableBatchBasicOperationsCheck(TablePayloadFormat.Json); - DoTableBatchBasicOperationsCheck(TablePayloadFormat.JsonNoMetadata); - DoTableBatchBasicOperationsCheck(TablePayloadFormat.JsonFullMetadata); - DoTableBatchBasicOperationsCheck(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchBasicOperationsCheck(payloadFormat); + } } private void DoTableBatchBasicOperationsCheck(TablePayloadFormat format) @@ -326,10 +326,10 @@ private void DoTableBatchBasicOperationsCheck(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchInsertFailSync() { - DoTableBatchInsertFailSync(TablePayloadFormat.Json); - DoTableBatchInsertFailSync(TablePayloadFormat.JsonNoMetadata); - DoTableBatchInsertFailSync(TablePayloadFormat.JsonFullMetadata); - DoTableBatchInsertFailSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchInsertFailSync(payloadFormat); + } } private void DoTableBatchInsertFailSync(TablePayloadFormat format) @@ -540,10 +540,10 @@ public void TableBatchInsertFailTask() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchInsertOrMergeSync() { - DoTableBatchInsertOrMergeSync(TablePayloadFormat.Json); - DoTableBatchInsertOrMergeSync(TablePayloadFormat.JsonNoMetadata); - DoTableBatchInsertOrMergeSync(TablePayloadFormat.JsonFullMetadata); - DoTableBatchInsertOrMergeSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchInsertOrMergeSync(payloadFormat); + } } private void DoTableBatchInsertOrMergeSync(TablePayloadFormat format) @@ -664,10 +664,10 @@ public void TableBatchInsertOrMergeAPM() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchInsertOrReplaceSync() { - DoTableBatchInsertOrReplaceSync(TablePayloadFormat.Json); - DoTableBatchInsertOrReplaceSync(TablePayloadFormat.JsonNoMetadata); - DoTableBatchInsertOrReplaceSync(TablePayloadFormat.JsonFullMetadata); - DoTableBatchInsertOrReplaceSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchInsertOrReplaceSync(payloadFormat); + } } private void DoTableBatchInsertOrReplaceSync(TablePayloadFormat format) @@ -781,10 +781,10 @@ public void TableBatchInsertOrReplaceAPM() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchDeleteSync() { - DoTableBatchDeleteSync(TablePayloadFormat.Json); - DoTableBatchDeleteSync(TablePayloadFormat.JsonNoMetadata); - DoTableBatchDeleteSync(TablePayloadFormat.JsonFullMetadata); - DoTableBatchDeleteSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchDeleteSync(payloadFormat); + } } private void DoTableBatchDeleteSync(TablePayloadFormat format) @@ -828,10 +828,10 @@ private void DoTableBatchDeleteSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchDeleteFailSync() { - DoTableBatchDeleteFailSync(TablePayloadFormat.Json); - DoTableBatchDeleteFailSync(TablePayloadFormat.JsonNoMetadata); - DoTableBatchDeleteFailSync(TablePayloadFormat.JsonFullMetadata); - DoTableBatchDeleteFailSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchDeleteFailSync(payloadFormat); + } } private void DoTableBatchDeleteFailSync(TablePayloadFormat format) @@ -998,10 +998,10 @@ public void TableBatchDeleteFailAPM() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchMergeSync() { - DoTableBatchMergeSync(TablePayloadFormat.Json); - DoTableBatchMergeSync(TablePayloadFormat.JsonNoMetadata); - DoTableBatchMergeSync(TablePayloadFormat.JsonFullMetadata); - DoTableBatchMergeSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchMergeSync(payloadFormat); + } } private void DoTableBatchMergeSync(TablePayloadFormat format) @@ -1039,10 +1039,10 @@ private void DoTableBatchMergeSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchMergeFailSync() { - DoTableBatchMergeFailSync(TablePayloadFormat.Json); - DoTableBatchMergeFailSync(TablePayloadFormat.JsonNoMetadata); - DoTableBatchMergeFailSync(TablePayloadFormat.JsonFullMetadata); - DoTableBatchMergeFailSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchMergeFailSync(payloadFormat); + } } private void DoTableBatchMergeFailSync(TablePayloadFormat format) @@ -1258,10 +1258,10 @@ public void TableBatchMergeFailAPM() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchReplaceSync() { - DoTableBatchReplaceSync(TablePayloadFormat.Json); - DoTableBatchReplaceSync(TablePayloadFormat.JsonNoMetadata); - DoTableBatchReplaceSync(TablePayloadFormat.JsonFullMetadata); - DoTableBatchReplaceSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchReplaceSync(payloadFormat); + } } private void DoTableBatchReplaceSync(TablePayloadFormat format) @@ -1298,10 +1298,10 @@ private void DoTableBatchReplaceSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchReplaceFailSync() { - DoTableBatchReplaceFailSync(TablePayloadFormat.Json); - DoTableBatchReplaceFailSync(TablePayloadFormat.JsonNoMetadata); - DoTableBatchReplaceFailSync(TablePayloadFormat.JsonFullMetadata); - DoTableBatchReplaceFailSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchReplaceFailSync(payloadFormat); + } } private void DoTableBatchReplaceFailSync(TablePayloadFormat format) @@ -1512,10 +1512,10 @@ public void TableBatchReplaceFailAPM() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchAllSupportedOperationsSync() { - DoTableBatchAllSupportedOperationsSync(TablePayloadFormat.Json); - DoTableBatchAllSupportedOperationsSync(TablePayloadFormat.JsonNoMetadata); - DoTableBatchAllSupportedOperationsSync(TablePayloadFormat.JsonFullMetadata); - DoTableBatchAllSupportedOperationsSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchAllSupportedOperationsSync(payloadFormat); + } } private void DoTableBatchAllSupportedOperationsSync(TablePayloadFormat format) @@ -1526,7 +1526,7 @@ private void DoTableBatchAllSupportedOperationsSync(TablePayloadFormat format) string pk = Guid.NewGuid().ToString(); // insert - batch.Insert(GenerateRandomEntity(pk)); + batch.Insert(GenerateRandomEntity(pk), true); // delete { @@ -1597,7 +1597,7 @@ public void TableBatchAllSupportedOperationsAPM() string pk = Guid.NewGuid().ToString(); // insert - batch.Insert(GenerateRandomEntity(pk)); + batch.Insert(GenerateRandomEntity(pk), true); // delete { @@ -1678,10 +1678,10 @@ public void TableBatchAllSupportedOperationsAPM() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchRetrieveSync() { - DoTableBatchRetrieveSync(TablePayloadFormat.Json); - DoTableBatchRetrieveSync(TablePayloadFormat.JsonNoMetadata); - DoTableBatchRetrieveSync(TablePayloadFormat.JsonFullMetadata); - DoTableBatchRetrieveSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchRetrieveSync(payloadFormat); + } } private void DoTableBatchRetrieveSync(TablePayloadFormat format) @@ -1759,7 +1759,9 @@ public void TableBatchRetrieveWithResolverSync() DoTableBatchRetrieveWithResolverSync(TablePayloadFormat.Json); DoTableBatchRetrieveWithResolverSync(TablePayloadFormat.JsonNoMetadata); DoTableBatchRetrieveWithResolverSync(TablePayloadFormat.JsonFullMetadata); + #pragma warning disable 0618 DoTableBatchRetrieveWithResolverSync(TablePayloadFormat.AtomPub); + #pragma warning restore 0618 } private void DoTableBatchRetrieveWithResolverSync(TablePayloadFormat format) @@ -1804,10 +1806,10 @@ private void DoTableBatchRetrieveWithResolverSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchRetrieveWithITableEntitySync() { - DoTableBatchRetrieveWithITableEntitySync(TablePayloadFormat.Json); - DoTableBatchRetrieveWithITableEntitySync(TablePayloadFormat.JsonNoMetadata); - DoTableBatchRetrieveWithITableEntitySync(TablePayloadFormat.JsonFullMetadata); - DoTableBatchRetrieveWithITableEntitySync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchRetrieveWithITableEntitySync(payloadFormat); + } } private void DoTableBatchRetrieveWithITableEntitySync(TablePayloadFormat format) @@ -2067,10 +2069,10 @@ public void TableBatchRetrieveWithITableEntityAPM() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchOperationsWithEmptyKeys() { - DoTableBatchOperationsWithEmptyKeys(TablePayloadFormat.Json); - DoTableBatchOperationsWithEmptyKeys(TablePayloadFormat.JsonNoMetadata); - DoTableBatchOperationsWithEmptyKeys(TablePayloadFormat.JsonFullMetadata); - DoTableBatchOperationsWithEmptyKeys(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchOperationsWithEmptyKeys(payloadFormat); + } } private void DoTableBatchOperationsWithEmptyKeys(TablePayloadFormat format) @@ -2175,10 +2177,10 @@ private void DoTableBatchOperationsWithEmptyKeys(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchInsert1() { - InsertAndDeleteBatchWithNEntities(1, TablePayloadFormat.Json); - InsertAndDeleteBatchWithNEntities(1, TablePayloadFormat.JsonNoMetadata); - InsertAndDeleteBatchWithNEntities(1, TablePayloadFormat.JsonFullMetadata); - InsertAndDeleteBatchWithNEntities(1, TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + InsertAndDeleteBatchWithNEntities(1, payloadFormat); + } } [TestMethod] @@ -2189,10 +2191,10 @@ public void TableBatchInsert1() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchInsert10() { - InsertAndDeleteBatchWithNEntities(10, TablePayloadFormat.Json); - InsertAndDeleteBatchWithNEntities(10, TablePayloadFormat.JsonNoMetadata); - InsertAndDeleteBatchWithNEntities(10, TablePayloadFormat.JsonFullMetadata); - InsertAndDeleteBatchWithNEntities(10, TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + InsertAndDeleteBatchWithNEntities(10, payloadFormat); + } } @@ -2204,10 +2206,10 @@ public void TableBatchInsert10() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchInsert99() { - InsertAndDeleteBatchWithNEntities(99, TablePayloadFormat.Json); - InsertAndDeleteBatchWithNEntities(99, TablePayloadFormat.JsonNoMetadata); - InsertAndDeleteBatchWithNEntities(99, TablePayloadFormat.JsonFullMetadata); - InsertAndDeleteBatchWithNEntities(99, TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + InsertAndDeleteBatchWithNEntities(99, payloadFormat); + } } [TestMethod] @@ -2218,10 +2220,10 @@ public void TableBatchInsert99() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchInsert100() { - InsertAndDeleteBatchWithNEntities(100, TablePayloadFormat.Json); - InsertAndDeleteBatchWithNEntities(100, TablePayloadFormat.JsonNoMetadata); - InsertAndDeleteBatchWithNEntities(100, TablePayloadFormat.JsonFullMetadata); - InsertAndDeleteBatchWithNEntities(100, TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + InsertAndDeleteBatchWithNEntities(100, payloadFormat); + } } [TestMethod] @@ -2232,10 +2234,10 @@ public void TableBatchInsert100() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchInsertPOCO100() { - POCOInsertAndDeleteBatchWithNEntities(100, TablePayloadFormat.Json); - POCOInsertAndDeleteBatchWithNEntities(100, TablePayloadFormat.JsonNoMetadata); - POCOInsertAndDeleteBatchWithNEntities(100, TablePayloadFormat.JsonFullMetadata); - POCOInsertAndDeleteBatchWithNEntities(100, TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + POCOInsertAndDeleteBatchWithNEntities(100, payloadFormat); + } } private void InsertAndDeleteBatchWithNEntities(int n, TablePayloadFormat format) @@ -2274,7 +2276,7 @@ private void POCOInsertAndDeleteBatchWithNEntities(int n, TablePayloadFormat for string pk = Guid.NewGuid().ToString(); for (int m = 0; m < n; m++) { - batch.Insert(new BaseEntity(pk, Guid.NewGuid().ToString())); + batch.Insert(new BaseEntity(pk, Guid.NewGuid().ToString()), true); } IList results = currentTable.ExecuteBatch(batch); @@ -2305,10 +2307,10 @@ private void POCOInsertAndDeleteBatchWithNEntities(int n, TablePayloadFormat for [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchInsertOrMerge1() { - InsertOrMergeBatchWithNEntities(1, TablePayloadFormat.Json); - InsertOrMergeBatchWithNEntities(1, TablePayloadFormat.JsonNoMetadata); - InsertOrMergeBatchWithNEntities(1, TablePayloadFormat.JsonFullMetadata); - InsertOrMergeBatchWithNEntities(1, TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + InsertOrMergeBatchWithNEntities(1, payloadFormat); + } } [TestMethod] @@ -2319,10 +2321,10 @@ public void TableBatchInsertOrMerge1() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchInsertOrMerge10() { - InsertOrMergeBatchWithNEntities(10, TablePayloadFormat.Json); - InsertOrMergeBatchWithNEntities(10, TablePayloadFormat.JsonNoMetadata); - InsertOrMergeBatchWithNEntities(10, TablePayloadFormat.JsonFullMetadata); - InsertOrMergeBatchWithNEntities(10, TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + InsertOrMergeBatchWithNEntities(10, payloadFormat); + } } @@ -2334,10 +2336,10 @@ public void TableBatchInsertOrMerge10() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchInsertOrMerge99() { - InsertOrMergeBatchWithNEntities(99, TablePayloadFormat.Json); - InsertOrMergeBatchWithNEntities(99, TablePayloadFormat.JsonNoMetadata); - InsertOrMergeBatchWithNEntities(99, TablePayloadFormat.JsonFullMetadata); - InsertOrMergeBatchWithNEntities(99, TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + InsertOrMergeBatchWithNEntities(99, payloadFormat); + } } [TestMethod] @@ -2348,10 +2350,10 @@ public void TableBatchInsertOrMerge99() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchInsertOrMerge100() { - InsertOrMergeBatchWithNEntities(100, TablePayloadFormat.Json); - InsertOrMergeBatchWithNEntities(100, TablePayloadFormat.JsonNoMetadata); - InsertOrMergeBatchWithNEntities(100, TablePayloadFormat.JsonFullMetadata); - InsertOrMergeBatchWithNEntities(100, TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + InsertOrMergeBatchWithNEntities(100, payloadFormat); + } } private void InsertOrMergeBatchWithNEntities(int n, TablePayloadFormat format) @@ -2473,10 +2475,10 @@ public void TableBatchAddMultiQueryShouldThrow() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchWithMultipleOperationsOnSameEntityShouldFail() { - DoTableBatchWithMultipleOperationsOnSameEntityShouldFail(TablePayloadFormat.Json); - DoTableBatchWithMultipleOperationsOnSameEntityShouldFail(TablePayloadFormat.JsonNoMetadata); - DoTableBatchWithMultipleOperationsOnSameEntityShouldFail(TablePayloadFormat.JsonFullMetadata); - DoTableBatchWithMultipleOperationsOnSameEntityShouldFail(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchWithMultipleOperationsOnSameEntityShouldFail(payloadFormat); + } } private void DoTableBatchWithMultipleOperationsOnSameEntityShouldFail(TablePayloadFormat format) @@ -2519,10 +2521,10 @@ private void DoTableBatchWithMultipleOperationsOnSameEntityShouldFail(TablePaylo [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchOver100EntitiesShouldThrow() { - DoTableBatchOver100EntitiesShouldThrow(TablePayloadFormat.Json); - DoTableBatchOver100EntitiesShouldThrow(TablePayloadFormat.JsonNoMetadata); - DoTableBatchOver100EntitiesShouldThrow(TablePayloadFormat.JsonFullMetadata); - DoTableBatchOver100EntitiesShouldThrow(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchOver100EntitiesShouldThrow(payloadFormat); + } } private void DoTableBatchOver100EntitiesShouldThrow(TablePayloadFormat format) @@ -2555,10 +2557,10 @@ private void DoTableBatchOver100EntitiesShouldThrow(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchEntityOver1MBShouldThrow() { - DoTableBatchEntityOver1MBShouldThrow(TablePayloadFormat.Json); - DoTableBatchEntityOver1MBShouldThrow(TablePayloadFormat.JsonNoMetadata); - DoTableBatchEntityOver1MBShouldThrow(TablePayloadFormat.JsonFullMetadata); - DoTableBatchEntityOver1MBShouldThrow(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchEntityOver1MBShouldThrow(payloadFormat); + } } private void DoTableBatchEntityOver1MBShouldThrow(TablePayloadFormat format) @@ -2592,10 +2594,10 @@ private void DoTableBatchEntityOver1MBShouldThrow(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchOver4MBShouldThrow() { - DoTableBatchOver4MBShouldThrow(TablePayloadFormat.Json); - DoTableBatchOver4MBShouldThrow(TablePayloadFormat.JsonNoMetadata); - DoTableBatchOver4MBShouldThrow(TablePayloadFormat.JsonFullMetadata); - DoTableBatchOver4MBShouldThrow(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchOver4MBShouldThrow(payloadFormat); + } } private void DoTableBatchOver4MBShouldThrow(TablePayloadFormat format) @@ -2716,10 +2718,10 @@ public void TableBatchAddQueryAndOneMoreOperationShouldThrow() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchEmptyBatchShouldThrow() { - DoTableBatchEmptyBatchShouldThrow(TablePayloadFormat.Json); - DoTableBatchEmptyBatchShouldThrow(TablePayloadFormat.JsonNoMetadata); - DoTableBatchEmptyBatchShouldThrow(TablePayloadFormat.JsonFullMetadata); - DoTableBatchEmptyBatchShouldThrow(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchEmptyBatchShouldThrow(payloadFormat); + } } private void DoTableBatchEmptyBatchShouldThrow(TablePayloadFormat format) @@ -2783,10 +2785,10 @@ public void TableBatchLockToPartitionKey() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableBatchWithPropertyOver255CharsShouldThrow() { - DoTableBatchWithPropertyOver255CharsShouldThrow(TablePayloadFormat.Json); - DoTableBatchWithPropertyOver255CharsShouldThrow(TablePayloadFormat.JsonNoMetadata); - DoTableBatchWithPropertyOver255CharsShouldThrow(TablePayloadFormat.JsonFullMetadata); - DoTableBatchWithPropertyOver255CharsShouldThrow(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchWithPropertyOver255CharsShouldThrow(payloadFormat); + } } private void DoTableBatchWithPropertyOver255CharsShouldThrow(TablePayloadFormat format) @@ -2823,10 +2825,10 @@ private void DoTableBatchWithPropertyOver255CharsShouldThrow(TablePayloadFormat [TestCategory(TenantTypeCategory.Cloud)] public void TableBatchOperationWithRetryHasCorrectNumberOfResults() { - DoTableBatchOperationWithRetryHasCorrectNumberOfResults(TablePayloadFormat.Json); - DoTableBatchOperationWithRetryHasCorrectNumberOfResults(TablePayloadFormat.JsonNoMetadata); - DoTableBatchOperationWithRetryHasCorrectNumberOfResults(TablePayloadFormat.JsonFullMetadata); - DoTableBatchOperationWithRetryHasCorrectNumberOfResults(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableBatchOperationWithRetryHasCorrectNumberOfResults(payloadFormat); + } } private void DoTableBatchOperationWithRetryHasCorrectNumberOfResults(TablePayloadFormat format) @@ -2943,7 +2945,7 @@ public void TableBatchOnSecondary() private static void AddInsertToBatch(string pk, TableBatchOperation batch) { - batch.Insert(GenerateRandomEntity(pk)); + batch.Insert(GenerateRandomEntity(pk), true); } private static DynamicTableEntity GenerateRandomEntity(string pk) diff --git a/Test/ClassLibraryCommon/Table/TableEntityEncryptionTests.cs b/Test/ClassLibraryCommon/Table/TableEntityEncryptionTests.cs index 57a49a8d9..664dd63e4 100644 --- a/Test/ClassLibraryCommon/Table/TableEntityEncryptionTests.cs +++ b/Test/ClassLibraryCommon/Table/TableEntityEncryptionTests.cs @@ -17,6 +17,7 @@ namespace Microsoft.WindowsAzure.Storage.Table { +#pragma warning disable 0618 using Microsoft.Azure.KeyVault; using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.WindowsAzure.Storage.Core; @@ -1101,4 +1102,5 @@ private static DynamicTableEntity GenerateRandomEntity(string pk) return ent; } } +#pragma warning restore 0618 } diff --git a/Test/ClassLibraryCommon/Table/TableOperationUnitTests.cs b/Test/ClassLibraryCommon/Table/TableOperationUnitTests.cs index d923b4ae6..9b49391c7 100644 --- a/Test/ClassLibraryCommon/Table/TableOperationUnitTests.cs +++ b/Test/ClassLibraryCommon/Table/TableOperationUnitTests.cs @@ -109,10 +109,10 @@ public void MyTestCleanup() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableOperationInsertSync() { - DoTableOperationInsertSync(TablePayloadFormat.Json); - DoTableOperationInsertSync(TablePayloadFormat.JsonNoMetadata); - DoTableOperationInsertSync(TablePayloadFormat.JsonFullMetadata); - DoTableOperationInsertSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableOperationInsertSync(payloadFormat); + } } private void DoTableOperationInsertSync(TablePayloadFormat format) @@ -166,10 +166,10 @@ private void DoTableOperationInsertSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableOperationInsertWithEchoContentSync() { - DoTableOperationInsertWithEchoContentSync(TablePayloadFormat.Json); - DoTableOperationInsertWithEchoContentSync(TablePayloadFormat.JsonNoMetadata); - DoTableOperationInsertWithEchoContentSync(TablePayloadFormat.JsonFullMetadata); - DoTableOperationInsertWithEchoContentSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableOperationInsertWithEchoContentSync(payloadFormat); + } } private void DoTableOperationInsertWithEchoContentSync(TablePayloadFormat format) @@ -181,15 +181,18 @@ private void DoTableOperationInsertWithEchoContentSync(TablePayloadFormat format TableResult insertResult = currentTable.Execute(TableOperation.Insert(ent, false)); Assert.AreEqual(HttpStatusCode.NoContent, (HttpStatusCode)insertResult.HttpStatusCode); + Assert.IsNotNull(insertResult.Etag); ent = new DynamicTableEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; insertResult = currentTable.Execute(TableOperation.Insert(ent, true)); Assert.AreEqual(HttpStatusCode.Created, (HttpStatusCode)insertResult.HttpStatusCode); + Assert.IsNotNull(insertResult.Etag); // Default is false. ent = new DynamicTableEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; insertResult = currentTable.Execute(TableOperation.Insert(ent)); Assert.AreEqual(HttpStatusCode.NoContent, (HttpStatusCode)insertResult.HttpStatusCode); + Assert.IsNotNull(insertResult.Etag); } [TestMethod] @@ -200,10 +203,10 @@ private void DoTableOperationInsertWithEchoContentSync(TablePayloadFormat format [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableOperationInsertSingleQuoteSync() { - DoTableOperationInsertSingleQuoteSync(TablePayloadFormat.Json); - DoTableOperationInsertSingleQuoteSync(TablePayloadFormat.JsonNoMetadata); - DoTableOperationInsertSingleQuoteSync(TablePayloadFormat.JsonFullMetadata); - DoTableOperationInsertSingleQuoteSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableOperationInsertSingleQuoteSync(payloadFormat); + } } private void DoTableOperationInsertSingleQuoteSync(TablePayloadFormat format) @@ -264,10 +267,10 @@ private void DoTableOperationInsertSingleQuoteSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableOperationInsertConflictSync() { - DoTableOperationInsertConflictSync(TablePayloadFormat.Json); - DoTableOperationInsertConflictSync(TablePayloadFormat.JsonNoMetadata); - DoTableOperationInsertConflictSync(TablePayloadFormat.JsonFullMetadata); - DoTableOperationInsertConflictSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableOperationInsertConflictSync(payloadFormat); + } } private void DoTableOperationInsertConflictSync(TablePayloadFormat format) @@ -303,10 +306,10 @@ private void DoTableOperationInsertConflictSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.Cloud)] public void CloudTableMaximumExecutionTime() { - DoCloudTableMaximumExecutionTime(TablePayloadFormat.Json); - DoCloudTableMaximumExecutionTime(TablePayloadFormat.JsonNoMetadata); - DoCloudTableMaximumExecutionTime(TablePayloadFormat.JsonFullMetadata); - DoCloudTableMaximumExecutionTime(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoCloudTableMaximumExecutionTime(payloadFormat); + } } private void DoCloudTableMaximumExecutionTime(TablePayloadFormat format) @@ -504,10 +507,10 @@ public void TableOperationInsertConflictTask() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableOperationInsertOrMergeSync() { - DoTableOperationInsertOrMergeSync(TablePayloadFormat.Json); - DoTableOperationInsertOrMergeSync(TablePayloadFormat.JsonNoMetadata); - DoTableOperationInsertOrMergeSync(TablePayloadFormat.JsonFullMetadata); - DoTableOperationInsertOrMergeSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableOperationInsertOrMergeSync(payloadFormat); + } } private void DoTableOperationInsertOrMergeSync(TablePayloadFormat format) @@ -617,10 +620,10 @@ public void TableOperationInsertOrMergeAPM() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableOperationInsertOrReplaceSync() { - DoTableOperationInsertOrReplaceSync(TablePayloadFormat.Json); - DoTableOperationInsertOrReplaceSync(TablePayloadFormat.JsonNoMetadata); - DoTableOperationInsertOrReplaceSync(TablePayloadFormat.JsonFullMetadata); - DoTableOperationInsertOrReplaceSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableOperationInsertOrReplaceSync(payloadFormat); + } } private void DoTableOperationInsertOrReplaceSync(TablePayloadFormat format) @@ -724,10 +727,10 @@ public void TableOperationInsertOrReplaceAPM() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableOperationDeleteSync() { - DoTableOperationDeleteSync(TablePayloadFormat.Json); - DoTableOperationDeleteSync(TablePayloadFormat.JsonNoMetadata); - DoTableOperationDeleteSync(TablePayloadFormat.JsonFullMetadata); - DoTableOperationDeleteSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableOperationDeleteSync(payloadFormat); + } } private void DoTableOperationDeleteSync(TablePayloadFormat format) @@ -760,10 +763,10 @@ private void DoTableOperationDeleteSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableOperationDeleteFailSync() { - DoTableOperationDeleteFailSync(TablePayloadFormat.Json); - DoTableOperationDeleteFailSync(TablePayloadFormat.JsonNoMetadata); - DoTableOperationDeleteFailSync(TablePayloadFormat.JsonFullMetadata); - DoTableOperationDeleteFailSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableOperationDeleteFailSync(payloadFormat); + } } private void DoTableOperationDeleteFailSync(TablePayloadFormat format) @@ -936,10 +939,10 @@ public void TableOperationDeleteFailAPM() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableOperationMergeSync() { - DoTableOperationMergeSync(TablePayloadFormat.Json); - DoTableOperationMergeSync(TablePayloadFormat.JsonNoMetadata); - DoTableOperationMergeSync(TablePayloadFormat.JsonFullMetadata); - DoTableOperationMergeSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableOperationMergeSync(payloadFormat); + } } private void DoTableOperationMergeSync(TablePayloadFormat format) @@ -974,10 +977,10 @@ private void DoTableOperationMergeSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableOperationMergeFailSync() { - DoTableOperationMergeFailSync(TablePayloadFormat.Json); - DoTableOperationMergeFailSync(TablePayloadFormat.JsonNoMetadata); - DoTableOperationMergeFailSync(TablePayloadFormat.JsonFullMetadata); - DoTableOperationMergeFailSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableOperationMergeFailSync(payloadFormat); + } } private void DoTableOperationMergeFailSync(TablePayloadFormat format) @@ -1177,10 +1180,10 @@ public void TableOperationMergeFailAPM() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableOperationReplaceSync() { - DoTableOperationReplaceSync(TablePayloadFormat.Json); - DoTableOperationReplaceSync(TablePayloadFormat.JsonNoMetadata); - DoTableOperationReplaceSync(TablePayloadFormat.JsonFullMetadata); - DoTableOperationReplaceSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableOperationReplaceSync(payloadFormat); + } } private void DoTableOperationReplaceSync(TablePayloadFormat format) @@ -1214,10 +1217,10 @@ private void DoTableOperationReplaceSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableOperationReplaceFailSync() { - DoTableOperationReplaceFailSync(TablePayloadFormat.Json); - DoTableOperationReplaceFailSync(TablePayloadFormat.JsonNoMetadata); - DoTableOperationReplaceFailSync(TablePayloadFormat.JsonFullMetadata); - DoTableOperationReplaceFailSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableOperationReplaceFailSync(payloadFormat); + } } private void DoTableOperationReplaceFailSync(TablePayloadFormat format) @@ -1541,10 +1544,10 @@ public void TableEdmTypeCheck() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableRetrieveSync() { - DoTableRetrieveSync(TablePayloadFormat.Json); - DoTableRetrieveSync(TablePayloadFormat.JsonNoMetadata); - DoTableRetrieveSync(TablePayloadFormat.JsonFullMetadata); - DoTableRetrieveSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableRetrieveSync(payloadFormat); + } } private void DoTableRetrieveSync(TablePayloadFormat format) @@ -1627,10 +1630,10 @@ private void DoTableRetrieveSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableRetrieveSyncWithReflection() { - DoTableRetrieveSyncWithReflection(TablePayloadFormat.Json); - DoTableRetrieveSyncWithReflection(TablePayloadFormat.JsonNoMetadata); - DoTableRetrieveSyncWithReflection(TablePayloadFormat.JsonFullMetadata); - DoTableRetrieveSyncWithReflection(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableRetrieveSyncWithReflection(payloadFormat); + } } private void DoTableRetrieveSyncWithReflection(TablePayloadFormat format) @@ -1796,10 +1799,10 @@ public void TableRetrieveEntityPropertySetter() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableRetrieveWithResolverSync() { - DoTableRetrieveWithResolverSync(TablePayloadFormat.Json); - DoTableRetrieveWithResolverSync(TablePayloadFormat.JsonNoMetadata); - DoTableRetrieveWithResolverSync(TablePayloadFormat.JsonFullMetadata); - DoTableRetrieveWithResolverSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableRetrieveWithResolverSync(payloadFormat); + } } private void DoTableRetrieveWithResolverSync(TablePayloadFormat format) @@ -1842,10 +1845,10 @@ private void DoTableRetrieveWithResolverSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableRetrieveWithIgnoreAttributeWrite() { - DoTableRetrieveWithIgnoreAttributeWrite(TablePayloadFormat.Json); - DoTableRetrieveWithIgnoreAttributeWrite(TablePayloadFormat.JsonNoMetadata); - DoTableRetrieveWithIgnoreAttributeWrite(TablePayloadFormat.JsonFullMetadata); - DoTableRetrieveWithIgnoreAttributeWrite(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableRetrieveWithIgnoreAttributeWrite(payloadFormat); + } } private void DoTableRetrieveWithIgnoreAttributeWrite(TablePayloadFormat format) @@ -1900,10 +1903,10 @@ private void DoTableRetrieveWithIgnoreAttributeWrite(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableRetrieveWithIgnoreAttributeRead() { - DoTableRetrieveWithIgnoreAttributeRead(TablePayloadFormat.Json); - DoTableRetrieveWithIgnoreAttributeRead(TablePayloadFormat.JsonNoMetadata); - DoTableRetrieveWithIgnoreAttributeRead(TablePayloadFormat.JsonFullMetadata); - DoTableRetrieveWithIgnoreAttributeRead(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableRetrieveWithIgnoreAttributeRead(payloadFormat); + } } private void DoTableRetrieveWithIgnoreAttributeRead(TablePayloadFormat format) @@ -2290,10 +2293,10 @@ public void TableRetrieveWithResolverAPM() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableOperationsWithEmptyKeys() { - DoTableOperationsWithEmptyKeys(TablePayloadFormat.Json); - DoTableOperationsWithEmptyKeys(TablePayloadFormat.JsonNoMetadata); - DoTableOperationsWithEmptyKeys(TablePayloadFormat.JsonFullMetadata); - DoTableOperationsWithEmptyKeys(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableOperationsWithEmptyKeys(payloadFormat); + } } private void DoTableOperationsWithEmptyKeys(TablePayloadFormat format) @@ -2447,7 +2450,7 @@ public void TableOperationRetrieveJsonNoMetadataResolverFail() () => currentTable.Execute(TableOperation.Retrieve(ent.PartitionKey, ent.RowKey), options, null), "Invalid property resolver should throw"); - Assert.AreEqual("The custom property resolver delegate threw an exception. Check the inner exception for more details", ex.Message); + Assert.AreEqual("The custom property resolver delegate threw an exception. Check the inner exception for more details.", ex.Message); Assert.IsInstanceOfType(ex.InnerException, typeof(InvalidOperationException)); } #endregion @@ -2462,10 +2465,10 @@ public void TableOperationRetrieveJsonNoMetadataResolverFail() [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableOperationInsertOver1MBSync() { - DoTableOperationInsertOver1MBSync(TablePayloadFormat.Json); - DoTableOperationInsertOver1MBSync(TablePayloadFormat.JsonNoMetadata); - DoTableOperationInsertOver1MBSync(TablePayloadFormat.JsonFullMetadata); - DoTableOperationInsertOver1MBSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableOperationInsertOver1MBSync(payloadFormat); + } } private void DoTableOperationInsertOver1MBSync(TablePayloadFormat format) @@ -2533,10 +2536,10 @@ public void TableOpsWithNonDerivedEntities() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void SimpleTableEntitySerilization() { - DoSimpleTableEntitySerilization(TablePayloadFormat.Json); - DoSimpleTableEntitySerilization(TablePayloadFormat.JsonNoMetadata); - DoSimpleTableEntitySerilization(TablePayloadFormat.JsonFullMetadata); - DoSimpleTableEntitySerilization(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoSimpleTableEntitySerilization(payloadFormat); + } } private void DoSimpleTableEntitySerilization(TablePayloadFormat format) diff --git a/Test/ClassLibraryCommon/Table/TableQueryGenericTests.cs b/Test/ClassLibraryCommon/Table/TableQueryGenericTests.cs index efb4d369d..438693d27 100644 --- a/Test/ClassLibraryCommon/Table/TableQueryGenericTests.cs +++ b/Test/ClassLibraryCommon/Table/TableQueryGenericTests.cs @@ -25,6 +25,7 @@ namespace Microsoft.WindowsAzure.Storage.Table { +#pragma warning disable 0618 [TestClass] public class TableQueryGenericTests : TableTestBase { @@ -123,10 +124,10 @@ public void MyTestCleanup() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableGenericQueryBasicSync() { - DoTableGenericQueryBasicSync(TablePayloadFormat.Json); - DoTableGenericQueryBasicSync(TablePayloadFormat.JsonNoMetadata); - DoTableGenericQueryBasicSync(TablePayloadFormat.JsonFullMetadata); - DoTableGenericQueryBasicSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableGenericQueryBasicSync(payloadFormat); + } } private void DoTableGenericQueryBasicSync(TablePayloadFormat format) @@ -152,10 +153,10 @@ private void DoTableGenericQueryBasicSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableGenericQueryComplexWithoutPropertyResolverSync() { - DoTableGenericQueryComplexWithoutPropertyResolverSync(TablePayloadFormat.Json); - DoTableGenericQueryComplexWithoutPropertyResolverSync(TablePayloadFormat.JsonNoMetadata); - DoTableGenericQueryComplexWithoutPropertyResolverSync(TablePayloadFormat.JsonFullMetadata); - DoTableGenericQueryComplexWithoutPropertyResolverSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableGenericQueryComplexWithoutPropertyResolverSync(payloadFormat); + } } private void DoTableGenericQueryComplexWithoutPropertyResolverSync(TablePayloadFormat format) @@ -193,10 +194,10 @@ private void DoTableGenericQueryComplexWithoutPropertyResolverSync(TablePayloadF [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableGenericQueryWithContinuationSync() { - DoTableGenericQueryWithContinuationSync(TablePayloadFormat.Json); - DoTableGenericQueryWithContinuationSync(TablePayloadFormat.JsonNoMetadata); - DoTableGenericQueryWithContinuationSync(TablePayloadFormat.JsonFullMetadata); - DoTableGenericQueryWithContinuationSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableGenericQueryWithContinuationSync(payloadFormat); + } } private void DoTableGenericQueryWithContinuationSync(TablePayloadFormat format) @@ -718,10 +719,10 @@ public void TableExecuteQuerySegmentedQueryResolverTokenRequestOptionsOperationC [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableGenericQueryWithFilter() { - DoTableGenericQueryWithFilter(TablePayloadFormat.Json); - DoTableGenericQueryWithFilter(TablePayloadFormat.JsonNoMetadata); - DoTableGenericQueryWithFilter(TablePayloadFormat.JsonFullMetadata); - DoTableGenericQueryWithFilter(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableGenericQueryWithFilter(payloadFormat); + } } private void DoTableGenericQueryWithFilter(TablePayloadFormat format) @@ -751,10 +752,10 @@ private void DoTableGenericQueryWithFilter(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableGenericQueryEnumerateTwice() { - DoTableGenericQueryEnumerateTwice(TablePayloadFormat.Json); - DoTableGenericQueryEnumerateTwice(TablePayloadFormat.JsonNoMetadata); - DoTableGenericQueryEnumerateTwice(TablePayloadFormat.JsonFullMetadata); - DoTableGenericQueryEnumerateTwice(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableGenericQueryEnumerateTwice(payloadFormat); + } } private void DoTableGenericQueryEnumerateTwice(TablePayloadFormat format) @@ -803,10 +804,10 @@ private void DoTableGenericQueryEnumerateTwice(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableGenericQueryProjection() { - DoTableGenericQueryProjection(TablePayloadFormat.Json); - DoTableGenericQueryProjection(TablePayloadFormat.JsonNoMetadata); - DoTableGenericQueryProjection(TablePayloadFormat.JsonFullMetadata); - DoTableGenericQueryProjection(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableGenericQueryProjection(payloadFormat); + } } private void DoTableGenericQueryProjection(TablePayloadFormat format) @@ -828,6 +829,94 @@ private void DoTableGenericQueryProjection(TablePayloadFormat format) } } + [TestMethod] + [Description("Projection test with nullable values.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void TableGenericQueryProjectionWithNull() + { + CloudTable table = tableClient.GetTableReference(GenerateRandomTableName()); + try + { + // Create a new table so we don't pollute the main query table + table.CreateIfNotExists(); + + // Insert an entity which is missing B, a string, and E, an int + DynamicTableEntity entity = new DynamicTableEntity() + { + PartitionKey = Guid.NewGuid().ToString(), + RowKey = DateTime.Now.Ticks.ToString() + }; + entity.Properties.Add("A", new EntityProperty("a")); + table.Execute(TableOperation.Insert(entity)); + + DoTableGenericQueryProjectionWithSpecialCases(table, TablePayloadFormat.Json, false); + DoTableGenericQueryProjectionWithSpecialCases(table, TablePayloadFormat.AtomPub, false); + DoTableGenericQueryProjectionWithSpecialCases(table, TablePayloadFormat.Json, true); + DoTableGenericQueryProjectionWithSpecialCases(table, TablePayloadFormat.AtomPub, true); + } + finally + { + table.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Projection test with incorrect types.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void TableGenericQueryProjectionWithIncorrectTypes() + { + CloudTable table = tableClient.GetTableReference(GenerateRandomTableName()); + try + { + // Create a new table so we don't pollute the main query table + table.CreateIfNotExists(); + + // Insert an entity with B as an int and E as a bool + // BaseEntity defines B as a string and E as an int + DynamicTableEntity entity = new DynamicTableEntity() + { + PartitionKey = Guid.NewGuid().ToString(), + RowKey = DateTime.Now.Ticks.ToString() + }; + entity.Properties.Add("A", new EntityProperty("a")); + entity.Properties.Add("B", new EntityProperty(1234)); + entity.Properties.Add("E", new EntityProperty(true)); + table.Execute(TableOperation.Insert(entity)); + + DoTableGenericQueryProjectionWithSpecialCases(table, TablePayloadFormat.Json, false); + DoTableGenericQueryProjectionWithSpecialCases(table, TablePayloadFormat.AtomPub, false); + DoTableGenericQueryProjectionWithSpecialCases(table, TablePayloadFormat.Json, true); + DoTableGenericQueryProjectionWithSpecialCases(table, TablePayloadFormat.AtomPub, true); + } + finally + { + table.DeleteIfExists(); + } + } + + public void DoTableGenericQueryProjectionWithSpecialCases(CloudTable table, TablePayloadFormat format, bool disableCompiledSerializers) + { + table.ServiceClient.DefaultRequestOptions.PayloadFormat = format; + BaseEntity.DisableCompiledSerializers = disableCompiledSerializers; + + // Query on A, B, and E + TableQuery query = new TableQuery().Select(new List() { "A", "B", "E" }); + IEnumerable entities = table.ExecuteQuery(query, null); + + // Verify A has a set value and B and E have type defaults + Assert.AreEqual(1, entities.Count()); + BaseEntity entity = entities.First(); + Assert.AreEqual("a", entity.A); + Assert.IsNull(entity.B); + Assert.AreEqual(0, entity.E); + } + [TestMethod] [Description("Basic with resolver")] [TestCategory(ComponentCategory.Table)] @@ -836,10 +925,10 @@ private void DoTableGenericQueryProjection(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableGenericWithResolver() { - DoTableGenericWithResolver(TablePayloadFormat.Json); - DoTableGenericWithResolver(TablePayloadFormat.JsonNoMetadata); - DoTableGenericWithResolver(TablePayloadFormat.JsonFullMetadata); - DoTableGenericWithResolver(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableGenericWithResolver(payloadFormat); + } } private void DoTableGenericWithResolver(TablePayloadFormat format) @@ -1011,10 +1100,10 @@ public void TableGenericWithResolverTask() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryResolverWithDynamic() { - DoTableQueryResolverWithDynamic(TablePayloadFormat.Json); - DoTableQueryResolverWithDynamic(TablePayloadFormat.JsonNoMetadata); - DoTableQueryResolverWithDynamic(TablePayloadFormat.JsonFullMetadata); - DoTableQueryResolverWithDynamic(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryResolverWithDynamic(payloadFormat); + } } private void DoTableQueryResolverWithDynamic(TablePayloadFormat format) @@ -1056,10 +1145,10 @@ private void DoTableQueryResolverWithDynamic(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQuerySegmentedResolver() { - DoTableQuerySegmentedResolver(TablePayloadFormat.Json); - DoTableQuerySegmentedResolver(TablePayloadFormat.JsonNoMetadata); - DoTableQuerySegmentedResolver(TablePayloadFormat.JsonFullMetadata); - DoTableQuerySegmentedResolver(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQuerySegmentedResolver(payloadFormat); + } } private void DoTableQuerySegmentedResolver(TablePayloadFormat format) @@ -1112,10 +1201,10 @@ private void DoTableQuerySegmentedResolver(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQuerySegmentedResolverWithDynamic() { - DoTableQuerySegmentedResolverWithDynamic(TablePayloadFormat.Json); - DoTableQuerySegmentedResolverWithDynamic(TablePayloadFormat.JsonNoMetadata); - DoTableQuerySegmentedResolverWithDynamic(TablePayloadFormat.JsonFullMetadata); - DoTableQuerySegmentedResolverWithDynamic(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQuerySegmentedResolverWithDynamic(payloadFormat); + } } private void DoTableQuerySegmentedResolverWithDynamic(TablePayloadFormat format) @@ -1287,10 +1376,10 @@ public void TableQueryResolverWithDynamicTask() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableGenericQueryOnSupportedTypes() { - DoTableGenericQueryOnSupportedTypes(TablePayloadFormat.Json); - DoTableGenericQueryOnSupportedTypes(TablePayloadFormat.JsonNoMetadata); - DoTableGenericQueryOnSupportedTypes(TablePayloadFormat.JsonFullMetadata); - DoTableGenericQueryOnSupportedTypes(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableGenericQueryOnSupportedTypes(payloadFormat); + } } private void DoTableGenericQueryOnSupportedTypes(TablePayloadFormat format) @@ -1424,10 +1513,10 @@ private void DoTableGenericQueryOnSupportedTypes(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableGenericQueryWithSpecificOnSupportedTypes() { - DoTableGenericQueryWithSpecificOnSupportedTypes(TablePayloadFormat.Json); - DoTableGenericQueryWithSpecificOnSupportedTypes(TablePayloadFormat.JsonNoMetadata); - DoTableGenericQueryWithSpecificOnSupportedTypes(TablePayloadFormat.JsonFullMetadata); - DoTableGenericQueryWithSpecificOnSupportedTypes(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableGenericQueryWithSpecificOnSupportedTypes(payloadFormat); + } } private void DoTableGenericQueryWithSpecificOnSupportedTypes(TablePayloadFormat format) @@ -1559,10 +1648,10 @@ private void DoTableGenericQueryWithSpecificOnSupportedTypes(TablePayloadFormat [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryGenericWithTakeCount() { - DoTableQueryGenericWithTakeCount(TablePayloadFormat.Json); - DoTableQueryGenericWithTakeCount(TablePayloadFormat.JsonNoMetadata); - DoTableQueryGenericWithTakeCount(TablePayloadFormat.JsonFullMetadata); - DoTableQueryGenericWithTakeCount(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryGenericWithTakeCount(payloadFormat); + } } private void DoTableQueryGenericWithTakeCount(TablePayloadFormat format) @@ -1601,10 +1690,10 @@ private void DoTableQueryGenericWithTakeCount(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryGenericWithTakeCountAndResolver() { - DoTableQueryGenericWithTakeCountAndResolver(TablePayloadFormat.Json); - DoTableQueryGenericWithTakeCountAndResolver(TablePayloadFormat.JsonNoMetadata); - DoTableQueryGenericWithTakeCountAndResolver(TablePayloadFormat.JsonFullMetadata); - DoTableQueryGenericWithTakeCountAndResolver(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryGenericWithTakeCountAndResolver(payloadFormat); + } } private void DoTableQueryGenericWithTakeCountAndResolver(TablePayloadFormat format) @@ -1637,10 +1726,10 @@ private void DoTableQueryGenericWithTakeCountAndResolver(TablePayloadFormat form [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableGenericQueryWithInternalType() { - DoTableGenericQueryWithInternalType(TablePayloadFormat.Json); - DoTableGenericQueryWithInternalType(TablePayloadFormat.JsonNoMetadata); - DoTableGenericQueryWithInternalType(TablePayloadFormat.JsonFullMetadata); - DoTableGenericQueryWithInternalType(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableGenericQueryWithInternalType(payloadFormat); + } } private void DoTableGenericQueryWithInternalType(TablePayloadFormat format) @@ -1717,10 +1806,10 @@ public void TableGenericQueryWithInvalidTakeCount() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableGenericQueryWithInvalidQuery() { - DoTableGenericQueryWithInvalidQuery(TablePayloadFormat.Json); - DoTableGenericQueryWithInvalidQuery(TablePayloadFormat.JsonNoMetadata); - DoTableGenericQueryWithInvalidQuery(TablePayloadFormat.JsonFullMetadata); - DoTableGenericQueryWithInvalidQuery(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableGenericQueryWithInvalidQuery(payloadFormat); + } } private void DoTableGenericQueryWithInvalidQuery(TablePayloadFormat format) @@ -1850,4 +1939,5 @@ private static BaseEntity GenerateRandomEntity(string pk) } #endregion } +#pragma warning restore 0618 } diff --git a/Test/ClassLibraryCommon/Table/TableQueryTests.cs b/Test/ClassLibraryCommon/Table/TableQueryTests.cs index 5863a6ec3..4c7494c07 100644 --- a/Test/ClassLibraryCommon/Table/TableQueryTests.cs +++ b/Test/ClassLibraryCommon/Table/TableQueryTests.cs @@ -127,10 +127,10 @@ public void MyTestCleanup() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryBasicSync() { - DoTableQueryBasicSync(TablePayloadFormat.Json); - DoTableQueryBasicSync(TablePayloadFormat.JsonNoMetadata); - DoTableQueryBasicSync(TablePayloadFormat.JsonFullMetadata); - DoTableQueryBasicSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryBasicSync(payloadFormat); + } } private void DoTableQueryBasicSync(TablePayloadFormat format) @@ -158,10 +158,10 @@ private void DoTableQueryBasicSync(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryWithContinuationSync() { - DoTableQueryWithContinuationSync(TablePayloadFormat.Json); - DoTableQueryWithContinuationSync(TablePayloadFormat.JsonNoMetadata); - DoTableQueryWithContinuationSync(TablePayloadFormat.JsonFullMetadata); - DoTableQueryWithContinuationSync(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryWithContinuationSync(payloadFormat); + } } private void DoTableQueryWithContinuationSync(TablePayloadFormat format) @@ -629,10 +629,10 @@ public void TableExecuteQuerySegmentedQueryResolverTokenRequestOptionsOperationC [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryWithFilter() { - DoTableQueryWithFilter(TablePayloadFormat.Json); - DoTableQueryWithFilter(TablePayloadFormat.JsonNoMetadata); - DoTableQueryWithFilter(TablePayloadFormat.JsonFullMetadata); - DoTableQueryWithFilter(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryWithFilter(payloadFormat); + } } private void DoTableQueryWithFilter(TablePayloadFormat format) @@ -663,10 +663,10 @@ private void DoTableQueryWithFilter(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryEnumerateTwice() { - DoTableQueryEnumerateTwice(TablePayloadFormat.Json); - DoTableQueryEnumerateTwice(TablePayloadFormat.JsonNoMetadata); - DoTableQueryEnumerateTwice(TablePayloadFormat.JsonFullMetadata); - DoTableQueryEnumerateTwice(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryEnumerateTwice(payloadFormat); + } } private void DoTableQueryEnumerateTwice(TablePayloadFormat format) @@ -714,10 +714,10 @@ private void DoTableQueryEnumerateTwice(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryProjection() { - DoTableQueryProjection(TablePayloadFormat.Json); - DoTableQueryProjection(TablePayloadFormat.JsonNoMetadata); - DoTableQueryProjection(TablePayloadFormat.JsonFullMetadata); - DoTableQueryProjection(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryProjection(payloadFormat); + } } private void DoTableQueryProjection(TablePayloadFormat format) @@ -746,10 +746,10 @@ private void DoTableQueryProjection(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryOnSupportedTypes() { - DoTableQueryOnSupportedTypes(TablePayloadFormat.Json); - DoTableQueryOnSupportedTypes(TablePayloadFormat.JsonNoMetadata); - DoTableQueryOnSupportedTypes(TablePayloadFormat.JsonFullMetadata); - DoTableQueryOnSupportedTypes(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryOnSupportedTypes(payloadFormat); + } } private void DoTableQueryOnSupportedTypes(TablePayloadFormat format) @@ -775,6 +775,7 @@ private void DoTableQueryOnSupportedTypes(TablePayloadFormat format) complexEntity.Bool = m % 2 == 0 ? true : false; complexEntity.BoolPrimitive = m % 2 == 0 ? true : false; complexEntity.Double = m + ((double)m / 100); + complexEntity.DoubleInteger = m; complexEntity.DoublePrimitive = m + ((double)m / 100); complexEntity.Int32 = m; complexEntity.IntegerPrimitive = m; @@ -821,6 +822,10 @@ private void DoTableQueryOnSupportedTypes(TablePayloadFormat format) ExecuteQueryAndAssertResults(table, TableQuery.GenerateFilterConditionForDouble("DoublePrimitive", QueryComparisons.GreaterThanOrEqual, middleRef["DoublePrimitive"].DoubleValue.Value), 50); + ExecuteQueryAndAssertResults(table, + TableQuery.GenerateFilterConditionForDouble("Double", QueryComparisons.GreaterThanOrEqual, + middleRef["DoubleInteger"].DoubleValue.Value), 50); + // 5. Filter on Integer ExecuteQueryAndAssertResults(table, TableQuery.GenerateFilterConditionForInt("Int32", QueryComparisons.GreaterThanOrEqual, @@ -883,10 +888,10 @@ private void DoTableQueryOnSupportedTypes(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableRegionalQueryOnSupportedTypes() { - DoTableRegionalQueryOnSupportedTypes(TablePayloadFormat.Json); - DoTableRegionalQueryOnSupportedTypes(TablePayloadFormat.JsonNoMetadata); - DoTableRegionalQueryOnSupportedTypes(TablePayloadFormat.JsonFullMetadata); - DoTableRegionalQueryOnSupportedTypes(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableRegionalQueryOnSupportedTypes(payloadFormat); + } } private void DoTableRegionalQueryOnSupportedTypes(TablePayloadFormat format) @@ -1024,10 +1029,10 @@ private void DoTableRegionalQueryOnSupportedTypes(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryEmptyValue() { - DoTableQueryEmptyValue(TablePayloadFormat.Json); - DoTableQueryEmptyValue(TablePayloadFormat.JsonNoMetadata); - DoTableQueryEmptyValue(TablePayloadFormat.JsonFullMetadata); - DoTableQueryEmptyValue(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryEmptyValue(payloadFormat); + } } private void DoTableQueryEmptyValue(TablePayloadFormat format) @@ -1061,10 +1066,10 @@ private void DoTableQueryEmptyValue(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryWithTakeCount() { - DoTableQueryWithTakeCount(TablePayloadFormat.Json); - DoTableQueryWithTakeCount(TablePayloadFormat.JsonNoMetadata); - DoTableQueryWithTakeCount(TablePayloadFormat.JsonFullMetadata); - DoTableQueryWithTakeCount(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryWithTakeCount(payloadFormat); + } } private void DoTableQueryWithTakeCount(TablePayloadFormat format) @@ -1096,10 +1101,10 @@ private void DoTableQueryWithTakeCount(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryWithTakeCountAndResolver() { - DoTableQueryWithTakeCountAndResolver(TablePayloadFormat.Json); - DoTableQueryWithTakeCountAndResolver(TablePayloadFormat.JsonNoMetadata); - DoTableQueryWithTakeCountAndResolver(TablePayloadFormat.JsonFullMetadata); - DoTableQueryWithTakeCountAndResolver(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryWithTakeCountAndResolver(payloadFormat); + } } private void DoTableQueryWithTakeCountAndResolver(TablePayloadFormat format) @@ -1136,10 +1141,10 @@ private void DoTableQueryWithTakeCountAndResolver(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryWithInvalidTakeCount() { - DoTableQueryWithInvalidTakeCount(TablePayloadFormat.Json); - DoTableQueryWithInvalidTakeCount(TablePayloadFormat.JsonNoMetadata); - DoTableQueryWithInvalidTakeCount(TablePayloadFormat.JsonFullMetadata); - DoTableQueryWithInvalidTakeCount(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryWithInvalidTakeCount(payloadFormat); + } } private void DoTableQueryWithInvalidTakeCount(TablePayloadFormat format) @@ -1182,10 +1187,10 @@ private void DoTableQueryWithInvalidTakeCount(TablePayloadFormat format) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryWithInvalidQuery() { - DoTableQueryWithInvalidQuery(TablePayloadFormat.Json); - DoTableQueryWithInvalidQuery(TablePayloadFormat.JsonNoMetadata); - DoTableQueryWithInvalidQuery(TablePayloadFormat.JsonFullMetadata); - DoTableQueryWithInvalidQuery(TablePayloadFormat.AtomPub); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryWithInvalidQuery(payloadFormat); + } } private void DoTableQueryWithInvalidQuery(TablePayloadFormat format) diff --git a/Test/ClassLibraryCommon/TestHelper.cs b/Test/ClassLibraryCommon/TestHelper.cs index 16e46299e..36122b32f 100644 --- a/Test/ClassLibraryCommon/TestHelper.cs +++ b/Test/ClassLibraryCommon/TestHelper.cs @@ -288,7 +288,7 @@ internal static void ValidateIngressEgress(Func selector, Func { isChunked = session.oResponse.headers.ExistsAndContains("Transfer-Encoding", "chunked"); - observedIngressBodyBytes+= session.responseBodyBytes.Length; + observedIngressBodyBytes += session.responseBodyBytes.Length; }, selector, null, TriggerType.AfterSessionComplete), })) { diff --git a/Test/Common/Table/Entities/ComplexEntity.cs b/Test/Common/Table/Entities/ComplexEntity.cs index 61612eb73..7aa16e96c 100644 --- a/Test/Common/Table/Entities/ComplexEntity.cs +++ b/Test/Common/Table/Entities/ComplexEntity.cs @@ -27,7 +27,7 @@ namespace Microsoft.WindowsAzure.Storage.Table.Entities { public class ComplexEntity : TableEntity { - public const int NumberOfNonNullProperties = 26; + public const int NumberOfNonNullProperties = 27; public ComplexEntity() : base() @@ -188,6 +188,13 @@ public Double Double set { doubleOBj = value; } } + private Double doubleInteger = (Double)1234; + public Double DoubleInteger + { + get { return doubleInteger; } + set { doubleInteger = value; } + } + private Guid? guidNull = null; public Guid? GuidNull { diff --git a/Test/Common/TestBase.Common.cs b/Test/Common/TestBase.Common.cs index 697edf50e..97275c6f5 100644 --- a/Test/Common/TestBase.Common.cs +++ b/Test/Common/TestBase.Common.cs @@ -127,8 +127,22 @@ public static CloudBlobClient GenerateCloudBlobClient() public static CloudFileClient GenerateCloudFileClient() { - Uri baseAddressUri = new Uri(TestBase.TargetTenantConfig.FileServiceEndpoint); - return new CloudFileClient(baseAddressUri, TestBase.StorageCredentials); + CloudFileClient client; + if (string.IsNullOrEmpty(TestBase.TargetTenantConfig.FileServiceSecondaryEndpoint)) + { + Uri baseAddressUri = new Uri(TestBase.TargetTenantConfig.FileServiceEndpoint); + client = new CloudFileClient(baseAddressUri, TestBase.StorageCredentials); + } + else + { + StorageUri baseAddressUri = new StorageUri( + new Uri(TestBase.TargetTenantConfig.FileServiceEndpoint), + new Uri(TestBase.TargetTenantConfig.FileServiceSecondaryEndpoint)); + client = new CloudFileClient(baseAddressUri, TestBase.StorageCredentials); + } + + client.AuthenticationScheme = DefaultAuthenticationScheme; + return client; } public static CloudQueueClient GenerateCloudQueueClient() @@ -160,7 +174,9 @@ public static CloudQueueClient GenerateCloudQueueClient() [Obsolete("Support for accessing Windows Azure Tables via WCF Data Services is now obsolete. It's recommended that you use the Microsoft.WindowsAzure.Storage.Table namespace for working with tables.")] public static void SetPayloadFormatOnDataServiceContext(TableServiceContext ctx, TablePayloadFormat format, CloudTableClient tableClient) { +#pragma warning disable 0618 if (format == TablePayloadFormat.AtomPub) +#pragma warning restore 0618 { ctx.Format.UseAtom(); } diff --git a/Test/Common/TestConfigProcess/TenantConfiguration.cs b/Test/Common/TestConfigProcess/TenantConfiguration.cs index c347f8365..011e2e012 100644 --- a/Test/Common/TestConfigProcess/TenantConfiguration.cs +++ b/Test/Common/TestConfigProcess/TenantConfiguration.cs @@ -26,7 +26,12 @@ public class TenantConfiguration public string FileServiceEndpoint { get; internal set; } public string QueueServiceEndpoint { get; internal set; } public string TableServiceEndpoint { get; internal set; } + public string BlobSecurePortOverride { get; internal set; } + public string FileSecurePortOverride { get; internal set; } + public string TableSecurePortOverride { get; internal set; } + public string QueueSecurePortOverride { get; internal set; } public string BlobServiceSecondaryEndpoint { get; internal set; } + public string FileServiceSecondaryEndpoint { get; internal set; } public string QueueServiceSecondaryEndpoint { get; internal set; } public string TableServiceSecondaryEndpoint { get; internal set; } public TenantType TenantType { get; internal set; } diff --git a/Test/Common/TestConfigProcess/TestConfigurations.cs b/Test/Common/TestConfigProcess/TestConfigurations.cs index c11bdcab8..9cde9f910 100644 --- a/Test/Common/TestConfigProcess/TestConfigurations.cs +++ b/Test/Common/TestConfigProcess/TestConfigurations.cs @@ -50,9 +50,14 @@ public static TestConfigurations ReadFromXml(XElement testConfigurationsElement) config.QueueServiceEndpoint = (string)tenantConfigurationElement.Element("QueueServiceEndpoint"); config.TableServiceEndpoint = (string)tenantConfigurationElement.Element("TableServiceEndpoint"); config.BlobServiceSecondaryEndpoint = (string)tenantConfigurationElement.Element("BlobServiceSecondaryEndpoint"); + config.FileServiceSecondaryEndpoint = (string)tenantConfigurationElement.Element("FileServiceSecondaryEndpoint"); config.QueueServiceSecondaryEndpoint = (string)tenantConfigurationElement.Element("QueueServiceSecondaryEndpoint"); config.TableServiceSecondaryEndpoint = (string)tenantConfigurationElement.Element("TableServiceSecondaryEndpoint"); config.TenantType = (TenantType)Enum.Parse(typeof(TenantType), (string)tenantConfigurationElement.Element("TenantType"), true); + config.BlobSecurePortOverride = (string)tenantConfigurationElement.Element("BlobSecurePortOverride"); + config.FileSecurePortOverride = (string)tenantConfigurationElement.Element("FileSecurePortOverride"); + config.QueueSecurePortOverride = (string)tenantConfigurationElement.Element("QueueSecurePortOverride"); + config.TableSecurePortOverride = (string)tenantConfigurationElement.Element("TableSecurePortOverride"); tenantConfigurationList.Add(config); } diff --git a/Test/Common/TestHelper.Common.cs b/Test/Common/TestHelper.Common.cs index 2a6f29d3b..08894ecff 100644 --- a/Test/Common/TestHelper.Common.cs +++ b/Test/Common/TestHelper.Common.cs @@ -398,37 +398,29 @@ internal static void VerifyServiceStats(ServiceStats stats) internal static void AssertFileServicePropertiesAreEqual(FileServiceProperties propsA, FileServiceProperties propsB) { - if (propsA.Cors != null && propsB.Cors != null) + if (propsA == null || propsB == null) { - Assert.AreEqual(propsA.Cors.CorsRules.Count, propsB.Cors.CorsRules.Count); - - // Check that rules are equal and in the same order. - for (int i = 0; i < propsA.Cors.CorsRules.Count; i++) - { - CorsRule ruleA = propsA.Cors.CorsRules.ElementAt(i); - CorsRule ruleB = propsB.Cors.CorsRules.ElementAt(i); - - Assert.IsTrue( - ruleA.AllowedOrigins.Count == ruleB.AllowedOrigins.Count - && !ruleA.AllowedOrigins.Except(ruleB.AllowedOrigins).Any()); - - Assert.IsTrue( - ruleA.ExposedHeaders.Count == ruleB.ExposedHeaders.Count - && !ruleA.ExposedHeaders.Except(ruleB.ExposedHeaders).Any()); - - Assert.IsTrue( - ruleA.AllowedHeaders.Count == ruleB.AllowedHeaders.Count - && !ruleA.AllowedHeaders.Except(ruleB.AllowedHeaders).Any()); - - Assert.IsTrue(ruleA.AllowedMethods == ruleB.AllowedMethods); - - Assert.IsTrue(ruleA.MaxAgeInSeconds == ruleB.MaxAgeInSeconds); - } + Assert.IsNull(propsA); + Assert.IsNull(propsB); + } else + { + AssertPropertiesAreEqual(propsA, propsA.serviceProperties); + AssertPropertiesAreEqual(propsB, propsB.serviceProperties); + AssertServicePropertiesAreEqual(propsA.serviceProperties, propsB.serviceProperties); } - else + } + + private static void AssertPropertiesAreEqual(FileServiceProperties fileProps, ServiceProperties props) + { + if (fileProps == null || props == null) { - Assert.IsNull(propsA.Cors); - Assert.IsNull(propsB.Cors); + Assert.IsNull(fileProps); + Assert.IsNull(props); + } else + { + Assert.AreEqual(fileProps.Cors, props.Cors); + Assert.AreEqual(fileProps.MinuteMetrics, props.MinuteMetrics); + Assert.AreEqual(fileProps.HourMetrics, props.HourMetrics); } } @@ -446,7 +438,7 @@ internal static void AssertServicePropertiesAreEqual(ServiceProperties propsA, S Assert.IsNull(propsA.Logging); } - if (propsA.HourMetrics != null && propsB.Logging != null) + if (propsA.HourMetrics != null && propsB.HourMetrics != null) { Assert.AreEqual(propsA.HourMetrics.MetricsLevel, propsB.HourMetrics.MetricsLevel); Assert.AreEqual(propsA.HourMetrics.RetentionDays, propsB.HourMetrics.RetentionDays); diff --git a/Test/WindowsDesktop/Microsoft.WindowsAzure.Storage.Test.csproj b/Test/WindowsDesktop/Microsoft.WindowsAzure.Storage.Test.csproj index 5b98bb254..6c0124841 100644 --- a/Test/WindowsDesktop/Microsoft.WindowsAzure.Storage.Test.csproj +++ b/Test/WindowsDesktop/Microsoft.WindowsAzure.Storage.Test.csproj @@ -120,9 +120,9 @@ ..\..\packages\Microsoft.Bcl.1.1.9\lib\net40\System.Runtime.dll - + False - ..\..\packages\System.Spatial.5.6.2\lib\net40\System.Spatial.dll + ..\..\packages\System.Spatial.5.6.4\lib\net40\System.Spatial.dll False @@ -178,64 +178,7 @@ Analytics\Analytics - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - - Blob\Blob - - + Blob\Blob diff --git a/Test/WindowsDesktop/Properties/AssemblyInfo.cs b/Test/WindowsDesktop/Properties/AssemblyInfo.cs index 3607bb298..dcb17b45f 100644 --- a/Test/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Test/WindowsDesktop/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("5.0.2.0")] -[assembly: AssemblyFileVersion("5.0.2.0")] +[assembly: AssemblyVersion("6.0.0.0")] +[assembly: AssemblyFileVersion("6.0.0.0")] diff --git a/Test/WindowsDesktop/Table/DataServices/CancellationUnitTests.cs b/Test/WindowsDesktop/Table/DataServices/CancellationUnitTests.cs index ff6a0f4b7..5e9903915 100644 --- a/Test/WindowsDesktop/Table/DataServices/CancellationUnitTests.cs +++ b/Test/WindowsDesktop/Table/DataServices/CancellationUnitTests.cs @@ -156,7 +156,7 @@ public void TableTestSegmentedQueryCancellation() } ctx.SaveChangesWithRetries(); - TableServiceQuery query = (from ent in ctx.CreateQuery(currentTable.Name) + TableServiceQuery query = (from ent in ctx.CreateQuery(currentTable.Name) select ent).AsTableServiceQuery(ctx); diff --git a/Test/WindowsDesktop/Table/DataServices/SAS/TableSasUnitTests.cs b/Test/WindowsDesktop/Table/DataServices/SAS/TableSasUnitTests.cs index a8168e373..b24a3cc77 100644 --- a/Test/WindowsDesktop/Table/DataServices/SAS/TableSasUnitTests.cs +++ b/Test/WindowsDesktop/Table/DataServices/SAS/TableSasUnitTests.cs @@ -962,7 +962,9 @@ private void TestPointQuery( queryDelegate, "point query", expectSuccess, - expectSuccess ? HttpStatusCode.OK : HttpStatusCode.NotFound); + expectSuccess ? HttpStatusCode.OK : HttpStatusCode.NotFound, + false, + expectSuccess); } @@ -1008,7 +1010,7 @@ private void TestUpdateMerge( updateDelegate, "update merge", expectSuccess, - expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.NotFound); + expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.Forbidden); } /// @@ -1053,7 +1055,7 @@ private void TestUpdateReplace( updateDelegate, "update replace", expectSuccess, - expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.NotFound); + expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.Forbidden); } /// @@ -1097,7 +1099,7 @@ private void TestAdd( addDelegate, "add", expectSuccess, - expectSuccess ? HttpStatusCode.Created : HttpStatusCode.NotFound); + expectSuccess ? HttpStatusCode.Created : HttpStatusCode.Forbidden); } /// @@ -1143,7 +1145,7 @@ private void TestDelete( deleteDelegate, "delete", expectSuccess, - expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.NotFound); + expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.Forbidden); } /// @@ -1189,7 +1191,7 @@ private void TestUpsertMerge( upsertDelegate, "upsert merge", expectSuccess, - expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.NotFound); + expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.Forbidden); } /// @@ -1235,7 +1237,7 @@ private void TestUpsertReplace( upsertDelegate, "upsert replace", expectSuccess, - expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.NotFound); + expectSuccess ? HttpStatusCode.NoContent : HttpStatusCode.Forbidden); } /// @@ -1295,6 +1297,44 @@ private void TestOperationWithRange( bool expectSuccess, HttpStatusCode expectedStatusCode, bool isRangeQuery) + { + TestOperationWithRange( + tableName, + startPk, + startRk, + endPk, + endRk, + runOperationDelegate, + opName, + expectSuccess, + expectedStatusCode, + isRangeQuery /* isRangeQuery */, + false /* isPointQuery */); + } + + /// + /// Test a table operation on entities inside and outside the given range. + /// + /// The name of the table to test. + /// The start partition key range. + /// The start row key range. + /// The end partition key range. + /// The end row key range. + /// A delegate with the table operation to test. + /// The name of the operation being tested. + /// Whether the operation should succeed on entities within the range. + private void TestOperationWithRange( + string tableName, + string startPk, + string startRk, + string endPk, + string endRk, + Action runOperationDelegate, + string opName, + bool expectSuccess, + HttpStatusCode expectedStatusCode, + bool isRangeQuery, + bool isPointQuery) { CloudTableClient referenceClient = GenerateCloudTableClient(); TableServiceContext referenceContext = referenceClient.GetTableServiceContext(); @@ -1333,7 +1373,7 @@ private void TestOperationWithRange( TestHelper.ExpectedException( () => runOperationDelegate(tableEntity), string.Format("{0} without appropriate permission.", opName), - HttpStatusCode.NotFound); + HttpStatusCode.Forbidden); } if (startPk != null) @@ -1347,7 +1387,7 @@ private void TestOperationWithRange( TestHelper.ExpectedException( () => runOperationDelegate(tableEntity), string.Format("{0} before allowed partition key range", opName), - HttpStatusCode.NotFound); + isPointQuery ? HttpStatusCode.NotFound : HttpStatusCode.Forbidden); tableEntity.PartitionKey = partitionKey; } @@ -1362,7 +1402,7 @@ private void TestOperationWithRange( TestHelper.ExpectedException( () => runOperationDelegate(tableEntity), string.Format("{0} after allowed partition key range", opName), - HttpStatusCode.NotFound); + isPointQuery ? HttpStatusCode.NotFound : HttpStatusCode.Forbidden); tableEntity.PartitionKey = partitionKey; } @@ -1381,7 +1421,7 @@ private void TestOperationWithRange( TestHelper.ExpectedException( () => runOperationDelegate(tableEntity), string.Format("{0} before allowed row key range", opName), - HttpStatusCode.NotFound); + isPointQuery ? HttpStatusCode.NotFound : HttpStatusCode.Forbidden); tableEntity.RowKey = rowKey; } @@ -1401,7 +1441,7 @@ private void TestOperationWithRange( TestHelper.ExpectedException( () => runOperationDelegate(tableEntity), string.Format("{0} after allowed row key range", opName), - HttpStatusCode.NotFound); + isPointQuery ? HttpStatusCode.NotFound : HttpStatusCode.Forbidden); tableEntity.RowKey = rowKey; } @@ -1450,22 +1490,22 @@ public void TableSasInvalidOperations() // Test invalid client operations // BUGBUG: ListTables hides the exception. We should fix this - // TestHelpers.ExpectedException(() => sasClient.ListTablesSegmented(), "List tables with SAS", HttpStatusCode.NotFound); - TestHelper.ExpectedException(() => sasClient.GetServiceProperties(), "Get service properties with SAS", HttpStatusCode.NotFound); - TestHelper.ExpectedException(() => sasClient.SetServiceProperties(properties), "Set service properties with SAS", HttpStatusCode.NotFound); + // TestHelpers.ExpectedException(() => sasClient.ListTablesSegmented(), "List tables with SAS", HttpStatusCode.Forbidden); + TestHelper.ExpectedException(() => sasClient.GetServiceProperties(), "Get service properties with SAS", HttpStatusCode.Forbidden); + TestHelper.ExpectedException(() => sasClient.SetServiceProperties(properties), "Set service properties with SAS", HttpStatusCode.Forbidden); CloudTable sasTable = sasClient.GetTableReference(table.Name); // Verify that creation fails with SAS - TestHelper.ExpectedException(() => sasTable.Create(), "Create a table with SAS", HttpStatusCode.NotFound); + TestHelper.ExpectedException(() => sasTable.Create(), "Create a table with SAS", HttpStatusCode.Forbidden); // Create the table. table.Create(); // Test invalid table operations - TestHelper.ExpectedException(() => sasTable.Delete(), "Delete a table with SAS", HttpStatusCode.NotFound); - TestHelper.ExpectedException(() => sasTable.GetPermissions(), "Get ACL with SAS", HttpStatusCode.NotFound); - TestHelper.ExpectedException(() => sasTable.SetPermissions(new TablePermissions()), "Set ACL with SAS", HttpStatusCode.NotFound); + TestHelper.ExpectedException(() => sasTable.Delete(), "Delete a table with SAS", HttpStatusCode.Forbidden); + TestHelper.ExpectedException(() => sasTable.GetPermissions(), "Get ACL with SAS", HttpStatusCode.Forbidden); + TestHelper.ExpectedException(() => sasTable.SetPermissions(new TablePermissions()), "Set ACL with SAS", HttpStatusCode.Forbidden); } finally { diff --git a/Test/WindowsDesktop/Table/DataServices/TableEntityUnitTests.cs b/Test/WindowsDesktop/Table/DataServices/TableEntityUnitTests.cs index a87772d29..3c8238a32 100644 --- a/Test/WindowsDesktop/Table/DataServices/TableEntityUnitTests.cs +++ b/Test/WindowsDesktop/Table/DataServices/TableEntityUnitTests.cs @@ -26,6 +26,7 @@ namespace Microsoft.WindowsAzure.Storage.Table.DataServices { +#pragma warning disable 0618 [TestClass] [Obsolete("Support for accessing Windows Azure Tables via WCF Data Services is now obsolete. It's recommended that you use the Microsoft.WindowsAzure.Storage.Table namespace for working with tables.")] public class TableEntityUnitTests : TableTestBase @@ -1089,4 +1090,5 @@ private void DoSingleEntityInsertEntityOver1MB(TablePayloadFormat format) } #endregion } +#pragma warning restore 0618 } diff --git a/Test/WindowsDesktop/Table/DataServices/TableServiceQueryUnitTests.cs b/Test/WindowsDesktop/Table/DataServices/TableServiceQueryUnitTests.cs index de28b75e6..fdb0094ae 100644 --- a/Test/WindowsDesktop/Table/DataServices/TableServiceQueryUnitTests.cs +++ b/Test/WindowsDesktop/Table/DataServices/TableServiceQueryUnitTests.cs @@ -26,6 +26,7 @@ namespace Microsoft.WindowsAzure.Storage.Table.DataServices { +#pragma warning disable 0618 /// /// Summary description for TableServiceQueryUnitTests /// @@ -508,4 +509,5 @@ private void DoTableServiceQueryProjectionWithUpdate(TablePayloadFormat format) #endregion } +#pragma warning restore 0618 } diff --git a/Test/WindowsDesktop/Table/TableManglerTests.cs b/Test/WindowsDesktop/Table/TableManglerTests.cs index a1c173614..31ea36112 100644 --- a/Test/WindowsDesktop/Table/TableManglerTests.cs +++ b/Test/WindowsDesktop/Table/TableManglerTests.cs @@ -188,7 +188,7 @@ public void TableIngressEgressQuery() // Insert Entity DynamicTableEntity insertEntity = new DynamicTableEntity("insert test", m.ToString()); insertEntity.Properties.Add("prop" + m.ToString(), new EntityProperty(new byte[30 * 1024])); - batch.Insert(insertEntity); + batch.Insert(insertEntity, true); } currentTable.ExecuteBatch(batch); diff --git a/Test/WindowsPhone/Properties/AssemblyInfo.cs b/Test/WindowsPhone/Properties/AssemblyInfo.cs index e683ddfc3..5624223c2 100644 --- a/Test/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone/Properties/AssemblyInfo.cs @@ -32,6 +32,6 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("5.0.2.0")] -[assembly: AssemblyFileVersion("5.0.2.0")] +[assembly: AssemblyVersion("6.0.0.0")] +[assembly: AssemblyFileVersion("6.0.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsPhone81/Properties/AssemblyInfo.cs b/Test/WindowsPhone81/Properties/AssemblyInfo.cs index bd6edff6c..1a4e9a4a9 100644 --- a/Test/WindowsPhone81/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone81/Properties/AssemblyInfo.cs @@ -33,6 +33,6 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("5.0.2.0")] -[assembly: AssemblyFileVersion("5.0.2.0")] +[assembly: AssemblyVersion("6.0.0.0")] +[assembly: AssemblyFileVersion("6.0.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsPhoneCommon/Table/CloudTableCRUDUnitTaskTests.cs b/Test/WindowsPhoneCommon/Table/CloudTableCRUDUnitTaskTests.cs index c6550a433..23093c9e8 100644 --- a/Test/WindowsPhoneCommon/Table/CloudTableCRUDUnitTaskTests.cs +++ b/Test/WindowsPhoneCommon/Table/CloudTableCRUDUnitTaskTests.cs @@ -22,6 +22,7 @@ namespace Microsoft.WindowsAzure.Storage.Table { +#pragma warning disable 0618 /// /// Summary description for CloudTableCRUDUnitTaskTests /// @@ -351,4 +352,5 @@ private async Task DoCloudTableExistsAsync(TablePayloadFormat format) } #endregion } +#pragma warning restore 0618 } diff --git a/Test/WindowsPhoneCommon/Table/CloudTableClientTaskTest.cs b/Test/WindowsPhoneCommon/Table/CloudTableClientTaskTest.cs index 036aafafc..b23a56ac0 100644 --- a/Test/WindowsPhoneCommon/Table/CloudTableClientTaskTest.cs +++ b/Test/WindowsPhoneCommon/Table/CloudTableClientTaskTest.cs @@ -23,6 +23,7 @@ namespace Microsoft.WindowsAzure.Storage.Table { +#pragma warning disable 0618 [TestClass] public class CloudTableClientTaskTest : TableTestBase { @@ -224,4 +225,5 @@ private async Task DoListTablesSegmentedWithPrefixAsync(TablePayloadFormat forma #endregion } +#pragma warning restore 0618 } diff --git a/Test/WindowsPhoneCommon/Table/TableBatchOperationTaskTest.cs b/Test/WindowsPhoneCommon/Table/TableBatchOperationTaskTest.cs index f5282eb52..fc2547f97 100644 --- a/Test/WindowsPhoneCommon/Table/TableBatchOperationTaskTest.cs +++ b/Test/WindowsPhoneCommon/Table/TableBatchOperationTaskTest.cs @@ -27,6 +27,7 @@ namespace Microsoft.WindowsAzure.Storage.Table { +#pragma warning disable 0618 [TestClass] public class TableBatchOperationTaskTest : TableTestBase { @@ -1632,4 +1633,5 @@ private static DynamicTableEntity GenerateRandomEntity(string pk) } #endregion } +#pragma warning restore 0618 } diff --git a/Test/WindowsPhoneCommon/Table/TableOperationUnitTaskTests.cs b/Test/WindowsPhoneCommon/Table/TableOperationUnitTaskTests.cs index f003ea1ba..bddf7d6d3 100644 --- a/Test/WindowsPhoneCommon/Table/TableOperationUnitTaskTests.cs +++ b/Test/WindowsPhoneCommon/Table/TableOperationUnitTaskTests.cs @@ -25,6 +25,7 @@ namespace Microsoft.WindowsAzure.Storage.Table { +#pragma warning disable 0618 [TestClass] public class TableOperationUnitTaskTests : TableTestBase { @@ -1136,4 +1137,5 @@ private async Task DoTableOperationInsertOver1MBAsync(TablePayloadFormat format) #endregion } +#pragma warning restore 0618 } diff --git a/Test/WindowsPhoneCommon/Table/TableQueryGenericTaskTests.cs b/Test/WindowsPhoneCommon/Table/TableQueryGenericTaskTests.cs index 22ab3140f..e4a2721b6 100644 --- a/Test/WindowsPhoneCommon/Table/TableQueryGenericTaskTests.cs +++ b/Test/WindowsPhoneCommon/Table/TableQueryGenericTaskTests.cs @@ -25,6 +25,7 @@ namespace Microsoft.WindowsAzure.Storage.Table { +#pragma warning disable 0618 [TestClass] public class TableQueryGenericTaskTests : TableTestBase { @@ -621,4 +622,5 @@ private static BaseEntity GenerateRandomEntity(string pk) } #endregion } +#pragma warning restore 0618 } diff --git a/Test/WindowsPhoneCommon/Table/TableQueryTaskTests.cs b/Test/WindowsPhoneCommon/Table/TableQueryTaskTests.cs index a919e46f7..418a1e317 100644 --- a/Test/WindowsPhoneCommon/Table/TableQueryTaskTests.cs +++ b/Test/WindowsPhoneCommon/Table/TableQueryTaskTests.cs @@ -27,6 +27,7 @@ namespace Microsoft.WindowsAzure.Storage.Table { +#pragma warning disable 0618 [TestClass] public class TableQueryTaskTests : TableTestBase { @@ -680,4 +681,5 @@ private static DynamicTableEntity GenerateRandomEntity(string pk) } #endregion } +#pragma warning restore 0618 } diff --git a/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs b/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs index f49151763..c034181b4 100644 --- a/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs @@ -24,6 +24,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("5.0.2.0")] -[assembly: AssemblyFileVersion("5.0.2.0")] +[assembly: AssemblyVersion("6.0.0.0")] +[assembly: AssemblyFileVersion("6.0.0.0")] [assembly: ComVisible(false)] \ No newline at end of file diff --git a/Test/WindowsRuntime/AccountSasTests.cs b/Test/WindowsRuntime/AccountSasTests.cs new file mode 100644 index 000000000..72a882831 --- /dev/null +++ b/Test/WindowsRuntime/AccountSasTests.cs @@ -0,0 +1,1439 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage +{ + using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; + using Microsoft.WindowsAzure.Storage; + using Microsoft.WindowsAzure.Storage.Auth; + using Microsoft.WindowsAzure.Storage.Blob; + using Microsoft.WindowsAzure.Storage.File; + using Microsoft.WindowsAzure.Storage.Queue; + using Microsoft.WindowsAzure.Storage.Queue.Protocol; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using Microsoft.WindowsAzure.Storage.Table; + using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Shared; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using System.Xml; + using System.Xml.Linq; + using Windows.Networking; + using Windows.Storage.Streams; + using Windows.Networking.Connectivity; + + [TestClass] + public class AccountSASTests : TestBase + { + [TestMethod] + [Description("Test account SAS all permissions, all services")] + [TestCategory(ComponentCategory.Core)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AccountSASPermissions() + { + // Single-threaded, takes 10 minutes to run + // Parallelized, 1 minute. + Task[] tasks = new Task[256 * 4]; //each permission (0x100) times four services. + for (int i = 0; i < 0x100; i++) + { + SharedAccessAccountPermissions permissions = (SharedAccessAccountPermissions)i; + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + policy.Permissions = permissions; + tasks[i] = this.RunPermissionsTestBlobs(policy); + tasks[256 + i] = this.RunPermissionsTestTables(policy); + tasks[512 + i] = this.RunPermissionsTestQueues(policy); + tasks[768 + i] = this.RunPermissionsTestFiles(policy); + } + Task.WaitAll(tasks); + } + + [TestMethod] + [Description("Test account SAS various combinations of resource types")] + [TestCategory(ComponentCategory.Core)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AccountSASResourceTypes() + { + Task[] tasks = new Task[8*4]; + for (int i = 0; i < 0x8; i++) + { + SharedAccessAccountResourceTypes resourceTypes = (SharedAccessAccountResourceTypes)i; + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + policy.ResourceTypes = resourceTypes; + tasks[i] = this.RunPermissionsTestBlobs(policy); + tasks[8 + i] = this.RunPermissionsTestTables(policy); + tasks[16 + i] = this.RunPermissionsTestQueues(policy); + tasks[24 + i] = this.RunPermissionsTestFiles(policy); + } + Task.WaitAll(tasks); + } + + + [TestMethod] + [Description("Test account SAS various combinations of services")] + [TestCategory(ComponentCategory.Core)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AccountSASServices() + { + for (int i = 1; i < 0x10; i++) + { + OperationContext opContext = new OperationContext(); + SharedAccessAccountPolicy policy = this.GetPolicyWithFullPermissions(); + policy.Services = (SharedAccessAccountServices)i; + bool expectBlobException = !((policy.Services & SharedAccessAccountServices.Blob) == SharedAccessAccountServices.Blob); + bool expectQueueException = !((policy.Services & SharedAccessAccountServices.Queue) == SharedAccessAccountServices.Queue); + bool expectTableException = !((policy.Services & SharedAccessAccountServices.Table) == SharedAccessAccountServices.Table); + bool expectFileException = !((policy.Services & SharedAccessAccountServices.File) == SharedAccessAccountServices.File); + + if (expectBlobException) + { + await TestHelper.ExpectedExceptionAsync((async () => await RunBlobTest(policy, null, opContext)), opContext, "Operation should have failed without Blob access.", HttpStatusCode.Forbidden, "AuthorizationServiceMismatch"); + } + else + { + await RunBlobTest(policy, null); + } + + if (expectQueueException) + { + await TestHelper.ExpectedExceptionAsync((async () => await RunQueueTest(policy, null, opContext)), opContext, "Operation should have failed without Queue access.", HttpStatusCode.Forbidden, "AuthorizationServiceMismatch"); + } + else + { + await RunQueueTest(policy, null); + } + + if (expectTableException) + { + await TestHelper.ExpectedExceptionAsync((async () => await RunTableTest(policy, null, opContext)), opContext, "Operation should have failed without Table access.", HttpStatusCode.Forbidden, "AuthorizationServiceMismatch"); + } + else + { + await RunTableTest(policy, null); + } + + if (expectFileException) + { + await TestHelper.ExpectedExceptionAsync((async () => await RunFileTest(policy, null, opContext)), opContext, "Operation should have failed without File access.", HttpStatusCode.Forbidden, "AuthorizationServiceMismatch"); + } + else + { + await RunFileTest(policy, null); + } + } + } + + [TestMethod] + [Description("Test account SAS various combinations of start and expiry times")] + [TestCategory(ComponentCategory.Core)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AccountSASStartExpiryTimes() + { + int?[] startOffsets = new int?[] { null, -5, 5 }; + int[] endOffsets = new int[] { -5, 5 }; + + foreach (int? startOffset in startOffsets) + { + foreach (int endOffset in endOffsets) + { + OperationContext opContext = new OperationContext(); + SharedAccessAccountPolicy policy = this.GetPolicyWithFullPermissions(); + + if (startOffset.HasValue) + { + policy.SharedAccessStartTime = DateTime.Now + TimeSpan.FromMinutes(startOffset.Value); + } + else + { + policy.SharedAccessStartTime = null; + } + + policy.SharedAccessExpiryTime = DateTime.Now + TimeSpan.FromMinutes(endOffset); + + bool expectException; + if (!policy.SharedAccessStartTime.HasValue) + { + expectException = (policy.SharedAccessExpiryTime < DateTime.Now); + } + else + { + expectException = ((policy.SharedAccessStartTime.Value > DateTime.Now) || (policy.SharedAccessExpiryTime < DateTime.Now)); + } + + if (expectException) + { + await TestHelper.ExpectedExceptionAsync((async () => await RunBlobTest(policy, null, opContext)), opContext, "Operation should have failed with invalid start/expiry times.", HttpStatusCode.Forbidden, "AuthenticationFailed"); + await TestHelper.ExpectedExceptionAsync((async () => await RunQueueTest(policy, null, opContext)), opContext, "Operation should have failed with invalid start/expiry times.", HttpStatusCode.Forbidden, "AuthenticationFailed"); + await TestHelper.ExpectedExceptionAsync((async () => await RunTableTest(policy, null, opContext)), opContext, "Operation should have failed with invalid start/expiry times.", HttpStatusCode.Forbidden, "AuthenticationFailed"); + await TestHelper.ExpectedExceptionAsync((async () => await RunFileTest(policy, null, opContext)), opContext, "Operation should have failed with invalid start/expiry times.", HttpStatusCode.Forbidden, "AuthenticationFailed"); + } + else + { + await RunBlobTest(policy, null); + await RunQueueTest(policy, null); + await RunTableTest(policy, null); + await RunFileTest(policy, null); + } + } + } + } + + [TestMethod] + [Description("Test account SAS various combinations of signedIPs")] + [TestCategory(ComponentCategory.Core)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AccountSASSignedIPs() + { + OperationContext opContext = new OperationContext(); + HostName invalidIP = new HostName("255.255.255.255"); + + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + policy.IPAddressOrRange = new IPAddressOrRange(invalidIP.ToString()); + + await TestHelper.ExpectedExceptionAsync((async () => await RunBlobTest(policy, null, opContext)), opContext, "Operation should have failed with invalid IP access.", HttpStatusCode.Forbidden, "AuthorizationSourceIPMismatch"); + await TestHelper.ExpectedExceptionAsync((async () => await RunQueueTest(policy, null, opContext)), opContext, "Operation should have failed with invalid IP access.", HttpStatusCode.Forbidden, "AuthorizationSourceIPMismatch"); + await TestHelper.ExpectedExceptionAsync((async () => await RunTableTest(policy, null, opContext)), opContext, "Operation should have failed with invalid IP access.", HttpStatusCode.Forbidden, "AuthorizationSourceIPMismatch"); + await TestHelper.ExpectedExceptionAsync((async () => await RunFileTest(policy, null, opContext)), opContext, "Operation should have failed with invalid IP access.", HttpStatusCode.Forbidden, "AuthorizationSourceIPMismatch"); + + string myBlobIP = await GetMyBlobIPAddressFromService(); + policy.IPAddressOrRange = new IPAddressOrRange(new HostName(myBlobIP).ToString()); + await RunBlobTest(policy, null); + + string myTableIP = await GetMyTableIPAddressFromService(); + policy.IPAddressOrRange = new IPAddressOrRange(new HostName(myTableIP).ToString()); + await RunTableTest(policy, null); + + string myQueueIP = await GetMyQueueIPAddressFromService(); + policy.IPAddressOrRange = new IPAddressOrRange(new HostName(myQueueIP).ToString()); + await RunQueueTest(policy, null); + + string myFileIP = await GetMyFileIPAddressFromService(); + policy.IPAddressOrRange = new IPAddressOrRange(new HostName(myFileIP).ToString()); + await RunFileTest(policy, null); + + policy.IPAddressOrRange = new IPAddressOrRange(new HostName("255.255.255.0").ToString(), invalidIP.ToString()); + + await TestHelper.ExpectedExceptionAsync((async () => await RunBlobTest(policy, null, opContext)), opContext, "Operation should have failed with invalid IP access.", HttpStatusCode.Forbidden, "AuthorizationSourceIPMismatch"); + await TestHelper.ExpectedExceptionAsync((async () => await RunQueueTest(policy, null, opContext)), opContext, "Operation should have failed with invalid IP access.", HttpStatusCode.Forbidden, "AuthorizationSourceIPMismatch"); + await TestHelper.ExpectedExceptionAsync((async () => await RunTableTest(policy, null, opContext)), opContext, "Operation should have failed with invalid IP access.", HttpStatusCode.Forbidden, "AuthorizationSourceIPMismatch"); + await TestHelper.ExpectedExceptionAsync((async () => await RunFileTest(policy, null, opContext)), opContext, "Operation should have failed with invalid IP access.", HttpStatusCode.Forbidden, "AuthorizationSourceIPMismatch"); + + string blobFirstTwelveString = myBlobIP.Substring(0, myBlobIP.LastIndexOf('.')); + string blobLastFourString = myBlobIP.Substring(myBlobIP.LastIndexOf('.') + 1); + int blobLastFourInt = int.Parse(blobLastFourString); + + string initialBlobAddress = blobFirstTwelveString + "." + (blobLastFourInt - 1).ToString(); + string endBlobAddress = blobFirstTwelveString + "." + (blobLastFourInt + 1).ToString(); + policy.IPAddressOrRange = new IPAddressOrRange(new HostName(initialBlobAddress).ToString(), new HostName(endBlobAddress).ToString()); + await RunBlobTest(policy, null); + + string tableFirstTwelveString = myTableIP.Substring(0, myTableIP.LastIndexOf('.')); + string tableLastFourString = myTableIP.Substring(myTableIP.LastIndexOf('.') + 1); + int tableLastFourInt = int.Parse(tableLastFourString); + + string initialTableAddress = tableFirstTwelveString + "." + (tableLastFourInt - 1).ToString(); + string endTableAddress = tableFirstTwelveString + "." + (tableLastFourInt + 1).ToString(); + policy.IPAddressOrRange = new IPAddressOrRange(new HostName(initialTableAddress).ToString(), new HostName(endTableAddress).ToString()); + await RunTableTest(policy, null); + + string queueFirstTwelveString = myQueueIP.Substring(0, myQueueIP.LastIndexOf('.')); + string queueLastFourString = myQueueIP.Substring(myQueueIP.LastIndexOf('.') + 1); + int queueLastFourInt = int.Parse(queueLastFourString); + + string initialQueueAddress = queueFirstTwelveString + "." + (queueLastFourInt - 1).ToString(); + string endQueueAddress = queueFirstTwelveString + "." + (queueLastFourInt + 1).ToString(); + policy.IPAddressOrRange = new IPAddressOrRange(new HostName(initialQueueAddress).ToString(), new HostName(endQueueAddress).ToString()); + await RunQueueTest(policy, null); + + string fileFirstTwelveString = myFileIP.Substring(0, myFileIP.LastIndexOf('.')); + string fileLastFourString = myFileIP.Substring(myFileIP.LastIndexOf('.') + 1); + int fileLastFourInt = int.Parse(fileLastFourString); + + string initialFileAddress = fileFirstTwelveString + "." + (fileLastFourInt - 1).ToString(); + string endFileAddress = fileFirstTwelveString + "." + (fileLastFourInt + 1).ToString(); + policy.IPAddressOrRange = new IPAddressOrRange(new HostName(initialFileAddress).ToString(), new HostName(endFileAddress).ToString()); + await RunFileTest(policy, null); + } + + [TestMethod] + [Description("Test account SAS various combinations of signed protocols")] + [TestCategory(ComponentCategory.Core)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AccountSASSignedProtocols() + { + int blobHttpsPort = 443; + int tableHttpsPort = 443; + int queueHttpsPort = 443; + int fileHttpsPort = 443; + + if (!string.IsNullOrEmpty(TestBase.TargetTenantConfig.BlobSecurePortOverride)) + { + blobHttpsPort = Int32.Parse(TestBase.TargetTenantConfig.BlobSecurePortOverride); + } + + if (!string.IsNullOrEmpty(TestBase.TargetTenantConfig.TableSecurePortOverride)) + { + tableHttpsPort = Int32.Parse(TestBase.TargetTenantConfig.TableSecurePortOverride); + } + + if (!string.IsNullOrEmpty(TestBase.TargetTenantConfig.QueueSecurePortOverride)) + { + queueHttpsPort = Int32.Parse(TestBase.TargetTenantConfig.QueueSecurePortOverride); + } + + if (!string.IsNullOrEmpty(TestBase.TargetTenantConfig.FileSecurePortOverride)) + { + fileHttpsPort = Int32.Parse(TestBase.TargetTenantConfig.FileSecurePortOverride); + } + + OperationContext opContext = new OperationContext(); + for (int i = 0; i < 3; i++) + { + SharedAccessAccountPolicy policy = this.GetPolicyWithFullPermissions(); + policy.Protocols = i == 0 ? (SharedAccessProtocol?)null : (SharedAccessProtocol)i; + + bool expectException = !(!policy.Protocols.HasValue || (policy.Protocols == SharedAccessProtocol.HttpsOrHttp)); + + if (expectException) + { + await TestHelper.ExpectedExceptionAsync((async () => await RunBlobTest(policy, null, opContext)), opContext, "Operation should have failed without using Https.", HttpStatusCode.Unused, null); + await TestHelper.ExpectedExceptionAsync((async () => await RunQueueTest(policy, null, opContext)), opContext, "Operation should have failed without using Https.", HttpStatusCode.Unused, null); + await TestHelper.ExpectedExceptionAsync((async () => await RunTableTest(policy, null, opContext)), opContext, "Operation should have failed without using Https.", HttpStatusCode.Unused, null); + await TestHelper.ExpectedExceptionAsync((async () => await RunFileTest(policy, null, opContext)), opContext, "Operation should have failed without using Https.", HttpStatusCode.Unused, null); + } + else + { + await RunBlobTest(policy, null); + await RunQueueTest(policy, null); + await RunTableTest(policy, null); + await RunFileTest(policy, null); + } + + await RunBlobTest(policy, blobHttpsPort); + await RunQueueTest(policy, queueHttpsPort); + await RunTableTest(policy, tableHttpsPort); + await RunFileTest(policy, fileHttpsPort); + } + } + + [TestMethod] + [Description("Test account SAS all parameters blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AccountSASSample() + { + CloudBlobClient blobClient = GenerateCloudBlobClient(); + string containerName = "c" + Guid.NewGuid().ToString("N"); + CloudBlobContainer container = blobClient.GetContainerReference(containerName); + try + { + await container.CreateAsync(); + string blobName = "blob"; + CloudBlockBlob blockBlob = container.GetBlockBlobReference(blobName); + byte[] data = new byte[] { 0x1, 0x2, 0x3, 0x4 }; + await blockBlob.UploadFromByteArrayAsync(data, 0, 4); + + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + HostName invalidIP = new HostName("255.255.255.255"); + policy.IPAddressOrRange = new IPAddressOrRange(invalidIP.ToString()); + + CloudStorageAccount account = new CloudStorageAccount(blobClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = CloudStorageAccount.Create(accountSAS, blobClient.StorageUri, null, null, null); + CloudBlobClient blobClientWithSAS = accountWithSAS.CreateCloudBlobClient(); + CloudBlobContainer containerWithSAS = blobClientWithSAS.GetContainerReference(containerName); + CloudBlockBlob blockblobWithSAS = containerWithSAS.GetBlockBlobReference(blobName); + + byte[] target = new byte[4]; + OperationContext opContext = new OperationContext(); + HostName actualIP = null; + + bool exceptionThrown = false; + try + { + await blockblobWithSAS.DownloadRangeToByteArrayAsync(target, 0, 0, 4, null, null, opContext); + } + catch (WrappedStorageException e) + { + exceptionThrown = true; + XDocument xdocument = XDocument.Parse(e.Message); + string ipString = xdocument.Descendants("SourceIP").First().Value; + actualIP = new HostName(ipString); + Assert.IsNotNull(actualIP); + } + + Assert.IsTrue(exceptionThrown); + + policy.IPAddressOrRange = new IPAddressOrRange(actualIP.ToString()); + accountSASToken = account.GetSharedAccessSignature(policy); + accountSAS = new StorageCredentials(accountSASToken); + accountWithSAS = CloudStorageAccount.Create(accountSAS, blobClient.StorageUri, null, null, null); + blobClientWithSAS = accountWithSAS.CreateCloudBlobClient(); + containerWithSAS = blobClientWithSAS.GetContainerReference(containerName); + blockblobWithSAS = containerWithSAS.GetBlockBlobReference(blobName); + + await blockblobWithSAS.DownloadRangeToByteArrayAsync(target, 0, 0, 4, null, null, null); + for (int i = 0; i < 4; i++) + { + Assert.AreEqual(data[i], target[i]); + } + Assert.IsTrue(blockblobWithSAS.StorageUri.PrimaryUri.Equals(blockBlob.Uri)); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + public async Task RunPermissionsTestBlobs(SharedAccessAccountPolicy policy) + { + CloudBlobClient blobClient = GenerateCloudBlobClient(); + string containerName = "c" + Guid.NewGuid().ToString("N"); + try + { + CloudStorageAccount account = new CloudStorageAccount(blobClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = CloudStorageAccount.Create(accountSAS, blobClient.StorageUri, null, null, null); + CloudBlobClient blobClientWithSAS = accountWithSAS.CreateCloudBlobClient(); + CloudBlobContainer containerWithSAS = blobClientWithSAS.GetContainerReference(containerName); + CloudBlobContainer container = blobClient.GetContainerReference(containerName); + + // General pattern - If current perms support doing a thing with SAS, do the thing with SAS and validate with shared + // Otherwise, make sure SAS fails and then do the thing with shared key. + + // Things to do: + // Create the container (Create / Write perms, Container RT) + // List containers with prefix (List perms, Service RT) + // Create an append blob (Create / Write perms, Object RT) + // Append a block to append blob (Add / Write perms, Object RT) + // Read the data from the append blob (Read perms, Object RT) + // Delete the blob (Delete perms, Object RT) + if ((((policy.Permissions & SharedAccessAccountPermissions.Create) == SharedAccessAccountPermissions.Create) || ((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Container) == SharedAccessAccountResourceTypes.Container)) + { + await containerWithSAS.CreateAsync(); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await containerWithSAS.CreateAsync(), "Create a container should fail with SAS without Create or Write and Container-level permissions."); + await container.CreateAsync(); + } + + Assert.IsTrue(await container.ExistsAsync()); + + if (((policy.Permissions & SharedAccessAccountPermissions.List) == SharedAccessAccountPermissions.List) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Service) == SharedAccessAccountResourceTypes.Service)) + { + ContainerResultSegment segment = await blobClientWithSAS.ListContainersSegmentedAsync(container.Name, null); + Assert.AreEqual(container.Name, segment.Results.First().Name); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => { ContainerResultSegment segment = await blobClientWithSAS.ListContainersSegmentedAsync(container.Name, null); segment.Results.First(); }, "List containers should fail with SAS without List and Service-level permissions."); + } + + string blobName = "blob"; + CloudAppendBlob appendBlob = container.GetAppendBlobReference(blobName); + CloudAppendBlob appendBlobWithSAS = containerWithSAS.GetAppendBlobReference(blobName); + if ((((policy.Permissions & SharedAccessAccountPermissions.Create) == SharedAccessAccountPermissions.Create) || ((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + await appendBlobWithSAS.CreateOrReplaceAsync(); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await appendBlobWithSAS.CreateOrReplaceAsync(), "Creating an append blob should fail with SAS without Create or Write and Object-level perms."); + await appendBlob.CreateOrReplaceAsync(); + } + + Assert.IsTrue(await appendBlob.ExistsAsync()); + + string blobText = "blobText"; + if ((((policy.Permissions & SharedAccessAccountPermissions.Add) == SharedAccessAccountPermissions.Add) || ((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + using (IInputStream stream = new MemoryStream(Encoding.UTF8.GetBytes(blobText)).AsInputStream()) + { + await appendBlobWithSAS.AppendBlockAsync(stream); + } + } + else + { + using (MemoryStream memStream = new MemoryStream(Encoding.UTF8.GetBytes(blobText))) + { + + await TestHelper.ExpectedExceptionAsync(async () => await appendBlobWithSAS.AppendBlockAsync(memStream.AsInputStream()), "Append a block to an append blob should fail with SAS without Add or Write and Object-level perms."); + memStream.Seek(0, SeekOrigin.Begin); + await appendBlob.AppendBlockAsync(memStream.AsInputStream()); + } + } + + Assert.AreEqual(blobText, await appendBlob.DownloadTextAsync()); + + if (((policy.Permissions & SharedAccessAccountPermissions.Read) == SharedAccessAccountPermissions.Read) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + Assert.AreEqual(blobText, await appendBlobWithSAS.DownloadTextAsync()); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await appendBlobWithSAS.DownloadTextAsync(), "Reading a blob's contents with SAS without Read and Object-level permissions should fail."); + } + + if (((policy.Permissions & SharedAccessAccountPermissions.Delete) == SharedAccessAccountPermissions.Delete) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + await appendBlobWithSAS.DeleteAsync(); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await appendBlobWithSAS.DeleteAsync(), "Deleting a blob with SAS without Delete and Object-level perms should fail."); + await appendBlob.DeleteAsync(); + } + + Assert.IsFalse(await appendBlob.ExistsAsync()); + } + finally + { + blobClient.GetContainerReference(containerName).DeleteIfExistsAsync().AsTask().Wait(); + } + } + + public async Task RunPermissionsTestTables(SharedAccessAccountPolicy policy) + { + CloudTableClient tableClient = GenerateCloudTableClient(); + string tableName = "t" + Guid.NewGuid().ToString("N"); + ServiceProperties initialProperties = await tableClient.GetServicePropertiesAsync(); + try + { + CloudStorageAccount account = new CloudStorageAccount(tableClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = CloudStorageAccount.Create(accountSAS, null, null, tableClient.StorageUri, null); + CloudTableClient tableClientWithSAS = accountWithSAS.CreateCloudTableClient(); + CloudTable tableWithSAS = tableClientWithSAS.GetTableReference(tableName); + CloudTable table = tableClient.GetTableReference(tableName); + + // General pattern - If current perms support doing a thing with SAS, do the thing with SAS and validate with shared + // Otherwise, make sure SAS fails and then do the thing with shared key. + + // Things to do: + // Create the table (Create or Write perms, Container RT) + // List tables (List perms, Container RT) + // Set table service properties (Write perms, Service RT) + // Insert an entity (Add perms, Object RT) + // Merge an entity (Update perms, Object RT) + // Insert or merge an entity (Add and update perms, Object RT) (test this twice, once for insert, once for merge.) + // Query the table for the entity (Read perms, Object RT) + // Delete the entity (Delete perms, Object RT) + + if ((((policy.Permissions & SharedAccessAccountPermissions.Create) == SharedAccessAccountPermissions.Create) || ((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Container) == SharedAccessAccountResourceTypes.Container)) + { + await tableWithSAS.CreateAsync(); + } + else + { + await TestHelper.ExpectedExceptionAsync( + async () => await tableWithSAS.CreateAsync().AsTask(), + "Creating a table with SAS should fail without Add and Container-level permissions."); + await table.CreateAsync(); + } + Assert.IsTrue(await table.ExistsAsync()); + + if (((policy.Permissions & SharedAccessAccountPermissions.List) == SharedAccessAccountPermissions.List) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Container) == SharedAccessAccountResourceTypes.Container)) + { + Assert.AreEqual(tableName, (await tableClientWithSAS.ListTablesSegmentedAsync(tableName, null)).First().Name); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => (await tableClientWithSAS.ListTablesSegmentedAsync(tableName, null)).Results.First(), "Listing tables with SAS should fail without Read and Container-level permissions."); + } + + ServiceProperties properties = new ServiceProperties(); + properties.Logging = initialProperties.Logging; + properties.HourMetrics = initialProperties.HourMetrics; + properties.MinuteMetrics = initialProperties.MinuteMetrics; + properties.DefaultServiceVersion = initialProperties.DefaultServiceVersion; + CorsRule rule = new CorsRule(); + string sampleOriginText = "sampleOrigin"; + rule.AllowedOrigins.Add(sampleOriginText); + rule.AllowedMethods = CorsHttpMethods.Get; + rule.MaxAgeInSeconds = 100; + properties.Cors.CorsRules.Add(rule); + if (((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Service) == SharedAccessAccountResourceTypes.Service)) + { + await tableClientWithSAS.SetServicePropertiesAsync(properties); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await tableClientWithSAS.SetServicePropertiesAsync(properties), "Setting table service properites should fail with SAS without Write and Service-level permissions."); + await tableClient.SetServicePropertiesAsync(properties); + } + Assert.AreEqual(sampleOriginText, rule.AllowedOrigins.First()); + + string propName = "prop"; + string propName2 = "prop2"; + DynamicTableEntity entity1 = new DynamicTableEntity(); + + string partitionKey = "PK"; + string rowKeyPrefix = "RK"; + entity1.PartitionKey = partitionKey; + entity1.RowKey = rowKeyPrefix + "1"; + entity1.Properties = new Dictionary() { { propName, EntityProperty.GeneratePropertyForInt(4) } }; + entity1.ETag = "*"; + + if (((policy.Permissions & SharedAccessAccountPermissions.Add) == SharedAccessAccountPermissions.Add) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + await tableWithSAS.ExecuteAsync(TableOperation.Insert(entity1)); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await tableWithSAS.ExecuteAsync(TableOperation.Insert(entity1)), "Inserting an entity should fail without Add and Object-level permissions."); + await table.ExecuteAsync(TableOperation.Insert(entity1)); + } + TableQuery query = new TableQuery().Where(string.Format("(PartitionKey eq '{0}') and (RowKey eq '{1}')", entity1.PartitionKey, entity1.RowKey)); + Assert.AreEqual(entity1.Properties[propName].Int32Value, table.ExecuteQuery(query).First().Properties[propName].Int32Value); + entity1.ETag = "*"; + + DynamicTableEntity entity1changed = new DynamicTableEntity(); + entity1changed.PartitionKey = "PK"; + entity1changed.RowKey = "RK1"; + entity1changed.Properties = new Dictionary() { { propName2, EntityProperty.GeneratePropertyForInt(5) } }; + entity1changed.ETag = "*"; + + if (((policy.Permissions & SharedAccessAccountPermissions.Update) == SharedAccessAccountPermissions.Update) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + await tableWithSAS.ExecuteAsync(TableOperation.Merge(entity1changed)); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await tableWithSAS.ExecuteAsync(TableOperation.Merge(entity1changed)), "Merging an entity should fail without Update and Object-level permissions."); + await table.ExecuteAsync(TableOperation.Merge(entity1changed)); + } + + DynamicTableEntity result = table.ExecuteQuery(query).First(); + Assert.AreEqual(entity1.Properties[propName].Int32Value, result.Properties[propName].Int32Value); + Assert.AreEqual(entity1changed.Properties[propName2].Int32Value, result.Properties[propName2].Int32Value); + entity1changed.ETag = "*"; + + DynamicTableEntity entity2 = new DynamicTableEntity(); + entity2.PartitionKey = partitionKey; + entity2.RowKey = rowKeyPrefix + "2"; + entity2.Properties = new Dictionary() { { propName, EntityProperty.GeneratePropertyForInt(4) } }; + entity2.ETag = "*"; + + if ((((policy.Permissions & SharedAccessAccountPermissions.Add) == SharedAccessAccountPermissions.Add) && ((policy.Permissions & SharedAccessAccountPermissions.Update) == SharedAccessAccountPermissions.Update)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + await tableWithSAS.ExecuteAsync(TableOperation.InsertOrMerge(entity2)); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await tableWithSAS.ExecuteAsync(TableOperation.InsertOrMerge(entity2)), "Inserting or merging an entity should fail without Add and Update and Object-level permissions."); + await table.ExecuteAsync(TableOperation.InsertOrMerge(entity2)); + } + query = new TableQuery().Where(string.Format("(PartitionKey eq '{0}') and (RowKey eq '{1}')", entity2.PartitionKey, entity2.RowKey)); + Assert.AreEqual(entity2.Properties[propName].Int32Value, table.ExecuteQuery(query).First().Properties[propName].Int32Value); + entity2.ETag = "*"; + + DynamicTableEntity entity2changed = new DynamicTableEntity(); + entity2changed.PartitionKey = partitionKey; + entity2changed.RowKey = rowKeyPrefix + "2"; + entity2changed.Properties = new Dictionary() { { propName2, EntityProperty.GeneratePropertyForInt(5) } }; + entity2changed.ETag = "*"; + if ((((policy.Permissions & SharedAccessAccountPermissions.Add) == SharedAccessAccountPermissions.Add) && ((policy.Permissions & SharedAccessAccountPermissions.Update) == SharedAccessAccountPermissions.Update)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + await tableWithSAS.ExecuteAsync(TableOperation.InsertOrMerge(entity2changed)); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await tableWithSAS.ExecuteAsync(TableOperation.InsertOrMerge(entity2changed)), "Inserting or merging an entity should fail without Add and Update and Object-level permissions."); + await table.ExecuteAsync(TableOperation.InsertOrMerge(entity2changed)); + } + entity2changed.ETag = "*"; + + result = table.ExecuteQuery(query).First(); + Assert.AreEqual(entity2.Properties[propName].Int32Value, result.Properties[propName].Int32Value); + Assert.AreEqual(entity2changed.Properties[propName2].Int32Value, result.Properties[propName2].Int32Value); + + query = new TableQuery().Where(string.Format("(PartitionKey eq '{0}') and (RowKey ge '{1}') and (RowKey le '{2}')", entity1.PartitionKey, entity1.RowKey, entity2.RowKey)); + if (((policy.Permissions & SharedAccessAccountPermissions.Read) == SharedAccessAccountPermissions.Read) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + List entities = tableWithSAS.ExecuteQuery(query).ToList(); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => (await tableWithSAS.ExecuteQuerySegmentedAsync(query, null)).ToList(), "Querying tables should fail with SAS without Read and Object-level permissions."); + } + + if (((policy.Permissions & SharedAccessAccountPermissions.Delete) == SharedAccessAccountPermissions.Delete) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + await tableWithSAS.ExecuteAsync(TableOperation.Delete(entity1)); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await tableWithSAS.ExecuteAsync(TableOperation.Delete(entity1)), "Deleting an entity should fail with SAS without Delete and Object-level permissions."); + await table.ExecuteAsync(TableOperation.Delete(entity1)); + } + + query = new TableQuery().Where(string.Format("(PartitionKey eq '{0}') and (RowKey eq '{1}')", entity1.PartitionKey, entity1.RowKey)); + Assert.IsFalse(table.ExecuteQuery(query).Any()); + } + finally + { + tableClient.GetTableReference(tableName).DeleteIfExistsAsync().AsTask().Wait(); + if (initialProperties != null) + { + tableClient.SetServicePropertiesAsync(initialProperties).AsTask().Wait(); + } + } + } + + public async Task RunPermissionsTestQueues(SharedAccessAccountPolicy policy) + { + CloudQueueClient queueClient = GenerateCloudQueueClient(); + string queueName = "q" + Guid.NewGuid().ToString("N"); + try + { + CloudStorageAccount account = new CloudStorageAccount(queueClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = CloudStorageAccount.Create(accountSAS, null, queueClient.StorageUri, null, null); + CloudQueueClient queueClientWithSAS = accountWithSAS.CreateCloudQueueClient(); + CloudQueue queueWithSAS = queueClientWithSAS.GetQueueReference(queueName); + CloudQueue queue = queueClient.GetQueueReference(queueName); + + // General pattern - If current perms support doing a thing with SAS, do the thing with SAS and validate with shared + // Otherwise, make sure SAS fails and then do the thing with shared key. + + // Things to do: + // Create the queue (Create or Write perms, Container RT) + // List queues (List perms, Service RT) + // Set queue metadata (Write perms, Container RT) + // Insert a message (Add perms, Object RT) + // Peek a message (Read perms, Object RT) + // Get a message (Process perms, Object RT) + // Update a message (Update perms, Object RT) + // Clear all messages (Delete perms, Object RT) + + if ((((policy.Permissions & SharedAccessAccountPermissions.Create) == SharedAccessAccountPermissions.Create) || ((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Container) == SharedAccessAccountResourceTypes.Container)) + { + await queueWithSAS.CreateAsync(); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await queueWithSAS.CreateAsync(), "Creating a queue with SAS should fail without Add and Container-level permissions."); + await queue.CreateAsync(); + } + Assert.IsTrue(await queue.ExistsAsync()); + + if (((policy.Permissions & SharedAccessAccountPermissions.List) == SharedAccessAccountPermissions.List) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Service) == SharedAccessAccountResourceTypes.Service)) + { + Assert.AreEqual(queueName, (await queueClientWithSAS.ListQueuesSegmentedAsync(queueName, null)).Results.First().Name); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => (await queueClientWithSAS.ListQueuesSegmentedAsync(queueName, null)).Results.First(), "Listing queues with SAS should fail without Read and Service-level permissions."); + } + + queueWithSAS.Metadata["metadatakey"] = "metadatavalue"; + if (((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Container) == SharedAccessAccountResourceTypes.Container)) + { + await queueWithSAS.SetMetadataAsync(); + await queue.FetchAttributesAsync(); + Assert.AreEqual("metadatavalue", queue.Metadata["metadatakey"]); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await queueWithSAS.SetMetadataAsync(), "Setting a queue's metadata with SAS should fail without Write and Container-level permissions."); + } + + string messageText = "messageText"; + CloudQueueMessage message = new CloudQueueMessage(messageText); + if (((policy.Permissions & SharedAccessAccountPermissions.Add) == SharedAccessAccountPermissions.Add) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + await queueWithSAS.AddMessageAsync(message); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await queueWithSAS.AddMessageAsync(message), "Adding a queue message should fail with SAS without Add and Object-level permissions."); + await queue.AddMessageAsync(message); + } + Assert.AreEqual(messageText, ((await queue.PeekMessageAsync()).AsString)); + + if (((policy.Permissions & SharedAccessAccountPermissions.Read) == SharedAccessAccountPermissions.Read) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + Assert.AreEqual(messageText, (await queueWithSAS.PeekMessageAsync()).AsString); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await queueWithSAS.PeekMessageAsync(), "Peeking a queue message should fail with SAS without Read and Object-level permissions."); + } + + CloudQueueMessage messageResult = null; + if (((policy.Permissions & SharedAccessAccountPermissions.ProcessMessages) == SharedAccessAccountPermissions.ProcessMessages) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + messageResult = await queueWithSAS.GetMessageAsync(); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await queueWithSAS.GetMessageAsync(), "Getting a message should fail with SAS without Process and Object-level permissions."); + messageResult = await queue.GetMessageAsync(); + } + Assert.AreEqual(messageText, messageResult.AsString); + + string newMessageContent = "new content"; + messageResult.SetMessageContent(newMessageContent); + if (((policy.Permissions & SharedAccessAccountPermissions.Update) == SharedAccessAccountPermissions.Update) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + await queueWithSAS.UpdateMessageAsync(messageResult, TimeSpan.Zero, MessageUpdateFields.Content | MessageUpdateFields.Visibility); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await queueWithSAS.UpdateMessageAsync(messageResult, TimeSpan.Zero, MessageUpdateFields.Content | MessageUpdateFields.Visibility), "Updating a message should fail with SAS without Update and Object-level permissions."); + await queue.UpdateMessageAsync(messageResult, TimeSpan.Zero, MessageUpdateFields.Content | MessageUpdateFields.Visibility); + } + messageResult = await queue.PeekMessageAsync(); + Assert.AreEqual(newMessageContent, messageResult.AsString); + + if (((policy.Permissions & SharedAccessAccountPermissions.Delete) == SharedAccessAccountPermissions.Delete) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + await queueWithSAS.ClearAsync(); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await queueWithSAS.ClearAsync(), "Clearing messages should fail with SAS without delete and Object-level permissions."); + } + } + finally + { + queueClient.GetQueueReference(queueName).DeleteIfExistsAsync().AsTask().Wait(); + } + } + public async Task RunPermissionsTestFiles(SharedAccessAccountPolicy policy) + { + CloudFileClient fileClient = GenerateCloudFileClient(); + string shareName = "s" + Guid.NewGuid().ToString("N"); + try + { + CloudStorageAccount account = new CloudStorageAccount(fileClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = CloudStorageAccount.Create(accountSAS, null, null, null, fileClient.StorageUri); + CloudFileClient fileClientWithSAS = accountWithSAS.CreateCloudFileClient(); + CloudFileShare shareWithSAS = fileClientWithSAS.GetShareReference(shareName); + CloudFileShare share = fileClient.GetShareReference(shareName); + + // General pattern - If current perms support doing a thing with SAS, do the thing with SAS and validate with shared + // Otherwise, make sure SAS fails and then do the thing with shared key. + + // Things to do: + // Create the share (Create / Write perms, Container RT) + // List shares with prefix (List perms, Service RT) + // Create a new file (Create / Write, Object RT) + // Add a range to the file (Write, Object RT) + // Read the data from the file (Read, Object RT) + // Overwrite a file (Write, Object RT) + // Delete the file (Delete perms, Object RT) + + if ((((policy.Permissions & SharedAccessAccountPermissions.Create) == SharedAccessAccountPermissions.Create) || ((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Container) == SharedAccessAccountResourceTypes.Container)) + { + await shareWithSAS.CreateAsync(); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await shareWithSAS.CreateAsync(), "Creating a share with SAS should fail without Create or Write and Container-level perms."); + await share.CreateAsync(); + } + Assert.IsTrue(await share.ExistsAsync()); + + if (((policy.Permissions & SharedAccessAccountPermissions.List) == SharedAccessAccountPermissions.List) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Service) == SharedAccessAccountResourceTypes.Service)) + { + Assert.AreEqual(shareName, (await fileClientWithSAS.ListSharesSegmentedAsync(shareName, null)).Results.First().Name); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => (await fileClientWithSAS.ListSharesSegmentedAsync(shareName, null)).Results.First(), "Listing shared with SAS should fail without List and Service-level perms."); + } + + string filename = "fileName"; + CloudFile fileWithSAS = shareWithSAS.GetRootDirectoryReference().GetFileReference(filename); + CloudFile file = share.GetRootDirectoryReference().GetFileReference(filename); + + byte[] content = new byte[] { 0x1, 0x2, 0x3, 0x4 }; + if ((((policy.Permissions & SharedAccessAccountPermissions.Create) == SharedAccessAccountPermissions.Create) || ((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write)) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + await fileWithSAS.CreateAsync(content.Length); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await fileWithSAS.CreateAsync(content.Length), "Creating a file with SAS should fail without Create or Write and Object-level perms."); + await file.CreateAsync(content.Length); + } + Assert.IsTrue(await file.ExistsAsync()); + + using (MemoryStream stream = new MemoryStream(content)) + { + if (((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + await fileWithSAS.WriteRangeAsync(stream.AsInputStream(), 0, null); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await fileWithSAS.WriteRangeAsync(stream.AsInputStream(), 0, null), "Writing a range to a file with SAS should fail without Write and Object-level perms."); + stream.Seek(0, SeekOrigin.Begin); + await file.WriteRangeAsync(stream.AsInputStream(), 0, null); + } + } + + byte[] result = new byte[content.Length]; + await file.DownloadRangeToByteArrayAsync(result, 0, 0, content.Length); + for (int i = 0; i < content.Length; i++) + { + Assert.AreEqual(content[i], result[i]); + } + + if (((policy.Permissions & SharedAccessAccountPermissions.Read) == SharedAccessAccountPermissions.Read) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + result = new byte[content.Length]; + await fileWithSAS.DownloadRangeToByteArrayAsync(result, 0, 0, content.Length); + for (int i = 0; i < content.Length; i++) + { + Assert.AreEqual(content[i], result[i]); + } + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await fileWithSAS.DownloadRangeToByteArrayAsync(result, 0, 0, content.Length), "Reading a file with SAS should fail without Read and Object-level perms."); + } + + if (((policy.Permissions & SharedAccessAccountPermissions.Write) == SharedAccessAccountPermissions.Write) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + await fileWithSAS.CreateAsync(2); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await fileWithSAS.CreateAsync(2), "Overwriting a file with SAS should fail without Write and Object-level perms."); + await file.CreateAsync(2); + } + + result = new byte[content.Length]; + await file.DownloadRangeToByteArrayAsync(result, 0, 0, content.Length); + for (int i = 0; i < content.Length; i++) + { + Assert.AreEqual(0, result[i]); + } + + if (((policy.Permissions & SharedAccessAccountPermissions.Delete) == SharedAccessAccountPermissions.Delete) && + ((policy.ResourceTypes & SharedAccessAccountResourceTypes.Object) == SharedAccessAccountResourceTypes.Object)) + { + await fileWithSAS.DeleteAsync(); + } + else + { + await TestHelper.ExpectedExceptionAsync(async () => await fileWithSAS.DeleteAsync(), "Deleting a file with SAS should fail without Delete and Object-level perms."); + await file.DeleteAsync(); + } + + Assert.IsFalse(await file.ExistsAsync()); + } + finally + { + fileClient.GetShareReference(shareName).DeleteIfExistsAsync().AsTask().Wait(); + } + } + + public async Task RunBlobTest(SharedAccessAccountPolicy policy, int? httpsPort, OperationContext opContext = null) + { + CloudBlobClient blobClient = GenerateCloudBlobClient(); + string containerName = "c" + Guid.NewGuid().ToString("N"); + try + { + CloudStorageAccount account = new CloudStorageAccount(blobClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + + StorageUri storageUri = blobClient.StorageUri; + if (httpsPort != null) + { + storageUri = new StorageUri(TransformSchemeAndPort(storageUri.PrimaryUri, "https", httpsPort.Value), TransformSchemeAndPort(storageUri.SecondaryUri, "https", httpsPort.Value)); + } + + CloudStorageAccount accountWithSAS = CloudStorageAccount.Create(accountSAS, storageUri, null, null, null); + CloudBlobClient blobClientWithSAS = accountWithSAS.CreateCloudBlobClient(); + CloudBlobContainer containerWithSAS = blobClientWithSAS.GetContainerReference(containerName); + CloudBlobContainer container = blobClient.GetContainerReference(containerName); + await container.CreateAsync(); + string blobName = "blob"; + CloudBlockBlob blob = container.GetBlockBlobReference(blobName); + string blobText = "blobText"; + await blob.UploadTextAsync(blobText); + + CloudBlockBlob blobWithSAS = containerWithSAS.GetBlockBlobReference(blobName); + + Assert.AreEqual(blobText, await blobWithSAS.DownloadTextAsync(null, null, opContext)); + } + finally + { + blobClient.GetContainerReference(containerName).DeleteIfExistsAsync().AsTask().Wait(); + } + } + + public async Task RunTableTest(SharedAccessAccountPolicy policy, int? httpsPort, OperationContext opContext = null) + { + CloudTableClient tableClient = GenerateCloudTableClient(); + string tableName = "t" + Guid.NewGuid().ToString("N"); + try + { + CloudStorageAccount account = new CloudStorageAccount(tableClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + + StorageUri storageUri = tableClient.StorageUri; + if (httpsPort != null) + { + storageUri = new StorageUri(TransformSchemeAndPort(storageUri.PrimaryUri, "https", httpsPort.Value), TransformSchemeAndPort(storageUri.SecondaryUri, "https", httpsPort.Value)); + } + + CloudStorageAccount accountWithSAS = CloudStorageAccount.Create(accountSAS, null, null, storageUri, null); + CloudTableClient tableClientWithSAS = accountWithSAS.CreateCloudTableClient(); + CloudTable tableWithSAS = tableClientWithSAS.GetTableReference(tableName); + CloudTable table = tableClient.GetTableReference(tableName); + await table.CreateAsync(); + + string propName = "prop"; + int propValue = 4; + DynamicTableEntity entity1 = new DynamicTableEntity(); + + string partitionKey = "PK"; + string rowKeyPrefix = "RK"; + entity1.PartitionKey = partitionKey; + entity1.RowKey = rowKeyPrefix + "1"; + entity1.Properties = new Dictionary() { { propName, EntityProperty.GeneratePropertyForInt(propValue) } }; + + await table.ExecuteAsync(TableOperation.Insert(entity1)); + + TableQuery query = new TableQuery().Where(string.Format("(PartitionKey eq '{0}') and (RowKey eq '{1}')", entity1.PartitionKey, entity1.RowKey)); + + Assert.AreEqual(propValue, (await tableWithSAS.ExecuteQuerySegmentedAsync(query, null, null, opContext)).Results.First().Properties[propName].Int32Value); + } + finally + { + tableClient.GetTableReference(tableName).DeleteIfExistsAsync().AsTask().Wait(); + } + } + + public async Task RunQueueTest(SharedAccessAccountPolicy policy, int? httpsPort, OperationContext opContext = null) + { + CloudQueueClient queueClient = GenerateCloudQueueClient(); + string queueName = "q" + Guid.NewGuid().ToString("N"); + try + { + CloudStorageAccount account = new CloudStorageAccount(queueClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + + StorageUri storageUri = queueClient.StorageUri; + if (httpsPort != null) + { + storageUri = new StorageUri(TransformSchemeAndPort(storageUri.PrimaryUri, "https", httpsPort.Value), TransformSchemeAndPort(storageUri.SecondaryUri, "https", httpsPort.Value)); + } + + CloudStorageAccount accountWithSAS = CloudStorageAccount.Create(accountSAS, null, storageUri, null, null); + CloudQueueClient queueClientWithSAS = accountWithSAS.CreateCloudQueueClient(); + CloudQueue queueWithSAS = queueClientWithSAS.GetQueueReference(queueName); + CloudQueue queue = queueClient.GetQueueReference(queueName); + await queue.CreateAsync(); + string messageText = "message text"; + CloudQueueMessage message = new CloudQueueMessage(messageText); + await queue.AddMessageAsync(message); + + Assert.AreEqual(messageText, (await queueWithSAS.GetMessageAsync(null, null, opContext)).AsString); + } + finally + { + queueClient.GetQueueReference(queueName).DeleteIfExistsAsync().AsTask().Wait(); + } + } + + public async Task RunFileTest(SharedAccessAccountPolicy policy, int? httpsPort, OperationContext opContext = null) + { + CloudFileClient fileClient = GenerateCloudFileClient(); + string shareName = "s" + Guid.NewGuid().ToString("N"); + try + { + CloudStorageAccount account = new CloudStorageAccount(fileClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + + StorageUri storageUri = fileClient.StorageUri; + if (httpsPort != null) + { + storageUri = new StorageUri(TransformSchemeAndPort(storageUri.PrimaryUri, "https", httpsPort.Value), TransformSchemeAndPort(storageUri.SecondaryUri, "https", httpsPort.Value)); + } + + CloudStorageAccount accountWithSAS = CloudStorageAccount.Create(accountSAS, null, null, null, storageUri); + CloudFileClient fileClientWithSAS = accountWithSAS.CreateCloudFileClient(); + CloudFileShare shareWithSAS = fileClientWithSAS.GetShareReference(shareName); + CloudFileShare share = fileClient.GetShareReference(shareName); + await share.CreateAsync(); + string fileName = "file"; + CloudFile file = share.GetRootDirectoryReference().GetFileReference(fileName); + CloudFile fileWithSAS = shareWithSAS.GetRootDirectoryReference().GetFileReference(fileName); + byte[] content = new byte[] { 0x1, 0x2, 0x3, 0x4 }; + await file.CreateAsync(content.Length); + using (IInputStream stream = new MemoryStream(content).AsInputStream()) + { + await file.WriteRangeAsync(stream, 0, null); + } + + byte[] result = new byte[content.Length]; + await fileWithSAS.DownloadRangeToByteArrayAsync(result, 0, 0, content.Length, null, null, opContext); + for (int i = 0; i < content.Length; i++) + { + Assert.AreEqual(content[i], result[i]); + } + } + finally + { + fileClient.GetShareReference(shareName).DeleteIfExistsAsync().AsTask().Wait(); + } + } + + private async Task GetMyBlobIPAddressFromService() + { + CloudBlobClient blobClient = GenerateCloudBlobClient(); + string containerName = "c" + Guid.NewGuid().ToString("N"); + CloudBlobContainer container = blobClient.GetContainerReference(containerName); + try + { + await container.CreateAsync(); + string blobName = "blob"; + CloudBlockBlob blockBlob = container.GetBlockBlobReference(blobName); + byte[] data = new byte[] { 0x1, 0x2, 0x3, 0x4 }; + await blockBlob.UploadFromByteArrayAsync(data, 0, 4); + + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + HostName invalidIP = new HostName("255.255.255.255"); + policy.IPAddressOrRange = new IPAddressOrRange(invalidIP.ToString()); + + CloudStorageAccount account = new CloudStorageAccount(blobClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = CloudStorageAccount.Create(accountSAS, blobClient.StorageUri, null, null, null); + CloudBlobClient blobClientWithSAS = accountWithSAS.CreateCloudBlobClient(); + CloudBlobContainer containerWithSAS = blobClientWithSAS.GetContainerReference(containerName); + CloudBlockBlob blockblobWithSAS = containerWithSAS.GetBlockBlobReference(blobName); + + byte[] target = new byte[4]; + string actualIP = null; + bool exceptionThrown = false; + try + { + await blockblobWithSAS.DownloadRangeToByteArrayAsync(target, 0, 0, 4); + } + catch (WrappedStorageException e) + { + XDocument xdocument = XDocument.Parse(e.Message); + actualIP = xdocument.Descendants("SourceIP").First().Value; + exceptionThrown = true; + Assert.IsNotNull(actualIP); + } + + Assert.IsTrue(exceptionThrown); + return actualIP; + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + private async Task GetMyQueueIPAddressFromService() + { + CloudQueueClient queueClient = GenerateCloudQueueClient(); + string queueName = "c" + Guid.NewGuid().ToString("N"); + CloudQueue queue = queueClient.GetQueueReference(queueName); + try + { + await queue.CreateAsync(); + CloudQueueMessage message = new CloudQueueMessage("content"); + await queue.AddMessageAsync(message); + + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + HostName invalidIP = new HostName("255.255.255.255"); + policy.IPAddressOrRange = new IPAddressOrRange(invalidIP.ToString()); + + CloudStorageAccount account = new CloudStorageAccount(queueClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = CloudStorageAccount.Create(accountSAS, null, queueClient.StorageUri, null, null); + CloudQueueClient queueClientWithSAS = accountWithSAS.CreateCloudQueueClient(); + CloudQueue queueWithSAS = queueClientWithSAS.GetQueueReference(queueName); + + string actualIP = null; + bool exceptionThrown = false; + try + { + await queueWithSAS.GetMessageAsync(); + } + catch (WrappedStorageException e) + { + XDocument xdocument = XDocument.Parse(e.Message); + actualIP = xdocument.Descendants("SourceIP").First().Value; + exceptionThrown = true; + Assert.IsNotNull(actualIP); + } + + Assert.IsTrue(exceptionThrown); + return actualIP; + } + finally + { + queue.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + private async Task GetMyTableIPAddressFromService() + { + CloudTableClient tableClient = GenerateCloudTableClient(); + string tableName = "c" + Guid.NewGuid().ToString("N"); + CloudTable table = tableClient.GetTableReference(tableName); + try + { + await table.CreateAsync(); + + string propName = "prop"; + int propValue = 4; + DynamicTableEntity entity1 = new DynamicTableEntity(); + + string partitionKey = "PK"; + string rowKeyPrefix = "RK"; + entity1.PartitionKey = partitionKey; + entity1.RowKey = rowKeyPrefix + "1"; + entity1.Properties = new Dictionary() { { propName, EntityProperty.GeneratePropertyForInt(propValue) } }; + + await table.ExecuteAsync(TableOperation.Insert(entity1)); + + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + HostName invalidIP = new HostName("255.255.255.255"); + policy.IPAddressOrRange = new IPAddressOrRange(invalidIP.ToString()); + + CloudStorageAccount account = new CloudStorageAccount(tableClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = CloudStorageAccount.Create(accountSAS, null, null, tableClient.StorageUri, null); + CloudTableClient tableClientWithSAS = accountWithSAS.CreateCloudTableClient(); + CloudTable tableWithSAS = tableClientWithSAS.GetTableReference(tableName); + + string actualIP = null; + bool exceptionThrown = false; + + try + { + TableQuery query = new TableQuery().Where(string.Format("(PartitionKey eq '{0}') and (RowKey eq '{1}')", entity1.PartitionKey, entity1.RowKey)); + (await tableWithSAS.ExecuteQuerySegmentedAsync(query, null)).First(); + } + catch (WrappedStorageException e) + { + XDocument xdocument = XDocument.Parse(e.Message); + System.Diagnostics.Debug.WriteLine(xdocument.ToString()); + string message = xdocument.Descendants("HttpStatusMessage").First().Value; + string[] parts = message.Split(' '); + actualIP = parts[parts.Length - 1].Trim('.'); + exceptionThrown = true; + Assert.IsNotNull(actualIP); + } + + Assert.IsTrue(exceptionThrown); + return actualIP; + } + finally + { + table.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + private async Task GetMyFileIPAddressFromService() + { + CloudFileClient fileClient = GenerateCloudFileClient(); + string shareName = "c" + Guid.NewGuid().ToString("N"); + CloudFileShare share = fileClient.GetShareReference(shareName); + try + { + await share.CreateAsync(); + string fileName = "file"; + await share.GetRootDirectoryReference().CreateIfNotExistsAsync(); + CloudFile file = share.GetRootDirectoryReference().GetFileReference(fileName); + await file.CreateAsync(1024); + byte[] data = new byte[] { 0x1, 0x2, 0x3, 0x4 }; + await file.UploadFromByteArrayAsync(data, 0, 4); + + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + HostName invalidIP = new HostName("255.255.255.255"); + policy.IPAddressOrRange = new IPAddressOrRange(invalidIP.ToString()); + + CloudStorageAccount account = new CloudStorageAccount(fileClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = CloudStorageAccount.Create(accountSAS, null, null, null, fileClient.StorageUri); + CloudFileClient fileClientWithSAS = accountWithSAS.CreateCloudFileClient(); + CloudFileShare shareWithSAS = fileClientWithSAS.GetShareReference(shareName); + CloudFile fileWithSAS = shareWithSAS.GetRootDirectoryReference().GetFileReference(fileName); + + byte[] target = new byte[4]; + string actualIP = null; + bool exceptionThrown = false; + try + { + await fileWithSAS.DownloadRangeToByteArrayAsync(target, 0, 0, 4); + } + catch (WrappedStorageException e) + { + XDocument xdocument = XDocument.Parse(e.Message); + actualIP = xdocument.Descendants("SourceIP").First().Value; + exceptionThrown = true; + Assert.IsNotNull(actualIP); + } + + Assert.IsTrue(exceptionThrown); + return actualIP; + } + finally + { + share.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + private async Task GetMyIPAddressFromService() + { + CloudBlobClient blobClient = GenerateCloudBlobClient(); + string containerName = "c" + Guid.NewGuid().ToString("N"); + CloudBlobContainer container = blobClient.GetContainerReference(containerName); + try + { + await container.CreateAsync(); + + string blobName = "blob"; + CloudBlockBlob blockBlob = container.GetBlockBlobReference(blobName); + byte[] data = new byte[] { 0x1, 0x2, 0x3, 0x4 }; + await blockBlob.UploadFromByteArrayAsync(data, 0, 4); + + SharedAccessAccountPolicy policy = GetPolicyWithFullPermissions(); + HostName invalidIP = new HostName("255.255.255.255"); + policy.IPAddressOrRange = new IPAddressOrRange(invalidIP.ToString()); + + CloudStorageAccount account = new CloudStorageAccount(blobClient.Credentials, false); + string accountSASToken = account.GetSharedAccessSignature(policy); + StorageCredentials accountSAS = new StorageCredentials(accountSASToken); + CloudStorageAccount accountWithSAS = CloudStorageAccount.Create(accountSAS, blobClient.StorageUri, null, null, null); + CloudBlobClient blobClientWithSAS = accountWithSAS.CreateCloudBlobClient(); + CloudBlobContainer containerWithSAS = blobClientWithSAS.GetContainerReference(containerName); + CloudBlockBlob blockblobWithSAS = containerWithSAS.GetBlockBlobReference(blobName); + + byte[] target = new byte[4]; + OperationContext opContext = new OperationContext(); + string actualIP = null; + + bool exceptionThrown = false; + try + { + await blockblobWithSAS.DownloadRangeToByteArrayAsync(target, 0, 0, 4, null, null, opContext); + } + catch (WrappedStorageException e) + { + exceptionThrown = true; + XDocument xdocument = XDocument.Parse(e.Message); + actualIP = xdocument.Descendants("SourceIP").First().Value; + Assert.IsNotNull(actualIP); + } + + Assert.IsTrue(exceptionThrown); + return actualIP; + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + + } + + private SharedAccessAccountPolicy GetPolicyWithFullPermissions() + { + SharedAccessAccountPolicy policy = new SharedAccessAccountPolicy(); + policy.SharedAccessStartTime = DateTime.Now - TimeSpan.FromMinutes(5); + policy.SharedAccessExpiryTime = DateTime.Now + TimeSpan.FromMinutes(30); + policy.Permissions = (SharedAccessAccountPermissions)(0x100 - 0x1); + policy.Services = (SharedAccessAccountServices)(0x10 - 0x1); + policy.ResourceTypes = (SharedAccessAccountResourceTypes)(0x8 - 0x1); + policy.Protocols = SharedAccessProtocol.HttpsOrHttp; + policy.IPAddressOrRange = null; + return policy; + } + + private static Uri TransformSchemeAndPort(Uri input, string scheme, int port) + { + UriBuilder builder = new UriBuilder(input); + builder.Scheme = scheme; + builder.Port = port; + return builder.Uri; + } + } +} diff --git a/Test/WindowsRuntime/Microsoft.WindowsAzure.StorageRT.Test.csproj b/Test/WindowsRuntime/Microsoft.WindowsAzure.StorageRT.Test.csproj index b31ad8fd6..c5ce4a745 100644 --- a/Test/WindowsRuntime/Microsoft.WindowsAzure.StorageRT.Test.csproj +++ b/Test/WindowsRuntime/Microsoft.WindowsAzure.StorageRT.Test.csproj @@ -68,6 +68,7 @@ TestConfigProcess\TestConfigProcess + @@ -77,6 +78,7 @@ + @@ -155,8 +157,8 @@ - + diff --git a/Test/WindowsRuntime/Properties/AssemblyInfo.cs b/Test/WindowsRuntime/Properties/AssemblyInfo.cs index 3e9d74c45..aac769baf 100644 --- a/Test/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Test/WindowsRuntime/Properties/AssemblyInfo.cs @@ -24,6 +24,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("5.0.2.0")] -[assembly: AssemblyFileVersion("5.0.2.0")] +[assembly: AssemblyVersion("6.0.0.0")] +[assembly: AssemblyFileVersion("6.0.0.0")] [assembly: ComVisible(false)] diff --git a/Test/WindowsRuntime/Table/CloudTableCRUDUnitTests.cs b/Test/WindowsRuntime/Table/CloudTableCRUDUnitTests.cs index a4e30ec95..a490ab637 100644 --- a/Test/WindowsRuntime/Table/CloudTableCRUDUnitTests.cs +++ b/Test/WindowsRuntime/Table/CloudTableCRUDUnitTests.cs @@ -69,6 +69,8 @@ public TestContext TestContext #endregion + CloudTableClient tableClient = null; + #region Additional test attributes // // You can use the following additional attributes as you write your tests: @@ -86,6 +88,8 @@ public TestContext TestContext [TestInitialize()] public void MyTestInitialize() { + tableClient = GenerateCloudTableClient(); + if (TestBase.TableBufferManager != null) { TestBase.TableBufferManager.OutstandingBufferCount = 0; @@ -114,9 +118,16 @@ public void MyTestCleanup() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task CloudTableCreateAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); - string tableName = GenerateRandomTableName(); - CloudTable tableRef = tableClient.GetTableReference(tableName); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoCloudTableCreateAsync(payloadFormat); + } + } + + private async Task DoCloudTableCreateAsync(TablePayloadFormat payloadFormat) + { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; + CloudTable tableRef = tableClient.GetTableReference(GenerateRandomTableName()); try { @@ -138,9 +149,16 @@ public async Task CloudTableCreateAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task CloudTableCreateAlreadyExistsAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); - string tableName = GenerateRandomTableName(); - CloudTable tableRef = tableClient.GetTableReference(tableName); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoCloudTableCreateAlreadyExistsAsync(payloadFormat); + } + } + + private async Task DoCloudTableCreateAlreadyExistsAsync(TablePayloadFormat payloadFormat) + { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; + CloudTable tableRef = tableClient.GetTableReference(GenerateRandomTableName()); OperationContext ctx = new OperationContext(); try @@ -176,9 +194,16 @@ public async Task CloudTableCreateAlreadyExistsAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task CloudTableCreateIfNotExistsAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); - string tableName = GenerateRandomTableName(); - CloudTable tableRef = tableClient.GetTableReference(tableName); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoCloudTableCreateIfNotExistsAsync(payloadFormat); + } + } + + private async Task DoCloudTableCreateIfNotExistsAsync(TablePayloadFormat payloadFormat) + { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; + CloudTable tableRef = tableClient.GetTableReference(GenerateRandomTableName()); try { @@ -205,9 +230,16 @@ public async Task CloudTableCreateIfNotExistsAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task CloudTableDeleteAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); - string tableName = GenerateRandomTableName(); - CloudTable tableRef = tableClient.GetTableReference(tableName); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoCloudTableDeleteAsync(payloadFormat); + } + } + + private async Task DoCloudTableDeleteAsync(TablePayloadFormat payloadFormat) + { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; + CloudTable tableRef = tableClient.GetTableReference(GenerateRandomTableName()); try { @@ -231,9 +263,16 @@ public async Task CloudTableDeleteAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task CloudTableDeleteWhenNotExistAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); - string tableName = GenerateRandomTableName(); - CloudTable tableRef = tableClient.GetTableReference(tableName); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoCloudTableDeleteWhenNotExistAsync(payloadFormat); + } + } + + private async Task DoCloudTableDeleteWhenNotExistAsync(TablePayloadFormat payloadFormat) + { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; + CloudTable tableRef = tableClient.GetTableReference(GenerateRandomTableName()); OperationContext ctx = new OperationContext(); try @@ -268,9 +307,16 @@ public async Task CloudTableDeleteWhenNotExistAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task CloudTableDeleteIfExistsAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); - string tableName = GenerateRandomTableName(); - CloudTable tableRef = tableClient.GetTableReference(tableName); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoCloudTableDeleteIfExistsAsync(payloadFormat); + } + } + + private async Task DoCloudTableDeleteIfExistsAsync(TablePayloadFormat payloadFormat) + { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; + CloudTable tableRef = tableClient.GetTableReference(GenerateRandomTableName()); try { @@ -299,9 +345,16 @@ public async Task CloudTableDeleteIfExistsAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task CloudTableExistsAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); - string tableName = GenerateRandomTableName(); - CloudTable tableRef = tableClient.GetTableReference(tableName); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoCloudTableExistsAsync(payloadFormat); + } + } + + private async Task DoCloudTableExistsAsync(TablePayloadFormat payloadFormat) + { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; + CloudTable tableRef = tableClient.GetTableReference(GenerateRandomTableName()); try { diff --git a/Test/WindowsRuntime/Table/CloudTableClientTest.cs b/Test/WindowsRuntime/Table/CloudTableClientTest.cs index c81cb0991..0b3156bb7 100644 --- a/Test/WindowsRuntime/Table/CloudTableClientTest.cs +++ b/Test/WindowsRuntime/Table/CloudTableClientTest.cs @@ -164,8 +164,17 @@ public void CloudTableClientConstructor() [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task ListTablesSegmentedBasicAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoCloudTableDeleteIfExistsAsync(payloadFormat); + } + } + + private async Task DoCloudTableDeleteIfExistsAsync(TablePayloadFormat payloadFormat) { CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; TableResultSegment segment = null; List totalResults = new List(); @@ -177,7 +186,7 @@ public async Task ListTablesSegmentedBasicAsync() } while (segment.ContinuationToken != null); - // Assert.AreEqual(totalResults.Count, tableClient.ListTables().Count()); + // Assert.AreEqual(totalResults.Count, tableClient.ListTables().Count()); } [TestMethod] @@ -187,8 +196,17 @@ public async Task ListTablesSegmentedBasicAsync() [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task ListTablesSegmentedMaxResultsAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoListTablesSegmentedMaxResultsAsync(payloadFormat); + } + } + + private async Task DoListTablesSegmentedMaxResultsAsync(TablePayloadFormat payloadFormat) { CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; TableResultSegment segment = null; List totalResults = new List(); @@ -213,8 +231,17 @@ public async Task ListTablesSegmentedMaxResultsAsync() [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task ListTablesSegmentedWithPrefixAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoListTablesSegmentedWithPrefixAsync(payloadFormat); + } + } + + private async Task DoListTablesSegmentedWithPrefixAsync(TablePayloadFormat payloadFormat) { CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; TableResultSegment segment = null; List totalResults = new List(); @@ -241,9 +268,18 @@ public async Task ListTablesSegmentedWithPrefixAsync() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task CloudTableClientListTablesSegmentedSharedKeyLite() + public async Task CloudTableClientListTablesSegmentedSharedKeyLiteAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoCloudTableClientListTablesSegmentedSharedKeyLiteAsync(payloadFormat); + } + } + + private async Task DoCloudTableClientListTablesSegmentedSharedKeyLiteAsync(TablePayloadFormat payloadFormat) { CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; tableClient.AuthenticationScheme = AuthenticationScheme.SharedKeyLite; TableResultSegment segment = null; @@ -268,11 +304,20 @@ public async Task CloudTableClientListTablesSegmentedSharedKeyLite() [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task CloudTableClientGetServiceStatsAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoCloudTableClientGetServiceStatsAsync(payloadFormat); + } + } + + private async Task DoCloudTableClientGetServiceStatsAsync(TablePayloadFormat payloadFormat) { AssertSecondaryEndpoint(); CloudTableClient client = GenerateCloudTableClient(); client.DefaultRequestOptions.LocationMode = LocationMode.SecondaryOnly; + client.DefaultRequestOptions.PayloadFormat = payloadFormat; TestHelper.VerifyServiceStats(await client.GetServiceStatsAsync()); } @@ -284,8 +329,17 @@ public async Task CloudTableClientGetServiceStatsAsync() [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task CloudTableClientServerTimeoutAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoCloudTableClientServerTimeoutAsync(payloadFormat); + } + } + + private async Task DoCloudTableClientServerTimeoutAsync(TablePayloadFormat payloadFormat) { CloudTableClient client = GenerateCloudTableClient(); + client.DefaultRequestOptions.PayloadFormat = payloadFormat; string timeout = null; OperationContext context = new OperationContext(); diff --git a/Test/WindowsRuntime/Table/TableBatchOperationTest.cs b/Test/WindowsRuntime/Table/TableBatchOperationTest.cs index b9e0ad5b5..11055bcae 100644 --- a/Test/WindowsRuntime/Table/TableBatchOperationTest.cs +++ b/Test/WindowsRuntime/Table/TableBatchOperationTest.cs @@ -66,6 +66,7 @@ public TestContext TestContext } CloudTable currentTable = null; + CloudTableClient tableClient = null; #endregion @@ -85,7 +86,7 @@ public TestContext TestContext [TestInitialize()] public void MyTestInitialize() { - CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient = GenerateCloudTableClient(); currentTable = tableClient.GetTableReference(GenerateRandomTableName()); currentTable.CreateIfNotExistsAsync().AsTask().Wait(); @@ -101,7 +102,6 @@ public void MyTestInitialize() public void MyTestCleanup() { currentTable.DeleteIfExistsAsync().AsTask().Wait(); - if (TestBase.TableBufferManager != null) { Assert.AreEqual(0, TestBase.TableBufferManager.OutstandingBufferCount); @@ -120,6 +120,15 @@ public void MyTestCleanup() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchInsertAsync() { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchInsertAsync(payloadFormat); + } + } + + private async Task DoTableBatchInsertAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; TableBatchOperation batch = new TableBatchOperation(); string pk = Guid.NewGuid().ToString(); @@ -160,7 +169,15 @@ public async Task TableBatchInsertAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchInsertFailAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchInsertFailAsync(payloadFormat); + } + } + + private async Task DoTableBatchInsertFailAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; ITableEntity ent = GenerateRandomEntity("foo"); // add entity @@ -193,10 +210,18 @@ public async Task TableBatchInsertFailAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchInsertOrMergeAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchInsertOrMergeAsync(payloadFormat); + } + } + + private async Task DoTableBatchInsertOrMergeAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; // Insert Or Merge with no pre-existing entity - DynamicTableEntity insertOrMergeEntity = new DynamicTableEntity("insertOrMerge entity", "foo"); + DynamicTableEntity insertOrMergeEntity = new DynamicTableEntity("insertOrMerge entity", "foo" + format.ToString()); insertOrMergeEntity.Properties.Add("prop1", new EntityProperty("value1")); TableBatchOperation batch = new TableBatchOperation(); @@ -239,7 +264,15 @@ public async Task TableBatchInsertOrMergeAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchInsertOrReplaceAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchInsertOrReplaceAsync(payloadFormat); + } + } + + private async Task DoTableBatchInsertOrReplaceAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; // Insert Or Replace with no pre-existing entity DynamicTableEntity insertOrReplaceEntity = new DynamicTableEntity("insertOrReplace entity", "foo"); @@ -282,7 +315,15 @@ public async Task TableBatchInsertOrReplaceAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchDeleteAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchDeleteAsync(payloadFormat); + } + } + + private async Task DoTableBatchDeleteAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; string pk = Guid.NewGuid().ToString(); // Add insert @@ -320,7 +361,15 @@ public async Task TableBatchDeleteAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchDeleteFailAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchDeleteFailAsync(payloadFormat); + } + } + + private async Task DoTableBatchDeleteFailAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; ITableEntity ent = GenerateRandomEntity("foo"); // add entity @@ -361,12 +410,20 @@ public async Task TableBatchDeleteFailAsync() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task TableBatchMerge() + public async Task TableBatchMergeAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchMergeAsync(payloadFormat); + } + } + + private async Task DoTableBatchMergeAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; // Insert Entity - DynamicTableEntity baseEntity = new DynamicTableEntity("merge test", "foo"); + DynamicTableEntity baseEntity = new DynamicTableEntity("merge test", "foo" + format.ToString()); baseEntity.Properties.Add("prop1", new EntityProperty("value1")); await currentTable.ExecuteAsync(TableOperation.Insert(baseEntity)); @@ -394,9 +451,17 @@ public async Task TableBatchMerge() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task TableBatchMergeFail() + public async Task TableBatchMergeFailAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchMergeFailAsync(payloadFormat); + } + } + + private async Task DoTableBatchMergeFailAsync(TablePayloadFormat format) { - CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient.DefaultRequestOptions.PayloadFormat = format; // Insert Entity DynamicTableEntity baseEntity = new DynamicTableEntity("merge test", "foo"); @@ -465,12 +530,20 @@ public async Task TableBatchMergeFail() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task TableBatchReplace() + public async Task TableBatchReplaceAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchReplaceAsync(payloadFormat); + } + } + + private async Task DoTableBatchReplaceAsync(TablePayloadFormat format) { - CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient.DefaultRequestOptions.PayloadFormat = format; // Insert Entity - DynamicTableEntity baseEntity = new DynamicTableEntity("merge test", "foo"); + DynamicTableEntity baseEntity = new DynamicTableEntity("merge test", "foo" + format.ToString()); baseEntity.Properties.Add("prop1", new EntityProperty("value1")); await currentTable.ExecuteAsync(TableOperation.Insert(baseEntity)); @@ -497,9 +570,17 @@ public async Task TableBatchReplace() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task TableBatchReplaceFail() + public async Task TableBatchReplaceFailAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchReplaceFailAsync(payloadFormat); + } + } + + private async Task DoTableBatchReplaceFailAsync(TablePayloadFormat format) { - CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient.DefaultRequestOptions.PayloadFormat = format; // Insert Entity DynamicTableEntity baseEntity = new DynamicTableEntity("merge test", "foo"); @@ -568,12 +649,20 @@ public async Task TableBatchReplaceFail() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchAllSupportedOperationsAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchAllSupportedOperationsAsync(payloadFormat); + } + } + + private async Task DoTableBatchAllSupportedOperationsAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; TableBatchOperation batch = new TableBatchOperation(); string pk = Guid.NewGuid().ToString(); // insert - batch.Insert(GenerateRandomEntity(pk)); + batch.Insert(GenerateRandomEntity(pk), true); // delete { @@ -641,7 +730,21 @@ public async Task TableBatchAllSupportedOperationsAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchRetrieveAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchRetrieveAsync(payloadFormat); + } + } + + private async Task DoTableBatchRetrieveAsync(TablePayloadFormat format) + { + if (format == TablePayloadFormat.JsonNoMetadata) + { + // Would need additional resolver for no metadata json. + return; + } + + tableClient.DefaultRequestOptions.PayloadFormat = format; string pk = Guid.NewGuid().ToString(); // Add insert @@ -705,7 +808,15 @@ public async Task TableBatchRetrieveAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchRetrieveWithResolverAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchRetrieveWithResolverAsync(payloadFormat); + } + } + + private async Task DoTableBatchRetrieveWithResolverAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; // Add insert DynamicTableEntity sendEnt = GenerateRandomEntity(Guid.NewGuid().ToString()); @@ -748,7 +859,15 @@ public async Task TableBatchRetrieveWithResolverAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchOperationsWithEmptyKeysAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchOperationsWithEmptyKeysAsync(payloadFormat); + } + } + + private async Task DoTableBatchOperationsWithEmptyKeysAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; // Insert Entity DynamicTableEntity ent = new DynamicTableEntity() { PartitionKey = "", RowKey = "" }; @@ -848,7 +967,10 @@ public async Task TableBatchOperationsWithEmptyKeysAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchInsert1() { - await InsertAndDeleteBatchWithNEntities(1); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await InsertAndDeleteBatchWithNEntities(1, payloadFormat); + } } [TestMethod] @@ -859,7 +981,10 @@ public async Task TableBatchInsert1() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchInsert10() { - await InsertAndDeleteBatchWithNEntities(10); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await InsertAndDeleteBatchWithNEntities(10, payloadFormat); + } } @@ -871,7 +996,10 @@ public async Task TableBatchInsert10() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchInsert99() { - await InsertAndDeleteBatchWithNEntities(99); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await InsertAndDeleteBatchWithNEntities(99, payloadFormat); + } } [TestMethod] @@ -882,16 +1010,20 @@ public async Task TableBatchInsert99() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchInsert100() { - await InsertAndDeleteBatchWithNEntities(100); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await InsertAndDeleteBatchWithNEntities(100, payloadFormat); + } } - private async Task InsertAndDeleteBatchWithNEntities(int n) + private async Task InsertAndDeleteBatchWithNEntities(int n, TablePayloadFormat payloadFormat) { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; TableBatchOperation batch = new TableBatchOperation(); string pk = Guid.NewGuid().ToString(); for (int m = 0; m < n; m++) { - batch.Insert(GenerateRandomEntity(pk)); + batch.Insert(GenerateRandomEntity(pk), true); } IList results = await currentTable.ExecuteBatchAsync(batch); @@ -922,7 +1054,10 @@ private async Task InsertAndDeleteBatchWithNEntities(int n) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchInsertOrMerge1() { - await InsertOrMergeBatchWithNEntities(1); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await InsertOrMergeBatchWithNEntities(1, payloadFormat); + } } [TestMethod] @@ -933,7 +1068,10 @@ public async Task TableBatchInsertOrMerge1() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchInsertOrMerge10() { - await InsertOrMergeBatchWithNEntities(10); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await InsertOrMergeBatchWithNEntities(10, payloadFormat); + } } @@ -945,7 +1083,10 @@ public async Task TableBatchInsertOrMerge10() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchInsertOrMerge99() { - await InsertOrMergeBatchWithNEntities(99); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await InsertOrMergeBatchWithNEntities(99, payloadFormat); + } } [TestMethod] @@ -956,11 +1097,15 @@ public async Task TableBatchInsertOrMerge99() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchInsertOrMerge100() { - await InsertOrMergeBatchWithNEntities(100); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await InsertOrMergeBatchWithNEntities(100, payloadFormat); + } } - private async Task InsertOrMergeBatchWithNEntities(int n) + private async Task InsertOrMergeBatchWithNEntities(int n, TablePayloadFormat payloadFormat) { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; string pk = Guid.NewGuid().ToString(); TableBatchOperation insertBatch = new TableBatchOperation(); @@ -1013,6 +1158,15 @@ private async Task InsertOrMergeBatchWithNEntities(int n) [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchOnSecondaryAsync() { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchOnSecondaryAsync(payloadFormat); + } + } + + private async Task DoTableBatchOnSecondaryAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; AssertSecondaryEndpoint(); CloudTable table = GenerateCloudTableClient().GetTableReference(GenerateRandomTableName()); @@ -1152,7 +1306,15 @@ public void TableBatchAddMultiQueryShouldThrow() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchWithMultipleOperationsOnSameEntityShouldFailAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchWithMultipleOperationsOnSameEntityShouldFailAsync(payloadFormat); + } + } + + private async Task DoTableBatchWithMultipleOperationsOnSameEntityShouldFailAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; TableBatchOperation batch = new TableBatchOperation(); string pk = Guid.NewGuid().ToString(); ITableEntity first = GenerateRandomEntity(pk); @@ -1184,9 +1346,17 @@ public async Task TableBatchWithMultipleOperationsOnSameEntityShouldFailAsync() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task TableBatchOver100EntitiesShouldThrow() + public async Task TableBatchOver100EntitiesShouldThrowAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchOver100EntitiesShouldThrowAsync(payloadFormat); + } + } + + private async Task DoTableBatchOver100EntitiesShouldThrowAsync(TablePayloadFormat format) { - CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient.DefaultRequestOptions.PayloadFormat = format; TableBatchOperation batch = new TableBatchOperation(); string pk = Guid.NewGuid().ToString(); for (int m = 0; m < 101; m++) @@ -1212,9 +1382,17 @@ public async Task TableBatchOver100EntitiesShouldThrow() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task TableBatchEntityOver1MBShouldThrow() + public async Task TableBatchEntityOver1MBShouldThrowAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchEntityOver1MBShouldThrowAsync(payloadFormat); + } + } + + private async Task DoTableBatchEntityOver1MBShouldThrowAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; TableBatchOperation batch = new TableBatchOperation(); string pk = Guid.NewGuid().ToString(); @@ -1240,9 +1418,17 @@ public async Task TableBatchEntityOver1MBShouldThrow() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task TableBatchOver4MBShouldThrow() + public async Task TableBatchOver4MBShouldThrowAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchOver4MBShouldThrowAsync(payloadFormat); + } + } + + private async Task DoTableBatchOver4MBShouldThrowAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; TableBatchOperation batch = new TableBatchOperation(); string pk = Guid.NewGuid().ToString(); @@ -1273,7 +1459,7 @@ public async Task TableBatchOver4MBShouldThrow() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public void TableBatchAddQueryAndOneMoreOperationShouldThrow() + public void TableBatchAddQueryAndOneMoreOperationShouldThrowSync() { TableBatchOperation batch = new TableBatchOperation(); @@ -1365,7 +1551,7 @@ await TestHelper.ExpectedExceptionAsync( [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public void TableBatchLockToPartitionKey() + public void TableBatchLockToPartitionKeySync() { TableBatchOperation batch = new TableBatchOperation(); batch.Add(TableOperation.Insert(GenerateRandomEntity("foo"))); @@ -1408,9 +1594,17 @@ public void TableBatchLockToPartitionKey() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task TableBatchWithPropertyOver255CharsShouldThrow() + public async Task TableBatchWithPropertyOver255CharsShouldThrowAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchWithPropertyOver255CharsShouldThrowAsync(payloadFormat); + } + } + + private async Task DoTableBatchWithPropertyOver255CharsShouldThrowAsync(TablePayloadFormat format) { - CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient.DefaultRequestOptions.PayloadFormat = format; TableBatchOperation batch = new TableBatchOperation(); string pk = Guid.NewGuid().ToString(); @@ -1439,7 +1633,7 @@ public async Task TableBatchWithPropertyOver255CharsShouldThrow() private static void AddInsertToBatch(string pk, TableBatchOperation batch) { - batch.Insert(GenerateRandomEntity(pk)); + batch.Insert(GenerateRandomEntity(pk), true); } private static DynamicTableEntity GenerateRandomEntity(string pk) diff --git a/Test/WindowsRuntime/Table/TableOperationUnitTests.cs b/Test/WindowsRuntime/Table/TableOperationUnitTests.cs index fbc40f9af..b8c916be0 100644 --- a/Test/WindowsRuntime/Table/TableOperationUnitTests.cs +++ b/Test/WindowsRuntime/Table/TableOperationUnitTests.cs @@ -64,6 +64,7 @@ public TestContext TestContext } CloudTable currentTable = null; + CloudTableClient tableClient = null; #endregion @@ -83,7 +84,7 @@ public TestContext TestContext [TestInitialize()] public void MyTestInitialize() { - CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient = GenerateCloudTableClient(); currentTable = tableClient.GetTableReference(GenerateRandomTableName()); currentTable.CreateIfNotExistsAsync().AsTask().Wait(); @@ -118,7 +119,15 @@ public void MyTestCleanup() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableOperationInsertAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableOperationInsertAsync(payloadFormat); + } + } + + private async Task DoTableOperationInsertAsync(TablePayloadFormat payloadFormat) + { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; // Insert Entity DynamicTableEntity ent = new DynamicTableEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; @@ -140,6 +149,43 @@ public async Task TableOperationInsertAsync() Assert.AreEqual(ent.Properties["foo2"], retrievedEntity.Properties["foo2"]); } + [TestMethod] + [Description("TableOperation Insert")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task TableOperationInsertWithEchoContentAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableOperationInsertWithEchoContentAsync(payloadFormat); + } + } + + private async Task DoTableOperationInsertWithEchoContentAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; + + // Insert Entity + DynamicTableEntity ent = new DynamicTableEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; + + TableResult insertResult = await currentTable.ExecuteAsync(TableOperation.Insert(ent, false)); + Assert.AreEqual(HttpStatusCode.NoContent, (HttpStatusCode)insertResult.HttpStatusCode); + Assert.IsNotNull(insertResult.Etag); + + ent = new DynamicTableEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; + insertResult = await currentTable.ExecuteAsync(TableOperation.Insert(ent, true)); + Assert.AreEqual(HttpStatusCode.Created, (HttpStatusCode)insertResult.HttpStatusCode); + Assert.IsNotNull(insertResult.Etag); + + // Default is false. + ent = new DynamicTableEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; + insertResult = await currentTable.ExecuteAsync(TableOperation.Insert(ent)); + Assert.AreEqual(HttpStatusCode.NoContent, (HttpStatusCode)insertResult.HttpStatusCode); + Assert.IsNotNull(insertResult.Etag); + } + [TestMethod] [Description("TableOperation Insert")] [TestCategory(ComponentCategory.Table)] @@ -148,7 +194,15 @@ public async Task TableOperationInsertAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableOperationInsertSingleQuoteAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableOperationInsertSingleQuoteAsync(payloadFormat); + } + } + + private async Task DoTableOperationInsertSingleQuoteAsync(TablePayloadFormat payloadFormat) + { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; DynamicTableEntity ent = new DynamicTableEntity() { PartitionKey = "partition'key", RowKey = "row'key" }; ent.Properties.Add("stringprop", new EntityProperty("string'value")); @@ -194,7 +248,15 @@ public async Task TableOperationInsertSingleQuoteAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableOperationInsertConflictAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableOperationInsertConflictAsync(payloadFormat); + } + } + + private async Task DoTableOperationInsertConflictAsync(TablePayloadFormat payloadFormat) + { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; // Insert Entity DynamicTableEntity ent = new DynamicTableEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; @@ -227,12 +289,20 @@ public async Task TableOperationInsertConflictAsync() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task TableOperationInsertOrMerge() + public async Task TableOperationInsertOrMergeAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableOperationInsertOrMergeAsync(payloadFormat); + } + } + + private async Task DoTableOperationInsertOrMergeAsync(TablePayloadFormat payloadFormat) { - CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; // Insert Or Merge with no pre-existing entity - DynamicTableEntity insertOrMergeEntity = new DynamicTableEntity("insertOrMerge entity", "foo"); + DynamicTableEntity insertOrMergeEntity = new DynamicTableEntity("insertOrMerge entity", "foo" + payloadFormat.ToString()); insertOrMergeEntity.Properties.Add("prop1", new EntityProperty("value1")); await currentTable.ExecuteAsync(TableOperation.InsertOrMerge(insertOrMergeEntity)); @@ -267,9 +337,17 @@ public async Task TableOperationInsertOrMerge() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task TableOperationInsertOrReplace() + public async Task TableOperationInsertOrReplaceAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableOperationInsertOrReplaceAsync(payloadFormat); + } + } + + private async Task DoTableOperationInsertOrReplaceAsync(TablePayloadFormat payloadFormat) { - CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; // Insert Or Replace with no pre-existing entity DynamicTableEntity insertOrReplaceEntity = new DynamicTableEntity("insertOrReplace entity", "foo"); @@ -306,7 +384,15 @@ public async Task TableOperationInsertOrReplace() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableOperationDeleteAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableOperationDeleteAsync(payloadFormat); + } + } + + private async Task DoTableOperationDeleteAsync(TablePayloadFormat payloadFormat) + { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; // Insert Entity DynamicTableEntity ent = new DynamicTableEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; @@ -334,7 +420,15 @@ public async Task TableOperationDeleteAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableOperationDeleteFailAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableOperationDeleteFailAsync(payloadFormat); + } + } + + private async Task DoTableOperationDeleteFailAsync(TablePayloadFormat payloadFormat) + { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; OperationContext opContext = new OperationContext(); // Insert Entity @@ -390,12 +484,20 @@ public async Task TableOperationDeleteFailAsync() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task TableOperationMerge() + public async Task TableOperationMergeAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableOperationMergeAsync(payloadFormat); + } + } + + private async Task DoTableOperationMergeAsync(TablePayloadFormat payloadFormat) { - CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; // Insert Entity - DynamicTableEntity baseEntity = new DynamicTableEntity("merge test", "foo"); + DynamicTableEntity baseEntity = new DynamicTableEntity("merge test", "foo" + payloadFormat.ToString()); baseEntity.Properties.Add("prop1", new EntityProperty("value1")); await currentTable.ExecuteAsync(TableOperation.Insert(baseEntity)); @@ -420,9 +522,17 @@ public async Task TableOperationMerge() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task TableOperationMergeFail() + public async Task TableOperationMergeFailAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableOperationMergeFailAsync(payloadFormat); + } + } + + private async Task DoTableOperationMergeFailAsync(TablePayloadFormat payloadFormat) + { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; // Insert Entity DynamicTableEntity baseEntity = new DynamicTableEntity("merge test", "foo"); @@ -483,12 +593,20 @@ public async Task TableOperationMergeFail() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task TableOperationReplace() + public async Task TableOperationReplaceAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableOperationReplaceAsync(payloadFormat); + } + } + + private async Task DoTableOperationReplaceAsync(TablePayloadFormat payloadFormat) { - CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; // Insert Entity - DynamicTableEntity baseEntity = new DynamicTableEntity("merge test", "foo"); + DynamicTableEntity baseEntity = new DynamicTableEntity("merge test", "foo" + payloadFormat.ToString()); baseEntity.Properties.Add("prop1", new EntityProperty("value1")); await currentTable.ExecuteAsync(TableOperation.Insert(baseEntity)); @@ -512,9 +630,17 @@ public async Task TableOperationReplace() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task TableOperationReplaceFail() + public async Task TableOperationReplaceFailAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableOperationReplaceFailAsync(payloadFormat); + } + } + + private async Task DoTableOperationReplaceFailAsync(TablePayloadFormat payloadFormat) { - CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; // Insert Entity DynamicTableEntity baseEntity = new DynamicTableEntity("merge test", "foo"); @@ -577,12 +703,27 @@ public async Task TableOperationReplaceFail() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchRetrieveAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchRetrieveAsync(payloadFormat); + } + } + + private async Task DoTableBatchRetrieveAsync(TablePayloadFormat payloadFormat) + { + if (payloadFormat == TablePayloadFormat.JsonNoMetadata) + { + // Entities not resolved correctly for no metadata JSON. + return; + } + + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; + string pk = Guid.NewGuid().ToString(); // Add insert DynamicTableEntity sendEnt = new DynamicTableEntity(); - sendEnt.Properties.Add("foo", new EntityProperty("bar")); + sendEnt.Properties.Add("foo", new EntityProperty("bar" + payloadFormat.ToString())); sendEnt.PartitionKey = pk; sendEnt.RowKey = Guid.NewGuid().ToString(); @@ -642,7 +783,15 @@ public async Task TableBatchRetrieveAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableBatchRetrieveWithResolverAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableBatchRetrieveWithResolverAsync(payloadFormat); + } + } + + private async Task DoTableBatchRetrieveWithResolverAsync(TablePayloadFormat payloadFormat) + { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; DynamicTableEntity sendEnt = new DynamicTableEntity(); sendEnt.PartitionKey = Guid.NewGuid().ToString(); @@ -680,6 +829,22 @@ public async Task TableBatchRetrieveWithResolverAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableRetrieveWithIgnoreAttributeWriteAsync() { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableRetrieveWithIgnoreAttributeWriteAsync(payloadFormat); + } + } + + private async Task DoTableRetrieveWithIgnoreAttributeWriteAsync(TablePayloadFormat payloadFormat) + { + if (payloadFormat == TablePayloadFormat.JsonNoMetadata) + { + // Entities not resolved correctly for no metadata JSON. + return; + } + + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; + string pk = Guid.NewGuid().ToString(); string rk = Guid.NewGuid().ToString(); @@ -723,7 +888,22 @@ public async Task TableRetrieveWithIgnoreAttributeWriteAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableRetrieveWithIgnoreAttributeReadAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableRetrieveWithIgnoreAttributeReadAsync(payloadFormat); + } + } + + private async Task DoTableRetrieveWithIgnoreAttributeReadAsync(TablePayloadFormat payloadFormat) + { + if (payloadFormat == TablePayloadFormat.JsonNoMetadata) + { + // Entities not resolved correctly for no metadata JSON. + return; + } + + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; + string pk = Guid.NewGuid().ToString(); // Add insert @@ -776,6 +956,22 @@ public async Task TableRetrieveWithIgnoreAttributeReadAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableRetrieveSyncWithIgnoreAttributesAsync() { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableRetrieveSyncWithIgnoreAttributesAsync(payloadFormat); + } + } + + private async Task DoTableRetrieveSyncWithIgnoreAttributesAsync(TablePayloadFormat payloadFormat) + { + if (payloadFormat == TablePayloadFormat.JsonNoMetadata) + { + // Entities not resolved correctly for no metadata JSON. + return; + } + + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; + string pk = Guid.NewGuid().ToString(); string rk = Guid.NewGuid().ToString(); @@ -827,7 +1023,15 @@ public async Task TableRetrieveSyncWithIgnoreAttributesAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableOperationsWithEmptyKeysAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableOperationsWithEmptyKeysAsync(payloadFormat); + } + } + + private async Task DoTableOperationsWithEmptyKeysAsync(TablePayloadFormat payloadFormat) + { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; // Insert Entity DynamicTableEntity ent = new DynamicTableEntity() { PartitionKey = "", RowKey = "" }; @@ -913,7 +1117,15 @@ public async Task TableOperationsWithEmptyKeysAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableOperationInsertOver1MBAsync() { - CloudTableClient tableClient = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableOperationInsertOver1MBAsync(payloadFormat); + } + } + + private async Task DoTableOperationInsertOver1MBAsync(TablePayloadFormat payloadFormat) + { + tableClient.DefaultRequestOptions.PayloadFormat = payloadFormat; // Insert Entity DynamicTableEntity ent = new DynamicTableEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; @@ -943,7 +1155,7 @@ public async Task TableOperationInsertOver1MBAsync() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public void TableOpsWithNonDerivedEntitiesAsync() + public void TableOpsWithNonDerivedEntities() { List DTOObjects = new List(new ShapeEntity[3] { new ShapeEntity(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "square", 4, 4), new ShapeEntity(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "rectangle", 5, 4), new ShapeEntity(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "parallelogram", 6, 4) }); diff --git a/Test/WindowsRuntime/Table/TableQueryGenericTests.cs b/Test/WindowsRuntime/Table/TableQueryGenericTests.cs index 3a7eb2089..753fabe15 100644 --- a/Test/WindowsRuntime/Table/TableQueryGenericTests.cs +++ b/Test/WindowsRuntime/Table/TableQueryGenericTests.cs @@ -68,6 +68,7 @@ public TestContext TestContext } static CloudTable currentTable = null; + static CloudTableClient tableClient = null; #endregion @@ -79,7 +80,7 @@ public TestContext TestContext [ClassInitialize()] public static void MyClassInitialize(TestContext testContext) { - CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient = GenerateCloudTableClient(); currentTable = tableClient.GetTableReference(GenerateRandomTableName()); currentTable.CreateIfNotExistsAsync().AsTask().Wait(); @@ -137,6 +138,15 @@ public void MyTestCleanup() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableGenericQueryBasicAsync() { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableGenericQueryBasicAsync(payloadFormat); + } + } + + private async Task DoTableGenericQueryBasicAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; TableQuery query = new TableQuery().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "tables_batch_1")); TableQuerySegment seg = await currentTable.ExecuteQuerySegmentedAsync(query, null); @@ -156,6 +166,15 @@ public async Task TableGenericQueryBasicAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableGenericQueryWithContinuationAsync() { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableGenericQueryWithContinuationAsync(payloadFormat); + } + } + + private async Task DoTableGenericQueryWithContinuationAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; TableQuery query = new TableQuery(); OperationContext opContext = new OperationContext(); @@ -295,8 +314,17 @@ public void TableQueryResolverWithDynamic() [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableGenericQueryOnSupportedTypesAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableGenericQueryOnSupportedTypesAsync(payloadFormat); + } + } + + private async Task DoTableGenericQueryOnSupportedTypesAsync(TablePayloadFormat format) { CloudTableClient client = GenerateCloudTableClient(); + client.DefaultRequestOptions.PayloadFormat = format; CloudTable table = client.GetTableReference(GenerateRandomTableName()); await table.CreateAsync(); @@ -463,8 +491,17 @@ public void TableGenericQueryWithInvalidTakeCount() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task TableGenericQueryWithInvalidQuery() + public async Task TableGenericQueryWithInvalidQueryAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableGenericQueryWithInvalidQueryAsync(payloadFormat); + } + } + + private async Task DoTableGenericQueryWithInvalidQueryAsync(TablePayloadFormat format) { + tableClient.DefaultRequestOptions.PayloadFormat = format; TableQuery query = new TableQuery().Where(string.Format("(PartitionKey ) and (RowKey ge '{1}')", "tables_batch_1", "000050")); OperationContext opContext = new OperationContext(); diff --git a/Test/WindowsRuntime/Table/TableQueryTests.cs b/Test/WindowsRuntime/Table/TableQueryTests.cs index 8a4de761a..ecf3fdd97 100644 --- a/Test/WindowsRuntime/Table/TableQueryTests.cs +++ b/Test/WindowsRuntime/Table/TableQueryTests.cs @@ -75,7 +75,8 @@ public TestContext TestContext } static CloudTable currentTable = null; - + static CloudTableClient tableClient = null; + #endregion #region Additional test attributes @@ -86,7 +87,7 @@ public TestContext TestContext [ClassInitialize()] public static void MyClassInitialize(TestContext testContext) { - CloudTableClient tableClient = GenerateCloudTableClient(); + tableClient = GenerateCloudTableClient(); currentTable = tableClient.GetTableReference(GenerateRandomTableName()); currentTable.CreateIfNotExistsAsync().AsTask().Wait(); @@ -145,6 +146,15 @@ public void MyTestCleanup() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableQueryBasicAsync() { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableQueryBasicAsync(payloadFormat); + } + } + + private async Task DoTableQueryBasicAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; TableQuery query = new TableQuery().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "tables_batch_1")); TableQuerySegment seg = await currentTable.ExecuteQuerySegmentedAsync(query, null); @@ -164,6 +174,15 @@ public async Task TableQueryBasicAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableQueryWithContinuationAsync() { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableQueryWithContinuationAsync(payloadFormat); + } + } + + private async Task DoTableQueryWithContinuationAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; TableQuery query = new TableQuery(); OperationContext opContext = new OperationContext(); @@ -200,6 +219,15 @@ public async Task TableQueryWithContinuationAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryWithFilterAsync() { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryWithFilterAsync(payloadFormat); + } + } + + private void DoTableQueryWithFilterAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; TableQuery query = new TableQuery().Where(string.Format("(PartitionKey eq '{0}') and (RowKey ge '{1}')", "tables_batch_1", "0050")); OperationContext opContext = new OperationContext(); @@ -225,6 +253,15 @@ public void TableQueryWithFilterAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryProjectionAsync() { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryProjectionAsync(payloadFormat); + } + } + + private void DoTableQueryProjectionAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; TableQuery query = new TableQuery().Select(new List() { "a", "c" }); foreach (DynamicTableEntity ent in ExecuteQuery(currentTable, query)) @@ -248,9 +285,16 @@ public void TableQueryProjectionAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableQueryOnSupportedTypesAsync() { - CloudTableClient client = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableQueryOnSupportedTypesAsync(payloadFormat); + } + } - CloudTable table = client.GetTableReference(GenerateRandomTableName()); + private async Task DoTableQueryOnSupportedTypesAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; + CloudTable table = tableClient.GetTableReference(GenerateRandomTableName()); await table.CreateAsync(); try @@ -376,6 +420,15 @@ public async Task TableQueryOnSupportedTypesAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableRegionalQueryOnSupportedTypesAsync() { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableRegionalQueryOnSupportedTypesAsync(payloadFormat); + } + } + + private async Task DoTableRegionalQueryOnSupportedTypesAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; #if ASPNET_K //CultureInfo currentCulture = CultureInfo.CurrentCulture; //CultureInfo.CurrentCulture = new CultureInfo("tr"); @@ -383,10 +436,7 @@ public async Task TableRegionalQueryOnSupportedTypesAsync() string currentPrimaryLanguage = ApplicationLanguages.PrimaryLanguageOverride; ApplicationLanguages.PrimaryLanguageOverride = "tr"; #endif - - CloudTableClient client = GenerateCloudTableClient(); - - CloudTable table = client.GetTableReference(GenerateRandomTableName()); + CloudTable table = tableClient.GetTableReference(GenerateRandomTableName()); await table.CreateAsync(); try @@ -517,9 +567,16 @@ public async Task TableRegionalQueryOnSupportedTypesAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public async Task TableQueryEmptyValueAsync() { - CloudTableClient client = GenerateCloudTableClient(); + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableQueryEmptyValueAsync(payloadFormat); + } + } - CloudTable table = client.GetTableReference(GenerateRandomTableName()); + private async Task DoTableQueryEmptyValueAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; + CloudTable table = tableClient.GetTableReference(GenerateRandomTableName()); await table.CreateAsync(); // Setup @@ -548,6 +605,15 @@ public async Task TableQueryEmptyValueAsync() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void TableQueryWithInvalidTakeCount() { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableQueryWithInvalidTakeCount(payloadFormat); + } + } + + private void DoTableQueryWithInvalidTakeCount(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; try { TableQuery query = new TableQuery().Take(0); @@ -583,8 +649,17 @@ public void TableQueryWithInvalidTakeCount() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public async Task TableQueryWithInvalidQuery() + public async Task TableQueryWithInvalidQueryAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableQueryWithInvalidQueryAsync(payloadFormat); + } + } + + private async Task DoTableQueryWithInvalidQueryAsync(TablePayloadFormat format) { + tableClient.DefaultRequestOptions.PayloadFormat = format; TableQuery query = new TableQuery().Where(string.Format("(PartitionKey ) and (RowKey ge '{1}')", "tables_batch_1", "000050")); OperationContext opContext = new OperationContext(); diff --git a/changelog.txt b/changelog.txt index b872bd307..a0155fd46 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,18 @@ +Changes in 6.0.0 : + + - All: Support for 2015-04-05 REST version. Please see our REST API documentation and blogs for information about the related added features. + - All: Refreshed ASP.NET 5 targets to work with latest RC bits (DNX 4.5.1 and DNX Core 5.0, beta7). + - All: Removed deprecated Shared Access Signature methods that take version as a parameter. + - Tables: Altered default settings for echoContent and payloadFormat. AtomPub payloadFormat is now obsolete. + - Tables: Fixed a bug where filter conditions for doubles were improperly formatted. + - Tables: Clarified the error message for batch transactions. + - Tables: Added JSON support for Universal Apps, Windows Runtime, PCL, DNX 4.5.1, and DNX Core 5.0 targets. + - Tables: Added echo content configurability support for Universal Apps, Windows Runtime, PCL, DNX 4.5.1, and DNX Core 5.0 targets. + - Tables: Fixed a bug where a select on a non-existent field resulted in a null reference exception if the corresponding field in the TableEntity was not nullable. + - Files: Added support for Minute and Hour Metrics to FileServiceProperties and added support for File Metrics to CloudAnalyticsClient. + Changes in 5.0.2 : + - Blobs: Fixed a bug in the min AppendTextAsync overload that caused overwrite behavior instead of append behavior. Please upgrade to 5.0.2 as soon as possible to avoid this bug. Changes in 5.0.0 : @@ -29,6 +43,7 @@ Changes in 4.4.0 Preview : - All: Added preview support for client side encryption for blobs, queues and tables. - All: Added preview support for building applications via Portable Class Library (Profile 111), as well as Xamarin.iOS and Xamarin.Android. - All: Added StringToSign to the client logs in the Shared Access Signature case. + - Blobs: Added AppendBlobs. For more information on this, please refer to the REST API link. - Blobs: Created a CloudBlob instantiable class. Users can now get/set properties, metadata, download blobs, create snapshots, copy blobs and perform leasing actions on blobs without knowing the type of the blob. CloudPageBlob, CloudBlockBlob, and CloudAppendBlob now derive from this class. - Blobs: Added GetBlobReference on CloudBlobContainer to get a reference to a CloudBlob object that can be used for common operations across blob types. This api does not make a service call. - Blobs (ASP.NET): Added missing overloads for StartCopyFromBlob in CloudBlockBlob and CloudPageBlob. diff --git a/global.json b/global.json index e7fb99ba9..2ce19e975 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "projects": [ "lib", "Lib/AspNet" ], "sdk": { - "version": "1.0.0-beta5" + "version": "1.0.0-beta7" } }