Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d27f83b
Add middleware for Windows authentication provider
StephenCWills Jul 25, 2025
1cb4122
Adds a builder to set up the authentication in the request pipeline
StephenCWills Jul 25, 2025
f5a10df
Move security-related classes from Hosting to Security
StephenCWills Jul 25, 2025
3757a12
Authorization handler for controller actions
StephenCWills Jul 31, 2025
cf8d588
Use cookie authentication instead of session middleware
StephenCWills Aug 6, 2025
5fb1fbd
Fix errors in cookie authentication
StephenCWills Aug 6, 2025
2fb475b
Define helper for requiring controller access in an authorization policy
StephenCWills Aug 6, 2025
b9900ab
Use GetOrderedMetadata() instead of OfType() where applicable
StephenCWills Aug 7, 2025
3e011ca
Add role claims for resource access levels
StephenCWills Aug 7, 2025
1765819
Implement logout mechanism
StephenCWills Aug 7, 2025
b272189
Register provider endpoints based on authentication setup rather than…
StephenCWills Aug 8, 2025
ee8a80e
Fix resource access logic and introduce HttpMethod fallback
StephenCWills Aug 8, 2025
656f162
Fix logout middleware to retrieve named instance of cookie auth options
StephenCWills Aug 8, 2025
28a8666
Don't assume view/edit implies admin
StephenCWills Aug 8, 2025
86d042d
Remove unused authentication provider middleware
StephenCWills Aug 11, 2025
2638ce4
Add resource info to the authorization info controller
StephenCWills Aug 13, 2025
a79da09
Add a more human-readable alias to claim types returned by Authorizat…
StephenCWills Aug 13, 2025
4ce6442
Add sliding expiration to authentication session cache entries
StephenCWills Aug 14, 2025
597f2c1
Exclude authentication provider claims in authentication runtime midd…
StephenCWills Aug 18, 2025
fe5cee9
Simplify controller access logic
StephenCWills Aug 19, 2025
c788059
Add endpoint to check the user's level of access to resources
StephenCWills Aug 19, 2025
2f6b8ff
Update claim type endpoint in authorization controller
StephenCWills Aug 20, 2025
435fc11
Removed AuthChecks from ModelController
clackner-gpa Aug 21, 2025
bcd255e
update returned options to have Label, Value properties with a LongLabel
prestoncraw Aug 21, 2025
4e7ac30
Change access levels to access types
StephenCWills Aug 21, 2025
1966559
Add support for search text to the claimTypes endpoint
StephenCWills Aug 22, 2025
53d45e1
Remove user search and fix up claim search
StephenCWills Aug 22, 2025
1c41337
Use CookieSecurePolicy.SameAsRequest to support authentication for http
StephenCWills Aug 26, 2025
3e43402
Carry name claims over from the authentication provider
StephenCWills Aug 26, 2025
b5bd4d6
Removed GetAuth Check
clackner-gpa Aug 26, 2025
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
268 changes: 268 additions & 0 deletions src/Gemstone.Web/APIController/AuthorizationInfoControllerBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
//******************************************************************************************************
// AuthorizationInfoControllerBase.cs - Gbtc
//
// Copyright © 2025, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
// file except in compliance with the License. You may obtain a copy of the License at:
//
// http://opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 07/15/2025 - Stephen C. Wills
// Generated original version of source code.
//
//******************************************************************************************************

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Gemstone.Collections.CollectionExtensions;
using Gemstone.Security.AccessControl;
using Gemstone.Security.AuthenticationProviders;
using Gemstone.Web.Security;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;

namespace Gemstone.Web.APIController;

/// <summary>
/// Base class for a controller that provides information about claims to client applications.
/// </summary>
public abstract partial class AuthorizationInfoControllerBase : ControllerBase
{
#region [ Members ]

// Nested Types

/// <summary>
/// Represents an entry in a resource access list.
/// </summary>
public class ResourceAccessEntry
{
/// <summary>
/// Gets or sets the type of the resource.
/// </summary>
public string ResourceType { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the name of the resource.
/// </summary>
public string ResourceName { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the level of access needed.
/// </summary>
public ResourceAccessType Access { get; set; }
}

#endregion

#region [ Methods ]

/// <summary>
/// Gets all the claims associated with the authenticated user.
/// </summary>
/// <returns>All of the authenticated user's claims.</returns>
[HttpGet, Route("user/claims")]
public virtual IActionResult GetAllClaims()
{
var claims = HttpContext.User.Claims
.Select(claim => new { claim.Type, claim.Value });

return Ok(claims);
}

/// <summary>
/// Gets the claims of a given type associated with the authenticated user.
/// </summary>
/// <param name="claimType">The type of the claims to be returned</param>
/// <returns>The authenticated user's claims of the given type.</returns>
[HttpGet, Route("user/claims/{**claimType}")]
public virtual IEnumerable<string> GetClaims(string claimType)
{
return HttpContext.User
.FindAll(claim => claim.Type == claimType)
.Select(claim => claim.Value);
}

/// <summary>
/// Gets the types of claims that the authentication provider associates with its users.
/// </summary>
/// <param name="serviceProvider">Provides injected dependencies</param>
/// <param name="providerIdentity">Identity of the authentication provider</param>
/// <param name="searchText">Text used to narrow the list of results</param>
/// <returns>The list of claim types used by the authentication provider.</returns>
[HttpGet, Route("provider/{providerIdentity}/claimTypes")]
public virtual IActionResult FindClaimTypes(IServiceProvider serviceProvider, string providerIdentity, string? searchText)
{
IAuthenticationProvider? claimsProvider = serviceProvider
.GetKeyedService<IAuthenticationProvider>(providerIdentity);

if (claimsProvider is null)
return NotFound();

Regex? searchPattern = ToSearchPattern(searchText);

var claimTypes = claimsProvider
.GetClaimTypes()
.Where(type => searchPattern is null || searchPattern.IsMatch(type.Type))
.Select(type => new { Value = type.Type, Label = type.Alias, LongLabel = type.Description });

return Ok(claimTypes);
}

/// <summary>
/// Gets a list of claims that can be assigned to users who authenticate with the provider.
/// </summary>
/// <param name="serviceProvider">Provides injected dependencies</param>
/// <param name="providerIdentity">Identity of the authentication provider</param>
/// <param name="claimType">The type of claims to search for</param>
/// <param name="searchText">Text used to narrow the list of results</param>
/// <returns>A list of claims from the authentication provider.</returns>
[HttpGet, Route("provider/{providerIdentity}/claims/{**claimType}")]
public virtual IActionResult FindClaims(IServiceProvider serviceProvider, string providerIdentity, string claimType, string? searchText)
{
IAuthenticationProvider? claimsProvider = serviceProvider
.GetKeyedService<IAuthenticationProvider>(providerIdentity);

if (claimsProvider is null)
return NotFound();

if (!isSupported(claimType))
return BadRequest($"Claim type not supported");

var claims = claimsProvider
.FindClaims(claimType, searchText ?? "*")
.Select(claim => new { Label = claim.Description, claim.Value, LongLabel = claim.LongDescription });

return Ok(claims);

bool isSupported(string claimType) => claimsProvider
.GetClaimTypes()
.Any(type => type.Type == claimType);
}

/// <summary>
/// Gets a list of resources available for which permissions can be granted within the application.
/// </summary>
/// <param name="policyProvider">Provides authorization policies defined within the application</param>
/// <param name="endpointDataSource">Source for endpoint data used to look up controller and action metadata</param>
/// <returns>A list of resources within the application.</returns>
[HttpGet, Route("resources")]
public virtual async Task<IActionResult> GetResources(IAuthorizationPolicyProvider policyProvider, EndpointDataSource endpointDataSource)
{
Dictionary<string, HashSet<ResourceAccessType>> resourceAccessLookup = [];

foreach (Endpoint endpoint in endpointDataSource.Endpoints)
{
ControllerActionDescriptor? descriptor = endpoint.Metadata
.GetMetadata<ControllerActionDescriptor>();

if (descriptor is null)
continue;

IReadOnlyList<IAuthorizeData> authorizeData = endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? [];
IReadOnlyList<AuthorizationPolicy> policies = endpoint.Metadata.GetOrderedMetadata<AuthorizationPolicy>() ?? [];
IReadOnlyList<IAuthorizationRequirementData> requirementData = endpoint.Metadata.GetOrderedMetadata<IAuthorizationRequirementData>() ?? [];
AuthorizationPolicy? policy = await AuthorizationPolicy.CombineAsync(policyProvider, authorizeData, policies);

bool hasControllerAccessRequirement = requirementData
.SelectMany(datum => datum.GetRequirements())
.Concat(policy?.Requirements ?? [])
.Any(requirement => requirement is ControllerAccessRequirement);

if (!hasControllerAccessRequirement)
continue;

ResourceAccessAttribute? accessAttribute = endpoint.Metadata
.GetMetadata<ResourceAccessAttribute>();

string resourceName = accessAttribute.GetResourceName(descriptor);
IEnumerable<ResourceAccessType> accessTypes = ToAccessTypes(endpoint, accessAttribute);
HashSet<ResourceAccessType> access = resourceAccessLookup.GetOrAdd(resourceName, _ => []);
access.UnionWith(accessTypes);
}

var resources = resourceAccessLookup
.OrderBy(kvp => kvp.Key)
.Select(kvp => new
{
Type = "Controller",
Name = kvp.Key,
AccessTypes = kvp.Value.OrderBy(type => type)
});

return Ok(resources);

static IEnumerable<ResourceAccessType> ToAccessTypes(Endpoint endpoint, ResourceAccessAttribute? accessAttribute)
{
if (accessAttribute is not null)
return [accessAttribute.Access];

HttpMethodMetadata? httpMethodMetadata = endpoint.Metadata
.GetMetadata<HttpMethodMetadata>();

IReadOnlyList<string> httpMethods = httpMethodMetadata?.HttpMethods
?? [];

return httpMethods
.Select(accessAttribute.GetAccessType)
.Where(type => type is not null)
.Select(type => type.GetValueOrDefault());
}
}

/// <summary>
/// Checks whether the user has permission to access each of the resources listed in the access list.
/// </summary>
/// <param name="accessList">List of resources the user is attempting to access</param>
/// <returns>List of boolean values indicating whether the user has access to each requested resource.</returns>
[HttpPost, Route("access")]
public virtual IEnumerable<bool> CheckAccess([FromBody] ResourceAccessEntry[] accessList)
{
return accessList.Select(entry => User.HasAccessTo(entry.ResourceType, entry.ResourceName, entry.Access));
}

#endregion

#region [ Static ]

// Static Methods

private static Regex? ToSearchPattern(string? searchText)
{
if (searchText is null)
return null;

Regex conversionPattern = SearchTextConversionPattern();

string searchPattern = conversionPattern.Replace(searchText, match => match.Value switch
{
"*" => ".*",
@"\\" => @"\\",
string v when v.StartsWith('\\') => Regex.Escape(v[1..]),
string v => Regex.Escape(v)
});

return new(searchPattern);
}

[GeneratedRegex(@"\\.|\*|[^\\*]+")]
private static partial Regex SearchTextConversionPattern();

#endregion
}
117 changes: 0 additions & 117 deletions src/Gemstone.Web/APIController/ClaimsControllerBase.cs

This file was deleted.

Loading