using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using BTCPayServer.Data; using BTCPayServer.Models; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using OpenIddict.Abstractions; using OpenIddict.Core; using OpenIddict.Server; using static BTCPayServer.Authentication.RestAPIPolicies; namespace BTCPayServer.Security.OpenId { public static class OpenIdExtensions { public static ImmutableHashSet Restrict(this ImmutableHashSet scopes, ClaimsPrincipal claimsPrincipal) { HashSet restricted = new HashSet(); foreach (var scope in scopes) { if (scope == BTCPayScopes.ServerManagement && !claimsPrincipal.IsInRole(Roles.ServerAdmin)) continue; restricted.Add(scope); } return restricted.ToImmutableHashSet(); } public static async Task CreateClaimsPrincipalAsync(OpenIddictApplicationManager applicationManager, OpenIddictAuthorizationManager authorizationManager, IdentityOptions identityOptions, SignInManager signInManager, OpenIddictRequest request, ApplicationUser user) { var principal = await signInManager.CreateUserPrincipalAsync(user); if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType()) { principal.SetScopes(request.GetScopes().Restrict(principal)); } else if (request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(principal.GetInternalAuthorizationId())) { var app = await applicationManager.FindByClientIdAsync(request.ClientId); var authorizationId = await IsUserAuthorized(authorizationManager, request, user.Id, app.Id); if (!string.IsNullOrEmpty(authorizationId)) { principal.SetInternalAuthorizationId(authorizationId); } } principal.SetDestinations(identityOptions); return principal; } public static void SetDestinations(this ClaimsPrincipal principal, IdentityOptions identityOptions) { foreach (var claim in principal.Claims) { claim.SetDestinations(GetDestinations(identityOptions, claim, principal)); } } private static IEnumerable GetDestinations(IdentityOptions identityOptions, Claim claim, ClaimsPrincipal principal) { switch (claim.Type) { case OpenIddictConstants.Claims.Name: case OpenIddictConstants.Claims.Email: yield return OpenIddictConstants.Destinations.AccessToken; yield break; } } public static async Task IsUserAuthorized( OpenIddictAuthorizationManager authorizationManager, OpenIddictRequest request, string userId, string applicationId) { var authorizations = await authorizationManager.ListAsync(queryable => queryable.Where(authorization => authorization.Subject.Equals(userId, StringComparison.OrdinalIgnoreCase) && applicationId.Equals(authorization.Application.Id, StringComparison.OrdinalIgnoreCase) && authorization.Status.Equals(OpenIddictConstants.Statuses.Valid, StringComparison.OrdinalIgnoreCase))).ToArrayAsync(); if (authorizations.Length > 0) { var scopeTasks = authorizations.Select(authorization => (authorizationManager.GetScopesAsync(authorization).AsTask(), authorization.Id)); await Task.WhenAll(scopeTasks.Select((tuple) => tuple.Item1)); var authorizationsWithSufficientScopes = scopeTasks .Select((tuple) => (tuple.Id, Scopes: tuple.Item1.Result)) .Where((tuple) => !request.GetScopes().Except(tuple.Scopes).Any()); if (authorizationsWithSufficientScopes.Any()) { return authorizationsWithSufficientScopes.First().Id; } } return null; } } }