Permalink
Browse files

More Auth Refactoring.

RequiredPermissions & RequiredRoles now fallback to UserAuthRepo on failed validation
Added RequestLogger
Added UserAuthId cookie to response
Added X-Method-Override feature
  • Loading branch information...
mythz committed Jan 22, 2012
1 parent 78cffae commit 3711518ab6a1564366adb17b074024f301211266
Showing with 314 additions and 97 deletions.
  1. +12 −0 src/ServiceStack.Interfaces/ServiceHost/ICanResolve.cs
  2. +1 −8 src/ServiceStack.Interfaces/ServiceHost/IHttpRequest.cs
  3. +15 −0 src/ServiceStack.Interfaces/ServiceHost/IRequestLogger.cs
  4. +2 −0 src/ServiceStack.Interfaces/ServiceStack.Interfaces.csproj
  5. +7 −0 src/ServiceStack.ServiceInterface/Auth/AuthProvider.cs
  6. +1 −0 src/ServiceStack.ServiceInterface/AuthFeature.cs
  7. +17 −12 src/ServiceStack.ServiceInterface/AuthenticateAttribute.cs
  8. +1 −3 src/ServiceStack.ServiceInterface/IServiceBase.cs
  9. +26 −14 src/ServiceStack.ServiceInterface/RequiredPermissionAttribute.cs
  10. +22 −11 src/ServiceStack.ServiceInterface/RequiredRoleAttribute.cs
  11. +5 −0 src/ServiceStack.ServiceInterface/RestServiceBase.cs
  12. +24 −3 src/ServiceStack.ServiceInterface/ServiceBase.cs
  13. +21 −27 src/ServiceStack.ServiceInterface/ServiceExtensions.cs
  14. +8 −2 src/ServiceStack.ServiceInterface/SessionFeature.cs
  15. +1 −0 src/ServiceStack.sln
  16. +1 −1 src/ServiceStack/Markdown/HtmlHelper.cs
  17. +1 −1 src/ServiceStack/MiniProfiler/UI/includes.js
  18. +1 −1 src/ServiceStack/Properties/AssemblyInfo.cs
  19. +3 −3 src/ServiceStack/ServiceHost/HttpRequestExtensions.cs
  20. +6 −1 src/ServiceStack/WebHost.EndPoints/Extensions/HttpListenerRequestWrapper.cs
  21. +4 −1 src/ServiceStack/WebHost.EndPoints/Extensions/HttpRequestWrapper.cs
  22. +1 −8 src/ServiceStack/WebHost.EndPoints/IAppHost.cs
  23. +0 −1 tests/ServiceStack.Common.Tests/OAuth/OAuthUserSessionTestsBase.cs
  24. +45 −0 tests/ServiceStack.WebHost.Endpoints.Tests/AuthTests.cs
  25. +1 −0 tests/ServiceStack.WebHost.IntegrationTests/ServiceStack.WebHost.IntegrationTests.csproj
  26. +49 −0 tests/ServiceStack.WebHost.IntegrationTests/Services/EchoMethodService.cs
  27. +39 −0 tests/ServiceStack.WebHost.IntegrationTests/Tests/RestWebServiceTests.cs
@@ -0,0 +1,12 @@
+namespace ServiceStack.ServiceHost
+{
+ public interface IResolver
+ {
+ /// <summary>
+ /// Resolve a dependency from the AppHost's IOC
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <returns></returns>
+ T TryResolve<T>();
+ }
+}
@@ -9,20 +9,13 @@ namespace ServiceStack.ServiceHost
/// <summary>
/// A thin wrapper around ASP.NET or HttpListener's HttpRequest
/// </summary>
- public interface IHttpRequest
+ public interface IHttpRequest : IResolver
{
/// <summary>
/// The underlying ASP.NET or HttpListener HttpRequest
/// </summary>
object OriginalRequest { get; }
- /// <summary>
- /// Resolve a dependency from the AppHost's IOC
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <returns></returns>
- T TryResolve<T>();
-
/// <summary>
/// The name of the service being called (e.g. Request DTO Name)
/// </summary>
@@ -0,0 +1,15 @@
+namespace ServiceStack.ServiceHost
+{
+ /// <summary>
+ /// Log every service request
+ /// </summary>
+ public interface IRequestLogger
+ {
+ /// <summary>
+ /// Log a request
+ /// </summary>
+ /// <param name="requestContext"></param>
+ /// <param name="requestDto"></param>
+ void Log(IRequestContext requestContext, object requestDto);
+ }
+}
@@ -211,9 +211,11 @@
<Compile Include="SearchIndex\FullTextIndexAttribute.cs" />
<Compile Include="SearchIndex\FullTextIndexDocumentAttribute.cs" />
<Compile Include="SearchIndex\FullTextIndexFieldAttribute.cs" />
+ <Compile Include="ServiceHost\ICanResolve.cs" />
<Compile Include="ServiceHost\ICookies.cs" />
<Compile Include="ServiceHost\IHasRequestFilter.cs" />
<Compile Include="ServiceHost\IHasResponseFilter.cs" />
+ <Compile Include="ServiceHost\IRequestLogger.cs" />
<Compile Include="ServiceHost\IRestPatchService.cs" />
<Compile Include="ServiceHost\EndpointAttributes.cs" />
<Compile Include="ServiceHost\Feature.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<IHttpResponse>();
+ if (httpRes != null)
+ {
+ httpRes.Cookies.AddPermanentCookie(HttpHeaders.XUserAuthId, session.UserAuthId);
+ }
}
public virtual void OnSaveUserAuth(IServiceBase authService, IAuthSession session) { }
@@ -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<IAuthSession> sessionFactory, params IAuthProvider[] authProviders)
{
@@ -15,7 +15,7 @@ namespace ServiceStack.ServiceInterface
/// Indicates that the request dto, which is associated with this attribute,
/// requires authentication.
/// </summary>
- [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>();
- 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>();
+ 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
+ });
+ }
+ }
}
}
@@ -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
/// <returns></returns>
T ResolveService<T>();
- T TryResolve<T>();
-
IRequestContext RequestContext { get; }
}
}
@@ -12,8 +12,8 @@ namespace ServiceStack.ServiceInterface
/// <summary>
/// Indicates that the request dto, which is associated with this attribute,
/// can only execute, if the user has specific permissions.
- /// </summary>
- [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method /*MVC Actions*/, Inherited = false, AllowMultiple = true)]
public class RequiredPermissionAttribute : RequestFilterAttribute
{
public List<string> 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<IUserAuthRepository>();
- 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<IUserAuthRepository>();
+
+ 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
@@ -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.
/// </summary>
- [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method /*MVC Actions*/, Inherited = false, AllowMultiple = true)]
public class RequiredRoleAttribute : RequestFilterAttribute
{
public List<string> 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<IUserAuthRepository>();
+
+ if (userAuthRepo == null) return false;
- var userAuthRepo = req.TryResolve<IUserAuthRepository>();
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
@@ -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));
}
@@ -37,8 +37,7 @@ public abstract class ServiceBase<TRequest>
/// Combined service error logs are maintained in 'urn:ServiceErrors:All'
/// </summary>
public const string CombinedServiceLogId = "All";
-
-
+
/// <summary>
/// Can be overriden to supply Custom 'ServiceName' error logs
/// </summary>
@@ -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; }
+
+ /// <summary>
+ /// Easy way to log all requests
+ /// </summary>
+ /// <param name="requestDto"></param>
+ 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())
Oops, something went wrong.

0 comments on commit 3711518

Please sign in to comment.