Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/AspDotNetCore.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ public void ConfigureServices(IServiceCollection services)
// (default is everyone can access profilers)
options.ResultsAuthorize = request => MyGetUserFunction(request).CanSeeMiniProfiler;
options.ResultsListAuthorize = request => MyGetUserFunction(request).CanSeeMiniProfiler;
// Or, there are async versions available:
options.ResultsAuthorizeAsync = async request => (await MyGetUserFunctionAsync(request)).CanSeeMiniProfiler;
options.ResultsAuthorizeListAsync = async request => (await MyGetUserFunctionAsync(request)).CanSeeMiniProfilerLists;

// (Optional) To control which requests are profiled, use the Func<HttpRequest, bool> option:
// (default is everything should be profiled)
Expand Down
2 changes: 2 additions & 0 deletions docs/Releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ This page tracks major changes included in any update starting with version 4.0.
- Drops IE 11 support
- Fix for missing `IMemoryCache` depending on config ([#440](https://github.com/MiniProfiler/dotnet/pull/440))
- Updates `MySqlConnector` to 0.60.1 for misc fixes ([#432](https://github.com/MiniProfiler/dotnet/pull/432)) (thanks [@bgrainger](https://github.com/bgrainger)!)
- **.NET Core only**
- Added `MiniProfilerOptions.ResultsAuthorizeAsync` and `MiniProfiler.ResultsAuthorizeListAsync` ([#472](https://github.com/MiniProfiler/dotnet/pull/472))


#### Version 4.1.0
Expand Down
2 changes: 2 additions & 0 deletions samples/Samples.AspNetCore2/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public void ConfigureServices(IServiceCollection services)
// To control authorization, you can use the Func<HttpRequest, bool> options:
options.ResultsAuthorize = _ => !Program.DisableProfilingResults;
//options.ResultsListAuthorize = request => MyGetUserFunction(request).CanSeeMiniProfiler;
//options.ResultsAuthorizeAsync = async request => (await MyGetUserFunctionAsync(request)).CanSeeMiniProfiler;
//options.ResultsAuthorizeListAsync = async request => (await MyGetUserFunctionAsync(request)).CanSeeMiniProfilerLists;

// To control which requests are profiled, use the Func<HttpRequest, bool> option:
//options.ShouldProfile = request => MyShouldThisBeProfiledFunction(request);
Expand Down
2 changes: 2 additions & 0 deletions samples/Samples.AspNetCore3/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public void ConfigureServices(IServiceCollection services)
// To control authorization, you can use the Func<HttpRequest, bool> options:
options.ResultsAuthorize = _ => !Program.DisableProfilingResults;
//options.ResultsListAuthorize = request => MyGetUserFunction(request).CanSeeMiniProfiler;
//options.ResultsAuthorizeAsync = async request => (await MyGetUserFunctionAsync(request)).CanSeeMiniProfiler;
//options.ResultsAuthorizeListAsync = async request => (await MyGetUserFunctionAsync(request)).CanSeeMiniProfilerLists;

// To control which requests are profiled, use the Func<HttpRequest, bool> option:
//options.ShouldProfile = request => MyShouldThisBeProfiledFunction(request);
Expand Down
59 changes: 37 additions & 22 deletions src/MiniProfiler.AspNetCore/MiniProfilerMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public async Task Invoke(HttpContext context)
mp.User = Options.UserIdProvider?.Invoke(context.Request);

// Always add this profiler's header (and any async requests before it)
using (mp.StepIf("MiniProfiler Prep", minSaveMs: 0.1m))
using (mp.StepIf("MiniProfiler Init", minSaveMs: 0.1m))
{
await SetHeadersAndState(context, mp).ConfigureAwait(false);
}
Expand Down Expand Up @@ -172,18 +172,29 @@ private async Task SetHeadersAndState(HttpContext context, MiniProfiler current)
try
{
// Are we authorized???
var isAuthorized = Options.ResultsAuthorize?.Invoke(context.Request) ?? true;
bool isAuthorized;
using (current.StepIf("Authorize", 0.1m))
{
isAuthorized = await AuthorizeRequestAsync(context, isList: false, setResponse: false);
}

// Grab any past profilers (e.g. from a previous redirect)
var profilerIds = (isAuthorized ? await Options.ExpireAndGetUnviewedAsync(current.User).ConfigureAwait(false) : null)
?? new List<Guid>(1);
List<Guid> profilerIds;
using (current.StepIf("Get Profiler IDs", 0.1m))
{
profilerIds = (isAuthorized ? await Options.ExpireAndGetUnviewedAsync(current.User).ConfigureAwait(false) : null)
?? new List<Guid>(1);
}

// Always add the current
profilerIds.Add(current.Id);

if (profilerIds.Count > 0)
{
context.Response.Headers.Add("X-MiniProfiler-Ids", profilerIds.ToJson());
using (current.StepIf("Set Headers", 0.1m))
{
context.Response.Headers.Add("X-MiniProfiler-Ids", profilerIds.ToJson());
}
}

// Set the state to use in RenderIncludes() down the pipe later
Expand All @@ -206,7 +217,7 @@ private async Task HandleRequest(HttpContext context, PathString subPath)
switch (subPath.Value)
{
case "/results-index":
result = ResultsIndex(context);
result = await ResultsIndexAsync(context);
break;

case "/results-list":
Expand Down Expand Up @@ -237,18 +248,22 @@ private static string NotFound(HttpContext context, string message = null, strin
/// </summary>
/// <param name="context">The context to attempt to authorize a user for.</param>
/// <param name="isList">Whether this is a list route being accessed.</param>
/// <param name="message">The access denied message, if present.</param>
private bool AuthorizeRequest(HttpContext context, bool isList, out string message)
/// <param name="setResponse">Whether to set response properties</param>
private async Task<bool> AuthorizeRequestAsync(HttpContext context, bool isList, bool setResponse = true)
{
message = null;
var authorize = Options.ResultsAuthorize;
var authorizeList = Options.ResultsListAuthorize;

if ((authorize != null && !authorize(context.Request)) || (isList && (authorizeList != null && !authorizeList(context.Request))))
var req = context.Request;
// Deny access if we a) have a configured delegate, and b) it says no
if (Options.ResultsAuthorize != null && !Options.ResultsAuthorize.Invoke(req)
|| (Options.ResultsAuthorizeAsync != null && !await Options.ResultsAuthorizeAsync(req))
|| (isList && Options.ResultsListAuthorize != null && !Options.ResultsListAuthorize(req))
|| (isList && Options.ResultsListAuthorizeAsync != null && !await Options.ResultsListAuthorizeAsync(req))
)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
context.Response.ContentType = "text/plain";
message = "unauthorized";
if (setResponse)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
context.Response.ContentType = "text/plain";
}
return false;
}

Expand All @@ -259,11 +274,11 @@ private bool AuthorizeRequest(HttpContext context, bool isList, out string messa
/// Returns the list of profiling sessions
/// </summary>
/// <param name="context">The results list HTML, if authorized.</param>
private string ResultsIndex(HttpContext context)
private async Task<string> ResultsIndexAsync(HttpContext context)
{
if (!AuthorizeRequest(context, isList: true, message: out string message))
if (!await AuthorizeRequestAsync(context, isList: true))
{
return message;
return "Unauthorized";
}

context.Response.ContentType = "text/html; charset=utf-8";
Expand All @@ -278,9 +293,9 @@ private string ResultsIndex(HttpContext context)
/// <param name="context">The context to get the results list for.</param>
private async Task<string> ResultsListAsync(HttpContext context)
{
if (!AuthorizeRequest(context, isList: true, message: out string message))
if (!await AuthorizeRequestAsync(context, isList: true))
{
return message;
return "Unauthorized";
}

var guids = await Options.Storage.ListAsync(100).ConfigureAwait(false);
Expand Down Expand Up @@ -374,7 +389,7 @@ private async Task<string> GetSingleProfilerResultAsync(HttpContext context)
await Options.Storage.SaveAsync(profiler).ConfigureAwait(false);
}

if (!AuthorizeRequest(context, isList: false, message: out string authorizeMessage))
if (!await AuthorizeRequestAsync(context, isList: false))
{
context.Response.ContentType = "application/json";
return @"""hidden"""; // JSON
Expand Down
22 changes: 18 additions & 4 deletions src/MiniProfiler.AspNetCore/MiniProfilerOptions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using StackExchange.Profiling.Internal;

Expand All @@ -23,19 +24,32 @@ public class MiniProfilerOptions : MiniProfilerBaseOptions

/// <summary>
/// A function that determines who can access the MiniProfiler results URL and list URL. It should return true when
/// the request client has access to results, false for a 401 to be returned. HttpRequest parameter is the current request and
/// the request client has access to results, false for a 401 to be returned.
/// <see cref="HttpRequest"/> parameter is the current request and will not be null.
/// </summary>
/// <remarks>
/// The HttpRequest parameter that will be passed into this function should never be null.
/// </remarks>
public Func<HttpRequest, bool> ResultsAuthorize { get; set; }

/// <summary>
/// An async function that determines who can access the MiniProfiler results URL and list URL. It should return true when
/// the request client has access to results, false for a 401 to be returned.
/// <see cref="HttpRequest"/> parameter is the current request and will not be null.
/// </summary>
public Func<HttpRequest, Task<bool>> ResultsAuthorizeAsync { get; set; }

/// <summary>
/// Special authorization function that is called for the list results (listing all the profiling sessions),
/// we also test for results authorize always. This must be set and return true, to enable the listing feature.
/// <see cref="HttpRequest"/> parameter is the current request and will not be null.
/// </summary>
public Func<HttpRequest, bool> ResultsListAuthorize { get; set; }

/// <summary>
/// Special async authorization function that is called for the list results (listing all the profiling sessions),
/// we also test for results authorize always. This must be set and return true, to enable the listing feature.
/// <see cref="HttpRequest"/> parameter is the current request and will not be null.
/// </summary>
public Func<HttpRequest, Task<bool>> ResultsListAuthorizeAsync { get; set; }

/// <summary>
/// Function to provide the unique user ID based on the request, to store MiniProfiler IDs user
/// </summary>
Expand Down
Loading