diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config new file mode 100644 index 0000000..6a318ad --- /dev/null +++ b/.nuget/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.nuget/NuGet.targets b/.nuget/NuGet.targets new file mode 100644 index 0000000..e470f19 --- /dev/null +++ b/.nuget/NuGet.targets @@ -0,0 +1,71 @@ + + + + $(MSBuildProjectDirectory)\..\ + + + $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) + $([System.IO.Path]::Combine($(ProjectDir), "packages.config")) + $([System.IO.Path]::Combine($(SolutionDir), "packages")) + + + $(SolutionDir).nuget + packages.config + $(SolutionDir)packages + + + $(NuGetToolsPath)\nuget.exe + "$(NuGetExePath)" + mono --runtime=v4.0.30319 $(NuGetExePath) + + $(TargetDir.Trim('\\')) + + + "" + + + false + + + false + + + $(NuGetCommand) install "$(PackagesConfig)" -source $(PackageSources) -o "$(PackagesDir)" + $(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols + + + + RestorePackages; + $(BuildDependsOn); + + + + + $(BuildDependsOn); + BuildPackage; + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CloudServices.Identity.Membership/CloudServices.Identity.Membership.csproj b/CloudServices.Identity.Membership/CloudServices.Identity.Membership.csproj index 23f4b7f..18288b3 100644 --- a/CloudServices.Identity.Membership/CloudServices.Identity.Membership.csproj +++ b/CloudServices.Identity.Membership/CloudServices.Identity.Membership.csproj @@ -12,6 +12,8 @@ CloudServices.Identity.Membership v4.0 512 + ..\..\Source\ + true true @@ -35,45 +37,25 @@ ..\packages\aspnet.suppressformsredirect.0.0.1.4\lib\40\AspNet.SuppressFormsRedirect.dll - - ..\packages\WebApi.OData.0.6.0\lib\40-Full\EntityDataModel.dll - - - ..\packages\WebApi.0.6.0\lib\40-Full\Microsoft.ApplicationServer.Http.dll - - - ..\packages\WebApi.Enhancements.0.6.0\lib\40-Full\Microsoft.ApplicationServer.HttpEnhancements.dll - - - ..\packages\WebApi.OData.0.6.0\lib\40-Full\Microsoft.Data.Spatial.dll - - - ..\packages\JsonValue.0.6.0\lib\40\Microsoft.Json.dll - - - ..\packages\HttpClient.0.6.0\lib\40\Microsoft.Net.Http.Formatting.dll - - - ..\packages\WebApi.OData.0.6.0\lib\40-Full\Microsoft.Net.Http.Formatting.OData.dll - - - ..\packages\WebApi.0.6.0\lib\40-Full\Microsoft.Runtime.Serialization.Internal.dll - - - ..\packages\WebApi.0.6.0\lib\40-Full\Microsoft.Server.Common.dll - - - ..\packages\WebApi.0.6.0\lib\40-Full\Microsoft.ServiceModel.Internal.dll - - - ..\packages\WebApi.OData.0.6.0\lib\40-Full\System.Data.OData.dll + + True + ..\packages\System.Json.4.0.20126.16343\lib\net40\System.Json.dll - - ..\packages\HttpClient.0.6.0\lib\40\System.Net.Http.dll + + True + ..\packages\System.Net.Http.2.0.20126.16343\lib\net40\System.Net.Http.dll + + + True + ..\packages\System.Net.Http.Formatting.4.0.20126.16343\lib\net40\System.Net.Http.Formatting.dll + + + True + ..\packages\System.Net.Http.2.0.20126.16343\lib\net40\System.Net.Http.WebRequest.dll @@ -81,42 +63,47 @@ + + True + ..\packages\AspNetWebApi.Core.4.0.20126.16343\lib\net40\System.Web.Http.dll + + + True + ..\packages\System.Web.Http.Common.4.0.20126.16343\lib\net40\System.Web.Http.Common.dll + + + True + ..\packages\AspNetWebApi.4.0.20126.16343\lib\net40\System.Web.Http.WebHost.dll + - - ..\packages\WebApi.Membership.0.0.3.0\lib\40\WebApi.Membership.dll - + - - - True - True - Resources.resx - + + + + + + - - - ResXFileCodeGenerator - Resources.Designer.cs - - - $(ProjectDir)..\NuGet\NuGet.exe install $(ProjectDir)packages.config -OutputDirectory $(ProjectDir)..\packages\ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Invalid credentials. - - - Invalid user information. - - \ No newline at end of file diff --git a/CloudServices.Identity.Membership/RouteExtensions.cs b/CloudServices.Identity.Membership/RouteExtensions.cs index 31478cf..709ae42 100644 --- a/CloudServices.Identity.Membership/RouteExtensions.cs +++ b/CloudServices.Identity.Membership/RouteExtensions.cs @@ -16,16 +16,24 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Identity.Membership { - using System; + using System.Net.Http; + using System.Web.Http; using System.Web.Routing; - using Microsoft.ApplicationServer.Http; public static class RouteExtensions { - public static void MapAuthenticationServiceRoute(this RouteCollection routes, string prefix, params Type[] handlers) + public static void MapAuthenticationServiceRoute(this RouteCollection routes, string prefix, params DelegatingHandler[] handlers) { - var configuration = new HttpConfiguration().AddDelegatingHandlers(handlers).AddRequestHandlers(); - routes.MapServiceRoute(prefix, configuration); + var currentConfiguration = GlobalConfiguration.Configuration; + + // Handlers + currentConfiguration.AddDelegatingHandlers(handlers); + + // Routes + routes.MapHttpRoute( + name: prefix, + routeTemplate: prefix + "/{action}", + defaults: new { Controller = "Membership" }); } } -} +} \ No newline at end of file diff --git a/CloudServices.Notifications/Security/FuncBasedOperationHandler.cs b/CloudServices.Identity.Membership/WebApi.Membership/System/AuthenticateAttribute.cs similarity index 61% rename from CloudServices.Notifications/Security/FuncBasedOperationHandler.cs rename to CloudServices.Identity.Membership/WebApi.Membership/System/AuthenticateAttribute.cs index 53ec560..565c6ab 100644 --- a/CloudServices.Notifications/Security/FuncBasedOperationHandler.cs +++ b/CloudServices.Identity.Membership/WebApi.Membership/System/AuthenticateAttribute.cs @@ -14,27 +14,39 @@ // places, or events is intended or should be inferred. // ---------------------------------------------------------------------------------- -namespace Microsoft.WindowsAzure.Samples.CloudServices.Notifications +namespace System.Net.Http { - using System.Net.Http; + using System; using System.Web; - using ApplicationServer.Http.Dispatcher; + using System.Web.Http; + using System.Web.Http.Controllers; + using System.Web.Http.Filters; + using System.Web.Security; - internal class FuncBasedOperationHandler : HttpOperationHandler + public class AuthenticateAttribute : AuthorizationFilterAttribute { - private readonly FuncBasedFilterAttribute filterAttribute; - - public FuncBasedOperationHandler(FuncBasedFilterAttribute filterAttribute) - : base("response") + public override void OnAuthorization(HttpActionContext actionContext) { - this.filterAttribute = filterAttribute; + var input = actionContext.Request; + var principal = PrincipalHelper.GetPrincipalFromHttpRequest(input); + if (principal == null) + { + throw UnauthorizedException(); + } + + var user = Membership.GetUser(principal.Identity.Name); + if (user == null) + { + throw UnauthorizedException(); + } + + PrincipalHelper.SetPrincipal(input, principal); + + base.OnAuthorization(actionContext); } - protected override HttpRequestMessage OnHandle(HttpRequestMessage input) + private static Exception UnauthorizedException() { - if (this.filterAttribute.Filter(input)) - return input; - // HACK: Prevent ASP.NET Forms Authentication to redirect the user to the login page. // This thread-safe approach adds a header with the suppression to be read on the // OnEndRequest event of the pipelien. In order to fully support the supression you should have the ASP.NET Module @@ -42,7 +54,7 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Notifications var response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); response.Headers.Add(SuppressFormsAuthenticationRedirectModule.SuppressFormsHeaderName, "true"); - throw new HttpResponseException(response); + return new HttpResponseException(response); } } -} +} \ No newline at end of file diff --git a/CloudServices.Identity.Membership/WebApi.Membership/System/ControllerFilteredMessageProcessingHandler.cs b/CloudServices.Identity.Membership/WebApi.Membership/System/ControllerFilteredMessageProcessingHandler.cs new file mode 100644 index 0000000..31ab503 --- /dev/null +++ b/CloudServices.Identity.Membership/WebApi.Membership/System/ControllerFilteredMessageProcessingHandler.cs @@ -0,0 +1,52 @@ +// ---------------------------------------------------------------------------------- +// Microsoft Developer & Platform Evangelism +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +// ---------------------------------------------------------------------------------- + +namespace System.Net.Http +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Web.Http; + + public abstract class ControllerFilteredMessageProcessingHandler : MessageProcessingHandler + { + public IList ConfiguredControllers { get; set; } + + protected abstract HttpRequestMessage ProcessRequestHandler(HttpRequestMessage request, CancellationToken cancellationToken); + + protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken) + { + var routeData = request.GetRouteData(); + var controllerName = routeData.Values.ContainsKey("controller") ? + routeData.Values["controller"].ToString() : + string.Empty; + + if (this.ConfiguredControllers == null || + this.ConfiguredControllers.Any(c => c.Equals(controllerName, StringComparison.OrdinalIgnoreCase))) + { + return this.ProcessRequestHandler(request, cancellationToken); + } + + return request; + } + + protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken) + { + return response; + } + } +} \ No newline at end of file diff --git a/CloudServices.Identity.Membership/WebApi.Membership/System/HttpRequestMessageExtensions.cs b/CloudServices.Identity.Membership/WebApi.Membership/System/HttpRequestMessageExtensions.cs new file mode 100644 index 0000000..81f3897 --- /dev/null +++ b/CloudServices.Identity.Membership/WebApi.Membership/System/HttpRequestMessageExtensions.cs @@ -0,0 +1,65 @@ +// ---------------------------------------------------------------------------------- +// Microsoft Developer & Platform Evangelism +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +// ---------------------------------------------------------------------------------- + +// +// Copyright binary (c) 2011 by Johnny Halife, Juan Pablo Garcia, Mauro Krikorian, Mariano Converti, +// Damian Martinez, Nico Bello, and Ezequiel Morito +// +// Redistribution and use in source and binary forms, with or without modification, are permitted. +// +// The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace System.Net.Http +{ + using System.Security.Principal; + using System.Threading; + using System.Web; + + /// + /// HttpRequestMessage extension methods. + /// + public static class HttpRequestMessageExtensions + { + /// + /// Returns the IPrincipal instance authenticated and stored by the MembershipAuthenticationHandler + /// on the ongoing request. + /// + /// this (request) + /// IPrincipal instance authenticated and stored by the MembershipAuthenticationHandler + public static IPrincipal User(this HttpRequestMessage message) + { + if (message != null) + { + return message.Properties[MembershipAuthenticationHandler.MessagePrincipalKey] as IPrincipal; + } + + if (Thread.CurrentPrincipal != null) + { + return Thread.CurrentPrincipal; + } + + if (HttpContext.Current != null) + { + return HttpContext.Current.User; + } + + return null; + } + } +} \ No newline at end of file diff --git a/CloudServices.Identity.Membership/WebApi.Membership/System/MembershipAuthenticationHandler.cs b/CloudServices.Identity.Membership/WebApi.Membership/System/MembershipAuthenticationHandler.cs new file mode 100644 index 0000000..d716c15 --- /dev/null +++ b/CloudServices.Identity.Membership/WebApi.Membership/System/MembershipAuthenticationHandler.cs @@ -0,0 +1,93 @@ +// ---------------------------------------------------------------------------------- +// Microsoft Developer & Platform Evangelism +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +// ---------------------------------------------------------------------------------- + +// +// Copyright binary (c) 2011 by Johnny Halife, Juan Pablo Garcia, Mauro Krikorian, Mariano Converti, +// Damian Martinez, Nico Bello, and Ezequiel Morito +// +// Redistribution and use in source and binary forms, with or without modification, are permitted. +// +// The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace System.Net.Http +{ + using System.Threading; + using System.Web; + using System.Web.Http; + using System.Web.Security; + + /// + /// Authenticates the ongoing request using Membership and Forms Authentication cookie. + /// Grabs the forms authentication ticket token from the cookie header and performs the authentication + /// due to WebApi requirement for ASP.NET we're also suppressing Forms Redirect by leveraging + /// aspnet.suppressformsredirect Package. + /// + public class MembershipAuthenticationHandler : ControllerFilteredMessageProcessingHandler + { + /// + /// Key used to stuff the Request with the authenticated principal after a + /// successful login. + /// + public const string MessagePrincipalKey = "identity.currentprincipal"; + + /// + /// Performs Membership Authentication of the on going request. Turns Membership authentication into mandatory. + /// + /// Ongoing request + /// Async Cancellation token + /// The HTTP request message that was processed + protected override HttpRequestMessage ProcessRequestHandler(HttpRequestMessage request, CancellationToken cancellationToken) + { + try + { + var principal = PrincipalHelper.GetPrincipalFromHttpRequest(request); + if (principal == null) + { + this.Unauthorized(); + } + + var user = Membership.GetUser(principal.Identity.Name); + if (user == null) + { + this.Unauthorized(); + } + + PrincipalHelper.SetPrincipal(request, principal); + } + catch + { + this.Unauthorized(); + } + + return request; + } + + private void Unauthorized() + { + // HACK: Prevent ASP.NET Forms Authentication to redirect the user to the login page. + // This thread-safe approach adds a header with the suppression to be read on the + // OnEndRequest event of the pipelien. In order to fully support the supression you should have the ASP.NET Module + // that does this (SuppressFormsAuthenticationRedirectModule). + var response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); + response.Headers.Add(SuppressFormsAuthenticationRedirectModule.SuppressFormsHeaderName, "true"); + + throw new HttpResponseException(response); + } + } +} \ No newline at end of file diff --git a/CloudServices.Identity.Membership/WebApi.Membership/System/PrincipalHelper.cs b/CloudServices.Identity.Membership/WebApi.Membership/System/PrincipalHelper.cs new file mode 100644 index 0000000..cab0e6b --- /dev/null +++ b/CloudServices.Identity.Membership/WebApi.Membership/System/PrincipalHelper.cs @@ -0,0 +1,67 @@ +// ---------------------------------------------------------------------------------- +// Microsoft Developer & Platform Evangelism +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +// ---------------------------------------------------------------------------------- + +namespace System.Net.Http +{ + using System; + using System.Security.Principal; + using System.Web.Security; + + public static class PrincipalHelper + { + /// + /// Checks, extracs, and parses the SimpleWebToken from the Authentication Header. + /// + /// Ongoing request. + /// A parsed SimpleWebToken. + internal static FormsAuthenticationTicket ExtractTicketFromHeader(HttpRequestMessage request) + { + var authorizationHeader = request.Headers.Authorization; + + if (authorizationHeader != null && authorizationHeader.Scheme.Equals("Membership", StringComparison.OrdinalIgnoreCase)) + { + var ticket = FormsAuthentication.Decrypt(authorizationHeader.Parameter); + + if (ticket != null && !ticket.Expired) + { + return ticket; + } + } + + return null; + } + + internal static IPrincipal GetPrincipalFromHttpRequest(HttpRequestMessage request) + { + var ticket = PrincipalHelper.ExtractTicketFromHeader(request); + if (ticket == null) + { + return null; + } + + return new GenericPrincipal(new FormsIdentity(ticket), new string[0]); + } + + internal static void SetPrincipal(HttpRequestMessage request, IPrincipal principal) + { + // We authenticate the ongoing thread but also we stuff the principal on the message + // after a brief chat with Glenn Block we understood that the only way to authenticate + // a request and continue using it is to stuff it's authenticated user throught on + // the message properties. + request.Properties[MembershipAuthenticationHandler.MessagePrincipalKey] = principal; + } + } +} diff --git a/CloudServices.Identity.Membership/packages.config b/CloudServices.Identity.Membership/packages.config index 7aeda7b..321bc44 100644 --- a/CloudServices.Identity.Membership/packages.config +++ b/CloudServices.Identity.Membership/packages.config @@ -1,11 +1,11 @@  - - - - - - - + + + + + + + \ No newline at end of file diff --git a/CloudServices.Notifications.Client.WindowsAzure/CloudServices.Notifications.WindowsAzure.csproj b/CloudServices.Notifications.Client.WindowsAzure/CloudServices.Notifications.WindowsAzure.csproj index 2d2015d..65b8d1b 100644 --- a/CloudServices.Notifications.Client.WindowsAzure/CloudServices.Notifications.WindowsAzure.csproj +++ b/CloudServices.Notifications.Client.WindowsAzure/CloudServices.Notifications.WindowsAzure.csproj @@ -12,6 +12,8 @@ CloudServices.Notifications.WindowsAzure v4.0 512 + ..\..\Source\ + true true @@ -66,6 +68,7 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Parameter applicationId cannot be null. - - - Parameter deviceId cannot be null. - - - Parameter endpoint cannot be null. - - - The filterExpression cannot be null. - - - Parameter configure cannot be null - - \ No newline at end of file diff --git a/CloudServices.Notifications/RouteExtensions.cs b/CloudServices.Notifications/RouteExtensions.cs index 61ea5ce..2eb6bd8 100644 --- a/CloudServices.Notifications/RouteExtensions.cs +++ b/CloudServices.Notifications/RouteExtensions.cs @@ -16,10 +16,11 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Notifications { - using System; + using System.Collections.Generic; using System.Linq; + using System.Net.Http; + using System.Web.Http; using System.Web.Routing; - using ApplicationServer.Http; public static class RouteExtensions { @@ -30,11 +31,35 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Notifications routes.MapRegistrationServiceRoute(prefix, handlers); } - public static void MapRegistrationServiceRoute(this RouteCollection routes, string prefix, params Type[] handlers) + public static void MapRegistrationServiceRoute(this RouteCollection routes, string prefix, params DelegatingHandler[] handlers) { - var configuration = new HttpConfiguration().AddDelegatingHandlers(handlers).AddRequestHandlers(); + var currentConfiguration = GlobalConfiguration.Configuration; - routes.MapServiceRoute(prefix, configuration); + // Handlers + currentConfiguration.AddDelegatingHandlers(handlers); + + // Register Dependencies + // NOTE: For any types that your dependency resolver does not handle, + // GetService should return null and GetServices should return an empty collection object + currentConfiguration.ServiceResolver.SetResolver( + t => + { + if (t == typeof(EndpointController)) + { + return new EndpointController( + NotificationServiceContext.Current.Configuration.StorageProvider, + NotificationServiceContext.Current.Configuration.MapUsername); + } + + return null; + }, + t => new List()); + + // Routes + routes.MapHttpRoute( + name: prefix, + routeTemplate: prefix + "/{applicationId}/{clientId}/{tileId}", + defaults: new { Controller = "Endpoint", applicationId = RouteParameter.Optional, tileId = RouteParameter.Optional, clientId = RouteParameter.Optional }); } } -} +} \ No newline at end of file diff --git a/CloudServices.Notifications/Security/AuthenticateEndpointAttribute.cs b/CloudServices.Notifications/Security/AuthenticateEndpointAttribute.cs index 7a1e448..dd5a92c 100644 --- a/CloudServices.Notifications/Security/AuthenticateEndpointAttribute.cs +++ b/CloudServices.Notifications/Security/AuthenticateEndpointAttribute.cs @@ -17,14 +17,13 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Notifications { using System; - using System.Net.Http; + using System.Web.Http.Controllers; - [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class AuthenticateEndpointAttribute : FuncBasedFilterAttribute + internal sealed class AuthenticateEndpointAttribute : FuncBasedAuthorizationFilterAttribute { - public override Func Filter + public override Func Filter { get { return NotificationServiceContext.Current.Configuration.AuthenticateRequest; } } } -} +} \ No newline at end of file diff --git a/CloudServices.Notifications/Security/AuthorizeManagementEndpointAttribute.cs b/CloudServices.Notifications/Security/AuthorizeManagementEndpointAttribute.cs index 68de1ea..887245f 100644 --- a/CloudServices.Notifications/Security/AuthorizeManagementEndpointAttribute.cs +++ b/CloudServices.Notifications/Security/AuthorizeManagementEndpointAttribute.cs @@ -17,14 +17,13 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Notifications { using System; - using System.Net.Http; + using System.Web.Http.Controllers; - [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class AuthorizeManagementEndpointAttribute : FuncBasedFilterAttribute + internal sealed class AuthorizeManagementEndpointAttribute : FuncBasedAuthorizationFilterAttribute { - public override Func Filter + public override Func Filter { get { return NotificationServiceContext.Current.Configuration.AuthorizeManagementRequest; } } } -} +} \ No newline at end of file diff --git a/CloudServices.Notifications/Security/AuthorizeRegistrationEndpointAttribute.cs b/CloudServices.Notifications/Security/AuthorizeRegistrationEndpointAttribute.cs index 0b5c7dc..682f540 100644 --- a/CloudServices.Notifications/Security/AuthorizeRegistrationEndpointAttribute.cs +++ b/CloudServices.Notifications/Security/AuthorizeRegistrationEndpointAttribute.cs @@ -17,12 +17,11 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Notifications { using System; - using System.Net.Http; + using System.Web.Http.Controllers; - [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class AuthorizeRegistrationEndpointAttribute : FuncBasedFilterAttribute + internal sealed class AuthorizeRegistrationEndpointAttribute : FuncBasedAuthorizationFilterAttribute { - public override Func Filter + public override Func Filter { get { return NotificationServiceContext.Current.Configuration.AuthorizeRegistrationRequest; } } diff --git a/CloudServices.Notifications/Security/FuncBasedAuthorizationFilterAttribute.cs b/CloudServices.Notifications/Security/FuncBasedAuthorizationFilterAttribute.cs new file mode 100644 index 0000000..8bb1310 --- /dev/null +++ b/CloudServices.Notifications/Security/FuncBasedAuthorizationFilterAttribute.cs @@ -0,0 +1,50 @@ +// ---------------------------------------------------------------------------------- +// Microsoft Developer & Platform Evangelism +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +// ---------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Samples.CloudServices.Notifications +{ + using System; + using System.Net; + using System.Net.Http; + using System.Web; + using System.Web.Http; + using System.Web.Http.Controllers; + using System.Web.Http.Filters; + + internal abstract class FuncBasedAuthorizationFilterAttribute : AuthorizationFilterAttribute + { + public abstract Func Filter { get; } + + public override void OnAuthorization(HttpActionContext actionContext) + { + if (this.Filter(actionContext)) + { + base.OnAuthorization(actionContext); + } + else + { + // HACK: Prevent ASP.NET Forms Authentication to redirect the user to the login page. + // This thread-safe approach adds a header with the suppression to be read on the + // OnEndRequest event of the pipelien. In order to fully support the supression you should have the ASP.NET Module + // that does this (SuppressFormsAuthenticationRedirectModule). + var response = new HttpResponseMessage(HttpStatusCode.Unauthorized); + response.Headers.Add(SuppressFormsAuthenticationRedirectModule.SuppressFormsHeaderName, "true"); + + throw new HttpResponseException(response); + } + } + } +} \ No newline at end of file diff --git a/CloudServices.Notifications/Settings.StyleCop b/CloudServices.Notifications/Settings.StyleCop deleted file mode 100644 index 14dc091..0000000 --- a/CloudServices.Notifications/Settings.StyleCop +++ /dev/null @@ -1,271 +0,0 @@ - - - NoMerge - - - - - False - - \.g\.cs$ - \.generated\.cs$ - \.g\.i\.cs$ - - - - - - - - - as - do - id - if - in - is - my - no - on - to - ui - - - - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - - - \ No newline at end of file diff --git a/CloudServices.Notifications/WindowsAzure/DataServiceQueryExceptionExtensions.cs b/CloudServices.Notifications/WindowsAzure/DataServiceQueryExceptionExtensions.cs index 02edabc..e0c86c6 100644 --- a/CloudServices.Notifications/WindowsAzure/DataServiceQueryExceptionExtensions.cs +++ b/CloudServices.Notifications/WindowsAzure/DataServiceQueryExceptionExtensions.cs @@ -27,4 +27,4 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Notifications.WindowsAzur && ((DataServiceClientException)ex.InnerException).StatusCode == 404; } } -} +} \ No newline at end of file diff --git a/CloudServices.Notifications/WindowsAzure/EndpointTableRow.cs b/CloudServices.Notifications/WindowsAzure/EndpointTableRow.cs index 75f85fa..d864776 100644 --- a/CloudServices.Notifications/WindowsAzure/EndpointTableRow.cs +++ b/CloudServices.Notifications/WindowsAzure/EndpointTableRow.cs @@ -17,13 +17,22 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Notifications.WindowsAzure { using System; - using System.Data.Services.Common; + using System.Data.Services.Common; + using System.Globalization; + using System.Text; using Microsoft.WindowsAzure.Samples.Common.Storage; - + [DataServiceEntity] [DataServiceKey(new[] { "PartitionKey", "RowKey" })] public class EndpointTableRow : Endpoint, ITableServiceEntity { + private string rowKey; + + public EndpointTableRow() + { + this.rowKey = string.Empty; + } + public string PartitionKey { get { return this.ApplicationId; } @@ -32,10 +41,28 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Notifications.WindowsAzur public string RowKey { - get { return this.DeviceId; } - set { this.DeviceId = value; } + get + { + if (string.IsNullOrEmpty(this.rowKey)) + this.RowKey = CreateRowKey(this.TileId, this.ClientId); + + return this.rowKey; + } + + set + { + this.rowKey = value; + } } public virtual DateTime Timestamp { get; set; } + + public static string CreateRowKey(string tileId, string clientId) + { + var encodedTileId = Convert.ToBase64String(Encoding.UTF8.GetBytes(tileId ?? string.Empty)).Replace("/", "%"); + var encodedClientId = Convert.ToBase64String(Encoding.UTF8.GetBytes(clientId ?? string.Empty)).Replace("/", "%"); + + return string.Format(CultureInfo.InvariantCulture, "{0}_{1}", encodedTileId, encodedClientId); + } } } diff --git a/CloudServices.Notifications/WindowsAzure/WindowsAzureEndpointRepository.cs b/CloudServices.Notifications/WindowsAzure/WindowsAzureEndpointRepository.cs index 3a5179d..75791c2 100644 --- a/CloudServices.Notifications/WindowsAzure/WindowsAzureEndpointRepository.cs +++ b/CloudServices.Notifications/WindowsAzure/WindowsAzureEndpointRepository.cs @@ -20,11 +20,12 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Notifications.WindowsAzur using System.Collections.Generic; using System.Data.Services.Client; using System.Linq; - using Common.Storage; + using Microsoft.WindowsAzure; + using Microsoft.WindowsAzure.Samples.Common.Storage; public class WindowsAzureEndpointRepository : IEndpointRepository { - protected readonly IAzureTable Table; + protected readonly IAzureTable Table; private const string EndpointsTableName = "Endpoints"; @@ -77,6 +78,26 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Notifications.WindowsAzur return this.Endpoints.Where(filterExpression).ToList().FirstOrDefault(); } + public Endpoint Find(string applicationId, string tileId, string clientId) + { + var rowkey = EndpointTableRow.CreateRowKey(tileId, clientId); + Endpoint endpoint = null; + + try + { + endpoint = this.Endpoints.Where(e => e.PartitionKey.Equals(applicationId) && e.RowKey.Equals(rowkey)).ToList().FirstOrDefault(); + } + catch (DataServiceQueryException e) + { + if (e.Response.StatusCode != 404) + { + throw; + } + } + + return endpoint; + } + public void InsertOrUpdate(Endpoint endpoint) { if (endpoint == null) @@ -85,15 +106,15 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Notifications.WindowsAzur this.Table.AddOrUpdateEntity(Endpoint.To(endpoint)); } - public void Delete(string applicationId, string deviceId) + public void Delete(string applicationId, string tileId, string clientId) { - if (applicationId == null) + if (string.IsNullOrWhiteSpace(applicationId)) throw new ArgumentNullException("applicationId"); - if (deviceId == null) - throw new ArgumentNullException("deviceId"); + if (string.IsNullOrWhiteSpace(clientId)) + throw new ArgumentNullException("clientId"); - var storedEndpoint = this.Find(e => e.ApplicationId.Equals(applicationId) && e.DeviceId.Equals(deviceId)); + var storedEndpoint = this.Find(applicationId, tileId, clientId); if (storedEndpoint != null) this.Table.DeleteEntity(Endpoint.To(storedEndpoint)); diff --git a/CloudServices.Notifications/packages.config b/CloudServices.Notifications/packages.config index de336c4..d11c313 100644 --- a/CloudServices.Notifications/packages.config +++ b/CloudServices.Notifications/packages.config @@ -1,10 +1,10 @@  - - - - - - + + + + + + \ No newline at end of file diff --git a/CloudServices.Storage/CloudServices.Storage.csproj b/CloudServices.Storage/CloudServices.Storage.csproj index 57782d7..66bc6d3 100644 --- a/CloudServices.Storage/CloudServices.Storage.csproj +++ b/CloudServices.Storage/CloudServices.Storage.csproj @@ -12,6 +12,7 @@ WindowsAzure.Storage.Proxy v4.0 512 + ..\ true @@ -35,51 +36,43 @@ ..\packages\aspnet.suppressformsredirect.0.0.1.4\lib\40\AspNet.SuppressFormsRedirect.dll - - ..\packages\WebApi.OData.0.6.0\lib\40-Full\EntityDataModel.dll - - - ..\packages\WebApi.0.6.0\lib\40-Full\Microsoft.ApplicationServer.Http.dll - - - ..\packages\WebApi.Enhancements.0.6.0\lib\40-Full\Microsoft.ApplicationServer.HttpEnhancements.dll - - - ..\packages\WebApi.OData.0.6.0\lib\40-Full\Microsoft.Data.Spatial.dll - - - ..\packages\JsonValue.0.6.0\lib\40\Microsoft.Json.dll - - - ..\packages\HttpClient.0.6.0\lib\40\Microsoft.Net.Http.Formatting.dll - - - ..\packages\WebApi.OData.0.6.0\lib\40-Full\Microsoft.Net.Http.Formatting.OData.dll - - - ..\packages\WebApi.0.6.0\lib\40-Full\Microsoft.Runtime.Serialization.Internal.dll - - - ..\packages\WebApi.0.6.0\lib\40-Full\Microsoft.Server.Common.dll - - - ..\packages\WebApi.0.6.0\lib\40-Full\Microsoft.ServiceModel.Internal.dll - - - ..\packages\WebApi.OData.0.6.0\lib\40-Full\System.Data.OData.dll + + True + ..\packages\System.Json.4.0.20126.16343\lib\net40\System.Json.dll - - ..\packages\HttpClient.0.6.0\lib\40\System.Net.Http.dll + + True + ..\packages\System.Net.Http.2.0.20126.16343\lib\net40\System.Net.Http.dll + + + True + ..\packages\System.Net.Http.Formatting.4.0.20126.16343\lib\net40\System.Net.Http.Formatting.dll + + + True + ..\packages\System.Net.Http.2.0.20126.16343\lib\net40\System.Net.Http.WebRequest.dll + + True + ..\packages\AspNetWebApi.Core.4.0.20126.16343\lib\net40\System.Web.Http.dll + + + True + ..\packages\System.Web.Http.Common.4.0.20126.16343\lib\net40\System.Web.Http.Common.dll + + + True + ..\packages\AspNetWebApi.4.0.20126.16343\lib\net40\System.Web.Http.WebHost.dll + @@ -88,24 +81,21 @@ + + + - - True - True - Resource.resx - - + - - - + + @@ -114,23 +104,15 @@ - + - - - ResXFileCodeGenerator - Resource.Designer.cs - Designer - - - - $(ProjectDir)..\NuGet\NuGet.exe install $(ProjectDir)packages.config -OutputDirectory $(ProjectDir)..\packages\ + $(ProjectDir)..\.nuget\NuGet.exe install $(ProjectDir)packages.config -OutputDirectory $(ProjectDir)..\packages\ the create container mode is requested - if (string.IsNullOrEmpty(operation)) + if (string.IsNullOrEmpty(comp)) { responseStatusCode = HttpStatusCode.Created; container.Create(); @@ -103,7 +92,7 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage } // operation == metadata - if (!string.IsNullOrEmpty(operation) && operation.Equals("metadata", StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(comp) && comp.Equals("metadata", StringComparison.OrdinalIgnoreCase)) { responseStatusCode = HttpStatusCode.OK; container.SetMetadata(); @@ -117,11 +106,9 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage } } - [OperationContract] [AuthorizeBlobsAccess] [CLSCompliant(false)] - [WebInvoke(Method = "DELETE", UriTemplate = "/containers/{containerName}")] - public HttpResponseMessage DeleteContainer(string containerName) + public HttpResponseMessage Delete(string containerName) { try { @@ -136,10 +123,9 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage } } - [OperationContract] [AuthorizeBlobsAccess] [CLSCompliant(false)] - [WebInvoke(Method = "HEAD", UriTemplate = "/containers/{containerName}")] + [AcceptVerbs("HEAD")] public HttpResponseMessage GetContainerProperties(string containerName) { try @@ -165,17 +151,23 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage } } - [OperationContract] [AuthorizeBlobsAccess] [CLSCompliant(false)] - [WebGet(UriTemplate = "/containers?prefix={containerPrefix}")] - public HttpResponseMessage ListContainers(string containerPrefix) + public HttpResponseMessage GetContainers() + { + return this.GetContainers(string.Empty); + } + + [AuthorizeBlobsAccess] + [CLSCompliant(false)] + public HttpResponseMessage GetContainers(string prefix) { IEnumerable containers; - if (!string.IsNullOrEmpty(containerPrefix)) + + if (!string.IsNullOrEmpty(prefix)) { - containerPrefix = containerPrefix.TrimStart('/', '\\').Replace('\\', '/'); - containers = this.cloudBlobClient.ListContainers(containerPrefix); + prefix = prefix.TrimStart('/', '\\').Replace('\\', '/'); + containers = this.cloudBlobClient.ListContainers(prefix); } else { @@ -193,14 +185,12 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage return new HttpResponseMessage(result, HttpStatusCode.OK); } - [OperationContract] [AuthorizeBlobsAccess] [CLSCompliant(false)] - [WebGet(UriTemplate = "/containers/{containerName}?comp={operation}")] - public HttpResponseMessage GetContainerSharedAccessSignature(string containerName, string operation) + public HttpResponseMessage GetContainerSharedAccessSignature(string containerName, string comp) { - if (string.IsNullOrEmpty(operation) || !operation.Equals("sas", StringComparison.OrdinalIgnoreCase)) - throw WebException(Resource.CompMustBeSasArgumentErrorMessage, HttpStatusCode.BadRequest); + if (string.IsNullOrEmpty(comp) || !comp.Equals("sas", StringComparison.OrdinalIgnoreCase)) + throw WebException(Constants.CompMustBeSasArgumentErrorMessage, HttpStatusCode.BadRequest); var container = this.cloudBlobClient.GetContainerReference(containerName); var sas = container.GetSharedAccessSignature(new SharedAccessPolicy @@ -213,14 +203,12 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage return new HttpResponseMessage { Content = new StringContent(uriBuilder.Uri.AbsoluteUri), StatusCode = HttpStatusCode.OK }; } - [OperationContract] [AuthorizeBlobsAccess] [CLSCompliant(false)] - [WebGet(UriTemplate = "/blobs/{containerName}/{*blobName}?comp={operation}")] - public HttpResponseMessage GetBlobSharedAccessSignature(string containerName, string blobName, string operation) + public HttpResponseMessage GetBlobSharedAccessSignature(string containerName, string blobName, string comp) { - if (string.IsNullOrEmpty(operation) || !operation.Equals("sas", StringComparison.OrdinalIgnoreCase)) - throw WebException(Resource.CompMustBeSasArgumentErrorMessage, HttpStatusCode.BadRequest); + if (string.IsNullOrEmpty(comp) || !comp.Equals("sas", StringComparison.OrdinalIgnoreCase)) + throw WebException(Constants.CompMustBeSasArgumentErrorMessage, HttpStatusCode.BadRequest); try { @@ -314,4 +302,4 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage return publicAccessMode; } } -} +} \ No newline at end of file diff --git a/CloudServices.Storage/Controllers/TablesProxyController.cs b/CloudServices.Storage/Controllers/TablesProxyController.cs new file mode 100644 index 0000000..42d142a --- /dev/null +++ b/CloudServices.Storage/Controllers/TablesProxyController.cs @@ -0,0 +1,82 @@ +// ---------------------------------------------------------------------------------- +// Microsoft Developer & Platform Evangelism +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +// ---------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage +{ + using System; + using System.Net; + using System.Net.Http; + using System.Web.Http; + using Microsoft.WindowsAzure.Samples.CloudServices.Storage.Handlers; + using Microsoft.WindowsAzure.Samples.CloudServices.Storage.Helpers; + using Microsoft.WindowsAzure.Samples.CloudServices.Storage.Security; + + public class TablesProxyController : ApiController + { + private static readonly AzureTablesProxyHandler Proxy = new AzureTablesProxyHandler(); + + [AuthorizeTablesAccess, CLSCompliant(false)] + public HttpResponseMessage Post([FromUri]string path) + { + if (this.Request == null) + throw Extensions.StorageException(HttpStatusCode.BadRequest, Constants.RequestCannotBeNullErrorMessage, Constants.RequestCannotBeNullErrorMessage); + + this.Request.Properties[StorageProxyHandler.RequestedPathPropertyName] = path ?? string.Empty; + return Proxy.ProcessRequest(this.Request); + } + + [AuthorizeTablesAccess, CLSCompliant(false)] + public HttpResponseMessage Put([FromUri]string path) + { + if (this.Request == null) + throw Extensions.StorageException(HttpStatusCode.BadRequest, Constants.RequestCannotBeNullErrorMessage, Constants.RequestCannotBeNullErrorMessage); + + this.Request.Properties[StorageProxyHandler.RequestedPathPropertyName] = path ?? string.Empty; + return Proxy.ProcessRequest(this.Request); + } + + [AuthorizeTablesAccess, CLSCompliant(false)] + public HttpResponseMessage Get(string path) + { + if (this.Request == null) + throw Extensions.StorageException(HttpStatusCode.BadRequest, Constants.RequestCannotBeNullErrorMessage, Constants.RequestCannotBeNullErrorMessage); + + this.Request.Properties[StorageProxyHandler.RequestedPathPropertyName] = path ?? string.Empty; + return Proxy.ProcessRequest(this.Request); + } + + [AuthorizeTablesAccess, CLSCompliant(false)] + public HttpResponseMessage Delete(string path) + { + if (this.Request == null) + throw Extensions.StorageException(HttpStatusCode.BadRequest, Constants.RequestCannotBeNullErrorMessage, Constants.RequestCannotBeNullErrorMessage); + + this.Request.Properties[StorageProxyHandler.RequestedPathPropertyName] = path ?? string.Empty; + return Proxy.ProcessRequest(this.Request); + } + + [AuthorizeTablesAccess, CLSCompliant(false)] + [AcceptVerbs("MERGE")] + public HttpResponseMessage Merge([FromUri]string path) + { + if (this.Request == null) + throw Extensions.StorageException(HttpStatusCode.BadRequest, Constants.RequestCannotBeNullErrorMessage, Constants.RequestCannotBeNullErrorMessage); + + this.Request.Properties[StorageProxyHandler.RequestedPathPropertyName] = path ?? string.Empty; + return Proxy.ProcessRequest(this.Request); + } + } +} \ No newline at end of file diff --git a/CloudServices.Storage/Handlers/ContentTypeSanitizerMessageHandler.cs b/CloudServices.Storage/Handlers/ContentTypeSanitizerMessageHandler.cs new file mode 100644 index 0000000..c242076 --- /dev/null +++ b/CloudServices.Storage/Handlers/ContentTypeSanitizerMessageHandler.cs @@ -0,0 +1,36 @@ +// ---------------------------------------------------------------------------------- +// Microsoft Developer & Platform Evangelism +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +// ---------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Handlers +{ + using System.Net.Http; + using System.Net.Http.Headers; + using System.Threading; + + public class ContentTypeSanitizerMessageHandler : ControllerFilteredMessageProcessingHandler + { + protected override HttpRequestMessage ProcessRequestHandler(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (request.Content.Headers.ContentType == null) + { + // Set the default Content-Type when it is not specified + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/xml"); + } + + return request; + } + } +} \ No newline at end of file diff --git a/CloudServices.Storage/Handlers/ControllerFilteredMessageProcessingHandler.cs b/CloudServices.Storage/Handlers/ControllerFilteredMessageProcessingHandler.cs new file mode 100644 index 0000000..f8ebde9 --- /dev/null +++ b/CloudServices.Storage/Handlers/ControllerFilteredMessageProcessingHandler.cs @@ -0,0 +1,53 @@ +// ---------------------------------------------------------------------------------- +// Microsoft Developer & Platform Evangelism +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +// ---------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Handlers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Web.Http; + + public abstract class ControllerFilteredMessageProcessingHandler : MessageProcessingHandler + { + public IList ConfiguredControllers { get; set; } + + protected abstract HttpRequestMessage ProcessRequestHandler(HttpRequestMessage request, CancellationToken cancellationToken); + + protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken) + { + var routeData = request.GetRouteData(); + var controllerName = routeData.Values.ContainsKey("controller") ? + routeData.Values["controller"].ToString() : + string.Empty; + + if (this.ConfiguredControllers == null || + this.ConfiguredControllers.Any(c => c.Equals(controllerName, StringComparison.OrdinalIgnoreCase))) + { + return this.ProcessRequestHandler(request, cancellationToken); + } + + return request; + } + + protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken) + { + return response; + } + } +} \ No newline at end of file diff --git a/CloudServices.Storage/Handlers/StorageProxyHandler.cs b/CloudServices.Storage/Handlers/StorageProxyHandler.cs index 04c9d4e..3e4ba03 100644 --- a/CloudServices.Storage/Handlers/StorageProxyHandler.cs +++ b/CloudServices.Storage/Handlers/StorageProxyHandler.cs @@ -26,7 +26,6 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Handlers using System.Web; using Microsoft.WindowsAzure; using Microsoft.WindowsAzure.Samples.CloudServices.Storage.Helpers; - using Microsoft.WindowsAzure.Samples.CloudServices.Storage.Properties; public abstract class StorageProxyHandler { @@ -47,7 +46,7 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Handlers public HttpResponseMessage ProcessRequest(HttpRequestMessage request) { if (request == null) - throw new ArgumentNullException("request", Resource.RequestCannotBeNullErrorMessage); + throw new ArgumentNullException("request", Constants.RequestCannotBeNullErrorMessage); var originalUri = request.RequestUri; @@ -79,7 +78,7 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Handlers protected string GetAzureStorageRequestBody(string proxyRequestBody, HttpRequestMessage request) { if (request == null) - throw new ArgumentNullException("request", Resource.RequestCannotBeNullErrorMessage); + throw new ArgumentNullException("request", Constants.RequestCannotBeNullErrorMessage); if (string.IsNullOrWhiteSpace(proxyRequestBody)) { @@ -111,7 +110,6 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Handlers return azureStorageResponseBody.Replace(oldValue, newValue); } - [CLSCompliant(false)] protected abstract void SignRequest(HttpRequestMessage request); private static string GetLocalPath(Uri originalUri, Uri mappedUri, HttpRequestMessage request) @@ -167,18 +165,18 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Handlers { var task = azureClient.SendAsync(request); - // Synchronize Azure Invokation + // Synchronize Azure Invocation task.Wait(); return task.Result; } catch (HttpException webException) { - throw Extensions.StorageException(HttpStatusCode.InternalServerError, Resource.WindowsAzureStorageExceptionStringMessage, webException.Message); + throw Extensions.StorageException(HttpStatusCode.InternalServerError, Constants.WindowsAzureStorageExceptionStringMessage, webException.Message); } catch (ObjectDisposedException exception) { - throw Extensions.StorageException(HttpStatusCode.InternalServerError, Resource.HttpClientDisposedErrorString, exception.Message); + throw Extensions.StorageException(HttpStatusCode.InternalServerError, Constants.HttpClientDisposedErrorString, exception.Message); } } } diff --git a/CloudServices.Storage/Helpers/BlobExtensions.cs b/CloudServices.Storage/Helpers/BlobExtensions.cs index d04b654..719a952 100644 --- a/CloudServices.Storage/Helpers/BlobExtensions.cs +++ b/CloudServices.Storage/Helpers/BlobExtensions.cs @@ -17,15 +17,14 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Helpers { using System; - using Microsoft.WindowsAzure.Samples.CloudServices.Storage.Properties; public static class BlobExtensions { - public static SasCloudBlobContainer ToModel(this StorageClient.CloudBlobContainer container) + public static SasCloudBlobContainer ToModel(this Microsoft.WindowsAzure.StorageClient.CloudBlobContainer container) { if (container == null) { - throw new ArgumentNullException("container", Resource.ContainerCannotBeNullErrorMessage); + throw new ArgumentNullException("container", Constants.ContainerCannotBeNullErrorMessage); } return new SasCloudBlobContainer diff --git a/CloudServices.Storage/Helpers/CredentialExtensions.cs b/CloudServices.Storage/Helpers/CredentialExtensions.cs index 8ed80eb..7ce2d02 100644 --- a/CloudServices.Storage/Helpers/CredentialExtensions.cs +++ b/CloudServices.Storage/Helpers/CredentialExtensions.cs @@ -26,6 +26,7 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Helpers using System.Net.Http.Headers; using System.Text; using System.Web; + using Microsoft.WindowsAzure; internal static class CredentialExtensions { @@ -74,7 +75,7 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Helpers canonicalizedString.AppendCanonicalizedElement("0"); else { - canonicalizedString.AppendCanonicalizedElement(string.Empty); + canonicalizedString.AppendCanonicalizedElement(string.Empty); } canonicalizedString.AppendCanonicalizedElement(string.Empty); @@ -179,7 +180,7 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Helpers { var canonicalizedString = new CanonicalizedString(string.Empty); - var keyList = + var keyList = headers.Where(h => h.Key.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase)).Select(header => header.Key).ToList(); keyList.Sort(); diff --git a/CloudServices.Storage/Helpers/Extensions.cs b/CloudServices.Storage/Helpers/Extensions.cs index ddfe1a1..433c17a 100644 --- a/CloudServices.Storage/Helpers/Extensions.cs +++ b/CloudServices.Storage/Helpers/Extensions.cs @@ -21,7 +21,7 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Helpers using System.Net; using System.Net.Http; using System.Net.Http.Headers; - using Microsoft.ApplicationServer.Http.Dispatcher; + using System.Web.Http; public static class Extensions { @@ -54,7 +54,8 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Helpers response.ReasonPhrase = error; response.Content = new StringContent(errorMessage); response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/atom+xml"); + return new HttpResponseException(response); } } -} +} \ No newline at end of file diff --git a/CloudServices.Storage/Properties/Resource.Designer.cs b/CloudServices.Storage/Properties/Resource.Designer.cs deleted file mode 100644 index 4133875..0000000 --- a/CloudServices.Storage/Properties/Resource.Designer.cs +++ /dev/null @@ -1,187 +0,0 @@ -// ---------------------------------------------------------------------------------- -// Microsoft Developer & Platform Evangelism -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, -// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES -// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. -// ---------------------------------------------------------------------------------- -// The example companies, organizations, products, domain names, -// e-mail addresses, logos, people, places, and events depicted -// herein are fictitious. No association with any real company, -// organization, product, domain name, email address, logo, person, -// places, or events is intended or should be inferred. -// ---------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.235 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resource { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resource() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.WindowsAzure.Samples.CloudServices.Storage.Properties.Resource", typeof(Resource).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to The Storage Account setting cannot be null. - /// - internal static string CloudStorageAccountNullArgumentErrorMessage { - get { - return ResourceManager.GetString("CloudStorageAccountNullArgumentErrorMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Argument comp must be sas for this operation. - /// - internal static string CompMustBeSasArgumentErrorMessage { - get { - return ResourceManager.GetString("CompMustBeSasArgumentErrorMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Parameter configureAction cannot be null. - /// - internal static string ConfigureActionArgumentNullErrorMessage { - get { - return ResourceManager.GetString("ConfigureActionArgumentNullErrorMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Container can not be null.. - /// - internal static string ContainerCannotBeNullErrorMessage { - get { - return ResourceManager.GetString("ContainerCannotBeNullErrorMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The containerName cannot be null.. - /// - internal static string ContainerNameNullArgumentErrorMessage { - get { - return ResourceManager.GetString("ContainerNameNullArgumentErrorMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The connection with Windows Azure has been closed before the result could be read. - /// - internal static string HttpClientDisposedErrorString { - get { - return ResourceManager.GetString("HttpClientDisposedErrorString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Container Access Mode is invalid. - /// - internal static string InvalidPublicAccessModeArgumentErrorMessage { - get { - return ResourceManager.GetString("InvalidPublicAccessModeArgumentErrorMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You have specified an ACL operation but not sent the required HTTP header. - /// - internal static string PublicAccessNotSpecifiedErrorMessage { - get { - return ResourceManager.GetString("PublicAccessNotSpecifiedErrorMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Request can not be null.. - /// - internal static string RequestCannotBeNullErrorMessage { - get { - return ResourceManager.GetString("RequestCannotBeNullErrorMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Called service with null HttpRequestMessage. - /// - internal static string RequestNullErrorMessage { - get { - return ResourceManager.GetString("RequestNullErrorMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Response can not be null.. - /// - internal static string ResponseNullArgumentErrorString { - get { - return ResourceManager.GetString("ResponseNullArgumentErrorString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to There was an error when sending data to Windows Azure Storage. - /// - internal static string WindowsAzureStorageExceptionStringMessage { - get { - return ResourceManager.GetString("WindowsAzureStorageExceptionStringMessage", resourceCulture); - } - } - } -} diff --git a/CloudServices.Storage/Properties/Resource.resx b/CloudServices.Storage/Properties/Resource.resx deleted file mode 100644 index 7114fb0..0000000 --- a/CloudServices.Storage/Properties/Resource.resx +++ /dev/null @@ -1,156 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - The Storage Account setting cannot be null - - - The containerName cannot be null. - - - Argument comp must be sas for this operation - - - You have specified an ACL operation but not sent the required HTTP header - - - Called service with null HttpRequestMessage - - - Container Access Mode is invalid - - - Parameter configureAction cannot be null - - - Request can not be null. - - - Response can not be null. - - - Container can not be null. - - - There was an error when sending data to Windows Azure Storage - - - The connection with Windows Azure has been closed before the result could be read - - \ No newline at end of file diff --git a/CloudServices.Storage/QueuesProxyService.cs b/CloudServices.Storage/QueuesProxyService.cs deleted file mode 100644 index 89789eb..0000000 --- a/CloudServices.Storage/QueuesProxyService.cs +++ /dev/null @@ -1,87 +0,0 @@ -// ---------------------------------------------------------------------------------- -// Microsoft Developer & Platform Evangelism -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, -// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES -// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. -// ---------------------------------------------------------------------------------- -// The example companies, organizations, products, domain names, -// e-mail addresses, logos, people, places, and events depicted -// herein are fictitious. No association with any real company, -// organization, product, domain name, email address, logo, person, -// places, or events is intended or should be inferred. -// ---------------------------------------------------------------------------------- - -namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage -{ - using System; - using System.Net; - using System.Net.Http; - using System.ServiceModel; - using System.ServiceModel.Activation; - using System.ServiceModel.Web; - using Microsoft.WindowsAzure.Samples.CloudServices.Storage.Handlers; - using Microsoft.WindowsAzure.Samples.CloudServices.Storage.Helpers; - using Microsoft.WindowsAzure.Samples.CloudServices.Storage.Properties; - using Microsoft.WindowsAzure.Samples.CloudServices.Storage.Security; - - [ServiceContract] - [ServiceBehavior(IncludeExceptionDetailInFaults = true)] - [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)] - public class QueuesProxyService - { - private static readonly AzureQueuesProxyHandler Proxy = new AzureQueuesProxyHandler(); - - [WebInvoke(Method = "POST", UriTemplate = "{*path}"), OperationContract, AuthorizeQueuesAccess, CLSCompliant(false)] - public HttpResponseMessage HandlePost(HttpRequestMessage request, string path) - { - if (request == null) - throw Extensions.StorageException(HttpStatusCode.BadRequest, Resource.RequestCannotBeNullErrorMessage, Resource.RequestCannotBeNullErrorMessage); - - request.Properties[StorageProxyHandler.RequestedPathPropertyName] = path; - return Proxy.ProcessRequest(request); - } - - [WebInvoke(Method = "PUT", UriTemplate = "{*path}"), OperationContract, AuthorizeQueuesAccess, CLSCompliant(false)] - public HttpResponseMessage HandlePut(HttpRequestMessage request, string path) - { - if (request == null) - throw Extensions.StorageException(HttpStatusCode.BadRequest, Resource.RequestCannotBeNullErrorMessage, Resource.RequestCannotBeNullErrorMessage); - - request.Properties[StorageProxyHandler.RequestedPathPropertyName] = path; - return Proxy.ProcessRequest(request); - } - - [WebInvoke(Method = "GET", UriTemplate = "{*path}"), OperationContract, AuthorizeQueuesAccess, CLSCompliant(false)] - public HttpResponseMessage HandleGet(HttpRequestMessage request, string path) - { - if (request == null) - throw Extensions.StorageException(HttpStatusCode.BadRequest, Resource.RequestCannotBeNullErrorMessage, Resource.RequestCannotBeNullErrorMessage); - - request.Properties[StorageProxyHandler.RequestedPathPropertyName] = path; - return Proxy.ProcessRequest(request); - } - - [WebInvoke(Method = "DELETE", UriTemplate = "{*path}"), OperationContract, AuthorizeQueuesAccess, CLSCompliant(false)] - public HttpResponseMessage HandleDelete(HttpRequestMessage request, string path) - { - if (request == null) - throw Extensions.StorageException(HttpStatusCode.BadRequest, Resource.RequestCannotBeNullErrorMessage, Resource.RequestCannotBeNullErrorMessage); - - request.Properties[StorageProxyHandler.RequestedPathPropertyName] = path; - return Proxy.ProcessRequest(request); - } - - [WebInvoke(Method = "HEAD", UriTemplate = "{*path}"), OperationContract, AuthorizeQueuesAccess, CLSCompliant(false)] - public HttpResponseMessage HandleMerge(HttpRequestMessage request, string path) - { - if (request == null) - throw Extensions.StorageException(HttpStatusCode.BadRequest, Resource.RequestCannotBeNullErrorMessage, Resource.RequestCannotBeNullErrorMessage); - - request.Properties[StorageProxyHandler.RequestedPathPropertyName] = path; - return Proxy.ProcessRequest(request); - } - } -} \ No newline at end of file diff --git a/CloudServices.Storage/RouteExtensions.cs b/CloudServices.Storage/RouteExtensions.cs index 2b03be8..d62fe85 100644 --- a/CloudServices.Storage/RouteExtensions.cs +++ b/CloudServices.Storage/RouteExtensions.cs @@ -16,53 +16,87 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage { - using System; using System.Linq; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Web.Http; using System.Web.Routing; - using Microsoft.ApplicationServer.Http; public static class RouteExtensions { public static void MapQueuesProxyServiceRoute(this RouteCollection routes, string prefix) { - var handlers = StorageServicesContext.Current.Configuration.DelegatingHandlers.ToArray(); - - routes.MapQueuesProxyServiceRoute(prefix, handlers); + routes.MapQueuesProxyServiceRoute(prefix, new DelegatingHandler[] { }); } - public static void MapQueuesProxyServiceRoute(this RouteCollection routes, string prefix, params Type[] handlers) + public static void MapQueuesProxyServiceRoute(this RouteCollection routes, string prefix, params DelegatingHandler[] handlers) { - var configuration = new HttpConfiguration().AddDelegatingHandlers(handlers).AddRequestHandlers(); + var currentConfiguration = GlobalConfiguration.Configuration; - routes.MapServiceRoute(prefix, configuration); + // Handlers + currentConfiguration.AddDelegatingHandlers(handlers) + .AddDelegatingHandlers( + new[] + { + new Handlers.ContentTypeSanitizerMessageHandler { ConfiguredControllers = new[] { "QueuesProxy" } } + }); + + // Routes + routes.MapHttpRoute( + name: prefix, + routeTemplate: prefix + "/{*path}", + defaults: new { Controller = "QueuesProxy", path = RouteParameter.Optional }); } public static void MapTablesProxyServiceRoute(this RouteCollection routes, string prefix) { - var handlers = StorageServicesContext.Current.Configuration.DelegatingHandlers.ToArray(); - - routes.MapTablesProxyServiceRoute(prefix, handlers); + routes.MapTablesProxyServiceRoute(prefix, new DelegatingHandler[] { }); } - public static void MapTablesProxyServiceRoute(this RouteCollection routes, string prefix, params Type[] handlers) + public static void MapTablesProxyServiceRoute(this RouteCollection routes, string prefix, params DelegatingHandler[] handlers) { - var configuration = new HttpConfiguration().AddDelegatingHandlers(handlers).AddRequestHandlers(); + var currentConfiguration = GlobalConfiguration.Configuration; - routes.MapServiceRoute(prefix, configuration); + // Formatters + currentConfiguration.Formatters.XmlFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/atom+xml")); + + // Handlers + currentConfiguration.AddDelegatingHandlers(handlers); + + // Routes + routes.MapHttpRoute( + name: prefix, + routeTemplate: prefix + "/{*path}", + defaults: new { Controller = "TablesProxy", path = RouteParameter.Optional }); } public static void MapSasServiceRoute(this RouteCollection routes, string prefix) { - var handlers = StorageServicesContext.Current.Configuration.DelegatingHandlers.ToArray(); - - routes.MapSasServiceRoute(prefix, handlers); + routes.MapSasServiceRoute(prefix, new DelegatingHandler[] { }); } - public static void MapSasServiceRoute(this RouteCollection routes, string prefix, params Type[] handlers) + public static void MapSasServiceRoute(this RouteCollection routes, string prefix, params DelegatingHandler[] handlers) { - var configuration = new HttpConfiguration().AddDelegatingHandlers(handlers).AddRequestHandlers(); + var currentConfiguration = GlobalConfiguration.Configuration; - routes.MapServiceRoute(prefix, configuration); + // Handlers + currentConfiguration.AddDelegatingHandlers(handlers); + + // Routes + routes.MapHttpRoute( + name: prefix + "_Containers", + routeTemplate: prefix + "/containers", + defaults: new { Controller = "SharedAccessSignature" }); + + routes.MapHttpRoute( + name: prefix + "_Container", + routeTemplate: prefix + "/containers/{containerName}", + defaults: new { Controller = "SharedAccessSignature", containerName = RouteParameter.Optional }); + + routes.MapHttpRoute( + name: prefix + "_Blob", + routeTemplate: prefix + "/blobs/{containerName}/{*blobName}", + defaults: new { Controller = "SharedAccessSignature" }); } } -} +} \ No newline at end of file diff --git a/CloudServices.Storage/Security/AuthorizeBlobsAccessAttribute.cs b/CloudServices.Storage/Security/AuthorizeBlobsAccessAttribute.cs index 796f37d..62a2300 100644 --- a/CloudServices.Storage/Security/AuthorizeBlobsAccessAttribute.cs +++ b/CloudServices.Storage/Security/AuthorizeBlobsAccessAttribute.cs @@ -18,13 +18,14 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Security { using System; using System.Net.Http; + using System.Web.Http.Controllers; [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class AuthorizeBlobsAccessAttribute : FuncBasedFilterAttribute + internal sealed class AuthorizeBlobsAccessAttribute : FuncBasedAuthorizationFilterAttribute { - public override Func Filter + public override Func Filter { get { return StorageServicesContext.Current.Configuration.AuthorizeBlobsAccess; } } } -} +} \ No newline at end of file diff --git a/CloudServices.Storage/Security/AuthorizeQueuesAccessAttribute.cs b/CloudServices.Storage/Security/AuthorizeQueuesAccessAttribute.cs index ba400d7..f7a5dab 100644 --- a/CloudServices.Storage/Security/AuthorizeQueuesAccessAttribute.cs +++ b/CloudServices.Storage/Security/AuthorizeQueuesAccessAttribute.cs @@ -18,13 +18,14 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Security { using System; using System.Net.Http; + using System.Web.Http.Controllers; [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class AuthorizeQueuesAccessAttribute : FuncBasedFilterAttribute + internal sealed class AuthorizeQueuesAccessAttribute : FuncBasedAuthorizationFilterAttribute { - public override Func Filter + public override Func Filter { get { return StorageServicesContext.Current.Configuration.AuthorizeQueuesAccess; } } } -} +} \ No newline at end of file diff --git a/CloudServices.Storage/Security/AuthorizeTablesAccessAttribute.cs b/CloudServices.Storage/Security/AuthorizeTablesAccessAttribute.cs index 72b3054..be65929 100644 --- a/CloudServices.Storage/Security/AuthorizeTablesAccessAttribute.cs +++ b/CloudServices.Storage/Security/AuthorizeTablesAccessAttribute.cs @@ -18,13 +18,14 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Security { using System; using System.Net.Http; + using System.Web.Http.Controllers; [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class AuthorizeTablesAccessAttribute : FuncBasedFilterAttribute + internal sealed class AuthorizeTablesAccessAttribute : FuncBasedAuthorizationFilterAttribute { - public override Func Filter + public override Func Filter { get { return StorageServicesContext.Current.Configuration.AuthorizeTablesAccess; } } } -} +} \ No newline at end of file diff --git a/CloudServices.Storage/Security/FuncBasedAuthorizationFilterAttribute.cs b/CloudServices.Storage/Security/FuncBasedAuthorizationFilterAttribute.cs new file mode 100644 index 0000000..88e05b5 --- /dev/null +++ b/CloudServices.Storage/Security/FuncBasedAuthorizationFilterAttribute.cs @@ -0,0 +1,50 @@ +// ---------------------------------------------------------------------------------- +// Microsoft Developer & Platform Evangelism +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +// ---------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Security +{ + using System; + using System.Net; + using System.Net.Http; + using System.Web; + using System.Web.Http; + using System.Web.Http.Controllers; + using System.Web.Http.Filters; + + internal abstract class FuncBasedAuthorizationFilterAttribute : AuthorizationFilterAttribute + { + public abstract Func Filter { get; } + + public override void OnAuthorization(HttpActionContext actionContext) + { + if (this.Filter(actionContext)) + { + base.OnAuthorization(actionContext); + } + else + { + // HACK: Prevent ASP.NET Forms Authentication to redirect the user to the login page. + // This thread-safe approach adds a header with the suppression to be read on the + // OnEndRequest event of the pipelien. In order to fully support the supression you should have the ASP.NET Module + // that does this (SuppressFormsAuthenticationRedirectModule). + var response = new HttpResponseMessage(HttpStatusCode.Unauthorized); + response.Headers.Add(SuppressFormsAuthenticationRedirectModule.SuppressFormsHeaderName, "true"); + + throw new HttpResponseException(response); + } + } + } +} \ No newline at end of file diff --git a/CloudServices.Storage/Security/FuncBasedOperationHandler.cs b/CloudServices.Storage/Security/FuncBasedOperationHandler.cs deleted file mode 100644 index 76f2717..0000000 --- a/CloudServices.Storage/Security/FuncBasedOperationHandler.cs +++ /dev/null @@ -1,48 +0,0 @@ -// ---------------------------------------------------------------------------------- -// Microsoft Developer & Platform Evangelism -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, -// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES -// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. -// ---------------------------------------------------------------------------------- -// The example companies, organizations, products, domain names, -// e-mail addresses, logos, people, places, and events depicted -// herein are fictitious. No association with any real company, -// organization, product, domain name, email address, logo, person, -// places, or events is intended or should be inferred. -// ---------------------------------------------------------------------------------- - -namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage.Security -{ - using System.Net.Http; - using System.Web; - using Microsoft.ApplicationServer.Http.Dispatcher; - - internal class FuncBasedOperationHandler : HttpOperationHandler - { - private readonly FuncBasedFilterAttribute filterAttribute; - - public FuncBasedOperationHandler(FuncBasedFilterAttribute filterAttribute) - : base("response") - { - this.filterAttribute = filterAttribute; - } - - protected override HttpRequestMessage OnHandle(HttpRequestMessage input) - { - if (this.filterAttribute.Filter(input)) - return input; - - // HACK: Prevent ASP.NET Forms Authentication to redirect the user to the login page. - // This thread-safe approach adds a header with the suppression to be read on the - // OnEndRequest event of the pipelien. In order to fully support the supression you should have the ASP.NET Module - // that does this (SuppressFormsAuthenticationRedirectModule). - var response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); - response.Headers.Add(SuppressFormsAuthenticationRedirectModule.SuppressFormsHeaderName, "true"); - - throw new HttpResponseException(response); - } - } -} diff --git a/CloudServices.Storage/Settings.StyleCop b/CloudServices.Storage/Settings.StyleCop deleted file mode 100644 index 14dc091..0000000 --- a/CloudServices.Storage/Settings.StyleCop +++ /dev/null @@ -1,271 +0,0 @@ - - - NoMerge - - - - - False - - \.g\.cs$ - \.generated\.cs$ - \.g\.i\.cs$ - - - - - - - - - as - do - id - if - in - is - my - no - on - to - ui - - - - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - - - \ No newline at end of file diff --git a/CloudServices.Storage/StorageServicesConfig.cs b/CloudServices.Storage/StorageServicesConfig.cs index 8776fec..f2cb203 100644 --- a/CloudServices.Storage/StorageServicesConfig.cs +++ b/CloudServices.Storage/StorageServicesConfig.cs @@ -18,18 +18,23 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage { using System; using System.Collections.Generic; + using System.Linq; using System.Net.Http; + using System.Web.Http; + using System.Web.Http.Controllers; + using Microsoft.WindowsAzure; public class StorageServicesConfig { private const int DefaultMaximmumResponseSize = 1024 * 1024; private const int DefaultSasExpirationSize = 15; - private Func authorizeQueuesAccess; - private Func authorizeTablesAccess; - private Func authorizeBlobsAccess; - private Func authenticateRequest; + private Func authorizeQueuesAccess; + private Func authorizeTablesAccess; + private Func authorizeBlobsAccess; + private Func authenticateRequest; private Func mapUserName; + private IEnumerable delegatingHandlers; private int containerSasExpirationTime; private int blobsSasExpirationTime; @@ -37,7 +42,7 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage public StorageServicesConfig() { - this.DelegatingHandlers = new Type[] { }; + this.delegatingHandlers = new DelegatingHandler[] { }; } public CloudStorageAccount CloudStorageAccount { get; set; } @@ -90,7 +95,7 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage } } - public Func AuthorizeTablesAccess + public Func AuthorizeTablesAccess { get { @@ -100,7 +105,7 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage set { this.authorizeTablesAccess = value; } } - public Func AuthorizeQueuesAccess + public Func AuthorizeQueuesAccess { get { @@ -110,7 +115,7 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage set { this.authorizeQueuesAccess = value; } } - public Func AuthorizeBlobsAccess + public Func AuthorizeBlobsAccess { get { @@ -120,7 +125,7 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage set { this.authorizeBlobsAccess = value; } } - public Func AuthenticateRequest + public Func AuthenticateRequest { get { @@ -146,9 +151,21 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage set { this.mapUserName = value; } } - public IEnumerable DelegatingHandlers { get; set; } + public IEnumerable DelegatingHandlers + { + get + { + return this.delegatingHandlers; + } - private static bool DefaultAnonymousAccess(HttpRequestMessage message) + set + { + this.delegatingHandlers = value; + GlobalConfiguration.Configuration.AddDelegatingHandlers(value.ToArray()); + } + } + + private static bool DefaultAnonymousAccess(HttpActionContext message) { // By default, return always true (anonymous user) return true; @@ -160,4 +177,4 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage return string.Empty; } } -} +} \ No newline at end of file diff --git a/CloudServices.Storage/StorageServicesContext.cs b/CloudServices.Storage/StorageServicesContext.cs index a1cdb91..18238e9 100644 --- a/CloudServices.Storage/StorageServicesContext.cs +++ b/CloudServices.Storage/StorageServicesContext.cs @@ -18,8 +18,6 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage { using System; - using Microsoft.WindowsAzure.Samples.CloudServices.Storage.Properties; - public class StorageServicesContext { private static readonly StorageServicesContext Instance = new StorageServicesContext(); @@ -39,7 +37,7 @@ namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage public void Configure(Action configureAction) { if (configureAction == null) - throw new ArgumentException(Resource.ConfigureActionArgumentNullErrorMessage, "configureAction"); + throw new ArgumentException(Constants.ConfigureActionArgumentNullErrorMessage, "configureAction"); configureAction(this.config); } diff --git a/CloudServices.Storage/TablesProxyService.cs b/CloudServices.Storage/TablesProxyService.cs deleted file mode 100644 index 5817303..0000000 --- a/CloudServices.Storage/TablesProxyService.cs +++ /dev/null @@ -1,87 +0,0 @@ -// ---------------------------------------------------------------------------------- -// Microsoft Developer & Platform Evangelism -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, -// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES -// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. -// ---------------------------------------------------------------------------------- -// The example companies, organizations, products, domain names, -// e-mail addresses, logos, people, places, and events depicted -// herein are fictitious. No association with any real company, -// organization, product, domain name, email address, logo, person, -// places, or events is intended or should be inferred. -// ---------------------------------------------------------------------------------- - -namespace Microsoft.WindowsAzure.Samples.CloudServices.Storage -{ - using System; - using System.Net; - using System.Net.Http; - using System.ServiceModel; - using System.ServiceModel.Activation; - using System.ServiceModel.Web; - using Microsoft.WindowsAzure.Samples.CloudServices.Storage.Handlers; - using Microsoft.WindowsAzure.Samples.CloudServices.Storage.Helpers; - using Microsoft.WindowsAzure.Samples.CloudServices.Storage.Properties; - using Microsoft.WindowsAzure.Samples.CloudServices.Storage.Security; - - [ServiceContract] - [ServiceBehavior(IncludeExceptionDetailInFaults = true)] - [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)] - public class TablesProxyService - { - private static readonly AzureTablesProxyHandler Proxy = new AzureTablesProxyHandler(); - - [WebInvoke(Method = "POST", UriTemplate = "{*path}"), OperationContract, AuthorizeTablesAccess, CLSCompliant(false)] - public HttpResponseMessage HandlePost(HttpRequestMessage request, string path) - { - if (request == null) - throw Extensions.StorageException(HttpStatusCode.BadRequest, Resource.RequestCannotBeNullErrorMessage, Resource.RequestCannotBeNullErrorMessage); - - request.Properties[StorageProxyHandler.RequestedPathPropertyName] = path; - return Proxy.ProcessRequest(request); - } - - [WebInvoke(Method = "PUT", UriTemplate = "{*path}"), OperationContract, AuthorizeTablesAccess, CLSCompliant(false)] - public HttpResponseMessage HandlePut(HttpRequestMessage request, string path) - { - if (request == null) - throw Extensions.StorageException(HttpStatusCode.BadRequest, Resource.RequestCannotBeNullErrorMessage, Resource.RequestCannotBeNullErrorMessage); - - request.Properties[StorageProxyHandler.RequestedPathPropertyName] = path; - return Proxy.ProcessRequest(request); - } - - [WebInvoke(Method = "GET", UriTemplate = "{*path}"), OperationContract, AuthorizeTablesAccess, CLSCompliant(false)] - public HttpResponseMessage HandleGet(HttpRequestMessage request, string path) - { - if (request == null) - throw Extensions.StorageException(HttpStatusCode.BadRequest, Resource.RequestCannotBeNullErrorMessage, Resource.RequestCannotBeNullErrorMessage); - - request.Properties[StorageProxyHandler.RequestedPathPropertyName] = path; - return Proxy.ProcessRequest(request); - } - - [WebInvoke(Method = "DELETE", UriTemplate = "{*path}"), OperationContract, AuthorizeTablesAccess, CLSCompliant(false)] - public HttpResponseMessage HandleDelete(HttpRequestMessage request, string path) - { - if (request == null) - throw Extensions.StorageException(HttpStatusCode.BadRequest, Resource.RequestCannotBeNullErrorMessage, Resource.RequestCannotBeNullErrorMessage); - - request.Properties[StorageProxyHandler.RequestedPathPropertyName] = path; - return Proxy.ProcessRequest(request); - } - - [WebInvoke(Method = "MERGE", UriTemplate = "{*path}"), OperationContract, AuthorizeTablesAccess, CLSCompliant(false)] - public HttpResponseMessage HandleMerge(HttpRequestMessage request, string path) - { - if (request == null) - throw Extensions.StorageException(HttpStatusCode.BadRequest, Resource.RequestCannotBeNullErrorMessage, Resource.RequestCannotBeNullErrorMessage); - - request.Properties[StorageProxyHandler.RequestedPathPropertyName] = path; - return Proxy.ProcessRequest(request); - } - } -} \ No newline at end of file diff --git a/CloudServices.Storage/packages.config b/CloudServices.Storage/packages.config index de336c4..d11c313 100644 --- a/CloudServices.Storage/packages.config +++ b/CloudServices.Storage/packages.config @@ -1,10 +1,10 @@  - - - - - - + + + + + + \ No newline at end of file diff --git a/Common/Common.csproj b/Common/Common.csproj index 7c90db8..eb6c2d4 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -12,6 +12,8 @@ Common v4.0 512 + ..\ + true true @@ -81,6 +83,7 @@ +