Add post for Razor Pages access check
This commit is contained in:
parent
c2812ffce2
commit
b93ae0b875
171
src/posts/RazorPagesAccessCheck.md
Normal file
171
src/posts/RazorPagesAccessCheck.md
Normal file
|
@ -0,0 +1,171 @@
|
|||
---
|
||||
title: Check If User Has Access To Razor Page
|
||||
description: An extension method and tag helper for checking if the user has access to a Razor Page
|
||||
tags: [ Programming, C#, ASP.NET Core, Razor Pages ]
|
||||
---
|
||||
|
||||
These extension methods and tag helpers will check if the user has access to a Razor Page. This can be useful to only display links if the user has access to that page. For example only display a link to edit an item if the user has access to the edit page.
|
||||
|
||||
## How To Use
|
||||
|
||||
Call the method directly:
|
||||
|
||||
```csharp
|
||||
this.Url.HasPageAccess("/Things/View");
|
||||
```
|
||||
|
||||
Or use the tag helper:
|
||||
|
||||
```cshtml
|
||||
<a asp-page="/Things/View" asp-route-thing-id="@thingId" remove-if-unauthorized preserve-content>
|
||||
@thingId
|
||||
</a>
|
||||
```
|
||||
|
||||
`remove-if-unauthorized` will remove the link if the user doesn't have access to the page, and `preserve-content` will leave the text (the thing ID) when the link is removed.
|
||||
|
||||
## Code
|
||||
|
||||
The extension method:
|
||||
|
||||
```csharp
|
||||
public static class AspNetExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the current user has the permissions to access the razor page at
|
||||
/// <paramref name="pagePath"/>
|
||||
/// </summary>
|
||||
/// <param name="urlHelper"></param>
|
||||
/// <param name="pagePath">The view engine path to the razor page</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the user can access the page, <c>false</c> if they can't or if the page is
|
||||
/// invalid
|
||||
/// </returns>
|
||||
public static async Task<bool> HasPageAccess(this IUrlHelper urlHelper, string pagePath)
|
||||
{
|
||||
// Get all of the necessary services
|
||||
HttpContext httpContext = urlHelper.ActionContext.HttpContext;
|
||||
EndpointDataSource endpointDataSource = httpContext.RequestServices
|
||||
.GetRequiredService<EndpointDataSource>();
|
||||
IAuthorizationService authorizationService = httpContext.RequestServices
|
||||
.GetRequiredService<IAuthorizationService>();
|
||||
IAuthorizationPolicyProvider policyProvider = httpContext.RequestServices
|
||||
.GetRequiredService<IAuthorizationPolicyProvider>();
|
||||
|
||||
// The endpoint that is:
|
||||
// 1) a razor page
|
||||
// 2) the page path matches
|
||||
Endpoint pageEndpoint = endpointDataSource.Endpoints
|
||||
.Where(e => e.Metadata
|
||||
.Where(m => m is PageActionDescriptor pad
|
||||
&& pad.ViewEnginePath.ToUpper() == pagePath.ToUpper())
|
||||
.Any())
|
||||
.FirstOrDefault();
|
||||
|
||||
if (pageEndpoint is null)
|
||||
return false;
|
||||
|
||||
// .AuthorizeFolder policies and AuthorizeAttributes get included in this
|
||||
IList<IAuthorizeData> pageAuthorization = pageEndpoint.Metadata
|
||||
.Select(m => m as IAuthorizeData)
|
||||
.Where(m => m is not null)
|
||||
.ToList();
|
||||
|
||||
AuthorizationPolicy pagePolicy = await AuthorizationPolicy
|
||||
.CombineAsync(policyProvider, pageAuthorization);
|
||||
|
||||
if (pagePolicy is null)
|
||||
return true;
|
||||
|
||||
return (await authorizationService.AuthorizeAsync(httpContext.User, pageEndpoint, pagePolicy))
|
||||
.Succeeded;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The tag helper:
|
||||
|
||||
```csharp
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Mvc.TagHelpers;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
|
||||
[HtmlTargetElement("a", Attributes = "asp-page,remove-if-unauthorized")]
|
||||
public class AuthorizedLinkTagHelper : AnchorTagHelper
|
||||
{
|
||||
protected IUrlHelperFactory UrlHelperFactory { get; set; }
|
||||
protected IUrlHelper Url { get => this.UrlHelperFactory.GetUrlHelper(this.ViewContext); }
|
||||
|
||||
/// <summary>
|
||||
/// If the current user doesn't have access to the linked page, then remove the link
|
||||
/// </summary>
|
||||
public bool RemoveIfUnauthorized { get; set; } = false;
|
||||
/// <summary>
|
||||
/// When <c>true</c> only remove the link itself (the opening and closing tags) and keep the
|
||||
/// contents of the link
|
||||
/// </summary>
|
||||
public bool PreserveContent { get; set; } = false;
|
||||
|
||||
public AuthorizedLinkTagHelper(IHtmlGenerator generator,
|
||||
IUrlHelperFactory urlHelperFactory) : base(generator)
|
||||
{
|
||||
this.UrlHelperFactory = urlHelperFactory;
|
||||
}
|
||||
|
||||
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(this.Page))
|
||||
{
|
||||
// This is a link to a razor page
|
||||
|
||||
string pagePath = NormalizePagePath(
|
||||
this.ViewContext.HttpContext.GetRouteData().Values["page"].ToString(),
|
||||
this.Page);
|
||||
|
||||
bool hasAccessToPage = await this.Url.HasPageAccess(pagePath);
|
||||
|
||||
if (!hasAccessToPage && this.PreserveContent)
|
||||
{
|
||||
output.TagName = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasAccessToPage)
|
||||
{
|
||||
output.TagName = null;
|
||||
output.Content.Clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(this.Controller) && !string.IsNullOrEmpty(this.Action))
|
||||
{
|
||||
// TODO: This is a link to an MVC action
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Converts a relative Razor Page path to an absolute path</summary>
|
||||
/// <param name="currentPagePath">
|
||||
/// The path to the current page, used to get the path prefix
|
||||
/// </param>
|
||||
/// <param name="linkPath">The page to get the path to</param>
|
||||
/// <returns><paramref name="linkPath"/> as an absolute path</returns>
|
||||
private static string NormalizePagePath(string currentPagePath, string linkPath)
|
||||
{
|
||||
if (linkPath[0] == '/')
|
||||
return linkPath;
|
||||
|
||||
int index = currentPagePath.LastIndexOf('/');
|
||||
|
||||
if (index == currentPagePath.Length - 1)
|
||||
{
|
||||
// If the first ends in a trailing slash e.g. "/Home/", assume it's a directory.
|
||||
return currentPagePath + linkPath;
|
||||
}
|
||||
|
||||
return string.Concat(currentPagePath.AsSpan(0, index + 1), linkPath);
|
||||
|
||||
}
|
||||
}
|
||||
```
|
Loading…
Reference in a new issue