From 3711518ab6a1564366adb17b074024f301211266 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sun, 22 Jan 2012 04:00:15 -0500 Subject: [PATCH] More Auth Refactoring. RequiredPermissions & RequiredRoles now fallback to UserAuthRepo on failed validation Added RequestLogger Added UserAuthId cookie to response Added X-Method-Override feature --- .../ServiceHost/ICanResolve.cs | 12 +++++ .../ServiceHost/IHttpRequest.cs | 9 +--- .../ServiceHost/IRequestLogger.cs | 15 ++++++ .../ServiceStack.Interfaces.csproj | 2 + .../Auth/AuthProvider.cs | 7 +++ .../AuthFeature.cs | 1 + .../AuthenticateAttribute.cs | 29 ++++++----- .../IServiceBase.cs | 4 +- .../RequiredPermissionAttribute.cs | 40 +++++++++------ .../RequiredRoleAttribute.cs | 33 ++++++++----- .../RestServiceBase.cs | 5 ++ .../ServiceBase.cs | 27 ++++++++-- .../ServiceExtensions.cs | 48 ++++++++---------- .../SessionFeature.cs | 10 +++- src/ServiceStack.sln | 1 + src/ServiceStack/Markdown/HtmlHelper.cs | 2 +- src/ServiceStack/MiniProfiler/UI/includes.js | 2 +- src/ServiceStack/Properties/AssemblyInfo.cs | 2 +- .../ServiceHost/HttpRequestExtensions.cs | 6 +-- .../Extensions/HttpListenerRequestWrapper.cs | 7 ++- .../Extensions/HttpRequestWrapper.cs | 5 +- .../WebHost.EndPoints/IAppHost.cs | 9 +--- .../OAuth/OAuthUserSessionTestsBase.cs | 1 - .../AuthTests.cs | 45 +++++++++++++++++ ...rviceStack.WebHost.IntegrationTests.csproj | 1 + .../Services/EchoMethodService.cs | 49 +++++++++++++++++++ .../Tests/RestWebServiceTests.cs | 39 +++++++++++++++ 27 files changed, 314 insertions(+), 97 deletions(-) create mode 100644 src/ServiceStack.Interfaces/ServiceHost/ICanResolve.cs create mode 100644 src/ServiceStack.Interfaces/ServiceHost/IRequestLogger.cs create mode 100644 tests/ServiceStack.WebHost.IntegrationTests/Services/EchoMethodService.cs diff --git a/src/ServiceStack.Interfaces/ServiceHost/ICanResolve.cs b/src/ServiceStack.Interfaces/ServiceHost/ICanResolve.cs new file mode 100644 index 00000000000..2184c4517e8 --- /dev/null +++ b/src/ServiceStack.Interfaces/ServiceHost/ICanResolve.cs @@ -0,0 +1,12 @@ +namespace ServiceStack.ServiceHost +{ + public interface IResolver + { + /// + /// Resolve a dependency from the AppHost's IOC + /// + /// + /// + T TryResolve(); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Interfaces/ServiceHost/IHttpRequest.cs b/src/ServiceStack.Interfaces/ServiceHost/IHttpRequest.cs index 4e9363556c9..eb5146781ba 100644 --- a/src/ServiceStack.Interfaces/ServiceHost/IHttpRequest.cs +++ b/src/ServiceStack.Interfaces/ServiceHost/IHttpRequest.cs @@ -9,20 +9,13 @@ namespace ServiceStack.ServiceHost /// /// A thin wrapper around ASP.NET or HttpListener's HttpRequest /// - public interface IHttpRequest + public interface IHttpRequest : IResolver { /// /// The underlying ASP.NET or HttpListener HttpRequest /// object OriginalRequest { get; } - /// - /// Resolve a dependency from the AppHost's IOC - /// - /// - /// - T TryResolve(); - /// /// The name of the service being called (e.g. Request DTO Name) /// diff --git a/src/ServiceStack.Interfaces/ServiceHost/IRequestLogger.cs b/src/ServiceStack.Interfaces/ServiceHost/IRequestLogger.cs new file mode 100644 index 00000000000..5a9edb2dfe9 --- /dev/null +++ b/src/ServiceStack.Interfaces/ServiceHost/IRequestLogger.cs @@ -0,0 +1,15 @@ +namespace ServiceStack.ServiceHost +{ + /// + /// Log every service request + /// + public interface IRequestLogger + { + /// + /// Log a request + /// + /// + /// + void Log(IRequestContext requestContext, object requestDto); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Interfaces/ServiceStack.Interfaces.csproj b/src/ServiceStack.Interfaces/ServiceStack.Interfaces.csproj index e16d76a999a..98f2d9f1060 100644 --- a/src/ServiceStack.Interfaces/ServiceStack.Interfaces.csproj +++ b/src/ServiceStack.Interfaces/ServiceStack.Interfaces.csproj @@ -211,9 +211,11 @@ + + diff --git a/src/ServiceStack.ServiceInterface/Auth/AuthProvider.cs b/src/ServiceStack.ServiceInterface/Auth/AuthProvider.cs index 22179b1a128..7b4360d26cf 100644 --- a/src/ServiceStack.ServiceInterface/Auth/AuthProvider.cs +++ b/src/ServiceStack.ServiceInterface/Auth/AuthProvider.cs @@ -4,6 +4,7 @@ using ServiceStack.Common.Web; using ServiceStack.Configuration; using ServiceStack.Logging; +using ServiceStack.ServiceHost; using ServiceStack.Text; namespace ServiceStack.ServiceInterface.Auth @@ -73,6 +74,12 @@ protected virtual void SaveUserAuth(IServiceBase authService, IAuthSession sessi } authRepo.SaveUserAuth(session); + + var httpRes = authService.RequestContext.Get(); + if (httpRes != null) + { + httpRes.Cookies.AddPermanentCookie(HttpHeaders.XUserAuthId, session.UserAuthId); + } } public virtual void OnSaveUserAuth(IServiceBase authService, IAuthSession session) { } diff --git a/src/ServiceStack.ServiceInterface/AuthFeature.cs b/src/ServiceStack.ServiceInterface/AuthFeature.cs index a1b48193566..d61e22d8d84 100644 --- a/src/ServiceStack.ServiceInterface/AuthFeature.cs +++ b/src/ServiceStack.ServiceInterface/AuthFeature.cs @@ -10,6 +10,7 @@ namespace ServiceStack.ServiceInterface public class AuthFeature { public const string AdminRole = "Admin"; + public static bool AddUserIdHttpHeader = true; public static void Init(IAppHost appHost, Func sessionFactory, params IAuthProvider[] authProviders) { diff --git a/src/ServiceStack.ServiceInterface/AuthenticateAttribute.cs b/src/ServiceStack.ServiceInterface/AuthenticateAttribute.cs index a5edec805a2..2396ae897cd 100644 --- a/src/ServiceStack.ServiceInterface/AuthenticateAttribute.cs +++ b/src/ServiceStack.ServiceInterface/AuthenticateAttribute.cs @@ -15,7 +15,7 @@ namespace ServiceStack.ServiceInterface /// Indicates that the request dto, which is associated with this attribute, /// requires authentication. /// - [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method /*MVC Actions*/, Inherited = false, AllowMultiple = false)] public class AuthenticateAttribute : RequestFilterAttribute { public string Provider { get; set; } @@ -59,17 +59,7 @@ public override void Execute(IHttpRequest req, IHttpResponse res, object request return; } - var userPass = req.GetBasicAuthUserAndPassword(); - if (userPass != null) - { - var authService = req.TryResolve(); - authService.RequestContext = new HttpRequestContext(req, res, requestDto); - var response = authService.Post(new Auth.Auth { - provider = BasicAuthProvider.Name, - UserName = userPass.Value.Key, - Password = userPass.Value.Value - }); - } + AuthenticateIfBasicAuth(req, res); using (var cache = req.GetCacheClient()) { @@ -86,5 +76,20 @@ public override void Execute(IHttpRequest req, IHttpResponse res, object request } } } + + public static void AuthenticateIfBasicAuth(IHttpRequest req, IHttpResponse res) + { + var userPass = req.GetBasicAuthUserAndPassword(); + if (userPass != null) + { + var authService = req.TryResolve(); + authService.RequestContext = new HttpRequestContext(req, res, null); + var response = authService.Post(new Auth.Auth { + provider = BasicAuthProvider.Name, + UserName = userPass.Value.Key, + Password = userPass.Value.Value + }); + } + } } } \ No newline at end of file diff --git a/src/ServiceStack.ServiceInterface/IServiceBase.cs b/src/ServiceStack.ServiceInterface/IServiceBase.cs index d944315ec66..9dd49422e8e 100644 --- a/src/ServiceStack.ServiceInterface/IServiceBase.cs +++ b/src/ServiceStack.ServiceInterface/IServiceBase.cs @@ -3,7 +3,7 @@ namespace ServiceStack.ServiceInterface { - public interface IServiceBase + public interface IServiceBase : IResolver { IAppHost AppHost { get; set; } @@ -14,8 +14,6 @@ public interface IServiceBase /// T ResolveService(); - T TryResolve(); - IRequestContext RequestContext { get; } } } \ No newline at end of file diff --git a/src/ServiceStack.ServiceInterface/RequiredPermissionAttribute.cs b/src/ServiceStack.ServiceInterface/RequiredPermissionAttribute.cs index dad81e44b5f..0604f82ead4 100644 --- a/src/ServiceStack.ServiceInterface/RequiredPermissionAttribute.cs +++ b/src/ServiceStack.ServiceInterface/RequiredPermissionAttribute.cs @@ -12,8 +12,8 @@ namespace ServiceStack.ServiceInterface /// /// Indicates that the request dto, which is associated with this attribute, /// can only execute, if the user has specific permissions. - /// - [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method /*MVC Actions*/, Inherited = false, AllowMultiple = true)] public class RequiredPermissionAttribute : RequestFilterAttribute { public List RequiredPermissions { get; set; } @@ -32,25 +32,37 @@ public RequiredPermissionAttribute(ApplyTo applyTo, params string[] permissions) public override void Execute(IHttpRequest req, IHttpResponse res, object requestDto) { + AuthenticateAttribute.AuthenticateIfBasicAuth(req, res); + var session = req.GetSession(); - if (HasAllPermissions(session)) return; - - var userAuthRepo = req.TryResolve(); - var userAuth = userAuthRepo.GetUserAuth(session, null); - session.UpdateSession(userAuth); - - if (HasAllPermissions(session)) - { - req.SaveSession(session); - return; - } + if (HasAllPermissions(req, session)) return; res.StatusCode = (int)HttpStatusCode.Unauthorized; res.StatusDescription = "Invalid Permissions"; res.Close(); } - private bool HasAllPermissions(IAuthSession session) + public bool HasAllPermissions(IHttpRequest req, IAuthSession session, IUserAuthRepository userAuthRepo=null) + { + if (HasAllPermissions(session)) return true; + + if (userAuthRepo == null) + userAuthRepo = req.TryResolve(); + + if (userAuthRepo == null) return false; + + var userAuth = userAuthRepo.GetUserAuth(session, null); + session.UpdateSession(userAuth); + + if (HasAllPermissions(session)) + { + req.SaveSession(session); + return true; + } + return false; + } + + public bool HasAllPermissions(IAuthSession session) { return this.RequiredPermissions .All(requiredPermission => session != null diff --git a/src/ServiceStack.ServiceInterface/RequiredRoleAttribute.cs b/src/ServiceStack.ServiceInterface/RequiredRoleAttribute.cs index a440d4d342c..47e192a2db7 100644 --- a/src/ServiceStack.ServiceInterface/RequiredRoleAttribute.cs +++ b/src/ServiceStack.ServiceInterface/RequiredRoleAttribute.cs @@ -16,7 +16,7 @@ namespace ServiceStack.ServiceInterface /// Indicates that the request dto, which is associated with this attribute, /// can only execute, if the user has specific roles. /// - [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method /*MVC Actions*/, Inherited = false, AllowMultiple = true)] public class RequiredRoleAttribute : RequestFilterAttribute { public List RequiredRoles { get; set; } @@ -32,29 +32,40 @@ public RequiredRoleAttribute(ApplyTo applyTo, params string[] roles) this.RequiredRoles = roles.ToList(); this.ApplyTo = applyTo; } - - + public override void Execute(IHttpRequest req, IHttpResponse res, object requestDto) { + AuthenticateAttribute.AuthenticateIfBasicAuth(req, res); + var session = req.GetSession(); - if (HasAllRoles(session)) return; + if (HasAllRoles(req, session)) return; + + res.StatusCode = (int)HttpStatusCode.Unauthorized; + res.StatusDescription = "Invalid Role"; + res.Close(); + } + + public bool HasAllRoles(IHttpRequest req, IAuthSession session, IUserAuthRepository userAuthRepo=null) + { + if (HasAllRoles(session)) return true; + + if (userAuthRepo == null) + userAuthRepo = req.TryResolve(); + + if (userAuthRepo == null) return false; - var userAuthRepo = req.TryResolve(); var userAuth = userAuthRepo.GetUserAuth(session, null); session.UpdateSession(userAuth); if (HasAllRoles(session)) { req.SaveSession(session); - return; + return true; } - - res.StatusCode = (int)HttpStatusCode.Unauthorized; - res.StatusDescription = "Invalid Role"; - res.Close(); + return false; } - private bool HasAllRoles(IAuthSession session) + public bool HasAllRoles(IAuthSession session) { return this.RequiredRoles .All(requiredRole => session != null diff --git a/src/ServiceStack.ServiceInterface/RestServiceBase.cs b/src/ServiceStack.ServiceInterface/RestServiceBase.cs index 8f941f25180..62381783f09 100644 --- a/src/ServiceStack.ServiceInterface/RestServiceBase.cs +++ b/src/ServiceStack.ServiceInterface/RestServiceBase.cs @@ -50,6 +50,7 @@ public object Get(TRequest request) { try { + OnEachRequest(request); OnBeforeExecute(request); return OnAfterExecute(OnGet(request)); } @@ -86,6 +87,7 @@ public object Put(TRequest request) { try { + OnEachRequest(request); OnBeforeExecute(request); return OnAfterExecute(OnPut(request)); } @@ -122,6 +124,7 @@ public object Post(TRequest request) { try { + OnEachRequest(request); OnBeforeExecute(request); return OnAfterExecute(OnPost(request)); } @@ -158,6 +161,7 @@ public object Delete(TRequest request) { try { + OnEachRequest(request); OnBeforeExecute(request); return OnAfterExecute(OnDelete(request)); } @@ -194,6 +198,7 @@ public object Patch(TRequest request) { try { + OnEachRequest(request); OnBeforeExecute(request); return OnAfterExecute(OnPatch(request)); } diff --git a/src/ServiceStack.ServiceInterface/ServiceBase.cs b/src/ServiceStack.ServiceInterface/ServiceBase.cs index 9bec8a7943a..c1558adcf88 100644 --- a/src/ServiceStack.ServiceInterface/ServiceBase.cs +++ b/src/ServiceStack.ServiceInterface/ServiceBase.cs @@ -37,8 +37,7 @@ public abstract class ServiceBase /// Combined service error logs are maintained in 'urn:ServiceErrors:All' /// public const string CombinedServiceLogId = "All"; - - + /// /// Can be overriden to supply Custom 'ServiceName' error logs /// @@ -65,7 +64,26 @@ public virtual IAppHost AppHost public IRequestContext RequestContext { get; set; } - public ISessionFactory SessionFactory { get; set; } + public ISessionFactory SessionFactory { get; set; } + + public IRequestLogger RequestLogger { get; set; } + + /// + /// Easy way to log all requests + /// + /// + protected void OnEachRequest(object requestDto) + { + if (this.RequestLogger == null) return; + try + { + RequestLogger.Log(this.RequestContext, requestDto); + } + catch (Exception ex) + { + Log.Error("Error while logging request: " + requestDto.Dump(), ex); + } + } private ISession session; public ISession Session @@ -164,6 +182,7 @@ public object Execute(TRequest request) { try { + OnEachRequest(request); OnBeforeExecute(request); return OnAfterExecute(Run(request)); } @@ -263,6 +282,8 @@ public virtual object ExecuteAsync(TRequest request) return Execute(request); } + OnEachRequest(request); + //Capture and persist this async request on this Services 'In Queue' //for execution after this request has been completed using (var producer = MessageFactory.CreateMessageProducer()) diff --git a/src/ServiceStack.ServiceInterface/ServiceExtensions.cs b/src/ServiceStack.ServiceInterface/ServiceExtensions.cs index 5ce0df51cc5..e4aea4a708e 100644 --- a/src/ServiceStack.ServiceInterface/ServiceExtensions.cs +++ b/src/ServiceStack.ServiceInterface/ServiceExtensions.cs @@ -78,56 +78,50 @@ public static string GetSessionId(this IServiceBase service) /// private static readonly MemoryCacheClient DefaultCache = new MemoryCacheClient { FlushOnDispose = true }; - public static ICacheClient GetCacheClient(this IServiceBase service) + public static ICacheClient GetCacheClient(this IResolver service) { return service.TryResolve() ?? (ICacheClient)service.TryResolve() ?? DefaultCache; } - public static ICacheClient GetCacheClient(this IAppHost appHost) - { - return appHost.TryResolve() - ?? (ICacheClient)appHost.TryResolve() - ?? DefaultCache; - } - - public static ICacheClient GetCacheClient(this IHttpRequest httpRequest) - { - return httpRequest.TryResolve() - ?? (ICacheClient)httpRequest.TryResolve() - ?? DefaultCache; - } - public static void SaveSession(this IServiceBase service, IAuthSession session) { - using (var cache = service.GetCacheClient()) - { - var sessionKey = SessionFeature.GetSessionKey(service.GetSessionId()); - cache.Set(sessionKey, session); - service.RequestContext.Get().SaveSession(session); - } + if (service == null) return; + + service.RequestContext.Get().SaveSession(session); } public static void RemoveSession(this IServiceBase service) { - using (var cache = service.GetCacheClient()) - { - var sessionKey = SessionFeature.GetSessionKey(service.GetSessionId()); - cache.Remove(sessionKey); - service.RequestContext.Get().RemoveSession(); - } + if (service == null) return; + + service.RequestContext.Get().RemoveSession(); } public static void SaveSession(this IHttpRequest httpReq, IAuthSession session) { if (httpReq == null) return; + + using (var cache = httpReq.GetCacheClient()) + { + var sessionKey = SessionFeature.GetSessionKey(httpReq.GetSessionId()); + cache.Set(sessionKey, session); + } + httpReq.Items[RequestItemsSessionKey] = session; } public static void RemoveSession(this IHttpRequest httpReq) { if (httpReq == null) return; + + using (var cache = httpReq.GetCacheClient()) + { + var sessionKey = SessionFeature.GetSessionKey(httpReq.GetSessionId()); + cache.Remove(sessionKey); + } + httpReq.Items.Remove(RequestItemsSessionKey); } diff --git a/src/ServiceStack.ServiceInterface/SessionFeature.cs b/src/ServiceStack.ServiceInterface/SessionFeature.cs index 137467e9337..11321a45502 100644 --- a/src/ServiceStack.ServiceInterface/SessionFeature.cs +++ b/src/ServiceStack.ServiceInterface/SessionFeature.cs @@ -5,6 +5,7 @@ using System.Web; using ServiceStack.Common; using ServiceStack.Common.Utils; +using ServiceStack.Common.Web; using ServiceStack.ServiceHost; using ServiceStack.ServiceInterface.Auth; using ServiceStack.WebHost.Endpoints; @@ -27,6 +28,7 @@ public static class SessionFeature public const string SessionId = "ss-id"; public const string PermanentSessionId = "ss-pid"; public const string SessionOptionsKey = "ss-opt"; + public const string XUserAuthId = HttpHeaders.XUserAuthId; private static bool alreadyConfigured; @@ -158,12 +160,16 @@ public static string GetSessionId() public static IHttpRequest ToRequest(this HttpRequest aspnetHttpReq) { - return new HttpRequestWrapper(aspnetHttpReq); + return new HttpRequestWrapper(aspnetHttpReq) { + Container = AppHostBase.Instance.Container + }; } public static IHttpRequest ToRequest(this HttpListenerRequest listenerHttpReq) { - return new HttpListenerRequestWrapper(listenerHttpReq); + return new HttpListenerRequestWrapper(listenerHttpReq) { + Container = AppHostBase.Instance.Container + }; } public static IHttpResponse ToResponse(this HttpResponse aspnetHttpRes) diff --git a/src/ServiceStack.sln b/src/ServiceStack.sln index b20c5759ef4..1e3810135ac 100644 --- a/src/ServiceStack.sln +++ b/src/ServiceStack.sln @@ -21,6 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{EB65E771 ..\NuGet\ServiceStack.Common\servicestack.common.nuspec = ..\NuGet\ServiceStack.Common\servicestack.common.nuspec ..\NuGet\ServiceStack.Host.AspNet\servicestack.host.aspnet.nuspec = ..\NuGet\ServiceStack.Host.AspNet\servicestack.host.aspnet.nuspec ..\NuGet\ServiceStack.Host.Mvc\servicestack.host.mvc.nuspec = ..\NuGet\ServiceStack.Host.Mvc\servicestack.host.mvc.nuspec + ..\NuGet\ServiceStack.Mvc\servicestack.mvc.nuspec = ..\NuGet\ServiceStack.Mvc\servicestack.mvc.nuspec ..\NuGet\ServiceStack\servicestack.nuspec = ..\NuGet\ServiceStack\servicestack.nuspec EndProjectSection EndProject diff --git a/src/ServiceStack/Markdown/HtmlHelper.cs b/src/ServiceStack/Markdown/HtmlHelper.cs index c9d2d92af3e..8ffc6953e2f 100644 --- a/src/ServiceStack/Markdown/HtmlHelper.cs +++ b/src/ServiceStack/Markdown/HtmlHelper.cs @@ -239,7 +239,7 @@ public MvcHtmlString HttpMethodOverride(string httpMethod) var tagBuilder = new TagBuilder("input"); tagBuilder.Attributes["type"] = "hidden"; - tagBuilder.Attributes["name"] = HttpHeaders.XHttpMethodOverrideKey; + tagBuilder.Attributes["name"] = HttpHeaders.XHttpMethodOverride; tagBuilder.Attributes["value"] = httpMethod; return tagBuilder.ToMvcHtmlString(TagRenderMode.SelfClosing); diff --git a/src/ServiceStack/MiniProfiler/UI/includes.js b/src/ServiceStack/MiniProfiler/UI/includes.js index c918f47f9be..f5c033260d0 100644 --- a/src/ServiceStack/MiniProfiler/UI/includes.js +++ b/src/ServiceStack/MiniProfiler/UI/includes.js @@ -609,7 +609,7 @@ var MiniProfiler = (function ($) shareUrl: function (id) { - return options.path + 'ss-profiler-results?id=' + id; + return options.path + 'ss-results?id=' + id; }, getSqlTimings: function (root) diff --git a/src/ServiceStack/Properties/AssemblyInfo.cs b/src/ServiceStack/Properties/AssemblyInfo.cs index 6c696d0e51a..00ef1e37f80 100644 --- a/src/ServiceStack/Properties/AssemblyInfo.cs +++ b/src/ServiceStack/Properties/AssemblyInfo.cs @@ -33,5 +33,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.3.0.*")] +[assembly: AssemblyVersion("3.3.4.*")] //[assembly: AssemblyFileVersion("1.0.*")] diff --git a/src/ServiceStack/ServiceHost/HttpRequestExtensions.cs b/src/ServiceStack/ServiceHost/HttpRequestExtensions.cs index 2f3f6bd15bf..5bf5fd56672 100644 --- a/src/ServiceStack/ServiceHost/HttpRequestExtensions.cs +++ b/src/ServiceStack/ServiceHost/HttpRequestExtensions.cs @@ -135,9 +135,9 @@ public static string GetHttpMethodOverride(this IHttpRequest httpReq) return httpMethod; var overrideHttpMethod = - httpReq.Headers[HttpHeaders.XHttpMethodOverrideKey].ToNullIfEmpty() - ?? httpReq.FormData[HttpHeaders.XHttpMethodOverrideKey].ToNullIfEmpty() - ?? httpReq.QueryString[HttpHeaders.XHttpMethodOverrideKey].ToNullIfEmpty(); + httpReq.Headers[HttpHeaders.XHttpMethodOverride].ToNullIfEmpty() + ?? httpReq.FormData[HttpHeaders.XHttpMethodOverride].ToNullIfEmpty() + ?? httpReq.QueryString[HttpHeaders.XHttpMethodOverride].ToNullIfEmpty(); if (overrideHttpMethod != null) { diff --git a/src/ServiceStack/WebHost.EndPoints/Extensions/HttpListenerRequestWrapper.cs b/src/ServiceStack/WebHost.EndPoints/Extensions/HttpListenerRequestWrapper.cs index 7112c7f35a9..f0581a9c524 100644 --- a/src/ServiceStack/WebHost.EndPoints/Extensions/HttpListenerRequestWrapper.cs +++ b/src/ServiceStack/WebHost.EndPoints/Extensions/HttpListenerRequestWrapper.cs @@ -200,9 +200,14 @@ public NameValueCollection FormData get { return this.Form; } } + private string httpMethod; public string HttpMethod { - get { return request.HttpMethod; } + get + { + return httpMethod + ?? (httpMethod = request.Headers[Common.Web.HttpHeaders.XHttpMethodOverride] ?? request.HttpMethod); + } } public string ContentType diff --git a/src/ServiceStack/WebHost.EndPoints/Extensions/HttpRequestWrapper.cs b/src/ServiceStack/WebHost.EndPoints/Extensions/HttpRequestWrapper.cs index 2789cdfc4bb..2603ee024af 100644 --- a/src/ServiceStack/WebHost.EndPoints/Extensions/HttpRequestWrapper.cs +++ b/src/ServiceStack/WebHost.EndPoints/Extensions/HttpRequestWrapper.cs @@ -58,9 +58,12 @@ public string ContentType get { return request.ContentType; } } + private string httpMethod; public string HttpMethod { - get { return request.HttpMethod; } + get { return httpMethod + ?? (httpMethod = request.Headers[Common.Web.HttpHeaders.XHttpMethodOverride] ?? request.HttpMethod); + } } public string UserAgent diff --git a/src/ServiceStack/WebHost.EndPoints/IAppHost.cs b/src/ServiceStack/WebHost.EndPoints/IAppHost.cs index f0fb65ab1f1..e454326fc76 100644 --- a/src/ServiceStack/WebHost.EndPoints/IAppHost.cs +++ b/src/ServiceStack/WebHost.EndPoints/IAppHost.cs @@ -7,7 +7,7 @@ namespace ServiceStack.WebHost.Endpoints /// /// ASP.NET or HttpListener ServiceStack host /// - public interface IAppHost + public interface IAppHost : IResolver { /// /// Register dependency in AppHost IOC on Startup @@ -23,13 +23,6 @@ public interface IAppHost /// void RegisterAs() where T : TAs; - /// - /// Resolve a dependency from the AppHost IOC - /// - /// - /// - T TryResolve(); - /// /// Register custom ContentType serializers /// diff --git a/tests/ServiceStack.Common.Tests/OAuth/OAuthUserSessionTestsBase.cs b/tests/ServiceStack.Common.Tests/OAuth/OAuthUserSessionTestsBase.cs index a26968d389c..aaf2fe880f1 100644 --- a/tests/ServiceStack.Common.Tests/OAuth/OAuthUserSessionTestsBase.cs +++ b/tests/ServiceStack.Common.Tests/OAuth/OAuthUserSessionTestsBase.cs @@ -34,7 +34,6 @@ public static AuthUserSession GetNewSession2() public CredentialsAuthProvider GetCredentialsAuthConfig() { return new CredentialsAuthProvider(new AppSettings()) { - AuthHttpGateway = new MockAuthHttpGateway(), }; } diff --git a/tests/ServiceStack.WebHost.Endpoints.Tests/AuthTests.cs b/tests/ServiceStack.WebHost.Endpoints.Tests/AuthTests.cs index 91e3d553105..899843da999 100644 --- a/tests/ServiceStack.WebHost.Endpoints.Tests/AuthTests.cs +++ b/tests/ServiceStack.WebHost.Endpoints.Tests/AuthTests.cs @@ -6,6 +6,7 @@ using ServiceStack.CacheAccess.Providers; using ServiceStack.Service; using ServiceStack.ServiceClient.Web; +using ServiceStack.ServiceHost; using ServiceStack.ServiceInterface; using ServiceStack.ServiceInterface.Auth; using ServiceStack.ServiceInterface.ServiceModel; @@ -35,8 +36,36 @@ protected override object Run(Secured request) } } + public class RequiresRole + { + public string Name { get; set; } + } + + public class RequiresRoleResponse + { + public string Result { get; set; } + + public ResponseStatus ResponseStatus { get; set; } + } + + [RequiredRole("TheRole")] + public class RequiresRoleService : ServiceBase + { + protected override object Run(RequiresRole request) + { + return new RequiresRoleResponse { Result = request.Name }; + } + } + public class CustomUserSession : AuthUserSession { + public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, System.Collections.Generic.Dictionary authInfo) + { + if (!session.Roles.Contains("TheRole")) + session.Roles.Add("TheRole"); + + authService.RequestContext.Get().SaveSession(session); + } } public class AuthTests @@ -196,6 +225,22 @@ public void Does_work_with_CredentailsAuth() Assert.Fail(webEx.Message); } } + + [Test] + public void Can_call_RequiredRole_service_with_BasicAuth() + { + try + { + var client = GetClientWithUserPassword(); + var request = new RequiresRole { Name = "test" }; + var response = client.Send(request); + Assert.That(response.Result, Is.EqualTo(request.Name)); + } + catch (WebServiceException webEx) + { + Assert.Fail(webEx.Message); + } + } } } \ No newline at end of file diff --git a/tests/ServiceStack.WebHost.IntegrationTests/ServiceStack.WebHost.IntegrationTests.csproj b/tests/ServiceStack.WebHost.IntegrationTests/ServiceStack.WebHost.IntegrationTests.csproj index afeffb4b197..36c04609bfa 100644 --- a/tests/ServiceStack.WebHost.IntegrationTests/ServiceStack.WebHost.IntegrationTests.csproj +++ b/tests/ServiceStack.WebHost.IntegrationTests/ServiceStack.WebHost.IntegrationTests.csproj @@ -167,6 +167,7 @@ Default.aspx + diff --git a/tests/ServiceStack.WebHost.IntegrationTests/Services/EchoMethodService.cs b/tests/ServiceStack.WebHost.IntegrationTests/Services/EchoMethodService.cs new file mode 100644 index 00000000000..f7f7ffef29f --- /dev/null +++ b/tests/ServiceStack.WebHost.IntegrationTests/Services/EchoMethodService.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.Serialization; +using ServiceStack.Common.Web; +using ServiceStack.ServiceHost; +using ServiceStack.ServiceInterface; + +namespace ServiceStack.WebHost.IntegrationTests.Services +{ + [RestService("/echomethod")] + public class EchoMethod + { + } + + [DataContract] + public class EchoMethodResponse + { + [DataMember] + public string Result { get; set; } + } + + public class EchoMethodService + : RestServiceBase + { + public override object OnGet(EchoMethod request) + { + return new EchoMethodResponse { Result = HttpMethods.Get }; + } + + public override object OnPost(EchoMethod request) + { + return new EchoMethodResponse { Result = HttpMethods.Post }; + } + + public override object OnPut(EchoMethod request) + { + return new EchoMethodResponse { Result = HttpMethods.Put }; + } + + public override object OnDelete(EchoMethod request) + { + return new EchoMethodResponse { Result = HttpMethods.Delete }; + } + + public override object OnPatch(EchoMethod request) + { + return new EchoMethodResponse { Result = HttpMethods.Patch }; + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.WebHost.IntegrationTests/Tests/RestWebServiceTests.cs b/tests/ServiceStack.WebHost.IntegrationTests/Tests/RestWebServiceTests.cs index 9735d508f56..d176b9eee9c 100644 --- a/tests/ServiceStack.WebHost.IntegrationTests/Tests/RestWebServiceTests.cs +++ b/tests/ServiceStack.WebHost.IntegrationTests/Tests/RestWebServiceTests.cs @@ -70,6 +70,45 @@ public void Can_call_EchoRequest_with_QueryString() }); } + private HttpWebResponse EmulateHttpMethod(string emulateMethod, string useMethod) + { + var webRequest = (HttpWebRequest) WebRequest.Create(ServiceClientBaseUri + "/echomethod"); + webRequest.Accept = ContentType.Json; + webRequest.Method = useMethod; + webRequest.Headers[HttpHeaders.XHttpMethodOverride] = emulateMethod; + if (useMethod == HttpMethods.Post) + webRequest.ContentLength = 0; + var response = (HttpWebResponse) webRequest.GetResponse(); + return response; + } + + [Test] + public void Can_emulate_Put_HttpMethod_with_POST() + { + var response = EmulateHttpMethod(HttpMethods.Put, HttpMethods.Post); + + AssertResponse(response, ContentType.Json, x => + Assert.That(x.Result, Is.EqualTo(HttpMethods.Put))); + } + + [Test] + public void Can_emulate_Put_HttpMethod_with_GET() + { + var response = EmulateHttpMethod(HttpMethods.Put, HttpMethods.Get); + + AssertResponse(response, ContentType.Json, x => + Assert.That(x.Result, Is.EqualTo(HttpMethods.Put))); + } + + [Test] + public void Can_emulate_Delete_HttpMethod_with_GET() + { + var response = EmulateHttpMethod(HttpMethods.Delete, HttpMethods.Get); + + AssertResponse(response, ContentType.Json, x => + Assert.That(x.Result, Is.EqualTo(HttpMethods.Delete))); + } + [Test] public void Can_call_WildCardRequest_with_alternate_WildCard_defined() {