using System; using System.Linq; using xIds.Helpers; using IdentityModel; using xIds.Interfaces; using xIdentityHelper; using xCommons.Constants; using xCommons.Extensions; using System.Security.Claims; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Http; using xIdentityModels.Constants; using xIdentityService.Constants; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Authorization; using System.Threading.Tasks; namespace xIds.Providers { [AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true )] public class XApiKeyAuthorizationFilter : AuthorizeAttribute, IAuthorizationFilter { // /// // /// Optional required scope for this endpoint. // /// Example: [XApiKeyAuthorizationFilter(RequiredScope = "read")] // /// // public string RequiredScope { get; set; } // /// // /// If false, a missing header will only result in NoResult (anonymous) // /// instead of a 401. Default is true (must provide a key). // /// // public bool ApiKeyRequired { get; set; } = true; // /// // /// Optional allowed scopes. If empty, no scope enforcement. // /// // public string[] AllowedScopes { get; set; } public XApiKeyAuthorizationFilter() { } /// /// Authorization Filtering ... /// /// public void OnAuthorization(AuthorizationFilterContext context) { Task.FromResult(OnAuthorizationAsync(context)); } /// /// Handle Authorization Filtering Using Tasks ... /// /// /// private async Task OnAuthorizationAsync(AuthorizationFilterContext context) { // // Access HttpContext ... var http = context.HttpContext; // try { // // Obtain the endpoint currently being executed var endpoint = http.GetEndpoint(); // // Retrieve all AuthorizeAttribute metadata from the endpoint var hasApiKeyScheme = false; var apiKeyScopes = XApiKeyHelper.GetXApiKeyScopes(); var apiKeyPolicies = XApiKeyHelper.GetXApiKeyPolicies(); var authorizeAttributes = endpoint?.Metadata.GetOrderedMetadata(); if ( apiKeyPolicies.HasChild() && authorizeAttributes.HasChild() ) { // // Retrieve a List of ApiKey Policies ... hasApiKeyScheme = authorizeAttributes.Any(attr => apiKeyPolicies.Any(asc => asc.ToNormalString() == attr.Policy.ToNormalString()) ); } // // if there is not any Policies for ApiKey Authentication // or Authorization, pass no Result ... if (!hasApiKeyScheme) { return; } // // Retrieve the application provider from DI ... var applicationProvider = http.RequestServices .GetService(typeof(IXApplicationProvider)) as IXApplicationProvider; if (applicationProvider.IsNull()) { // context.Result = new UnauthorizedObjectResult("Provider Service Not Registered ..."); return; } // // Checking Header ... var apiKeyHeader = XHeader.ApiKey.GetStringValue(); var apiKeyAuthentication = XAuthenticationScheme.XApiKey.GetStringValue(); if (!http.Request.Headers.ContainsKey(apiKeyHeader)) { // context.Result = new UnauthorizedObjectResult("ApiKey not Exists ..."); return; } // // Extract ApiKey from Header ... var apiKey = http.Request.Headers[apiKeyHeader].ToString(); if (apiKey.IsNullOrEmpty()) { // context.Result = new UnauthorizedObjectResult("ApiKey not Provided ..."); return; } // // Extract Client IP ... var clientIP = http.Request.HttpContext.Connection.RemoteIpAddress?.ToString(); // // Validating ApiKey ... var validationResult = await applicationProvider .ValidateApiKey( apiKey: apiKey, clientIP: clientIP ); if (validationResult.Errors.HasChild()) { // var message = validationResult.Errors.ToListString('\n'); context.Result = new UnauthorizedObjectResult(message); return; } // // Creating Claims ... var claims = new List { // // Owner Identifier of API Key ... new(ClaimTypes.Name, validationResult.OwnerId), new(JwtClaimTypes.Subject, validationResult.OwnerId), // // Application Id ... new(XCustomClaims.ApplicationId, validationResult.ApplicationId.ToString()), // // Authentication Type ... new(XCustomClaims.AuthType, apiKeyAuthentication), }; // // Add Scopes ... if (validationResult.Scopes != null) { // foreach (var scope in validationResult.Scopes) { claims.Add(new Claim(JwtClaimTypes.Scope, scope)); } } // // Create Principal and Ticket ... var identity = new ClaimsIdentity(claims, apiKeyAuthentication); var principal = new ClaimsPrincipal(identity); // http.User = principal; } catch { context.Result = new UnauthorizedObjectResult("ApiKey authorization failed ..."); } } } }