Created
April 23, 2014 09:36
-
-
Save b1ff/11208757 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[TestFixture] | |
public class FormsAuthenticationFilterTests | |
{ | |
private class TestController : ApiController | |
{ | |
[AllowAnonymous] | |
public void PublicMethod() | |
{ | |
} | |
public void MethodWithAuth() | |
{ | |
} | |
} | |
private FormsAuthenticationFilter _filter; | |
private Mock<HttpContextBase> _httpCtxMock; | |
private Mock<IFormsAuthWrapper> _formsAuthMock; | |
private string _defaultTicket; | |
private const string DefaultUsername = "testuser"; | |
[SetUp] | |
public void SetUp() | |
{ | |
_defaultTicket = Guid.NewGuid().ToString("N"); | |
_httpCtxMock = new Mock<HttpContextBase>(); | |
_formsAuthMock = new Mock<IFormsAuthWrapper>(); | |
_filter = new FormsAuthenticationFilter( | |
_formsAuthMock.Object, | |
_httpCtxMock.Object | |
); | |
} | |
[Test] | |
public void OnAuthorization_AuthenticationHeaderIsMissed_SetUnthorizedRequest() | |
{ | |
var ctx = AuthActionContext(); | |
_filter.OnAuthorization(ctx); | |
ctx.Response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); | |
} | |
[Test] | |
public void OnAuthorization_AuthenticationHeaderIsMissedButActionAllowsAnonymous_DontSetUnotharizedResponse() | |
{ | |
var ctx = NewActionContext("PublicMethod"); | |
_filter.OnAuthorization(ctx); | |
ctx.Response.Should().BeNull(); | |
} | |
[Test] | |
public void OnAuthorization_AuthenticationHeaderExists_SetPrincipalToHttpCtx() | |
{ | |
_httpCtxMock.SetupSet(_ => _.User = It.IsAny<IPrincipal>()).Verifiable(); | |
var ctx = SetDefaultAuthData(); | |
SetDefaultTicketToRequest(ctx); | |
_filter.OnAuthorization(ctx); | |
_httpCtxMock.VerifySet( | |
c => c.User = It.Is<IPrincipal>( | |
p => p.Identity.Name == DefaultUsername | |
&& p.Identity.IsAuthenticated)); | |
} | |
[Test] | |
public void OnAuthorization_AuthenticationCookieExists_SetPrincipalToCurrentThread() | |
{ | |
var ctx = SetDefaultAuthData(); | |
AddDefaultAuthCookie(ctx); | |
_filter.OnAuthorization(ctx); | |
Thread.CurrentPrincipal.Identity.Name.Should().Be(DefaultUsername); | |
Thread.CurrentPrincipal.Identity.IsAuthenticated.Should().BeTrue(); | |
} | |
[Test] | |
public void OnAuthorization_AuthenticationCookieExists_SetPrincipalToHttpCtx() | |
{ | |
_httpCtxMock.SetupSet(_ => _.User = It.IsAny<IPrincipal>()).Verifiable(); | |
var ctx = SetDefaultAuthData(); | |
AddDefaultAuthCookie(ctx); | |
_filter.OnAuthorization(ctx); | |
_httpCtxMock.VerifySet( | |
c => c.User = It.Is<IPrincipal>( | |
p => p.Identity.Name == DefaultUsername | |
&& p.Identity.IsAuthenticated)); | |
} | |
[Test] | |
public void OnAuthorization_AuthenticationHeaderExists_SetPrincipalToCurrentThread() | |
{ | |
var ctx = SetDefaultAuthData(); | |
SetDefaultTicketToRequest(ctx); | |
_filter.OnAuthorization(ctx); | |
Thread.CurrentPrincipal.Identity.Name.Should().Be(DefaultUsername); | |
Thread.CurrentPrincipal.Identity.IsAuthenticated.Should().BeTrue(); | |
} | |
private void AddDefaultAuthCookie(HttpActionContext ctx) | |
{ | |
var cookieValue = string.Format("blah=blah_value;{0}={1}", FormsAuthentication.FormsCookieName, _defaultTicket); | |
ctx.Request.Headers.Add("Cookie", cookieValue); | |
} | |
private HttpActionContext SetDefaultAuthData() | |
{ | |
SetupUserFromTicket(_defaultTicket, DefaultUsername); | |
var ctx = AuthActionContext(); | |
return ctx; | |
} | |
private void SetDefaultTicketToRequest(HttpActionContext ctx) | |
{ | |
ctx.Request.Headers.Add("Authentication", _defaultTicket); | |
} | |
private void SetupUserFromTicket(string ticket, string username) | |
{ | |
_formsAuthMock.Setup(_ => _.Decrypt(ticket)) | |
.Returns(new FormsAuthenticationTicket(username, false, 30)); | |
} | |
private HttpActionContext AuthActionContext() | |
{ | |
return NewActionContext("MethodWithAuth"); | |
} | |
private HttpActionContext NewActionContext(string methodName) | |
{ | |
return new HttpActionContext | |
{ | |
ActionDescriptor = CreateActionDescriptor<TestController>(methodName), | |
ControllerContext = new HttpControllerContext { Request = new HttpRequestMessage() } | |
}; | |
} | |
protected ReflectedHttpActionDescriptor CreateActionDescriptor<TApiController>(string actionName) | |
where TApiController : ApiController | |
{ | |
var controllerType = typeof(TApiController); | |
return new ReflectedHttpActionDescriptor( | |
new HttpControllerDescriptor( | |
new HttpConfiguration(), | |
controllerType.Name.Split('.').Last(), | |
controllerType), | |
controllerType.GetMethod(actionName)); | |
} | |
} | |
public class FormsAuthenticationFilter: IAutofacAuthorizationFilter | |
{ | |
private readonly IFormsAuthWrapper _formsAuth; | |
private readonly HttpContextBase _httpContext; | |
public FormsAuthenticationFilter(IFormsAuthWrapper formsAuth, HttpContextBase httpContext) | |
{ | |
_formsAuth = formsAuth; | |
_httpContext = httpContext; | |
} | |
public void OnAuthorization(HttpActionContext actionContext) | |
{ | |
var anonymousAllowed = ControllerOrActionMarkedWith<AllowAnonymousAttribute>(actionContext); | |
if (anonymousAllowed) | |
{ | |
return; | |
} | |
var request = actionContext.Request; | |
if (request == null) | |
{ | |
SetUnathorizedResponse(actionContext); | |
return; | |
} | |
IEnumerable<string> authHeaderValues = null; | |
var authHeaderValue = request.Headers.TryGetValues("Authentication", out authHeaderValues) | |
? authHeaderValues.FirstOrDefault() | |
: GetTokenFromCookie(request); | |
if (string.IsNullOrEmpty(authHeaderValue)) | |
{ | |
SetUnathorizedResponse(actionContext); | |
return; | |
} | |
var ticket = _formsAuth.Decrypt(authHeaderValue); | |
if (ticket.Expired) | |
{ | |
SetUnathorizedResponse(actionContext); | |
return; | |
} | |
var principal = new SimplePrincipal(ticket.Name); | |
_httpContext.User = principal; | |
Thread.CurrentPrincipal = principal; | |
} | |
private static string GetTokenFromCookie(HttpRequestMessage request) | |
{ | |
var cookieHeaderValue = request.Headers.GetCookies(FormsAuthentication.FormsCookieName).FirstOrDefault(); | |
if (cookieHeaderValue == null) | |
{ | |
return null; | |
} | |
var targetCookie = cookieHeaderValue | |
.Cookies | |
.FirstOrDefault(cookie => cookie.Name == FormsAuthentication.FormsCookieName); | |
return targetCookie != null ? targetCookie.Value : null; | |
} | |
private static void SetUnathorizedResponse(HttpActionContext actionContext) | |
{ | |
actionContext.Response = actionContext.Request.CreateErrorResponse( | |
HttpStatusCode.Unauthorized, "Authentication ticket was not specified or it is wrong."); | |
} | |
private static bool ControllerOrActionMarkedWith<TAttr>(HttpActionContext actionContext) | |
where TAttr : class | |
{ | |
return ActionHasAttribute<TAttr>(actionContext) | |
|| ControllerHasAttribute<TAttr>(actionContext); | |
} | |
private static bool ControllerHasAttribute<TAttribute>(HttpActionContext actionCtx) | |
where TAttribute : class | |
{ | |
var attributes = actionCtx | |
.ActionDescriptor | |
.ControllerDescriptor | |
.GetCustomAttributes<TAttribute>(); | |
return attributes != null && attributes.Any(); | |
} | |
private static bool ActionHasAttribute<TAttribute>(HttpActionContext actionCtx) | |
where TAttribute : class | |
{ | |
var attributes = actionCtx | |
.ActionDescriptor | |
.GetCustomAttributes<TAttribute>(); | |
return attributes != null && attributes.Any(); | |
} | |
private class SimplePrincipal: IPrincipal | |
{ | |
public SimplePrincipal(string userName) | |
{ | |
Identity = new SimpleIdentity(userName); | |
} | |
public bool IsInRole(string role) | |
{ | |
return true; | |
} | |
public IIdentity Identity { get; private set; } | |
} | |
private class SimpleIdentity: IIdentity | |
{ | |
public SimpleIdentity(string name) | |
{ | |
Name = name; | |
} | |
public string Name { get; set; } | |
public string AuthenticationType | |
{ | |
get { return "Forms authentication thourgh HTTP headers."; } | |
} | |
public bool IsAuthenticated | |
{ | |
get { return !string.IsNullOrEmpty(Name); } | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment