Nancy static-file handler not part of application pipeline? - c#

I'm trying to log every request made to Nancy in the application pipeline, but for some reason I cannot seem to log the static file requests. Neither of the following captures the request for static files.
ApplicationPipelines.BeforeRequest += ((ctx) => {
LogTo.Info($"{ctx.Request.UserHostAddress} - {ctx.Request.Method} {ctx.Request.Path}");
return null;
});
ApplicationPipelines.AfterRequest += ((ctx) => {
LogTo.Info($"{ctx.Request.UserHostAddress} - {ctx.Request.Method} {ctx.Request.Path} - {ctx.Response.StatusCode}");
});
Anything that isn't a static file is logged perfectly fine.
Also, I'm using the convention located here: https://stackoverflow.com/a/21477824/1657476. Nancy is Self-Hosted using Nowin and is the only component added to the owin app builder.
Is the static file hander not part of the Nancy application pipeline? If so, How can I hook into the static file requests?
Documentation says it should be (part of the pipeline). So I don't understand why I can't pick it up. I've even tried directly inserting the pipeline hook at the beginning too with AddItemToStartOfPipeline

The static file handler is no longer part of the Nancy pipeline. Documentation was out of date. See Issue: https://github.com/NancyFx/Nancy/issues/2328
If you use Nancy with Owin you can use middleware to capture the request and response (equivalent to AfterRequest):
app.Use(new Func<AppFunc, AppFunc>(next => (async context =>
{
var sendingHeaders = (Action<Action<object>, object>) context["server.OnSendingHeaders"];
sendingHeaders(state =>
{
var ip = context["server.RemoteIpAddress"];
var port = context["server.RemotePort"];
var method = context["owin.RequestMethod"];
var path = context["owin.RequestPath"];
var status = context["owin.ResponseStatusCode"];
LogTo.Info($"{ip}:{port} - {method} {path} - {status}");
}, context);
await next.Invoke(context);
})));
You need the OnSendingHeaders hook because Nancy does not pass through if it handles the request.

Related

xUnit testing Azure Functions w/ .NET 5

I'm trying to figure out a way to design xUnit tests for my Azure Functions, which are based on this example.
As you can see, with .net 5 functions, you are expected to start func host start in your functions' project directory, then you can fire functions and debug.
Here's the program.cs of the functions project
var host = new HostBuilder()
.ConfigureAppConfiguration(c =>
{
c.AddCommandLine(args);
})
.ConfigureFunctionsWorker((c, b) =>
{
b.UseMiddleware<FunctionExceptionMiddleware>(); //an extension method of mine to register middlewares...
b.UseFunctionExecutionMiddleware();
})
.ConfigureServices(s =>
{
//add other services here,
CloudStorageAccount storageAccount = CloudStorageAccount.Parse("");
BlobServiceClient blobServiceClient = new BlobServiceClient("");
s.AddScoped(r => storageAccount);
s.AddScoped(r => blobServiceClient);
})
.Build();
As you can see, I register different types for DI to work properly, and I also register a middleware, to handle unhandled exceptions, so testing only the function's class Run method isn't optimal, because they rely on DI and also I need the middleware(s) to be tested as well.
Here an example function
[FunctionName("GetAsset")]
public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req, FunctionExecutionContext executionContext)
{
var response = new HttpResponseData(HttpStatusCode.OK);
var container = req.GetQueryStringParam("container"); //extension method to retrieve query parameters...
var blob = req.GetQueryStringParam("blob");
if (string.IsNullOrEmpty(container) || string.IsNullOrEmpty(blob))
{
executionContext.Logger.LogInformation($"GetAsset function - missing arguments in request string");
return new BadRequestObjectResult("");
}
return new OkObjectResult("Success");
}
As far as I can see, to get optimal tests, func.exe from the toolkit would need to be launched, then with an HttpClient I could make requests to http://localhost:7071/api/MyFunction.
I've thought of launching func.exe through Process.Start() before each of the tests, but it's kind of hackish as I don't really know when it is ready (func.exe compiles all assemblies before showing a "Worker process started and initialized" message) and tests could give false fails because of that.
Any ideas ?

How to get the id of the current user when logging web api requests via middleware

I have the following Invoke function within a middleware to log the web api requests. It is based on this example, and it works fine. However, I also want to save the username of the current user who is sending the request.
public async Task Invoke(HttpContext httpContext, IApiLogService apiLogService)
{
try
{
_apiLogService = apiLogService;
var request = httpContext.Request;
if (request.Path.StartsWithSegments(new PathString("/api")))
{
var stopWatch = Stopwatch.StartNew();
var requestTime = DateTime.UtcNow;
var requestBodyContent = await ReadRequestBody(request);
var originalBodyStream = httpContext.Response.Body;
await SafeLog(requestTime,
stopWatch.ElapsedMilliseconds,
200,//response.StatusCode,
request.Method,
request.Path,
request.QueryString.ToString(),
requestBodyContent
);
}
else
{
await _next(httpContext);
}
}
catch (Exception ex)
{
await _next(httpContext);
}
}
However, the User property of httpContext seems to missing user information. Below I share its content. I wonder how I can get the id of the user who is sending the request. I am not using windows authentication. Any suggestions?
The key point to explain why this happens
the User property of httpContext seems to missing user information
Is the order of middleware in the Configure method of the Startup class.
The ASP.NET Core request pipeline processing works by running middleware components as per the sequence they are placed in the Configure method of the Startup class.
so you needs to move app.UseAuthentication(); to be before you add your custom middleware to pipline app.UseYourMiddleware(); in the Configure method of the Startup class.

Only accept local request in ASP.Net Core 2.1 MVC

I have a simple ASP.Net Core 2.1 MVC app and in one of the controllers, I would like to implement an action that only accepts requests from local (i.e. request originates from 127.0.0.1, or from the same address as the server's IP).
I've been looking for a filter in ASP.Net Core that is suitable for this purpose but can't seem to find one. I can use an AuthorizeAttribute, e.g. [Authorize(Policy = "LocalOnly")]
and registering the corresponding policy in ConfigureServices in Startup:
services.AddAuthorization(options =>
{
options.AddPolicy("LocalOnly", policy =>
{
policy.RequireAssertion(context =>
{
if (context.Resource is AuthorizationFilterContext mvcContext)
{
return mvcContext.HttpContext.Request.IsLocal();
}
return false;
});
});
});
where IsLocal() is an extension method of HttpRequest.
However I don't think this is the right way to do it -- what I'm trying to do is not actually authorization, and since I don't have authentication in my program, the error produced isn't correct either.
Is there a simple and legit way to do what I want with filters? Or is this actually something that should be done in the action logic in controllers? Or perhaps this whole idea of checking for local request isn't very correct to begin with?
Thanks a lot for any help.
Do it as ASP.NET Core middleware.
In the easiest case with a app.Use(...) method.
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
if (!context.Request.IsLocal())
{
// Forbidden http status code
context.Response.StatusCode = 403;
return;
}
await next.Invoke();
});
}
}
The delegate calls return on local requests, stopping the middleware pipeline here.
But I'm not 100% certain what you are trying to archive.
Do you want the service only callable from your internal network? The way easier way to do that would be to use docker containers, add the services which need to communicate to each other to the same network and only expose the application to outside the container which really need to communicate with the outside world.
The extension method of IsLocal() should look as the following:
public static class HttpRequestExtensions
{
public static bool IsLocal(this HttpRequest req)
{
var connection = req.HttpContext.Connection;
if (connection.RemoteIpAddress != null)
{
if (connection.LocalIpAddress != null)
{
return connection.RemoteIpAddress.Equals(connection.LocalIpAddress);
}
else
{
return IPAddress.IsLoopback(connection.RemoteIpAddress);
}
}
// for in memory TestServer or when dealing with default connection info
if (connection.RemoteIpAddress == null && connection.LocalIpAddress == null)
{
return true;
}
return false;
}
}
To determine whether request is local, we can check if the RemoteIpAddress and LocalIpAddress on the connection are the same, or, in case the local IP is unknown for some reason, if the RemoteIpAddress is a loopback address.
You can read more about it in strathweb article.
Note that it will not work if connection.RemoteIpAddress == "::1".
If you do want it to work change it to:
Request.Host.Value.StartsWith(“localhost:”)

Difference between UseHttpsRedirection and AddDirectToHttpsPermanent in ASP.NET Core

In the Startup.cs file, consider the following:
public void ConfigureServices(IServiceCollection services)
{
// Irrelevant code removed
services.AddHttpsRedirection(options =>
{
options.RedirectStatusCode = StatusCodes.Status301MovedPermanently;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Irrelevant code removed
app.UseHttpsRedirection();
app.UseRewriter(new RewriteOptions()
.AddRedirectToWwwPermanent()
.AddRedirectToHttpsPermanent()
);
}
As far as I know, I have to use the Rewriter to set the AddRedirectToWwwPermanent. My question is, should I be using both app.UseHttpsRedirection() and AddRedirectToHttpsPermanent()? Or if they doing the exact same thing, which should I remove?
I just want to make sure I'm redirecting to Https properly, in conjunction with the Wwww redirect.
AddRedirectToHttpsPermanent (or its sibling AddRedirectToHttps) adds a RedirectToHttpsRule to the rewriter. This rule works like this:
if (!context.HttpContext.Request.IsHttps)
{
var host = context.HttpContext.Request.Host;
if (SSLPort.HasValue && SSLPort.Value > 0)
{
// a specific SSL port is specified
host = new HostString(host.Host, SSLPort.Value);
}
else
{
// clear the port
host = new HostString(host.Host);
}
var req = context.HttpContext.Request;
var newUrl = new StringBuilder().Append("https://").Append(host).Append(req.PathBase).Append(req.Path).Append(req.QueryString);
var response = context.HttpContext.Response;
response.StatusCode = StatusCode;
response.Headers[HeaderNames.Location] = newUrl.ToString();
context.Result = RuleResult.EndResponse;
context.Logger?.RedirectedToHttps();
}
So this basically gets the current host name, and builds a new URL that looks the same except it has a https:// in front. And then it sets a 301 HTTP status code and returns the new URL through the Location header.
The rule is then executed as part of the RewriteMiddleware which will basically just loop through all registered rules and will eventually run the above code and then end the response.
In contrast, this is how the HttpsRedirectionMiddleware works internally:
if (context.Request.IsHttps || !TryGetHttpsPort(out var port))
{
return _next(context);
}
var host = context.Request.Host;
if (port != 443)
{
host = new HostString(host.Host, port);
}
else
{
host = new HostString(host.Host);
}
var request = context.Request;
var redirectUrl = UriHelper.BuildAbsolute(
"https",
host,
request.PathBase,
request.Path,
request.QueryString);
context.Response.StatusCode = _statusCode;
context.Response.Headers[HeaderNames.Location] = redirectUrl;
_logger.RedirectingToHttps(redirectUrl);
return Task.CompletedTask;
So this will get the host name from the incoming request, and then build an absolute URL using the UriHelper that looks exactly like the current request except that it uses the https:// scheme. And then it sets a 307 HTTP status code result and returns the new URL through the Location header. Since it does not call later middlewares, this will also end the response.
So yeah, those two solutions are very different (not): They use pretty much equivalent code and produce the same result. The only actual difference is that the HttpsRedirectionMiddleware uses a HTTP 307 status code by default.
If you prefer one status code over the other, you can totally configure both middlewares to use your preferred status code instead.
So which middleware should you use to enable HTTPS redirection? It doesn’t really matter. The ASP.NET Core templates come with the HttpsRedirectionMiddleware by default, but that middleware also only exists since ASP.NET Core 2.1.
I’d personally stick with the HttpsRedirectionMiddleware since it conveys its purpose very clearly. But if you have a RewriteMiddleware in place anyway, I would just replace the HttpsRedirectionMiddleware by a RedirectToHttpsRule for the rewrite middleware, so you just have a single middleware that performs redirects. – But in the end, it really doesn’t matter.

Creating a proxy to another web api with Asp.net core

I'm developing an ASP.Net Core web application where I need to create a kind of "authentication proxy" to another (external) web service.
What I mean by authentication proxy is that I will receive requests through a specific path of my web app and will have to check the headers of those requests for an authentication token that I'll have issued earlier, and then redirect all the requests with the same request string / content to an external web API which my app will authenticate with through HTTP Basic auth.
Here's the whole process in pseudo-code
Client requests a token by making a POST to a unique URL that I sent him earlier
My app sends him a unique token in response to this POST
Client makes a GET request to a specific URL of my app, say /extapi and adds the auth-token in the HTTP header
My app gets the request, checks that the auth-token is present and valid
My app does the same request to the external web API and authenticates the request using BASIC authentication
My app receives the result from the request and sends it back to the client
Here's what I have for now. It seems to be working fine, but I'm wondering if it's really the way this should be done or if there isn't a more elegant or better solution to this? Could that solution create issues in the long run for scaling the application?
[HttpGet]
public async Task GetStatement()
{
//TODO check for token presence and reject if issue
var queryString = Request.QueryString;
var response = await _httpClient.GetAsync(queryString.Value);
var content = await response.Content.ReadAsStringAsync();
Response.StatusCode = (int)response.StatusCode;
Response.ContentType = response.Content.Headers.ContentType.ToString();
Response.ContentLength = response.Content.Headers.ContentLength;
await Response.WriteAsync(content);
}
[HttpPost]
public async Task PostStatement()
{
using (var streamContent = new StreamContent(Request.Body))
{
//TODO check for token presence and reject if issue
var response = await _httpClient.PostAsync(string.Empty, streamContent);
var content = await response.Content.ReadAsStringAsync();
Response.StatusCode = (int)response.StatusCode;
Response.ContentType = response.Content.Headers.ContentType?.ToString();
Response.ContentLength = response.Content.Headers.ContentLength;
await Response.WriteAsync(content);
}
}
_httpClient being a HttpClient class instantiated somewhere else and being a singleton and with a BaseAddressof http://someexternalapp.com/api/
Also, is there a simpler approach for the token creation / token check than doing it manually?
If anyone is interested, I took the Microsoft.AspNetCore.Proxy code and made it a little better with middleware.
Check it out here: https://github.com/twitchax/AspNetCore.Proxy. NuGet here: https://www.nuget.org/packages/AspNetCore.Proxy/. Microsoft archived the other one mentioned in this post, and I plan on responding to any issues on this project.
Basically, it makes reverse proxying another web server a lot easier by allowing you to use attributes on methods that take a route with args and compute the proxied address.
[ProxyRoute("api/searchgoogle/{query}")]
public static Task<string> SearchGoogleProxy(string query)
{
// Get the proxied address.
return Task.FromResult($"https://www.google.com/search?q={query}");
}
I ended up implementing a proxy middleware inspired by a project in Asp.Net's GitHub.
It basically implements a middleware that reads the request received, creates a copy from it and sends it back to a configured service, reads the response from the service and sends it back to the caller.
This post talks about writing a simple HTTP proxy logic in C# or ASP.NET Core. And allowing your project to proxy the request to any other URL. It is not about deploying a proxy server for your ASP.NET Core project.
Add the following code anywhere of your project.
public static HttpRequestMessage CreateProxyHttpRequest(this HttpContext context, Uri uri)
{
var request = context.Request;
var requestMessage = new HttpRequestMessage();
var requestMethod = request.Method;
if (!HttpMethods.IsGet(requestMethod) &&
!HttpMethods.IsHead(requestMethod) &&
!HttpMethods.IsDelete(requestMethod) &&
!HttpMethods.IsTrace(requestMethod))
{
var streamContent = new StreamContent(request.Body);
requestMessage.Content = streamContent;
}
// Copy the request headers
foreach (var header in request.Headers)
{
if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
{
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
}
requestMessage.Headers.Host = uri.Authority;
requestMessage.RequestUri = uri;
requestMessage.Method = new HttpMethod(request.Method);
return requestMessage;
}
This method covert user sends HttpContext.Request to a reusable HttpRequestMessage. So you can send this message to the target server.
After your target server response, you need to copy the responded HttpResponseMessage to the HttpContext.Response so the user's browser just gets it.
public static async Task CopyProxyHttpResponse(this HttpContext context, HttpResponseMessage responseMessage)
{
if (responseMessage == null)
{
throw new ArgumentNullException(nameof(responseMessage));
}
var response = context.Response;
response.StatusCode = (int)responseMessage.StatusCode;
foreach (var header in responseMessage.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}
foreach (var header in responseMessage.Content.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}
// SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response.
response.Headers.Remove("transfer-encoding");
using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
{
await responseStream.CopyToAsync(response.Body, _streamCopyBufferSize, context.RequestAborted);
}
}
And now the preparation is complete. Back to our controller:
private readonly HttpClient _client;
public YourController()
{
_client = new HttpClient(new HttpClientHandler()
{
AllowAutoRedirect = false
});
}
public async Task<IActionResult> Rewrite()
{
var request = HttpContext.CreateProxyHttpRequest(new Uri("https://www.google.com"));
var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, HttpContext.RequestAborted);
await HttpContext.CopyProxyHttpResponse(response);
return new EmptyResult();
}
And try to access it. It will be proxied to google.com
A nice reverse proxy middleware implementation can also be found here: https://auth0.com/blog/building-a-reverse-proxy-in-dot-net-core/
Note that I replaced this line here
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
with
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToString());
Original headers (e.g. like an authorization header with a bearer token) would not be added without my modification in my case.
I had luck using twitchax's AspNetCore.Proxy NuGet package, but could not get it to work using the ProxyRoute method shown in twitchax's answer. (Could have easily been a mistake on my end.)
Instead I defined the mapping in Statup.cs Configure() method similar to the code below.
app.UseProxy("api/someexternalapp-proxy/{arg1}", async (args) =>
{
string url = "https://someexternalapp.com/" + args["arg1"];
return await Task.FromResult<string>(url);
});
Piggy-backing on James Lawruk's answer https://stackoverflow.com/a/54149906/6596451 to get the twitchax Proxy attribute to work, I was also getting a 404 error until I specified the full route in the ProxyRoute attribute. I had my static route in a separate controller and the relative path from Controller's route was not working.
This worked:
public class ProxyController : Controller
{
[ProxyRoute("api/Proxy/{name}")]
public static Task<string> Get(string name)
{
return Task.FromResult($"http://www.google.com/");
}
}
This does not:
[Route("api/[controller]")]
public class ProxyController : Controller
{
[ProxyRoute("{name}")]
public static Task<string> Get(string name)
{
return Task.FromResult($"http://www.google.com/");
}
}
Hope this helps someone!
Twitchax's answer seems to be the best solution at the moment. In researching this, I found that Microsoft is developing a more robust solution that fits the exact problem the OP was trying to solve.
Repo: https://github.com/microsoft/reverse-proxy
Article for Preview 1 (they actually just released prev 2): https://devblogs.microsoft.com/dotnet/introducing-yarp-preview-1/
From the Article...
YARP is a project to create a reverse proxy server. It started when we noticed a pattern of questions from internal teams at Microsoft who were either building a reverse proxy for their service or had been asking about APIs and technology for building one, so we decided to get them all together to work on a common solution, which has become YARP.
YARP is a reverse proxy toolkit for building fast proxy servers in .NET using the infrastructure from ASP.NET and .NET. The key differentiator for YARP is that it is being designed to be easily customized and tweaked to match the specific needs of each deployment scenario. YARP plugs into the ASP.NET pipeline for handling incoming requests, and then has its own sub-pipeline for performing the steps to proxy the requests to backend servers. Customers can add additional modules, or replace stock modules as needed.
...
YARP works with either .NET Core 3.1 or .NET 5 preview 4 (or later). Download the preview 4 (or greater) of .NET 5 SDK from https://dotnet.microsoft.com/download/dotnet/5.0
More specifically, one of their sample apps implements authentication (as for the OP's original intent)
https://github.com/microsoft/reverse-proxy/blob/master/samples/ReverseProxy.Auth.Sample/Startup.cs
Here is a basic implementation of Proxy library for ASP.NET Core:
This does not implement the authorization but could be useful to someone looking for a simple reverse proxy with ASP.NET Core. We only use this for development stages.
using System;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
namespace Sample.Proxy
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(options =>
{
options.AddDebug();
options.AddConsole(console =>
{
console.IncludeScopes = true;
});
});
services.AddProxy(options =>
{
options.MessageHandler = new HttpClientHandler
{
AllowAutoRedirect = false,
UseCookies = true
};
options.PrepareRequest = (originalRequest, message) =>
{
var host = GetHeaderValue(originalRequest, "X-Forwarded-Host") ?? originalRequest.Host.Host;
var port = GetHeaderValue(originalRequest, "X-Forwarded-Port") ?? originalRequest.Host.Port.Value.ToString(CultureInfo.InvariantCulture);
var prefix = GetHeaderValue(originalRequest, "X-Forwarded-Prefix") ?? originalRequest.PathBase;
message.Headers.Add("X-Forwarded-Host", host);
if (!string.IsNullOrWhiteSpace(port)) message.Headers.Add("X-Forwarded-Port", port);
if (!string.IsNullOrWhiteSpace(prefix)) message.Headers.Add("X-Forwarded-Prefix", prefix);
return Task.FromResult(0);
};
});
}
private static string GetHeaderValue(HttpRequest request, string headerName)
{
return request.Headers.TryGetValue(headerName, out StringValues list) ? list.FirstOrDefault() : null;
}
public void Configure(IApplicationBuilder app)
{
app.UseWebSockets()
.Map("/api", api => api.RunProxy(new Uri("http://localhost:8833")))
.Map("/image", api => api.RunProxy(new Uri("http://localhost:8844")))
.Map("/admin", api => api.RunProxy(new Uri("http://localhost:8822")))
.RunProxy(new Uri("http://localhost:8811"));
}
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}

Categories

Resources