I am trying to unit test an implementation of DelegateHandler. My simplified implementation:
public class FooHandler
: DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
Thread.CurrentPrincipal = new GenericPrincipal(
new GenericIdentity("Vegard"), new[] { "A", "B" });
return await base.SendAsync(request, cancellationToken);
}
}
When I try to unit test this, I do it like this:
public class TestHandler : DelegatingHandler
{
private readonly Func<HttpRequestMessage,
CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
public TestHandler()
{
_handlerFunc = (r, c) => Return(HttpStatusCode.OK);
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
return _handlerFunc(request, cancellationToken);
}
public static Task<HttpResponseMessage> Return(HttpStatusCode status)
{
return Task.Factory.StartNew(
() => new HttpResponseMessage(status));
}
}
[TestMethod]
public async Task SendAsync_CorrectTokens_IsAuthorized()
{
var message = new HttpRequestMessage(HttpMethod.Get, "http://www.test.com");
var handler = new AuthorizationHeaderHandler
{
InnerHandler = new TestHandler()
};
var invoker = new HttpMessageInvoker(handler);
var result = await invoker.SendAsync(message, new CancellationToken());
Assert.AreEqual(HttpStatusCode.OK, result.StatusCode);
Assert.IsTrue(Thread.CurrentPrincipal.Identity.IsAuthenticated); // fails
Assert.AreEqual("Vegard", Thread.CurrentPrincipal.Identity.Name); // fails
}
My guess is that this happens because HttpMessageInvoker runs the DelegateHandler on a separate thread. Can I force these to be on the same thread?
Can I force these to be on the same thread?
You can't.
A better question is "how do I flow Thread.CurrentPrincipal to whatever thread is executing the request"? There is an answer to this question.
Thread.CurrentPrincipal is odd in ASP.NET. In fact, I recommend you don't use it at all; use HttpContext.User instead. But if you want, you can get it to work by understanding these points:
HttpContext.User is flowed by the ASP.NET SynchronizationContext.
Thread.CurrentPrincipal is overwritten by HttpContext.User whenever a thread enters an ASP.NET request SynchronizationContext.
Unfortunately, your current test is flawed in a couple of key points:
After a request is completed, the value of Thread.CurrentPrincipal is undefined.
The current way you're running your tests, there is no HttpContext (or ASP.NET SynchronizationContext), and this interferes with the flowing of the principal user.
To fully test authorization, you'd need an integration test.
Also see my answer to this question.
What you're actually running into is the behavior of await. Await will reset the principal to whatever it was when you entered the await when you exit the await. So since there is no current principal when you call await invoker.SendAsync, there will be no current principal after you await that call.
However, your test handler should see the right principal. What you could do is have your test handler store the current principal in its SendAsync implementation, expose it as a public property, and then have your test assert that the test handler saw the principal it was supposed to. That should work fine, and that should be the behavior you care about.
Related
I have an web api on dotnet core 3.1 and I want to set different timeout specific controller action.I try to create an actionfilter something like below
public class TimeOutAttribute : ActionFilterAttribute
{
private readonly int _timeout;
public TimeOutAttribute(int timeout)
{
_timeout = timeout;
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
try
{
var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(_timeout));
await Task.Run(async () => await next(), cts.Token);
}
catch (TaskCanceledException)
{
var request = context.HttpContext.Request;
var message = $"Action exceeded the set timeout limit {_timeout} milisecond for {request.PathBase}{request.Path}";
throw new ActionTimeOutException(message);
}
}
}
and I use it on controller method
[TimeOut(100)]
public async Task<IActionResult> Get()
{
}
Although Get method takes more than 100 ms I can not get exception.Could you see any problem on code or If you have a another options for controller timeout Im ready to try it
Could you see any problem on code
Yes; passing a cancellation token to Task.Run isn't going to work. The token for that method only cancels the scheduling of the task to the thread pool, not the delegate itself.
The only way to cancel your delegate code is to have your delegate take a CancellationToken and observe that (usually by passing it to other methods). I have a blog post series on the subject of cancellation.
If you have a another options for controller timeout Im ready to try it
So, that's a harder problem.
There is built-in support for CancellationToken in ASP.NET Core; you can add a CancellationToken argument to any controller action method. However, this token doesn't have anything to do with timeouts; it cancels if the user request is aborted (e.g., the user closes their browser).
One approach is to add a CancellationToken parameter to your action method and have your filter modify the model binding results, replacing the provided CancellationToken. Something like this should work:
public sealed class TimeoutAttribute : ActionFilterAttribute
{
private readonly TimeSpan _timeout;
public TimeoutAttribute(int timeoutMilliseconds) => _timeout = TimeSpan.FromMilliseconds(timeoutMilliseconds);
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Find the CancellationToken argument passed to the action
var cancellationTokenArgument = context.ActionArguments.FirstOrDefault(x => x.Value is CancellationToken);
if (cancellationTokenArgument.Key == null || cancellationTokenArgument.Value == null)
throw new InvalidOperationException("TimeoutAttribute must be used on an action with a CancellationToken");
// Create a new CancellationToken that will be cancelled if *either* the user disconnects *or* a timeout
using var cts = CancellationTokenSource.CreateLinkedTokenSource((CancellationToken)cancellationTokenArgument.Value);
cts.CancelAfter(_timeout);
// Replace the action's CancellationToken argument with our own
context.ActionArguments[cancellationTokenArgument.Key] = cts.Token;
await next();
}
}
This will work - to an extent. Your host (i.e., IIS) likely has its own timeout, and this timeout is completely separate from that one.
I am trying to roll out authorization in an entire environment and would like to feature flag this for quick rollback if it goes south. Once we know all services are aligned with OAuth this feature will be removed and become permanent. I have chosen the IAutofacAuthorizationFilter to inject an object to determine the feature flag state which a typical attribute doesn't offer.
I'd like to enable the default behavior as if I had decorated the controller with [Authorize] if the feature is true otherwise let the methods execute without it, but I'm having trouble enabling the default behavior from inside a IAutofacAuthorizationFilter where there is no base class to override like await base.OnAuthorizationAsync(actionContext, cancellationToken); inside a AuthorizeAttribute.
What I have working so far:
public class FeatureBasedAuthorizeAttribute : IAutofacAuthorizationFilter
{
private readonly IFeatureManager _featureManager;
public FeatureBasedAuthorizeAttribute(IFeatureManager featureManager)
{
_featureManager = featureManager;
}
public async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
if (_featureManager.IsEnabled<EnableAppAuthorization>())
{
// Return result of default ASP.Net authorization here... How?
}
// Return without Authorization (current state)
await Task.FromResult(0);
}
}
// Wire up in startup.cs
builder.Register(c => new FeatureBasedAuthorizeAttribute(c.Resolve<IFeatureManager>()))
.AsWebApiAuthorizationFilterForAllControllers()
.InstancePerRequest();
Ultimately time away from the screen solved it for me. My solution was this:
public async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
if (_featureManager.IsEnabled<EnableAppAuthorization>())
{
// Return result of default ASP.Net authorization
var authorizeAttribute = new AuthorizeAttribute();
await authorizeAttribute.OnAuthorizationAsync(actionContext, cancellationToken);
}
// Return without Authorization (current state)
await Task.FromResult(0);
}
I'm trying to write a custom policy for an ASP.NET Core 3.1 web application, using a custom Identity storage provider.
I've tried to wrap my head around the fact that policies in ASP.NET Core are designed to take user informations from an HttpContext object, when I read this in a MSDN Article:
once you hold a reference to the user, you can always find the username from the claims and run a query against any database or external service
I started writing my own policy (as of now a simple role requirement) injecting the UserManager into the constructor:
public class RoleHandler : AuthorizationHandler<RoleRequirement>
{
private UserManager<AppUser> UserManager;
public RoleHandler(UserManager<AppUser> usermanager)
{
UserManager = usermanager;
}
}
Now I have a couple problems:
INJECTING A SCOPED SERVICE IN A SINGLETON
Policies are supposed to be lasting for the entire application life, so that would be a Singleton:
services.AddSingleton<IAuthorizationHandler, RoleHandler>();
but the UserManager injected in the policy server is a scoped service and that is not allowed. Solution was very easy, changing the configuration of the policy service from a singleton to a scoped service
services.AddScoped<IAuthorizationHandler, RoleHandler>();
but I don't know whether that cause any issue or not.
WRITING AN ASYNCHRONOUS POLICY HANDLER
This is my implementation of the HandleRequirementAsync method:
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
AppUser user = UserManager.FindByIdAsync(context.User.Identity.Name).Result;
if (user != null)
{
bool result = UserManager.IsInRoleAsync(user, requirement.Role.ToString()).Result;
if (result) context.Succeed(requirement);
}
return Task.CompletedTask;
}
I used Task.Result but it blocks the thread. I can't use await because that would make the method returning a Task<Task> instead of a Task and I can't change it. How can I solve this?
Don't return Task.CompletedTask.
When you declare a method as async, it implicitly returns a Task when the first await is hit:
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
AppUser user = await UserManager.FindByIdAsync(context.User.Identity.Name);
if (user != null)
{
bool result = await UserManager.IsInRoleAsync(user, requirement.Role.ToString());
if (result) context.Succeed(requirement);
}
}
Task.CompletedTask is generally used when you need to implement a Task returning method synchronously, which you are not.
My HandleRequirementAsync also calls httpClient.GetAsync (Blazor server, .NET 5), adding async to the HandleRequirementAsync and execute the await hpptClient.GetAsync() breaks the authorization. With async method with delays, Try typing the route address in the browser and it will redirect to not authorized page, even though the context.Succeed(requirement) is executed.
The working solution for me is to keep the HandleRequirementAsync as it is, returning Task.CompletedTask. For the async method we need to call, just use pattern for calling async method from non async method.
The one I use is from https://stackoverflow.com/a/43148321/423356
my sample async method:
public async Task<IList<Permission>> GetGroupPermissions(int userId)
{
HttpResponseMessage response = await _httpClient.GetAsync(string.Format("Auth/GroupPermissions/{0}", userId));
try
{
var payload = await response.Content.ReadFromJsonAsync<List<Permission>>();
response.EnsureSuccessStatusCode();
return payload;
}
catch
{
return new List<Permission>();
}
}
HandleRequirementAsync:
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
var t2 = (Task.Run(() => GetGroupPermissions(userId)));
t2.Wait();
var userGroupPermissions = t2.Result;
if (!userGroupPermissions.Contains(requirement.Permission))
{
//context.Fail(); //no need to fail, other requirement might success
return Task.CompletedTask;
}
context.Succeed(requirement);
return Task.CompletedTask;
}
I am working on a DelegatingHandler to intercept all the calls from one service to another. And I am totally new to DelegatingHandlers.
This is the issue.
We currently have an API that takes calls and measure the concurrent calls and it has some mechanisms to throttle it if needed. it is in this form. (Since actual API is heavy for debugging I am actually using following stub methods to test this)
private Task<HttpResponseMessage> ExecuteAsync1(Func<Task<HttpResponseMessage>> action)
{
return ExecuteAsync2(action);
}
private async Task<HttpResponseMessage> ExecuteAsync2(Func<Task<HttpResponseMessage>> action)
{
return await action().ConfigureAwait(false);
}
Now within this Delegating handler I have to use this API like this.
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (this.ThrottlingFactory == null || !this.ThrottlingEnabled)
{
return await base.SendAsync(request, cancellationToken);
}
else
{
var response = await this.ExecuteAsync1(() =>
{
return base.SendAsync(request, cancellationToken);
});
// Analyse response for certain thing here.
return response;
}
}
My problem is, after executing ExecuteAsync1 (it actually completes the ExecuteAsync2) the code never return back. I tried numerous variation of this like adding async/await to all the methods in the chain including the anon method etc. non worked. So can somebody please point me to things I am doing wrong here?
This is what I had to do. I had to ConfigureAwait(false) to the call.
var response = await this.ExecuteAsync1(() =>
{
return base.SendAsync(request, cancellationToken);
}).ConfigureAwait(false);
I have to write unit test on ASP.NET MVC Web API Controller with Rhino.Mock
I have a handler named AHandler.cs with inherts from System.Net.Http.HttpClientHandler class.
The singnature SendAsync method of AHandler.cs is like followings :
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
.....
var response = base.SendAsync(request, cancellationToken).Result;
if (response.IsSuccessStatusCode)
{
.....
}
}
the base keyword above means HttpClientHandler and its SendAsync() method is "protected"!!!
Now I try to mock the object "base.SendAsync(request, cancellationToken).Result" and got the hand-made response result I wanted.
But it seems that Rhino mocks can't see the "base" keyword when I wrote the followings code :
var mockbase = MockRepository.GenerateMock<AHandler>;
mockbase.Stub(x => x.base <=== can't see base keyword
^^^^^
So I change another way and try to mock the HttpClientHandler class
var mockbase = MockRepository.GenerateMockHttpClientHandler>;
mockbase.Stub(x => x. <== I can't see SendAsync() method, becase it is protected !!
Now I really suffer in it !!
Can anybody give me some advice that how to made a custom response in MVC handler ?!
very thanks !!
Why do you want to mock a handler in first place ? You can inject an specific dummy implementation for your tests. That handler will return a new HttpResponse message expected by your tests.
public class MyDummyHttpHandler : DelegatingHandler
{
HttpResponseMessage response;
public MyDummyHttpHandler(HttpResponseMessage response)
{
this.response = response;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
.....
TaskCompletionSource<HttpResponseMessage> tsc = new TaskCompletionSource<HttpResponseMessage>();
tsc.SetResult(this.response);
return tsc.Task;
}
}