up
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Build Test Deploy / build-test (push) Has been cancelled
				
			
		
			
				
	
				Build Test Deploy / authority-container (push) Has been cancelled
				
			
		
			
				
	
				Build Test Deploy / docs (push) Has been cancelled
				
			
		
			
				
	
				Build Test Deploy / deploy (push) Has been cancelled
				
			
		
			
				
	
				Docs CI / lint-and-preview (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Build Test Deploy / build-test (push) Has been cancelled
				
			Build Test Deploy / authority-container (push) Has been cancelled
				
			Build Test Deploy / docs (push) Has been cancelled
				
			Build Test Deploy / deploy (push) Has been cancelled
				
			Docs CI / lint-and-preview (push) Has been cancelled
				
			This commit is contained in:
		| @@ -0,0 +1,54 @@ | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using NetTools; | ||||
| using System; | ||||
| using System.Linq; | ||||
| using System.Net; | ||||
| using System.Collections.Generic; | ||||
| using Ablera.Serdica.Authentication.Utilities; | ||||
| using Ablera.Serdica.Authentication.Models.Oidc; | ||||
| using Ablera.Serdica.Common.Tools.Utilities; | ||||
|  | ||||
| namespace Ablera.Serdica.Authority.Extensions; | ||||
|  | ||||
| public static class AllowedMaskExtensions | ||||
| { | ||||
|     // Lazily built the first time AllowedMaskExtensions is referenced. | ||||
|     private static readonly IReadOnlyCollection<IPAddressRange> AssociatedNetworks = ListeningNetworksRetriever.Retrieve(); | ||||
|  | ||||
|     public static AllowedMask? MergeWith(this AllowedMask? client, AllowedMask? global) | ||||
|         => (client, global) switch | ||||
|         { | ||||
|             (null, null) => null, | ||||
|             (null, _) => global, | ||||
|             _ => new() | ||||
|             { | ||||
|                 SameNetworks = client.SameNetworks ?? global?.SameNetworks, | ||||
|                 Networks = client.Networks ?? global?.Networks, | ||||
|                 Hosts = client.Hosts ?? global?.Hosts, | ||||
|                 Ports = client.Ports ?? global?.Ports | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|     public static bool MatchesRemote(this AllowedMask allow, HttpContext http) | ||||
|     { | ||||
|         var remoteIp = http.Connection.RemoteIpAddress ?? IPAddress.None; | ||||
|         var host = http.Request.Host.Host; | ||||
|         var port = http.Request.Host.Port ?? 0; | ||||
|  | ||||
|         bool ipOk = allow.Networks == null || | ||||
|                       allow.Networks.Any(net => IPAddressRange.Parse(net).Contains(remoteIp)); | ||||
|  | ||||
|         bool hostOk = allow.Hosts == null || | ||||
|                       allow.Hosts.Any(h => StringComparer.OrdinalIgnoreCase.Equals(h, host)); | ||||
|  | ||||
|         bool portOk = allow.Ports == null || allow.Ports.Contains(port); | ||||
|  | ||||
|         // Same-network rule: only enforced when SameNetwork == true | ||||
|         bool sameNetworkOk = | ||||
|             allow.SameNetworks != true ||         // Flag not enabled → no restriction | ||||
|             AssociatedNetworks == null ||         // Could not determine our own network | ||||
|             AssociatedNetworks.Any(network => network.Contains(remoteIp)); | ||||
|  | ||||
|         return ipOk && hostOk && portOk && sameNetworkOk; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,67 @@ | ||||
| using Microsoft.AspNetCore.Identity; | ||||
| using OpenIddict.Abstractions; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Security.Claims; | ||||
| using static OpenIddict.Abstractions.OpenIddictConstants; | ||||
|  | ||||
| namespace Ablera.Serdica.Authentication.Extensions | ||||
| { | ||||
|     public static class ClaimExtensions | ||||
|     { | ||||
|         public static IReadOnlyCollection<Claim> BuildClaims<TKeyType>( | ||||
|             this IdentityUser<TKeyType> identity, | ||||
|             string? userName = null, string? givenName = null, string? surname = null) | ||||
|             where TKeyType : IEquatable<TKeyType> => new[] | ||||
|         { | ||||
|             new Claim(ClaimTypes.NameIdentifier,            identity.Id?.ToString() ?? string.Empty), | ||||
|             new Claim(Claims.Subject,                       identity.Id?.ToString() ?? string.Empty), | ||||
|             new Claim(ClaimTypes.Name,                      userName ?? identity.UserName   ?? string.Empty), | ||||
|             new Claim(ClaimTypes.GivenName,                 givenName                       ?? string.Empty), | ||||
|             new Claim(ClaimTypes.Surname,                   surname                         ?? string.Empty), | ||||
|             new Claim(ClaimTypes.Email,                     identity.Email                  ?? string.Empty) | ||||
|         }; | ||||
|  | ||||
|         public static IEnumerable<string> DestinationsSelector(this Claim c) => c.Type switch | ||||
|         { | ||||
|             Claims.Name or Claims.PreferredUsername | ||||
|                 => new[] { Destinations.AccessToken, Destinations.IdentityToken }, | ||||
|  | ||||
|             Claims.Email when c.Subject?.HasScope(Scopes.Email) == true | ||||
|                 => new[] { Destinations.AccessToken, Destinations.IdentityToken }, | ||||
|  | ||||
|             Claims.Role when c.Subject?.HasScope(Scopes.Roles) == true | ||||
|                 => new[] { Destinations.AccessToken, Destinations.IdentityToken }, | ||||
|  | ||||
|             _ => new[] { Destinations.AccessToken } | ||||
|         }; | ||||
|  | ||||
|         public static string? GetUserId(this ClaimsPrincipal user) | ||||
|             => user.Claims.GetUserId() ?? Guid.Empty.ToString(); | ||||
|  | ||||
|         public static string? GetUserEmail(this ClaimsPrincipal user) | ||||
|             => user.Claims | ||||
|                 .FirstOrDefault(x => x.Type == ClaimTypes.Email) | ||||
|                 ?.Value?.ToString(); | ||||
|  | ||||
|         private static string? GetUserId(this IEnumerable<Claim> claims) | ||||
|             => claims | ||||
|                 .FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier) | ||||
|                 ?.Value?.ToString() | ||||
|             ?? claims | ||||
|                 .FirstOrDefault(x => x.Type == ClaimTypes.Name) | ||||
|                 ?.Value?.ToString(); | ||||
|  | ||||
|         public static string? GetClientApplicationId(this ClaimsPrincipal user) | ||||
|             => user.Claims.GetClientApplicationId(); | ||||
|  | ||||
|         private static string? GetClientApplicationId(this IEnumerable<Claim> claims) | ||||
|             => claims | ||||
|                 .FirstOrDefault(x => x.Type == Claims.Subject) | ||||
|                 ?.Value?.ToString() | ||||
|             ?? claims | ||||
|                 .FirstOrDefault(x => x.Type == Claims.ClientId) | ||||
|                 ?.Value?.ToString(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,16 @@ | ||||
| using System.Security.Claims; | ||||
| using OpenIddict.Abstractions; | ||||
| using static OpenIddict.Abstractions.OpenIddictConstants; | ||||
|  | ||||
| namespace Ablera.Serdica.Authentication.Extensions; | ||||
|  | ||||
| public static class PrincipalBuilder | ||||
| { | ||||
|     public static ClaimsPrincipal Build(string clientId, string authenticationSchema) | ||||
|     { | ||||
|         var claimsIdentity = new ClaimsIdentity(authenticationSchema); | ||||
|         claimsIdentity.AddClaim(Claims.Subject, clientId, Destinations.AccessToken); | ||||
|         var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); | ||||
|         return claimsPrincipal; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,18 @@ | ||||
| using Ablera.Serdica.Authentication.Models; | ||||
| using Ablera.Serdica.Common.Tools.Extensions; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using System.Text.Json; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace Ablera.Serdica.Authentication.Extensions; | ||||
|  | ||||
| public static class ProxyResultExtension | ||||
| { | ||||
|     public static async Task ReturnHttpRessponse(this ProxyResult proxyResult, HttpResponse httpResponse) | ||||
|     { | ||||
|         if (httpResponse.HasStarted) return; | ||||
|         httpResponse.StatusCode = (int)proxyResult.StatusCode; | ||||
|         httpResponse.ContentType = "application/json"; | ||||
|         await JsonSerializer.SerializeAsync(httpResponse.Body, proxyResult, proxyResult.GetType(), GlobalJsonSerializerOptions.JsonSerializerOptions); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user