From de605064f236f964375958ffc974ad7361b44e64 Mon Sep 17 00:00:00 2001 From: zezha-msft Date: Fri, 17 Nov 2017 00:00:40 -0800 Subject: [PATCH 1/9] Added OAuth support for Desktop --- .../Protocol/TokenAuthenticationHandler.cs | 77 +++++++ .../Blob/CloudBlobClient.cs | 4 + .../File/CloudFileClient.cs | 4 + .../Queue/CloudQueueClient.cs | 4 + .../Table/CloudTableClient.cs | 4 + .../Table/DataServices/TableServiceContext.cs | 4 + Lib/Common/Auth/StorageCredentials.cs | 37 +++- Lib/Common/Auth/TokenCredential.cs | 190 ++++++++++++++++ Lib/Common/AuthenticationScheme.cs | 7 +- Lib/Common/Blob/CloudBlobClient.Common.cs | 2 +- Lib/Common/Core/SR.cs | 2 + Lib/Common/Queue/CloudQueueClient.Common.cs | 2 +- Lib/Common/Shared/Protocol/Constants.cs | 2 +- .../StorageAuthenticationHttpHandler.Token.cs | 59 +++++ .../StorageAuthenticationHttpHandler.cs | 4 + .../Microsoft.WindowsAzure.Storage.RT.csproj | 1 + .../Blob/CloudBlobClientTest.cs | 16 ++ .../Core/TokenAuthenticationTest.cs | 206 ++++++++++++++++++ .../File/CloudFileClientTest.cs | 26 +++ .../Queue/CloudQueueClientTest.cs | 18 ++ .../Table/CloudTableClientTests.cs | 31 +++ Test/Common/Core/CloudStorageAccountTests.cs | 5 + Test/Common/TestBase.Common.cs | 17 ++ .../TestConfigProcess/TenantConfiguration.cs | 3 + .../TestConfigProcess/TestConfigurations.cs | 3 + ...Microsoft.WindowsAzure.Storage.Test.csproj | 8 + Test/WindowsDesktop/packages.config | 1 + changelog.txt | 1 + 28 files changed, 732 insertions(+), 6 deletions(-) create mode 100644 Lib/ClassLibraryCommon/Auth/Protocol/TokenAuthenticationHandler.cs create mode 100644 Lib/Common/Auth/TokenCredential.cs create mode 100644 Lib/WindowsRuntime/Auth/Protocol/StorageAuthenticationHttpHandler.Token.cs create mode 100644 Test/ClassLibraryCommon/Core/TokenAuthenticationTest.cs diff --git a/Lib/ClassLibraryCommon/Auth/Protocol/TokenAuthenticationHandler.cs b/Lib/ClassLibraryCommon/Auth/Protocol/TokenAuthenticationHandler.cs new file mode 100644 index 000000000..d234e5090 --- /dev/null +++ b/Lib/ClassLibraryCommon/Auth/Protocol/TokenAuthenticationHandler.cs @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------- +// +// 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.Auth.Protocol +{ + using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Core.Auth; + using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Linq; + using System.Net; + + /// + /// Represents a handler that signs HTTPS requests with a token. + /// + internal sealed class TokenAuthenticationHandler : IAuthenticationHandler + { + private readonly StorageCredentials credentials; + + /// + /// Initializes a new instance of the class. + /// + /// A object providing credentials for the request. + public TokenAuthenticationHandler(StorageCredentials credentials) + { + this.credentials = credentials; + } + + /// + /// Signs the specified HTTPS request with a token. + /// + /// The HTTPS request to sign. + /// An object that represents the context for the current operation. + public void SignRequest(HttpWebRequest request, OperationContext operationContext) + { + CommonUtility.AssertNotNull("request", request); + + // only HTTPS is allowed for token credential, as the token would be at risk of being intercepted with HTTP. + #if !WINDOWS_PHONE + if (!"https".Equals(request.Address.Scheme, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException(SR.OnlyHttpsIsSupportedForTokenCredential); + } + #endif + + if (!request.Headers.AllKeys.Contains(Constants.HeaderConstants.Date, StringComparer.Ordinal)) + { + string dateString = HttpWebUtility.ConvertDateTimeToHttpString(DateTime.UtcNow); + request.Headers.Add(Constants.HeaderConstants.Date, dateString); + } + + if (this.credentials.IsToken) + { + request.Headers.Add( + "Authorization", + string.Format(CultureInfo.InvariantCulture, "Bearer {0}", this.credentials.TokenCredential.Token)); + } + } + } +} diff --git a/Lib/ClassLibraryCommon/Blob/CloudBlobClient.cs b/Lib/ClassLibraryCommon/Blob/CloudBlobClient.cs index bde6cf43c..d02f6f352 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudBlobClient.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudBlobClient.cs @@ -79,6 +79,10 @@ internal IAuthenticationHandler AuthenticationHandler this.Credentials, this.Credentials.AccountName); } + else if (this.Credentials.IsToken) + { + result = new TokenAuthenticationHandler(this.Credentials); + } else { result = new NoOpAuthenticationHandler(); diff --git a/Lib/ClassLibraryCommon/File/CloudFileClient.cs b/Lib/ClassLibraryCommon/File/CloudFileClient.cs index 77a3cd3c9..dff756cc5 100644 --- a/Lib/ClassLibraryCommon/File/CloudFileClient.cs +++ b/Lib/ClassLibraryCommon/File/CloudFileClient.cs @@ -78,6 +78,10 @@ internal IAuthenticationHandler AuthenticationHandler this.Credentials, this.Credentials.AccountName); } + else if (this.Credentials.IsToken) + { + throw new InvalidOperationException(SR.TokenIsNotSupportedForThisService); + } else { result = new NoOpAuthenticationHandler(); diff --git a/Lib/ClassLibraryCommon/Queue/CloudQueueClient.cs b/Lib/ClassLibraryCommon/Queue/CloudQueueClient.cs index 75e8f485b..672a485de 100644 --- a/Lib/ClassLibraryCommon/Queue/CloudQueueClient.cs +++ b/Lib/ClassLibraryCommon/Queue/CloudQueueClient.cs @@ -81,6 +81,10 @@ internal IAuthenticationHandler AuthenticationHandler this.Credentials, this.Credentials.AccountName); } + else if (this.Credentials.IsToken) + { + result = new TokenAuthenticationHandler(this.Credentials); + } else { result = new NoOpAuthenticationHandler(); diff --git a/Lib/ClassLibraryCommon/Table/CloudTableClient.cs b/Lib/ClassLibraryCommon/Table/CloudTableClient.cs index 3dd243fb7..907a31632 100644 --- a/Lib/ClassLibraryCommon/Table/CloudTableClient.cs +++ b/Lib/ClassLibraryCommon/Table/CloudTableClient.cs @@ -88,6 +88,10 @@ internal IAuthenticationHandler AuthenticationHandler this.Credentials, this.Credentials.AccountName); } + else if (this.Credentials.IsToken) + { + throw new InvalidOperationException(SR.TokenIsNotSupportedForThisService); + } else { result = new NoOpAuthenticationHandler(); diff --git a/Lib/ClassLibraryCommon/Table/DataServices/TableServiceContext.cs b/Lib/ClassLibraryCommon/Table/DataServices/TableServiceContext.cs index d4915cd82..ddb6fe40e 100644 --- a/Lib/ClassLibraryCommon/Table/DataServices/TableServiceContext.cs +++ b/Lib/ClassLibraryCommon/Table/DataServices/TableServiceContext.cs @@ -220,6 +220,10 @@ private IAuthenticationHandler AuthenticationHandler this.ServiceClient.Credentials, this.ServiceClient.Credentials.AccountName); } + else if (this.ServiceClient.Credentials.IsToken) + { + throw new InvalidOperationException(SR.TokenIsNotSupportedForThisService); + } else { this.authenticationHandler = new NoOpAuthenticationHandler(); diff --git a/Lib/Common/Auth/StorageCredentials.cs b/Lib/Common/Auth/StorageCredentials.cs index 5ea254f52..6f2a72290 100644 --- a/Lib/Common/Auth/StorageCredentials.cs +++ b/Lib/Common/Auth/StorageCredentials.cs @@ -22,6 +22,7 @@ namespace Microsoft.WindowsAzure.Storage.Auth using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using Microsoft.WindowsAzure.Storage.Auth; using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -62,6 +63,11 @@ public string KeyName } } + /// + /// Gets the associated OAuth Token for the credentials. + /// + internal TokenCredential TokenCredential { get; private set; } + internal StorageAccountKey Key { get; private set; } /// @@ -97,7 +103,19 @@ public bool IsSharedKey { get { - return (this.SASToken == null) && (this.AccountName != null); + return (this.SASToken == null) && (this.TokenCredential == null) && (this.AccountName != null); + } + } + + /// + /// Gets a value indicating whether the credentials are a bearer token. + /// + /// true if the credentials are a bearer token; otherwise, false. + public bool IsToken + { + get + { + return this.TokenCredential != null; } } @@ -190,6 +208,15 @@ public StorageCredentials(string sasToken) this.UpdateQueryBuilder(); } + /// + /// Initializes a new instance of the class with the specified bearer token. + /// + /// The authentication token. + public StorageCredentials(TokenCredential tokenCredential) + { + this.TokenCredential = tokenCredential; + } + /// /// Updates the key value for the credentials. /// @@ -327,6 +354,11 @@ private static string GetBase64EncodedKey(StorageAccountKey accountKey) return (accountKey.KeyValue == null) ? null : Convert.ToBase64String(accountKey.KeyValue); } + private static string GetTokenValue(TokenCredential tokenCredential) + { + return tokenCredential == null ? null : tokenCredential.Token; + } + internal string ToString(bool exportSecrets) { if (this.IsSharedKey) @@ -367,7 +399,8 @@ public bool Equals(StorageCredentials other) return string.Equals(this.SASToken, other.SASToken) && string.Equals(this.AccountName, other.AccountName) && string.Equals(thisAccountKey.KeyName, otherAccountKey.KeyName) && - string.Equals(GetBase64EncodedKey(thisAccountKey), GetBase64EncodedKey(otherAccountKey)); + string.Equals(GetBase64EncodedKey(thisAccountKey), GetBase64EncodedKey(otherAccountKey)) && + string.Equals(GetTokenValue(this.TokenCredential), GetTokenValue(other.TokenCredential)); } } diff --git a/Lib/Common/Auth/TokenCredential.cs b/Lib/Common/Auth/TokenCredential.cs new file mode 100644 index 000000000..bc007d401 --- /dev/null +++ b/Lib/Common/Auth/TokenCredential.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.Auth +{ + using System; + using System.Threading; + using System.Threading.Tasks; + + #if WINDOWS_RT + + /// + /// Represents a token that is used to authorize HTTPS requests. + /// + public sealed class TokenCredential + { + private volatile string token; + + /// + /// The authorization token. It can be set by the user at any point in a thread-safe way. + /// + public string Token + { + get + { + return this.token; + } + set + { + this.token = value; + } + } + + /// + /// Create an instance of . + /// + /// Value of the token credential. + public TokenCredential(string token) + { + + } + } + + #else + /// + /// This is the return type of . + /// A new token and a new frequency is expected. + /// + public struct NewTokenAndFrequency + { + /// + /// Create a new instance of . + /// + /// The new token credential. + /// The new frequency to wait before calling again. + public NewTokenAndFrequency(String newToken, TimeSpan? newFrequency = null) : this() + { + Token = newToken; + Frequency = newFrequency; + } + + /// + /// The new token credential. + /// + public String Token { get; set; } + + /// + /// The new frequency to wait before calling again. + /// + public TimeSpan? Frequency { get; set; } + } + + /// + /// This type of delegate is used to update the token credential periodically. + /// + /// A state object, which can be of any type. + /// A cancellation token to receive the cancellation signal. + /// + public delegate Task RenewTokenFuncAsync(Object state, CancellationToken cancellationToken); + + /// + /// Represents a token that is used to authorize HTTPS requests. + /// + public sealed class TokenCredential: IDisposable + { + private volatile string token; + private readonly Timer timer = null; + private readonly RenewTokenFuncAsync renewTokenFuncAsync; + private readonly CancellationTokenSource cancellationTokenSource; + private TimeSpan renewFrequency; + + /// + /// The authorization token. It can be set by the user at any point in a thread-safe way. + /// + public string Token + { + get + { + return this.token; + } + set + { + this.token = value; + } + } + + /// + /// Create an instance of . + /// + /// Initial value of the token credential. + public TokenCredential(String initialToken) : this(initialToken, null, null, default(TimeSpan)) { } + + /// + /// Create an instance of . + /// + /// Initial value of the token credential. + /// If given, this delegate is called periodically to renew the token credential. + /// A state object is passed to the periodicTokenRenewer every time it is called. + /// If periodicTokenRenewer is given, user should define a frequency to call the periodicTokenRenewer. + public TokenCredential(String initialToken, + RenewTokenFuncAsync periodicTokenRenewer, + Object state, + TimeSpan renewFrequency) + { + this.token = initialToken; + + // if no renewer is given, then the token will not be updated automatically. + if (periodicTokenRenewer == null) return; + + this.renewTokenFuncAsync = periodicTokenRenewer; + this.renewFrequency = renewFrequency; + + // when "new Timer(...)" is called, it might call RenewTokenAsync before even being assigned to timer, if renewFrequency is very close to 0. + // since RenewTokenAsync refers to timer, we need to make sure that before it is invoked, timer is defined. + this.timer = new Timer(RenewTokenAsync, state, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); + this.timer.Change(this.renewFrequency, Timeout.InfiniteTimeSpan); + this.cancellationTokenSource = new CancellationTokenSource(); + } + + /// + /// Calling Dispose stops the timer and periodicTokenRenewer. + /// + public void Dispose() { this.timer.Dispose(); this.cancellationTokenSource.Cancel(); } + + /// + /// This method is triggered by the timer. + /// It calls the renew function provided by the user, updates the token, and then restarts the timer. + /// + /// + private async void RenewTokenAsync(Object state) + { + try + { + var newTokenAndFrequency = await this.renewTokenFuncAsync(state, this.cancellationTokenSource.Token); + this.token = newTokenAndFrequency.Token; + this.renewFrequency = newTokenAndFrequency.Frequency ?? this.renewFrequency; + // if nothing is given, use previous frequency. + } + // only catch the exception when it was caused by this credential object's cancellation token. + catch (OperationCanceledException ex) + { + if (!ex.CancellationToken.Equals(this.cancellationTokenSource.Token)) + { + throw ex; + } + } + finally + { + // the timer is restarted. + if (!this.cancellationTokenSource.IsCancellationRequested) + this.timer.Change(this.renewFrequency, Timeout.InfiniteTimeSpan); + } + } + } +#endif +} \ No newline at end of file diff --git a/Lib/Common/AuthenticationScheme.cs b/Lib/Common/AuthenticationScheme.cs index f660fbc69..d626655dc 100644 --- a/Lib/Common/AuthenticationScheme.cs +++ b/Lib/Common/AuthenticationScheme.cs @@ -30,6 +30,11 @@ public enum AuthenticationScheme /// /// Signs HTTP requests using the Shared Key authentication scheme. /// - SharedKey + SharedKey, + + /// + /// Signs HTTPS requests using the Bearer Token authentication scheme. + /// + Token } } diff --git a/Lib/Common/Blob/CloudBlobClient.Common.cs b/Lib/Common/Blob/CloudBlobClient.Common.cs index 79b651009..5c15543de 100644 --- a/Lib/Common/Blob/CloudBlobClient.Common.cs +++ b/Lib/Common/Blob/CloudBlobClient.Common.cs @@ -78,7 +78,7 @@ public CloudBlobClient(StorageUri storageUri, StorageCredentials credentials) ParallelOperationThreadCount = BlobRequestOptions.BaseDefaultRequestOptions.ParallelOperationThreadCount }; this.DefaultDelimiter = NavigationHelper.Slash; - this.AuthenticationScheme = AuthenticationScheme.SharedKey; + this.AuthenticationScheme = this.Credentials.IsToken ? AuthenticationScheme.Token : AuthenticationScheme.SharedKey; this.UsePathStyleUris = CommonUtility.UsePathStyleAddressing(this.BaseUri); } diff --git a/Lib/Common/Core/SR.cs b/Lib/Common/Core/SR.cs index a5d4a1889..db58812ab 100644 --- a/Lib/Common/Core/SR.cs +++ b/Lib/Common/Core/SR.cs @@ -165,6 +165,7 @@ internal class SR public const string NoPropertyResolverAvailable = "No property resolver available. Deserializing the entity properties as strings."; public const string OffsetNotInRange = "The offset provided is out of range. The range must be between 0 and the length of the byte array."; public const string ODataReaderNotInCompletedState = "OData Reader state expected to be Completed state. Actual state: {0}."; + public const string OnlyHttpsIsSupportedForTokenCredential = "Only HTTPS is allowed for token credential."; public const string OperationCanceled = "Operation was canceled by user."; public const string ParseError = "Error parsing value"; public const string PartitionKey = "All entities in a given batch must have the same partition key."; @@ -205,6 +206,7 @@ internal class SR public const string TableQueryTypeMustHaveDefaultParameterlessCtor = "TableQuery Generic Type must provide a default parameterless constructor."; public const string TakeCountNotPositive = "Take count must be positive and greater than 0."; public const string TimeoutExceptionMessage = "The client could not finish the operation within specified timeout."; + public const string TokenIsNotSupportedForThisService = "Token credential is not supported for this service."; public const string TooManyPolicyIdentifiers = "Too many '{0}' shared access policy identifiers provided. Server does not support setting more than '{1}' on a single container, queue, table, or share."; public const string TooManyPathSegments = "The count of URL path segments (strings between '/' characters) as part of the blob name cannot exceed 254."; public const string TraceAbort = "Aborting pending request due to timeout."; diff --git a/Lib/Common/Queue/CloudQueueClient.Common.cs b/Lib/Common/Queue/CloudQueueClient.Common.cs index d9fa838e5..66e95c54e 100644 --- a/Lib/Common/Queue/CloudQueueClient.Common.cs +++ b/Lib/Common/Queue/CloudQueueClient.Common.cs @@ -56,7 +56,7 @@ public CloudQueueClient(StorageUri storageUri, StorageCredentials credentials) { RetryPolicy = new ExponentialRetry() }; - this.AuthenticationScheme = AuthenticationScheme.SharedKey; + this.AuthenticationScheme = this.Credentials.IsToken ? AuthenticationScheme.Token : AuthenticationScheme.SharedKey; this.UsePathStyleUris = CommonUtility.UsePathStyleAddressing(this.BaseUri); } diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index 25f5ef82a..c96af2dfe 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -1165,7 +1165,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 = "2017-04-17"; + public const string TargetStorageVersion = "2017-11-09"; /// /// Specifies the file type. diff --git a/Lib/WindowsRuntime/Auth/Protocol/StorageAuthenticationHttpHandler.Token.cs b/Lib/WindowsRuntime/Auth/Protocol/StorageAuthenticationHttpHandler.Token.cs new file mode 100644 index 000000000..ae9c64d44 --- /dev/null +++ b/Lib/WindowsRuntime/Auth/Protocol/StorageAuthenticationHttpHandler.Token.cs @@ -0,0 +1,59 @@ +// ----------------------------------------------------------------------------------------- +// +// 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.Auth.Protocol +{ + using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Core.Auth; + using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System; + using System.Globalization; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Threading; + using System.Threading.Tasks; + + partial class StorageAuthenticationHttpHandler + { + private Task GetTokenAuthenticationTask(StorageRequestMessage request, CancellationToken cancellationToken) + { + StorageRequestMessage storageRequest = request as StorageRequestMessage; + StorageCredentials credentials = storageRequest.Credentials; + + if (!request.Headers.Contains(Constants.HeaderConstants.Date)) + { + string dateString = HttpWebUtility.ConvertDateTimeToHttpString(DateTimeOffset.UtcNow); + request.Headers.Add(Constants.HeaderConstants.Date, dateString); + } + + if (!"https".Equals(storageRequest.RequestUri.Scheme, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException(SR.OnlyHttpsIsSupportedForTokenCredential); + } + + if (credentials.IsToken) + { + request.Headers.Add( + "Authorization", + string.Format(CultureInfo.InvariantCulture, "Bearer {0}", credentials.TokenCredential.Token)); + } + + return base.SendAsync(request, cancellationToken); + } + } +} diff --git a/Lib/WindowsRuntime/Auth/Protocol/StorageAuthenticationHttpHandler.cs b/Lib/WindowsRuntime/Auth/Protocol/StorageAuthenticationHttpHandler.cs index c45d14622..a289d42de 100644 --- a/Lib/WindowsRuntime/Auth/Protocol/StorageAuthenticationHttpHandler.cs +++ b/Lib/WindowsRuntime/Auth/Protocol/StorageAuthenticationHttpHandler.cs @@ -73,6 +73,10 @@ private Func { authenticationHandler = this.GetSharedKeyAuthenticationTask; } + else if (request.Credentials.IsToken) + { + authenticationHandler = this.GetTokenAuthenticationTask; + } else { authenticationHandler = this.GetNoOpAuthenticationTask; diff --git a/Lib/WindowsRuntime/Microsoft.WindowsAzure.Storage.RT.csproj b/Lib/WindowsRuntime/Microsoft.WindowsAzure.Storage.RT.csproj index b45703703..230235e52 100644 --- a/Lib/WindowsRuntime/Microsoft.WindowsAzure.Storage.RT.csproj +++ b/Lib/WindowsRuntime/Microsoft.WindowsAzure.Storage.RT.csproj @@ -60,6 +60,7 @@ + diff --git a/Test/ClassLibraryCommon/Blob/CloudBlobClientTest.cs b/Test/ClassLibraryCommon/Blob/CloudBlobClientTest.cs index 8cb2afc28..d23245733 100644 --- a/Test/ClassLibraryCommon/Blob/CloudBlobClientTest.cs +++ b/Test/ClassLibraryCommon/Blob/CloudBlobClientTest.cs @@ -82,6 +82,22 @@ public void CloudBlobClientWithUppercaseAccountName() container.Exists(); } + [TestMethod] + [Description("Create a service client with token")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudBlobClientWithToken() + { + TokenCredential token = new TokenCredential(TestBase.GenerateOAuthToken()); + StorageCredentials credentials = new StorageCredentials(token); + Uri baseAddressUri = new Uri(TestBase.TargetTenantConfig.BlobServiceEndpoint); + CloudBlobClient blobClient = new CloudBlobClient(baseAddressUri, credentials); + CloudBlobContainer container = blobClient.GetContainerReference("container"); + container.Exists(); + } + [TestMethod] [Description("Compare service client properties of blob objects")] [TestCategory(ComponentCategory.Blob)] diff --git a/Test/ClassLibraryCommon/Core/TokenAuthenticationTest.cs b/Test/ClassLibraryCommon/Core/TokenAuthenticationTest.cs new file mode 100644 index 000000000..919aac70e --- /dev/null +++ b/Test/ClassLibraryCommon/Core/TokenAuthenticationTest.cs @@ -0,0 +1,206 @@ +// ----------------------------------------------------------------------------------------- +// +// 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. +// +// ----------------------------------------------------------------------------------------- + +using System.Runtime.Remoting; +using System.Runtime.Remoting.Messaging; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.WindowsAzure.Storage.Core +{ + using Microsoft.WindowsAzure.Storage.Auth; + using Microsoft.WindowsAzure.Storage.Blob; + using Microsoft.WindowsAzure.Storage.Queue; + using Microsoft.WindowsAzure.Storage.Table; + using Microsoft.WindowsAzure.Storage.File; + using System; + using System.Collections.Generic; + using System.Net; + +#if WINDOWS_DESKTOP + using Microsoft.VisualStudio.TestTools.UnitTesting; + +#else + using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +#endif + + [TestClass] + public class TokenAuthenticationTest : TestBase + { + /// + /// This is the test state used by the periodic token updater. + /// It can be anything in practice, in our test case it is simply a string holder. + /// + private class TestState + { + public string Buffer { get; set; } + + public TestState(string buffer) + { + this.Buffer = buffer; + } + } + + /// + /// This is the fast token updater. + /// It simply appends '0' to the current token. + /// + private static Task FastTokenUpdater(Object state, CancellationToken cancellationToken) + { + return + Task.Factory.StartNew( + () => + { + TestState testState = (TestState)state; + testState.Buffer += "0"; + return new NewTokenAndFrequency(testState.Buffer, TimeSpan.FromSeconds(5)); + }, cancellationToken); + } + + /// + /// This is the super slow token updater. It simulates situations where a token needs to be retrieved from a potato server. + /// It waits for 10 seconds and then simply appends '0' to the current token. + /// + private static Task SlowTokenUpdater(Object state, CancellationToken cancellationToken) + { + return + Task.Factory.StartNew( + () => + { + TestState testState = (TestState)state; + testState.Buffer += "0"; + Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).Wait(cancellationToken); + return new NewTokenAndFrequency(testState.Buffer, TimeSpan.FromSeconds(5)); + }, cancellationToken); + } + + /// + /// This updater throws exceptions. It simulates situations where errors occur while retrieving a token from a potato server. + /// + private static Task BrokenTokenUpdater(Object state, CancellationToken cancellationToken) + { + return + Task.Factory.StartNew( + () => + { + throw new ServerException(); + }, cancellationToken); + } + + [TestMethod] + [Description("Basic timer triggering test.")] + [TestCategory(ComponentCategory.Core)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.Smoke)] + [TestCategory(TenantTypeCategory.Cloud)] + public void TimerShouldTriggerPeriodically() + { + // token updater is triggered every 5 seconds + TestState state = new TestState("0"); + TokenCredential tokenCredential = new TokenCredential("0", FastTokenUpdater, state, TimeSpan.FromSeconds(5)); + + // make sure the token starts with the right value, t=0 + Assert.AreEqual("0", tokenCredential.Token); + + // wait until timer triggers for the first time and validate token value, t=6 + Task.Delay(TimeSpan.FromSeconds(6)).Wait(); + Assert.AreEqual("00", tokenCredential.Token); + + // wait until timer triggers for the second time and validate token value, t=12 + Task.Delay(TimeSpan.FromSeconds(6)).Wait(); + Assert.AreEqual("000", tokenCredential.Token); + + // stop the time and make sure it does not trigger anymore, t=18 + tokenCredential.Dispose(); + Task.Delay(TimeSpan.FromSeconds(6)).Wait(); + Assert.AreEqual("000", tokenCredential.Token); + } + + [TestMethod] + [Description("Make sure the token updater only gets triggered after the previous update finishes.")] + [TestCategory(ComponentCategory.Core)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.Smoke)] + [TestCategory(TenantTypeCategory.Cloud)] + public void UpdaterShouldRunOneAtATime() + { + // token updater is triggered every 5 seconds + // however, the slow updater takes 10 seconds to provide a new token + TestState state = new TestState("0"); + TokenCredential tokenCredential = new TokenCredential("0", SlowTokenUpdater, state, TimeSpan.FromSeconds(5)); + + // make sure the token starts with the right value, t=0 + Assert.AreEqual("0", tokenCredential.Token); + + // check on the token while updater is running, t=6 + Task.Delay(TimeSpan.FromSeconds(6)).Wait(); + Assert.AreEqual("0", tokenCredential.Token); + + // check on the token after updater is done for the first time, t=16 + // the first updater should have finished at t=15 + Task.Delay(TimeSpan.FromSeconds(10)).Wait(); + Assert.AreEqual("00", tokenCredential.Token); + + // check on the token while updater is running, t=22 + // the second updater should have been triggered at t=20 + Task.Delay(TimeSpan.FromSeconds(6)).Wait(); + Assert.AreEqual("00", tokenCredential.Token); + + // check on the token after updater is done for the second time, t=32 + // the second updater should have finished at t=30 + Task.Delay(TimeSpan.FromSeconds(10)).Wait(); + Assert.AreEqual("000", tokenCredential.Token); + + // stop the timer and make sure it is not triggered anymore, t=50 + tokenCredential.Dispose(); + Task.Delay(TimeSpan.FromSeconds(18)).Wait(); + Assert.AreEqual("000", tokenCredential.Token); + } + + /// + /// TODO: this does not seem to be the desired bahvior, validate with JR. + /// + [TestMethod] + [Description("Test the situation where the periodic token updater throws an exception.")] + [TestCategory(ComponentCategory.Core)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.Smoke)] + [TestCategory(TenantTypeCategory.Cloud)] + public void ErrorThrownWhenTimerIsTriggered() + { + // token updater is triggered every 5 seconds + TestState state = new TestState("0"); + TokenCredential tokenCredential = new TokenCredential("0", BrokenTokenUpdater, state, TimeSpan.FromSeconds(5)); + + // make sure the token starts with the right value, t=0 + Assert.AreEqual("0", tokenCredential.Token); + + // wait until timer triggers for the first time and validate token value, t=6 + Task.Delay(TimeSpan.FromSeconds(6)).Wait(); + Assert.AreEqual("0", tokenCredential.Token); + + // wait until timer triggers for the second time and validate token value, 6=12 + Task.Delay(TimeSpan.FromSeconds(6)).Wait(); + Assert.AreEqual("0", tokenCredential.Token); + + // stop the time and make sure it does not trigger anymore, t=18 + tokenCredential.Dispose(); + Task.Delay(TimeSpan.FromSeconds(6)).Wait(); + Assert.AreEqual("0", tokenCredential.Token); + } + } +} \ No newline at end of file diff --git a/Test/ClassLibraryCommon/File/CloudFileClientTest.cs b/Test/ClassLibraryCommon/File/CloudFileClientTest.cs index 8b2601c97..aa26ce087 100644 --- a/Test/ClassLibraryCommon/File/CloudFileClientTest.cs +++ b/Test/ClassLibraryCommon/File/CloudFileClientTest.cs @@ -82,6 +82,32 @@ public void CloudFileClientWithUppercaseAccountName() share.Exists(); } + [TestMethod] + [Description("Create a service client with token")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileClientWithToken() + { + TokenCredential token = new TokenCredential(TestBase.GenerateOAuthToken()); + StorageCredentials credentials = new StorageCredentials(token); + Uri baseAddressUri = new Uri(TestBase.TargetTenantConfig.FileServiceEndpoint); + + CloudFileClient client = new CloudFileClient(baseAddressUri, credentials); + CloudFileShare share = client.GetShareReference("share"); + + try + { + share.Exists(); + Assert.Fail(); + } + catch (Exception ex) + { + Assert.IsInstanceOfType(ex, typeof(InvalidOperationException)); + } + } + [TestMethod] [Description("Compare service client properties of file objects")] [TestCategory(ComponentCategory.File)] diff --git a/Test/ClassLibraryCommon/Queue/CloudQueueClientTest.cs b/Test/ClassLibraryCommon/Queue/CloudQueueClientTest.cs index 4ee76eb1b..e03667976 100644 --- a/Test/ClassLibraryCommon/Queue/CloudQueueClientTest.cs +++ b/Test/ClassLibraryCommon/Queue/CloudQueueClientTest.cs @@ -27,6 +27,7 @@ using System.Threading; using System.Xml; using System.Xml.Serialization; +using Microsoft.WindowsAzure.Storage.Auth; namespace Microsoft.WindowsAzure.Storage.Queue { @@ -83,6 +84,23 @@ public void CloudQueueClientConstructorInvalidParam() TestHelper.ExpectedException(() => new CloudQueueClient((Uri)null, TestBase.StorageCredentials), "Pass null into CloudQueueClient"); } + [TestMethod] + [Description("Create a service client with token")] + [TestCategory(ComponentCategory.Queue)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudQueueClientWithToken() + { + TokenCredential token = new TokenCredential(TestBase.GenerateOAuthToken()); + StorageCredentials credentials = new StorageCredentials(token); + Uri baseAddressUri = new Uri(TestBase.TargetTenantConfig.QueueServiceEndpoint); + + CloudQueueClient client = new CloudQueueClient(baseAddressUri, credentials); + CloudQueue queue = client.GetQueueReference("queue"); + queue.Exists(); + } + [TestMethod] [Description("List queues")] [TestCategory(ComponentCategory.Queue)] diff --git a/Test/ClassLibraryCommon/Table/CloudTableClientTests.cs b/Test/ClassLibraryCommon/Table/CloudTableClientTests.cs index 5dc35ad31..24bc526b7 100644 --- a/Test/ClassLibraryCommon/Table/CloudTableClientTests.cs +++ b/Test/ClassLibraryCommon/Table/CloudTableClientTests.cs @@ -26,6 +26,7 @@ using System.Threading; using System.Xml; using System.Xml.Serialization; +using Microsoft.WindowsAzure.Storage.Auth; namespace Microsoft.WindowsAzure.Storage.Table { @@ -154,6 +155,36 @@ public void CloudTableClientConstructor() #endregion + #region OAuth test + + [TestMethod] + [Description("Create a service client with token")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudTableClientWithToken() + { + TokenCredential token = new TokenCredential(TestBase.GenerateOAuthToken()); + StorageCredentials credentials = new StorageCredentials(token); + Uri baseAddressUri = new Uri(TestBase.TargetTenantConfig.TableServiceEndpoint); + + CloudTableClient client = new CloudTableClient(baseAddressUri, credentials); + CloudTable table = client.GetTableReference("Testy"); + + try + { + table.Exists(); + Assert.Fail(); + } + catch (Exception ex) + { + Assert.IsInstanceOfType(ex, typeof(InvalidOperationException)); + } + } + + #endregion + #region List Tables Iterator [TestMethod] diff --git a/Test/Common/Core/CloudStorageAccountTests.cs b/Test/Common/Core/CloudStorageAccountTests.cs index 104c4ce41..59d9e57b4 100644 --- a/Test/Common/Core/CloudStorageAccountTests.cs +++ b/Test/Common/Core/CloudStorageAccountTests.cs @@ -266,6 +266,9 @@ public void StorageCredentialsEquality() StorageCredentials credSAS3 = new StorageCredentials(token + "1"); StorageCredentials credAnonymous1 = new StorageCredentials(); StorageCredentials credAnonymous2 = new StorageCredentials(); + StorageCredentials tokenCredential1 = new StorageCredentials(new TokenCredential("0")); + StorageCredentials tokenCredential2 = new StorageCredentials(new TokenCredential("1")); + StorageCredentials tokenCredential3 = new StorageCredentials(new TokenCredential("0")); Assert.IsTrue(credSharedKey1.Equals(credSharedKey2)); Assert.IsFalse(credSharedKey1.Equals(credSharedKey3)); @@ -276,6 +279,8 @@ public void StorageCredentialsEquality() Assert.IsFalse(credSharedKey1.Equals(credSAS1)); Assert.IsFalse(credSharedKey1.Equals(credAnonymous1)); Assert.IsFalse(credSAS1.Equals(credAnonymous1)); + Assert.IsFalse(tokenCredential1.Equals(tokenCredential2)); + Assert.IsTrue(tokenCredential1.Equals(tokenCredential3)); } private void AccountsAreEqual(CloudStorageAccount a, CloudStorageAccount b) diff --git a/Test/Common/TestBase.Common.cs b/Test/Common/TestBase.Common.cs index fd6e96bb7..11622b8c5 100644 --- a/Test/Common/TestBase.Common.cs +++ b/Test/Common/TestBase.Common.cs @@ -26,6 +26,7 @@ using System.Linq; #if WINDOWS_DESKTOP +using Microsoft.IdentityModel.Clients.ActiveDirectory; using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.WindowsAzure.Storage.Table.DataServices; using System.ServiceModel.Channels; @@ -75,6 +76,22 @@ public static void AssertSecondaryEndpoint() public static MockBufferManager QueueBufferManager = new MockBufferManager((int)Constants.KB); #endif +#if WINDOWS_DESKTOP + public static string GenerateOAuthToken() + { + string authority = string.Format("https://login.microsoftonline.com/{0}/oauth2/token", + TestBase.TargetTenantConfig.ActiveDirectoryTenantId); + + ClientCredential credential = new ClientCredential(TestBase.TargetTenantConfig.ActiveDirectoryApplicationId, + TestBase.TargetTenantConfig.ActiveDirectoryApplicationSecret); + + AuthenticationContext context = new AuthenticationContext(authority); + AuthenticationResult result = context.AcquireTokenAsync("https://storage.azure.com", credential).Result; + + return result.AccessToken; + } +#endif + public static CloudTableClient GenerateCloudTableClient() { CloudTableClient client; diff --git a/Test/Common/TestConfigProcess/TenantConfiguration.cs b/Test/Common/TestConfigProcess/TenantConfiguration.cs index 011e2e012..fb21f2bdc 100644 --- a/Test/Common/TestConfigProcess/TenantConfiguration.cs +++ b/Test/Common/TestConfigProcess/TenantConfiguration.cs @@ -34,6 +34,9 @@ public class TenantConfiguration public string FileServiceSecondaryEndpoint { get; internal set; } public string QueueServiceSecondaryEndpoint { get; internal set; } public string TableServiceSecondaryEndpoint { get; internal set; } + public string ActiveDirectoryApplicationId { get; internal set; } + public string ActiveDirectoryApplicationSecret { get; internal set; } + public string ActiveDirectoryTenantId { get; internal set; } public TenantType TenantType { get; internal set; } } } diff --git a/Test/Common/TestConfigProcess/TestConfigurations.cs b/Test/Common/TestConfigProcess/TestConfigurations.cs index 87f66feff..5ef03a408 100644 --- a/Test/Common/TestConfigProcess/TestConfigurations.cs +++ b/Test/Common/TestConfigProcess/TestConfigurations.cs @@ -60,6 +60,9 @@ public static TestConfigurations ReadFromXml(XElement testConfigurationsElement) config.FileSecurePortOverride = (string)tenantConfigurationElement.Element("FileSecurePortOverride"); config.QueueSecurePortOverride = (string)tenantConfigurationElement.Element("QueueSecurePortOverride"); config.TableSecurePortOverride = (string)tenantConfigurationElement.Element("TableSecurePortOverride"); + config.ActiveDirectoryApplicationId = (string)tenantConfigurationElement.Element("ActiveDirectoryApplicationId"); + config.ActiveDirectoryApplicationSecret = (string)tenantConfigurationElement.Element("ActiveDirectoryApplicationSecret"); + config.ActiveDirectoryTenantId = (string)tenantConfigurationElement.Element("ActiveDirectoryTenantId"); tenantConfigurationList.Add(config); } diff --git a/Test/WindowsDesktop/Microsoft.WindowsAzure.Storage.Test.csproj b/Test/WindowsDesktop/Microsoft.WindowsAzure.Storage.Test.csproj index 65f2b1080..760360973 100644 --- a/Test/WindowsDesktop/Microsoft.WindowsAzure.Storage.Test.csproj +++ b/Test/WindowsDesktop/Microsoft.WindowsAzure.Storage.Test.csproj @@ -79,6 +79,14 @@ False ..\..\packages\Microsoft.Data.Services.Client.5.8.2\lib\net40\Microsoft.Data.Services.Client.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.17.2\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + True + + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.17.2\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll + True + False ..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll diff --git a/Test/WindowsDesktop/packages.config b/Test/WindowsDesktop/packages.config index 0420e7e8c..12d13569e 100644 --- a/Test/WindowsDesktop/packages.config +++ b/Test/WindowsDesktop/packages.config @@ -12,6 +12,7 @@ + diff --git a/changelog.txt b/changelog.txt index 00c49a24f..cde7682f4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,7 @@ Changes in ?.?.? : - Blobs (Desktop): Added support for downloading a blob to a file by making concurrent service requests. The concurrency count is mandatory. The concurrency and range size should be chosen based on the memory, CPU, and bandwidth available. - Blobs: Parsing the snapshot parameter when creating a CloudBlockBlob from a URI is now case insensitive +- Blob&Queue: Added OAuth token support for authentication. Changes in 8.6.0 : - Tables : Added support for Byte type property in TableEntity Flatten/ConvertBack methods. From a4559a17752fcd6cf5870a5a8fab8b0bc8c9e570 Mon Sep 17 00:00:00 2001 From: erezvani Date: Wed, 31 Jan 2018 15:54:38 -0800 Subject: [PATCH 2/9] [nov17] update service version --- Lib/Common/Shared/Protocol/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index 70d101b2e..40291c538 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -1215,7 +1215,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 = "2017-07-29"; + public const string TargetStorageVersion = "2017-11-09"; /// /// Specifies the file type. From 16921fc3e95d53db0209a219faafa572fe44ecc3 Mon Sep 17 00:00:00 2001 From: "Keith Farmer (MSFT)" Date: Thu, 22 Feb 2018 17:16:46 -0800 Subject: [PATCH 3/9] 1884507: [.NET] WORM support --- Lib/ClassLibraryCommon/Blob/CloudBlob.cs | 1 + .../Blob/Protocol/BlobHttpResponseParsers.cs | 4 +++ .../Protocol/ContainerHttpResponseParsers.cs | 10 +++++++ .../Shared/Protocol/HttpResponseParsers.cs | 11 +++++++ Lib/Common/Blob/BlobContainerProperties.cs | 12 ++++++++ Lib/Common/Blob/BlobProperties.cs | 7 +++++ Lib/Common/Blob/Protocol/ListBlobsResponse.cs | 4 +++ .../Blob/Protocol/ListContainersResponse.cs | 8 +++++ Lib/Common/Shared/Protocol/Constants.cs | 30 +++++++++++++++++++ Lib/WindowsRuntime/Blob/CloudBlob.cs | 1 + .../Blob/Protocol/BlobHttpResponseParsers.cs | 3 ++ .../Protocol/ContainerHttpResponseParsers.cs | 8 +++++ .../Blob/CloudBlobContainerTest.cs | 6 ++++ .../Blob/CloudBlockBlobTest.cs | 4 +++ changelog.txt | 5 ++++ 15 files changed, 114 insertions(+) diff --git a/Lib/ClassLibraryCommon/Blob/CloudBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudBlob.cs index f5c6b1c1c..71d14fdd2 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudBlob.cs @@ -4184,6 +4184,7 @@ internal static void UpdateETagLMTLengthAndSequenceNumber(BlobAttributes blobAtt { BlobProperties parsedProperties = BlobHttpResponseParsers.GetProperties(response); blobAttributes.Properties.ETag = parsedProperties.ETag ?? blobAttributes.Properties.ETag; + blobAttributes.Properties.Created = parsedProperties.Created ?? blobAttributes.Properties.Created; blobAttributes.Properties.LastModified = parsedProperties.LastModified ?? blobAttributes.Properties.LastModified; blobAttributes.Properties.PageBlobSequenceNumber = parsedProperties.PageBlobSequenceNumber ?? blobAttributes.Properties.PageBlobSequenceNumber; blobAttributes.Properties.AppendBlobCommittedBlockCount = parsedProperties.AppendBlobCommittedBlockCount ?? blobAttributes.Properties.AppendBlobCommittedBlockCount; diff --git a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpResponseParsers.cs b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpResponseParsers.cs index dd273af91..66ea2c16d 100644 --- a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpResponseParsers.cs +++ b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpResponseParsers.cs @@ -55,9 +55,13 @@ public static BlobProperties GetProperties(HttpWebResponse response) properties.ETag = HttpResponseParsers.GetETag(response); #if WINDOWS_PHONE + properties.Created = HttpResponseParsers.GetCreated(response); properties.LastModified = HttpResponseParsers.GetLastModified(response); properties.ContentLanguage = response.Headers[Constants.HeaderConstants.ContentLanguageHeader]; #else + string created = response.Headers[Constants.HeaderConstants.CreationTimeHeader]; + properties.Created = string.IsNullOrEmpty(created) ? (DateTimeOffset?)null : DateTimeOffset.Parse(created).ToUniversalTime(); + properties.LastModified = response.LastModified.ToUniversalTime(); properties.ContentLanguage = response.Headers[HttpResponseHeader.ContentLanguage]; #endif diff --git a/Lib/ClassLibraryCommon/Blob/Protocol/ContainerHttpResponseParsers.cs b/Lib/ClassLibraryCommon/Blob/Protocol/ContainerHttpResponseParsers.cs index 4e412b7ad..1d44da7bb 100644 --- a/Lib/ClassLibraryCommon/Blob/Protocol/ContainerHttpResponseParsers.cs +++ b/Lib/ClassLibraryCommon/Blob/Protocol/ContainerHttpResponseParsers.cs @@ -20,6 +20,7 @@ namespace Microsoft.WindowsAzure.Storage.Blob.Protocol using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System.Collections.Generic; + using System.Globalization; using System.Net; /// @@ -60,8 +61,17 @@ public static BlobContainerProperties GetProperties(HttpWebResponse response) containerProperties.LeaseStatus = BlobHttpResponseParsers.GetLeaseStatus(response); containerProperties.LeaseState = BlobHttpResponseParsers.GetLeaseState(response); containerProperties.LeaseDuration = BlobHttpResponseParsers.GetLeaseDuration(response); + // Reading public access containerProperties.PublicAccess = GetAcl(response); + + // WORM policies + string hasImmutability = response.Headers[Constants.HeaderConstants.HasImmutabilityPolicyHeader]; + containerProperties.HasImmutabilityPolicy = string.IsNullOrEmpty(hasImmutability) ? (bool?)null : bool.Parse(hasImmutability); + + string hasLegalHold = response.Headers[Constants.HeaderConstants.HasLegalHoldHeader]; + containerProperties.HasLegalHold = string.IsNullOrEmpty(hasLegalHold) ? (bool?)null : bool.Parse(hasLegalHold); + return containerProperties; } diff --git a/Lib/ClassLibraryCommon/Shared/Protocol/HttpResponseParsers.cs b/Lib/ClassLibraryCommon/Shared/Protocol/HttpResponseParsers.cs index a02bd2c7c..f3ebb3b07 100644 --- a/Lib/ClassLibraryCommon/Shared/Protocol/HttpResponseParsers.cs +++ b/Lib/ClassLibraryCommon/Shared/Protocol/HttpResponseParsers.cs @@ -46,6 +46,17 @@ internal static string GetETag(HttpWebResponse response) } #if WINDOWS_PHONE + /// + /// Gets the Creation-Time date and time from a response. + /// + /// The web response. + /// A that indicates the creation date and time. + internal static DateTimeOffset? GetCreated(HttpWebResponse response) + { + string created = response.Headers[Constants.HeaderConstants.CreationTimeHeader]; + return string.IsNullOrEmpty(created) ? (DateTimeOffset?)null : DateTimeOffset.Parse(created, CultureInfo.InvariantCulture); + } + /// /// Gets the Last-Modified date and time from a response. /// diff --git a/Lib/Common/Blob/BlobContainerProperties.cs b/Lib/Common/Blob/BlobContainerProperties.cs index ed879f0e5..e11d1eef8 100644 --- a/Lib/Common/Blob/BlobContainerProperties.cs +++ b/Lib/Common/Blob/BlobContainerProperties.cs @@ -30,6 +30,18 @@ public sealed class BlobContainerProperties /// A string containing the container's quoted ETag value. public string ETag { get; internal set; } + /// + /// Gets the HasImmutabilityPolicy value for the container. + /// + /// True if the container has an immutability policy, or False if not. The value may be null. + public bool? HasImmutabilityPolicy { get; internal set; } + + /// + /// Gets the HasLegalHold value for the container. + /// + /// True if the container has legal hold, or False if not. The value may be null. + public bool? HasLegalHold { get; internal set; } + /// /// Gets the container's last-modified time. /// diff --git a/Lib/Common/Blob/BlobProperties.cs b/Lib/Common/Blob/BlobProperties.cs index cd3bc270a..db282ffbe 100644 --- a/Lib/Common/Blob/BlobProperties.cs +++ b/Lib/Common/Blob/BlobProperties.cs @@ -51,6 +51,7 @@ public BlobProperties(BlobProperties other) this.ContentMD5 = other.ContentMD5; this.Length = other.Length; this.ETag = other.ETag; + this.Created = other.Created; this.LastModified = other.LastModified; this.PageBlobSequenceNumber = other.PageBlobSequenceNumber; this.AppendBlobCommittedBlockCount = other.AppendBlobCommittedBlockCount; @@ -124,6 +125,12 @@ public BlobProperties(BlobProperties other) /// A string containing the blob's ETag value. public string ETag { get; internal set; } + /// + /// Gets the the creation time for the blob, expressed as a UTC value. + /// + /// A containing the blob's creation time, in UTC format. + public DateTimeOffset? Created { get; internal set; } + /// /// Gets the the last-modified time for the blob, expressed as a UTC value. /// diff --git a/Lib/Common/Blob/Protocol/ListBlobsResponse.cs b/Lib/Common/Blob/Protocol/ListBlobsResponse.cs index 8f926b610..5a4ae0eba 100644 --- a/Lib/Common/Blob/Protocol/ListBlobsResponse.cs +++ b/Lib/Common/Blob/Protocol/ListBlobsResponse.cs @@ -250,6 +250,10 @@ private IListBlobEntry ParseBlobEntry(Uri baseUri) { switch (this.reader.Name) { + case Constants.CreationTimeElement: + blob.Properties.Created = reader.ReadElementContentAsString().ToUTCTime(); + break; + case Constants.LastModifiedElement: blob.Properties.LastModified = reader.ReadElementContentAsString().ToUTCTime(); break; diff --git a/Lib/Common/Blob/Protocol/ListContainersResponse.cs b/Lib/Common/Blob/Protocol/ListContainersResponse.cs index 462453827..b793489a4 100644 --- a/Lib/Common/Blob/Protocol/ListContainersResponse.cs +++ b/Lib/Common/Blob/Protocol/ListContainersResponse.cs @@ -211,6 +211,14 @@ private BlobContainerEntry ParseContainerEntry(Uri baseUri) containerProperties.ETag = reader.ReadElementContentAsString(); break; + case Constants.HasImmutabilityPolicyElement: + containerProperties.HasImmutabilityPolicy = reader.ReadElementContentAsBoolean(); + break; + + case Constants.HasLegalHoldElement: + containerProperties.HasLegalHold = reader.ReadElementContentAsBoolean(); + break; + case Constants.LeaseStatusElement: containerProperties.LeaseStatus = BlobHttpResponseParsers.GetLeaseStatus(reader.ReadElementContentAsString()); break; diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index 40291c538..87a770e86 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -347,6 +347,11 @@ static class Constants /// public const string EtagElement = "Etag"; + /// + /// XML element for the creation date. + /// + public const string CreationTimeElement = "Creation-Time"; + /// /// XML element for the last modified date. /// @@ -572,6 +577,16 @@ static class Constants /// public const string BlobTypeElement = "BlobType"; + /// + /// XML element for immutability policy. + /// + public const string HasImmutabilityPolicyElement = "HasImmutabilityPolicy"; + + /// + /// XML element for legal hold. + /// + public const string HasLegalHoldElement = "HasLegalHold"; + /// /// XML element for the lease status. /// @@ -946,11 +961,26 @@ static HeaderConstants() /// public const string ContentLanguageHeader = "Content-Language"; + /// + /// Header that specifies the creation time value for the resource. + /// + public const string CreationTimeHeader = PrefixForStorageHeader + "creation-time"; + /// /// Header that specifies the ETag value for the resource. /// public const string EtagHeader = "ETag"; + /// + /// Header that specifies the immutability policy value for the resource. + /// + public const string HasImmutabilityPolicyHeader = PrefixForStorageHeader + "has-immutability-policy"; + + /// + /// Header that specifies the legal hold value for the resource. + /// + public const string HasLegalHoldHeader = PrefixForStorageHeader + "has-legal-hold"; + /// /// Header that specifies if a resourse is fully encrypted server-side. /// diff --git a/Lib/WindowsRuntime/Blob/CloudBlob.cs b/Lib/WindowsRuntime/Blob/CloudBlob.cs index a6b9752aa..180ae10f3 100644 --- a/Lib/WindowsRuntime/Blob/CloudBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudBlob.cs @@ -1842,6 +1842,7 @@ internal static void UpdateETagLMTLengthAndSequenceNumber(BlobAttributes attribu { BlobProperties parsedProperties = BlobHttpResponseParsers.GetProperties(response); attributes.Properties.ETag = parsedProperties.ETag ?? attributes.Properties.ETag; + attributes.Properties.Created = parsedProperties.Created ?? attributes.Properties.Created; attributes.Properties.LastModified = parsedProperties.LastModified ?? attributes.Properties.LastModified; attributes.Properties.PageBlobSequenceNumber = parsedProperties.PageBlobSequenceNumber ?? attributes.Properties.PageBlobSequenceNumber; attributes.Properties.AppendBlobCommittedBlockCount = parsedProperties.AppendBlobCommittedBlockCount ?? attributes.Properties.AppendBlobCommittedBlockCount; diff --git a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpResponseParsers.cs b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpResponseParsers.cs index fb484cf5e..2fa948d81 100644 --- a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpResponseParsers.cs +++ b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpResponseParsers.cs @@ -43,6 +43,9 @@ public static BlobProperties GetProperties(HttpResponseMessage response) if (response.Content != null) { + string created = response.Headers.GetHeaderSingleValueOrDefault(Constants.HeaderConstants.CreationTimeHeader); + properties.Created = string.IsNullOrEmpty(created) ? (DateTimeOffset?)null : DateTimeOffset.Parse(created, CultureInfo.InvariantCulture); + properties.LastModified = response.Content.Headers.LastModified; #if NETCORE HttpContentHeaders contentHeaders = response.Content.Headers; diff --git a/Lib/WindowsRuntime/Blob/Protocol/ContainerHttpResponseParsers.cs b/Lib/WindowsRuntime/Blob/Protocol/ContainerHttpResponseParsers.cs index 32ad02e25..e8004a376 100644 --- a/Lib/WindowsRuntime/Blob/Protocol/ContainerHttpResponseParsers.cs +++ b/Lib/WindowsRuntime/Blob/Protocol/ContainerHttpResponseParsers.cs @@ -57,6 +57,14 @@ public static BlobContainerProperties GetProperties(HttpResponseMessage response // Reading public access containerProperties.PublicAccess = GetAcl(response); + + // WORM policy + string hasImmutability = response.Headers.GetHeaderSingleValueOrDefault(Constants.HeaderConstants.HasImmutabilityPolicyHeader); + containerProperties.HasImmutabilityPolicy = string.IsNullOrEmpty(hasImmutability) ? (bool?)null : bool.Parse(hasImmutability); + + string hasLegalHold = response.Headers.GetHeaderSingleValueOrDefault(Constants.HeaderConstants.HasLegalHoldHeader); + containerProperties.HasLegalHold = string.IsNullOrEmpty(hasLegalHold) ? (bool?)null : bool.Parse(hasLegalHold); + return containerProperties; } diff --git a/Test/ClassLibraryCommon/Blob/CloudBlobContainerTest.cs b/Test/ClassLibraryCommon/Blob/CloudBlobContainerTest.cs index 96d213866..4f4b0c2a0 100644 --- a/Test/ClassLibraryCommon/Blob/CloudBlobContainerTest.cs +++ b/Test/ClassLibraryCommon/Blob/CloudBlobContainerTest.cs @@ -767,6 +767,10 @@ public void CloudBlobContainerExists() { Assert.IsTrue(container2.Exists()); Assert.IsNotNull(container2.Properties.ETag); + Assert.IsTrue(container2.Properties.HasImmutabilityPolicy.HasValue); + Assert.IsTrue(container2.Properties.HasLegalHold.HasValue); + Assert.IsFalse(container2.Properties.HasImmutabilityPolicy.Value); + Assert.IsFalse(container2.Properties.HasLegalHold.Value); } finally { @@ -2117,6 +2121,8 @@ public async Task CloudBlobContainerListBlobs() foreach (IListBlobItem blobItem in results) { Assert.IsInstanceOfType(blobItem, typeof(CloudPageBlob)); + Assert.IsTrue(((CloudPageBlob)blobItem).Properties.Created.HasValue); + Assert.IsTrue(((CloudPageBlob)blobItem).Properties.Created.Value > DateTime.Now.AddMinutes(-1)); Assert.IsTrue(blobNames.Remove(((CloudPageBlob)blobItem).Name)); Assert.AreEqual(RetryPolicies.LocationMode.PrimaryThenSecondary, ((CloudPageBlob)blobItem).ServiceClient.DefaultRequestOptions.LocationMode); } diff --git a/Test/ClassLibraryCommon/Blob/CloudBlockBlobTest.cs b/Test/ClassLibraryCommon/Blob/CloudBlockBlobTest.cs index 040183029..cbcc8bc22 100644 --- a/Test/ClassLibraryCommon/Blob/CloudBlockBlobTest.cs +++ b/Test/ClassLibraryCommon/Blob/CloudBlockBlobTest.cs @@ -187,6 +187,10 @@ public void CloudBlockBlobCreateAndDelete() CloudBlockBlob blob = container.GetBlockBlobReference("blob1"); CreateForTest(blob, 0, 0, false); Assert.IsTrue(blob.Exists()); + + Assert.IsTrue(blob.Properties.Created.HasValue); + Assert.IsTrue(blob.Properties.Created.Value > DateTime.Now.AddMinutes(-1)); + blob.Delete(); } finally diff --git a/changelog.txt b/changelog.txt index 8041012dd..a4cde1920 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +Changes in ###: +- All: Support for 2017-11-09 REST version. +- Blobs: Added support for blob creation time property. +- Blobs: Added support for blob container immutability and legal hold policies. + Changes in 9.0.0: - All: Support for 2017-07-29 REST version. Please see our REST API documentation and blogs for information about the related added features. If you are using the Storage Emulator, please update to Emulator version 5.3. - Tables: Removed dependencies on OData libraries. From bbdf7d6376c92dd6e37031a55692e37319a79905 Mon Sep 17 00:00:00 2001 From: erezvani Date: Sun, 25 Feb 2018 18:21:43 -0800 Subject: [PATCH 4/9] [9.1] release assembly updates --- ...rosoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs | 2 +- ...dowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs | 2 +- ....WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs | 4 ++-- .../Properties/AssemblyInfo.cs | 4 ++-- .../Microsoft.WindowsAzure.Storage.Facade/project.json | 2 +- Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs | 6 +++--- .../WindowsAzure.StorageK.nuspec | 2 +- Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json | 2 +- Lib/Common/Shared/Protocol/Constants.cs | 2 +- Lib/WindowsRuntime/Blob/CloudBlob.cs | 6 +++--- Lib/WindowsRuntime/Properties/AssemblyInfo.cs | 4 ++-- .../project.json | 4 ++-- .../project.json | 2 +- .../project.json | 4 ++-- .../AspNet/Microsoft.WindowsAzure.Storage.Test/project.json | 4 ++-- Test/AspNet/XUnitForMsTest/project.json | 2 +- Test/WindowsRuntime/Properties/AssemblyInfo.cs | 4 ++-- 17 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs index 887f6b22a..543f9ac72 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs @@ -256,7 +256,7 @@ public static class EncryptionConstants public const string TableEncryptionKeyDetails = "_ClientEncryptionMetadata1"; public const string TableEncryptionPropertyDetails = "_ClientEncryptionMetadata2"; public const string AgentMetadataKey = "EncryptionLibrary"; - public const string AgentMetadataValue = ".NET 9.0.0"; + public const string AgentMetadataValue = ".NET 9.1.0"; } } diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs index cd1431e13..eb5f714bc 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs @@ -10,7 +10,7 @@ public static class EncryptionConstants public const string TableEncryptionKeyDetails = "_ClientEncryptionMetadata1"; public const string TableEncryptionPropertyDetails = "_ClientEncryptionMetadata2"; public const string AgentMetadataKey = "EncryptionLibrary"; - public const string AgentMetadataValue = ".NET 9.0.0"; + public const string AgentMetadataValue = ".NET 9.1.0"; } } \ No newline at end of file diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs index 5f7d9e9df..44818cc55 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs @@ -4,9 +4,9 @@ namespace Microsoft.WindowsAzure.Storage.Shared.Protocol public static class HeaderConstants { - public static readonly string UserAgent = "Azure-Storage/9.0.0 "; + public static readonly string UserAgent = "Azure-Storage/9.1.0 "; public const string UserAgentProductName = "Azure-Storage"; - public const string UserAgentProductVersion = "9.0.0"; + public const string UserAgentProductVersion = "9.1.0"; public const string PrefixForStorageHeader = "x-ms-"; public const string TrueHeader = "true"; public const string FalseHeader = "false"; diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs index 958d492ff..e213c9dc8 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs @@ -31,8 +31,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("9.0.0.0")] -[assembly: AssemblyFileVersion("9.0.0.0")] +[assembly: AssemblyVersion("9.1.0.0")] +[assembly: AssemblyFileVersion("9.1.0.0")] [assembly: InternalsVisibleTo( "Microsoft.WindowsAzure.Storage.Facade.Portable, PublicKey=" + diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json index dda23554b..bd4646ea1 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json @@ -1,6 +1,6 @@ { "title": "Microsoft.WindowsAzure.Storage", - "version": "9.0.0.0", + "version": "9.1.0.0", "authors": [ "Microsoft Corporation" ], "description": "Azure Storage SDK for NetCore", diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs index c104137ff..674b8b508 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs @@ -34,9 +34,9 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("9.0.0.0")] -[assembly: AssemblyFileVersion("9.0.0.0")] -[assembly: AssemblyInformationalVersion("9.0.0.0")] +[assembly: AssemblyVersion("9.1.0.0")] +[assembly: AssemblyFileVersion("9.1.0.0")] +[assembly: AssemblyInformationalVersion("9.1.0.0")] [assembly: InternalsVisibleTo( diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec b/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec index 64355dc43..ab0d8ed51 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 - 9.0.0 + 9.1.0 Windows Azure Storage Microsoft Microsoft diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json index 06e2ccd2f..887989ef9 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json @@ -1,5 +1,5 @@ { - "version": "9.0.0.0", + "version": "9.1.0.0", "authors": [ "Microsoft Corporation" ], "description": "Azure Storage SDK for NetCore", diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index c5b740ba9..59f96fb57 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -904,7 +904,7 @@ static HeaderConstants() /// /// Specifies the value to use for UserAgent header. /// - public const string UserAgentProductVersion = "9.0.0"; + public const string UserAgentProductVersion = "9.1.0"; /// /// Master Microsoft Azure Storage header prefix. diff --git a/Lib/WindowsRuntime/Blob/CloudBlob.cs b/Lib/WindowsRuntime/Blob/CloudBlob.cs index fdd2dac95..57490357f 100644 --- a/Lib/WindowsRuntime/Blob/CloudBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudBlob.cs @@ -878,14 +878,14 @@ public virtual Task UndeleteAsync(AccessCondition accessCondition, BlobRequestOp /// A to observe while waiting for a task to complete. /// A object that represents the asynchronous operation. [DoesServiceRequest] - public virtual async Task UndeleteAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + public virtual Task UndeleteAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.Unspecified, this.ServiceClient); - await Executor.ExecuteAsyncNullReturn( + return Executor.ExecuteAsyncNullReturn( this.UndeleteBlobImpl(this.attributes, accessCondition, modifiedOptions), modifiedOptions.RetryPolicy, operationContext, - cancellationToken).ConfigureAwait(false); + cancellationToken); } /// diff --git a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs index 3d152cee1..b08d66077 100644 --- a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs @@ -26,8 +26,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("9.0.0.0")] -[assembly: AssemblyFileVersion("9.0.0.0")] +[assembly: AssemblyVersion("9.1.0.0")] +[assembly: AssemblyFileVersion("9.1.0.0")] [assembly: ComVisible(false)] diff --git a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json index 4922cbfca..ee73c7a3b 100644 --- a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json +++ b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json @@ -1,13 +1,13 @@ { "title": "Microsoft.WindowsAzure.Storage.Facade.NetCore.Test", - "version": "9.0.0.0", + "version": "9.1.0.0", "testRunner": "xunit", "dependencies": { "xunit": "2.1.0", "dotnet-test-xunit": "2.2.0-preview2-build1029", "XUnitForMsTest": "1.0.0.0", - "WindowsAzure.Storage": "9.0.0" + "WindowsAzure.Storage": "9.1.0" }, "frameworks": { diff --git a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.PhoneSilverlight81.Test/project.json b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.PhoneSilverlight81.Test/project.json index e38652df2..04039d9c0 100644 --- a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.PhoneSilverlight81.Test/project.json +++ b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.PhoneSilverlight81.Test/project.json @@ -1,7 +1,7 @@ { "version": "8.0.0.0", "dependencies": { - "WindowsAzure.Storage": "9.0.0" + "WindowsAzure.Storage": "9.1.0" }, "frameworks": { "wp81": {} diff --git a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json index 2b17fbe2b..20cfa575e 100644 --- a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json +++ b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json @@ -1,8 +1,8 @@ { - "version": "9.0.0.0", + "version": "9.1.0.0", "supports": {}, "dependencies": { - "WindowsAzure.Storage": "9.0.0" + "WindowsAzure.Storage": "9.1.0" }, "frameworks": { ".NETPortable,Version=v4.5,Profile=Profile259": {} diff --git a/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json b/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json index 187bc870c..61f9aeda1 100644 --- a/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json +++ b/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json @@ -1,11 +1,11 @@ { - "version": "9.0.0.0", + "version": "9.1.0.0", "testRunner": "xunit", "dependencies": { "xunit": "2.1.0", "dotnet-test-xunit": "2.2.0-preview2-build1029", - "Microsoft.WindowsAzure.Storage": "9.0.0.0", + "Microsoft.WindowsAzure.Storage": "9.1.0.0", "XUnitForMsTest": "1.0.0-*" }, diff --git a/Test/AspNet/XUnitForMsTest/project.json b/Test/AspNet/XUnitForMsTest/project.json index bd8fa1f5a..60ba9cb63 100644 --- a/Test/AspNet/XUnitForMsTest/project.json +++ b/Test/AspNet/XUnitForMsTest/project.json @@ -4,7 +4,7 @@ "dependencies": { "xunit": "2.1.0", - "WindowsAzure.Storage": "9.0.0", + "WindowsAzure.Storage": "9.1.0", "dotnet-test-xunit": "2.2.0-preview2-build1029" }, diff --git a/Test/WindowsRuntime/Properties/AssemblyInfo.cs b/Test/WindowsRuntime/Properties/AssemblyInfo.cs index c94d9aac3..768b3f8fa 100644 --- a/Test/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Test/WindowsRuntime/Properties/AssemblyInfo.cs @@ -25,7 +25,7 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("9.0.0.0")] -[assembly: AssemblyFileVersion("9.0.0.0")] +[assembly: AssemblyVersion("9.1.0.0")] +[assembly: AssemblyFileVersion("9.1.0.0")] [assembly: ComVisible(false)] From 51561b89d25a6e6fd94a2af3ecfa7b2e44d1cc8d Mon Sep 17 00:00:00 2001 From: "Keith Farmer (MSFT)" Date: Wed, 2 May 2018 10:09:29 -0700 Subject: [PATCH 5/9] Add OperationContext.Proxy (#667) * Enforce serializer settings for table parsers and fix support for double.NaN, double.PositiveInfinity, double.NegativeInfinity (#656) * [NET] Fix support for double.Nan, double.PositiveInfinity, double.NegativeInfinity. Expand tests around the various EDM types. * [NET] Fix case where JSON serialization gets inadvertently overridden * Adding missing copyright notice * Fix non-table settings * Address CR comments, fix bug introduced in 9.0 where doubles were not marked with EdmType when sent to server. * Remove quotes from normal double values. * Fix NetCore support of NaN * Cap language use to C#5 * Further NetCore test fixes * Remove incorrect project nuget dependency, fix test * Test fixes * Revert serializer settings changes for encryption cases * [9.1.1] Release (#665) * [NET] Fix support for double.Nan, double.PositiveInfinity, double.NegativeInfinity. Expand tests around the various EDM types. * [NET] Fix case where JSON serialization gets inadvertently overridden * Adding missing copyright notice * Fix non-table settings * Address CR comments, fix bug introduced in 9.0 where doubles were not marked with EdmType when sent to server. * Remove quotes from normal double values. * Fix NetCore support of NaN * Cap language use to C#5 * Further NetCore test fixes * Remove incorrect project nuget dependency, fix test * Test fixes * Revert serializer settings changes for encryption cases * [9.1.1]Update assembly versions for release * [9.1.1] Completed the reverts related to encryption SerializerSetting changes * Add OperationContext.Proxy to desktop * Update changelog for OperationContext.Proxy --- .../Shared/Protocol/HttpWebRequestFactory.cs | 14 +- Lib/Common/OperationContext.cs | 10 +- .../Blob/CloudBlobContainerTest.cs | 179 ++++++++++++++++++ changelog.txt | 3 + 4 files changed, 200 insertions(+), 6 deletions(-) diff --git a/Lib/ClassLibraryCommon/Shared/Protocol/HttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/Shared/Protocol/HttpWebRequestFactory.cs index 41a5ed714..5f92d9fcc 100644 --- a/Lib/ClassLibraryCommon/Shared/Protocol/HttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/Shared/Protocol/HttpWebRequestFactory.cs @@ -63,11 +63,15 @@ internal static HttpWebRequest CreateWebRequest(string method, Uri uri, int? tim HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uriRequest); request.Method = method; - // Set the Content-Length of requests to 0 by default for all put requests. - if (method.Equals(WebRequestMethods.Http.Put, StringComparison.OrdinalIgnoreCase)) - { - request.ContentLength = 0; - } +#if !WINDOWS_PHONE + request.Proxy = operationContext.Proxy ?? request.Proxy; +#endif + + // Set the Content-Length of requests to 0 by default for all put requests. + if (method.Equals(WebRequestMethods.Http.Put, StringComparison.OrdinalIgnoreCase)) + { + request.ContentLength = 0; + } request.UserAgent = Constants.HeaderConstants.UserAgent; diff --git a/Lib/Common/OperationContext.cs b/Lib/Common/OperationContext.cs index f6bbe2858..d4f8dba72 100644 --- a/Lib/Common/OperationContext.cs +++ b/Lib/Common/OperationContext.cs @@ -20,6 +20,7 @@ namespace Microsoft.WindowsAzure.Storage using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Net; /// /// Represents the context for a request operation against the storage service, and provides additional runtime information about its execution. @@ -40,6 +41,13 @@ public OperationContext() this.LogLevel = OperationContext.DefaultLogLevel; } +#if !WINDOWS_PHONE && !NETCORE + /// + /// Gets or sets proxy information for the request. + /// + public IWebProxy Proxy { get; set; } +#endif + #region Headers /// @@ -247,7 +255,7 @@ public RequestResult LastResult } } } - #endregion + #endregion } } diff --git a/Test/ClassLibraryCommon/Blob/CloudBlobContainerTest.cs b/Test/ClassLibraryCommon/Blob/CloudBlobContainerTest.cs index 96d213866..65624b4ce 100644 --- a/Test/ClassLibraryCommon/Blob/CloudBlobContainerTest.cs +++ b/Test/ClassLibraryCommon/Blob/CloudBlobContainerTest.cs @@ -3387,6 +3387,185 @@ public void CloudBlobContainerGetBlobReferenceFromServerBlobNameAccessConditionR } } + class MockProxy: IWebProxy + { + private WebProxy webProxy; + + public MockProxy(Uri address, NetworkCredential credentials) + { + this.webProxy = new WebProxy { Address = address, Credentials = credentials }; + } + + public class MemberAccessEventArgs: EventArgs + { + public MemberAccessEventArgs(string memberName, object value) + { + this.MemberName = memberName; + this.Value = value; + } + + public string MemberName { get; private set; } + public object Value { get; private set; } + } + + public event EventHandler MemberAccess + { + add + { + memberAccess += value; + } + + remove + { + memberAccess -= value; + } + } + + EventHandler memberAccess; + + private void OnMemberAccess(string memberName, object value) + { + var h = this.memberAccess; + + if (h != null) + { + h(this, new MemberAccessEventArgs(memberName, value)); + } + } + + public ICredentials Credentials + { + get + { + var value = this.webProxy.Credentials; + this.OnMemberAccess("Credentials", value); + return value; + } + + set + { + this.webProxy.Credentials = value; + } + } + + public Uri GetProxy(Uri destination) + { + this.OnMemberAccess("GetProxy", destination); + return this.webProxy.GetProxy(destination); + } + + public bool IsBypassed(Uri host) + { + this.OnMemberAccess("IsBypassed", host); + return this.webProxy.IsBypassed(host); + } + } + + [TestMethod] + [Description("Verify that a proxy gets used")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudBlobContainerVerifyProxyHit() + { + CloudBlobContainer container = GetRandomContainerReference(); + + try + { + const string proxyAddress = "http://127.0.0.1"; + const string proxyUser = "user"; + const string proxyPassword = "password"; + + var cts = new CancellationTokenSource(); + + var proxyHit = false; + + var mockProxy = + new MockProxy( + new Uri(proxyAddress), + new NetworkCredential(proxyUser, proxyPassword) + ); + + mockProxy.MemberAccess += (s, e) => + { + cts.Cancel(); + proxyHit = true; + }; + + OperationContext operationContext = new OperationContext() + { + Proxy = mockProxy + }; + + try + { + await container.CreateAsync(BlobContainerPublicAccessType.Off, default(BlobRequestOptions), operationContext, cts.Token); + } + catch (TaskCanceledException) + { + // expected, but not required + } + + Assert.IsTrue(proxyHit, "Proxy not hit"); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Verify that a proxy doesn't interfere")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudBlobContainerCreateWithProxy() + { + CloudBlobContainer container = GetRandomContainerReference(); + + try + { + const string proxyAddress = "http://localhost:8877"; // HttpMangler's proxy address + const string proxyUser = "user"; + const string proxyPassword = "password"; + + var cts = new CancellationTokenSource(); + + var proxyHit = false; + + var mockProxy = + new MockProxy( + new Uri(proxyAddress), + new NetworkCredential(proxyUser, proxyPassword) + ); + + mockProxy.MemberAccess += (s, e) => + { + proxyHit = true; + }; + + OperationContext operationContext = new OperationContext() + { + Proxy = mockProxy + }; + + using (new Test.Network.HttpMangler()) + { + await container.CreateAsync(BlobContainerPublicAccessType.Off, default(BlobRequestOptions), operationContext, cts.Token); + } + + // if we get here without an exception, assume the call was + // successful and verify that the proxy was used + Assert.IsTrue(proxyHit, "Proxy not hit"); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + [TestMethod] [Description("Get a blob reference without knowing its type")] [TestCategory(ComponentCategory.Blob)] diff --git a/changelog.txt b/changelog.txt index b3117f7ec..9f24b2040 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,6 @@ +Changes in #####: +- All (Desktop): Added support for specifying Proxy on OperationContext, for per-request proxy scenarios. + Changes in 9.1.1: - Table: Updated Table related APIs to use the default Json Serializer settings. - Table: Fixed a bug introduced after OData dependencies were removed, where support for double.NaN, double.PositiveInfinity and double.NegativeInfinity were lost. From 6d8df6bec4256418f475121e34d490c5076ee4d9 Mon Sep 17 00:00:00 2001 From: erezvani Date: Wed, 2 May 2018 14:54:46 -0700 Subject: [PATCH 6/9] [9.2][nov17] release update assemblies --- ...rosoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs | 2 +- ...dowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs | 2 +- ....WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs | 4 ++-- .../Properties/AssemblyInfo.cs | 4 ++-- Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs | 6 +++--- .../WindowsAzure.StorageK.nuspec | 2 +- Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json | 2 +- Lib/Common/Shared/Protocol/Constants.cs | 2 +- Lib/WindowsDesktop/Properties/AssemblyInfo.cs | 4 ++-- Lib/WindowsDesktop/WindowsAzure.Storage.nuspec | 2 +- Lib/WindowsPhone/Properties/AssemblyInfo.cs | 4 ++-- Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs | 4 ++-- Lib/WindowsRuntime/Properties/AssemblyInfo.cs | 4 ++-- README.md | 4 ++-- .../project.json | 4 ++-- .../project.json | 4 ++-- .../AspNet/Microsoft.WindowsAzure.Storage.Test/project.json | 4 ++-- Test/WindowsDesktop/Properties/AssemblyInfo.cs | 4 ++-- Test/WindowsPhone/Properties/AssemblyInfo.cs | 4 ++-- Test/WindowsPhone81/Properties/AssemblyInfo.cs | 4 ++-- Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs | 4 ++-- Test/WindowsRuntime/Properties/AssemblyInfo.cs | 4 ++-- changelog.txt | 2 +- 23 files changed, 40 insertions(+), 40 deletions(-) diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs index feb295257..8c4675fde 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs @@ -256,7 +256,7 @@ public static class EncryptionConstants public const string TableEncryptionKeyDetails = "_ClientEncryptionMetadata1"; public const string TableEncryptionPropertyDetails = "_ClientEncryptionMetadata2"; public const string AgentMetadataKey = "EncryptionLibrary"; - public const string AgentMetadataValue = ".NET 9.1.1"; + public const string AgentMetadataValue = ".NET 9.2.2"; } } diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs index 6fba06676..9818b7a6b 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs @@ -10,7 +10,7 @@ public static class EncryptionConstants public const string TableEncryptionKeyDetails = "_ClientEncryptionMetadata1"; public const string TableEncryptionPropertyDetails = "_ClientEncryptionMetadata2"; public const string AgentMetadataKey = "EncryptionLibrary"; - public const string AgentMetadataValue = ".NET 9.1.1"; + public const string AgentMetadataValue = ".NET 9.2.2"; } } \ No newline at end of file diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs index e10c02c38..d5bbc3cd8 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs @@ -4,9 +4,9 @@ namespace Microsoft.WindowsAzure.Storage.Shared.Protocol public static class HeaderConstants { - public static readonly string UserAgent = "Azure-Storage/9.1.1 "; + public static readonly string UserAgent = "Azure-Storage/9.2.2 "; public const string UserAgentProductName = "Azure-Storage"; - public const string UserAgentProductVersion = "9.1.1"; + public const string UserAgentProductVersion = "9.2.2"; public const string PrefixForStorageHeader = "x-ms-"; public const string TrueHeader = "true"; public const string FalseHeader = "false"; diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs index 1aa48f6a7..db140858a 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs @@ -31,8 +31,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("9.1.1.0")] -[assembly: AssemblyFileVersion("9.1.1.0")] +[assembly: AssemblyVersion("9.2.2.0")] +[assembly: AssemblyFileVersion("9.2.2.0")] [assembly: InternalsVisibleTo( "Microsoft.WindowsAzure.Storage.Facade.Portable, PublicKey=" + diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs index 26f15abba..50dd0ee12 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs @@ -34,9 +34,9 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("9.1.1.0")] -[assembly: AssemblyFileVersion("9.1.1.0")] -[assembly: AssemblyInformationalVersion("9.1.1.0")] +[assembly: AssemblyVersion("9.2.0.0")] +[assembly: AssemblyFileVersion("9.2.0.0")] +[assembly: AssemblyInformationalVersion("9.2.0.0")] [assembly: InternalsVisibleTo( diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec b/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec index f02fc1c4d..e82aad217 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 - 9.1.1 + 9.2.0 Windows Azure Storage Microsoft Microsoft diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json index 4c40bebc8..3f6cbd056 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json @@ -1,5 +1,5 @@ { - "version": "9.1.1.0", + "version": "9.2.0.0", "authors": [ "Microsoft Corporation" ], "description": "Azure Storage SDK for NetCore", diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index 85d06bab6..3f133050a 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -923,7 +923,7 @@ static HeaderConstants() /// /// Specifies the value to use for UserAgent header. /// - public const string UserAgentProductVersion = "9.1.1"; + public const string UserAgentProductVersion = "9.2.0"; /// /// Master Microsoft Azure Storage header prefix. diff --git a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs index ed0a4ee85..568f13d14 100644 --- a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs @@ -35,8 +35,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("9.1.1.0")] -[assembly: AssemblyFileVersion("9.1.1.0")] +[assembly: AssemblyVersion("9.2.0.0")] +[assembly: AssemblyFileVersion("9.2.0.0")] #if SIGN [assembly: InternalsVisibleTo( diff --git a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec index f02fc1c4d..e82aad217 100644 --- a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec +++ b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage - 9.1.1 + 9.2.0 Windows Azure Storage Microsoft Microsoft diff --git a/Lib/WindowsPhone/Properties/AssemblyInfo.cs b/Lib/WindowsPhone/Properties/AssemblyInfo.cs index b487c49fd..ea36c8a43 100644 --- a/Lib/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Lib/WindowsPhone/Properties/AssemblyInfo.cs @@ -33,8 +33,8 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("9.1.1.0")] -[assembly: AssemblyFileVersion("9.1.1.0")] +[assembly: AssemblyVersion("9.2.0.0")] +[assembly: AssemblyFileVersion("9.2.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs b/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs index aa2a545a9..07eadf7f9 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("9.1.1.0")] -[assembly: AssemblyFileVersion("9.1.1.0")] +[assembly: AssemblyVersion("9.2.0.0")] +[assembly: AssemblyFileVersion("9.2.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] [assembly: ComVisible(false)] diff --git a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs index ee5bd7f6e..421f8ec39 100644 --- a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs @@ -26,8 +26,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("9.1.1.0")] -[assembly: AssemblyFileVersion("9.1.1.0")] +[assembly: AssemblyVersion("9.2.0.0")] +[assembly: AssemblyFileVersion("9.2.0.0")] [assembly: ComVisible(false)] diff --git a/README.md b/README.md index a69bc90d3..0a2526027 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Microsoft Azure Storage SDK for .NET (9.1.1) +# Microsoft Azure Storage SDK for .NET (9.2.0) The Microsoft Azure Storage SDK for .NET allows you to build Azure applications that take advantage of scalable cloud computing resources. @@ -56,7 +56,7 @@ Through the bait and switch technique, the reference assembly enables other port ## Use with the Azure Storage Emulator - The Client Library uses a particular Storage Service version. In order to use the Storage Client Library with the Storage Emulator, a corresponding minimum version of the Azure Storage Emulator must be used. Older versions of the Storage Emulator do not have the necessary code to successfully respond to new requests. -- Currently, the minimum version of the Azure Storage Emulator needed for this library is 5.3. If you encounter a `VersionNotSupportedByEmulator` (400 Bad Request) error, please [update the Storage Emulator.](https://azure.microsoft.com/en-us/downloads/) +- Currently, the minimum version of the Azure Storage Emulator needed for this library is 5.5. If you encounter a `VersionNotSupportedByEmulator` (400 Bad Request) error, please [update the Storage Emulator.](https://azure.microsoft.com/en-us/downloads/) ## Download & Install diff --git a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json index 80a1641d0..08743e6fa 100644 --- a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json +++ b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json @@ -1,13 +1,13 @@ { "title": "Microsoft.WindowsAzure.Storage.Facade.NetCore.Test", - "version": "9.1.1.0", + "version": "9.2.2.0", "testRunner": "xunit", "dependencies": { "xunit": "2.1.0", "dotnet-test-xunit": "2.2.0-preview2-build1029", "XUnitForMsTest": "1.0.0.0", - "WindowsAzure.Storage": "9.1.1" + "WindowsAzure.Storage": "9.2.2" }, "frameworks": { diff --git a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json index f85d688b2..7dc2d3eb2 100644 --- a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json +++ b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json @@ -1,8 +1,8 @@ { - "version": "9.1.1.0", + "version": "9.2.2.0", "supports": {}, "dependencies": { - "WindowsAzure.Storage": "9.1.1" + "WindowsAzure.Storage": "9.2.2" }, "frameworks": { ".NETPortable,Version=v4.5,Profile=Profile259": {} diff --git a/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json b/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json index 9bd6249b2..ab0e59eed 100644 --- a/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json +++ b/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json @@ -1,11 +1,11 @@ { - "version": "9.1.1.0", + "version": "9.2.0.0", "testRunner": "xunit", "dependencies": { "xunit": "2.1.0", "dotnet-test-xunit": "2.2.0-preview2-build1029", - "Microsoft.WindowsAzure.Storage": "9.1.1.0", + "Microsoft.WindowsAzure.Storage": "9.2.0.0", "XUnitForMsTest": "1.0.0-*" }, diff --git a/Test/WindowsDesktop/Properties/AssemblyInfo.cs b/Test/WindowsDesktop/Properties/AssemblyInfo.cs index 7f8040777..73bf8f23b 100644 --- a/Test/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Test/WindowsDesktop/Properties/AssemblyInfo.cs @@ -33,6 +33,6 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("9.1.1.0")] -[assembly: AssemblyFileVersion("9.1.1.0")] +[assembly: AssemblyVersion("9.2.0.0")] +[assembly: AssemblyFileVersion("9.2.0.0")] diff --git a/Test/WindowsPhone/Properties/AssemblyInfo.cs b/Test/WindowsPhone/Properties/AssemblyInfo.cs index 784409fff..c28fd83e7 100644 --- a/Test/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone/Properties/AssemblyInfo.cs @@ -33,7 +33,7 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("9.1.1.0")] -[assembly: AssemblyFileVersion("9.1.1.0")] +[assembly: AssemblyVersion("9.2.0.0")] +[assembly: AssemblyFileVersion("9.2.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsPhone81/Properties/AssemblyInfo.cs b/Test/WindowsPhone81/Properties/AssemblyInfo.cs index f888c4010..bd5a54c63 100644 --- a/Test/WindowsPhone81/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone81/Properties/AssemblyInfo.cs @@ -34,7 +34,7 @@ // by using the '*' as shown below: -[assembly: AssemblyVersion("9.1.1.0")] -[assembly: AssemblyFileVersion("9.1.1.0")] +[assembly: AssemblyVersion("9.2.0.0")] +[assembly: AssemblyFileVersion("9.2.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs b/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs index 4943d5337..c89387872 100644 --- a/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs @@ -25,7 +25,7 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("9.1.1.0")] -[assembly: AssemblyFileVersion("9.1.1.0")] +[assembly: AssemblyVersion("9.2.0.0")] +[assembly: AssemblyFileVersion("9.2.0.0")] [assembly: ComVisible(false)] \ No newline at end of file diff --git a/Test/WindowsRuntime/Properties/AssemblyInfo.cs b/Test/WindowsRuntime/Properties/AssemblyInfo.cs index 7a02ca056..64605afd3 100644 --- a/Test/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Test/WindowsRuntime/Properties/AssemblyInfo.cs @@ -25,7 +25,7 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("9.1.1.0")] -[assembly: AssemblyFileVersion("9.1.1.0")] +[assembly: AssemblyVersion("9.2.0.0")] +[assembly: AssemblyFileVersion("9.2.0.0")] [assembly: ComVisible(false)] diff --git a/changelog.txt b/changelog.txt index acc2e22e0..84dcf447a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,5 @@ Change in 9.2: -- All: Support for 2017-11-09 REST version. Please see our REST API documentation and blogs for information about the related added features. +- All: Support for 2017-11-09 REST version. Please see our REST API documentation and blogs for information about the related added features. If you are using the Storage Emulator, please update to Emulator version 5.5. - Blobs: Added support for blob creation time property. - Blobs: Added support for blob container immutability and legal hold policies. - Blob/Queue: Added OAuth token support for authentication. From d4ba7d4a094b036da20ffbeccc6f8f79efbb08e8 Mon Sep 17 00:00:00 2001 From: erezvani Date: Wed, 2 May 2018 15:03:05 -0700 Subject: [PATCH 7/9] [9.2][nov17]make OpContext.IWebProxy desktop only --- Lib/Common/OperationContext.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/Common/OperationContext.cs b/Lib/Common/OperationContext.cs index d4f8dba72..2bfb163d7 100644 --- a/Lib/Common/OperationContext.cs +++ b/Lib/Common/OperationContext.cs @@ -20,7 +20,9 @@ namespace Microsoft.WindowsAzure.Storage using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +#if !WINDOWS_PHONE && WINDOWS_DESKTOP using System.Net; +#endif /// /// Represents the context for a request operation against the storage service, and provides additional runtime information about its execution. @@ -41,7 +43,7 @@ public OperationContext() this.LogLevel = OperationContext.DefaultLogLevel; } -#if !WINDOWS_PHONE && !NETCORE +#if !WINDOWS_PHONE && WINDOWS_DESKTOP /// /// Gets or sets proxy information for the request. /// From 1c531e84deb899364f686782ee3c3163bea69bb3 Mon Sep 17 00:00:00 2001 From: erezvani Date: Wed, 9 May 2018 20:31:29 -0700 Subject: [PATCH 8/9] [9.2] Facade version update + test reference fix --- ...icrosoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs | 2 +- ...indowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs | 2 +- ...ft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs | 4 ++-- .../Properties/AssemblyInfo.cs | 4 ++-- Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json | 2 +- .../project.json | 4 ++-- .../project.json | 4 ++-- Test/WindowsDesktop/packages.config | 1 + 8 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs index 8c4675fde..8c6169a79 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs @@ -256,7 +256,7 @@ public static class EncryptionConstants public const string TableEncryptionKeyDetails = "_ClientEncryptionMetadata1"; public const string TableEncryptionPropertyDetails = "_ClientEncryptionMetadata2"; public const string AgentMetadataKey = "EncryptionLibrary"; - public const string AgentMetadataValue = ".NET 9.2.2"; + public const string AgentMetadataValue = ".NET 9.2.0"; } } diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs index 9818b7a6b..6bc62b4f9 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs @@ -10,7 +10,7 @@ public static class EncryptionConstants public const string TableEncryptionKeyDetails = "_ClientEncryptionMetadata1"; public const string TableEncryptionPropertyDetails = "_ClientEncryptionMetadata2"; public const string AgentMetadataKey = "EncryptionLibrary"; - public const string AgentMetadataValue = ".NET 9.2.2"; + public const string AgentMetadataValue = ".NET 9.2.0"; } } \ No newline at end of file diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs index d5bbc3cd8..c11e45cdc 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs @@ -4,9 +4,9 @@ namespace Microsoft.WindowsAzure.Storage.Shared.Protocol public static class HeaderConstants { - public static readonly string UserAgent = "Azure-Storage/9.2.2 "; + public static readonly string UserAgent = "Azure-Storage/9.2.0 "; public const string UserAgentProductName = "Azure-Storage"; - public const string UserAgentProductVersion = "9.2.2"; + public const string UserAgentProductVersion = "9.2.0"; public const string PrefixForStorageHeader = "x-ms-"; public const string TrueHeader = "true"; public const string FalseHeader = "false"; diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs index db140858a..87a56bc07 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs @@ -31,8 +31,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("9.2.2.0")] -[assembly: AssemblyFileVersion("9.2.2.0")] +[assembly: AssemblyVersion("9.2.0.0")] +[assembly: AssemblyFileVersion("9.2.0.0")] [assembly: InternalsVisibleTo( "Microsoft.WindowsAzure.Storage.Facade.Portable, PublicKey=" + diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json index 6290900bd..3a8eb4de9 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json @@ -1,6 +1,6 @@ { "title": "Microsoft.WindowsAzure.Storage", - "version": "9.1.1.0", + "version": "9.2.0.0", "authors": [ "Microsoft Corporation" ], "description": "Azure Storage SDK for NetCore", diff --git a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json index 08743e6fa..0dd76c458 100644 --- a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json +++ b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json @@ -1,13 +1,13 @@ { "title": "Microsoft.WindowsAzure.Storage.Facade.NetCore.Test", - "version": "9.2.2.0", + "version": "9.2.0.0", "testRunner": "xunit", "dependencies": { "xunit": "2.1.0", "dotnet-test-xunit": "2.2.0-preview2-build1029", "XUnitForMsTest": "1.0.0.0", - "WindowsAzure.Storage": "9.2.2" + "WindowsAzure.Storage": "9.2.0" }, "frameworks": { diff --git a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json index 7dc2d3eb2..75c0952d7 100644 --- a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json +++ b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json @@ -1,8 +1,8 @@ { - "version": "9.2.2.0", + "version": "9.2.0.0", "supports": {}, "dependencies": { - "WindowsAzure.Storage": "9.2.2" + "WindowsAzure.Storage": "9.2.0" }, "frameworks": { ".NETPortable,Version=v4.5,Profile=Profile259": {} diff --git a/Test/WindowsDesktop/packages.config b/Test/WindowsDesktop/packages.config index 22a9d17e5..3b04d7a19 100644 --- a/Test/WindowsDesktop/packages.config +++ b/Test/WindowsDesktop/packages.config @@ -11,4 +11,5 @@ + \ No newline at end of file From 8f426eb56a6f809dc841a2574c26ca4f462ae34f Mon Sep 17 00:00:00 2001 From: erezvani Date: Tue, 22 May 2018 13:25:21 -0700 Subject: [PATCH 9/9] [9.2]update changelog --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 84dcf447a..97d9ae4db 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,7 +2,7 @@ Change in 9.2: - All: Support for 2017-11-09 REST version. Please see our REST API documentation and blogs for information about the related added features. If you are using the Storage Emulator, please update to Emulator version 5.5. - Blobs: Added support for blob creation time property. - Blobs: Added support for blob container immutability and legal hold policies. -- Blob/Queue: Added OAuth token support for authentication. +- Blob/Queue: Added OAuth token support for authentication(Please note that this feature is available in preview). - All (Desktop): Added support for specifying Proxy on OperationContext, for per-request proxy scenarios. Changes in 9.1.1: