From fda6a1a77b3151be1138190153c94d540f06d9b0 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Thu, 10 Oct 2019 13:42:39 +0900 Subject: [PATCH] Use ClaimTransformer instead of Authentication's JWT --- BTCPayServer.Data/BTCPayServer.Data.csproj | 2 +- BTCPayServer.Tests/BTCPayServerTester.cs | 2 +- BTCPayServer/Authentication/BTCPayScopes.cs | 2 + .../Authentication/OpenId/OpenIdExtensions.cs | 2 - .../Controllers/RestApi/TestController.cs | 2 +- BTCPayServer/Data/StoreDataExtensions.cs | 1 + BTCPayServer/Hosting/BTCPayServerServices.cs | 39 +---------- BTCPayServer/Security/ClaimTransformer.cs | 66 +++++++++++++++++++ 8 files changed, 74 insertions(+), 42 deletions(-) create mode 100644 BTCPayServer/Security/ClaimTransformer.cs diff --git a/BTCPayServer.Data/BTCPayServer.Data.csproj b/BTCPayServer.Data/BTCPayServer.Data.csproj index 993f18f4b..3e7c7b134 100644 --- a/BTCPayServer.Data/BTCPayServer.Data.csproj +++ b/BTCPayServer.Data/BTCPayServer.Data.csproj @@ -13,7 +13,7 @@ - + diff --git a/BTCPayServer.Tests/BTCPayServerTester.cs b/BTCPayServer.Tests/BTCPayServerTester.cs index 583de2796..59ca6f23e 100644 --- a/BTCPayServer.Tests/BTCPayServerTester.cs +++ b/BTCPayServer.Tests/BTCPayServerTester.cs @@ -238,7 +238,7 @@ namespace BTCPayServer.Tests private async Task WaitSiteIsOperational() { - using (var cts = new CancellationTokenSource(10_000)) + using (var cts = new CancellationTokenSource(20_000)) { var synching = WaitIsFullySynched(cts.Token); var accessingHomepage = WaitCanAccessHomepage(cts.Token); diff --git a/BTCPayServer/Authentication/BTCPayScopes.cs b/BTCPayServer/Authentication/BTCPayScopes.cs index df9efbcdb..0b0e602ba 100644 --- a/BTCPayServer/Authentication/BTCPayScopes.cs +++ b/BTCPayServer/Authentication/BTCPayScopes.cs @@ -33,6 +33,7 @@ namespace BTCPayServer.Authentication } public const string CanViewStores = nameof(CanViewStores); + public const string CanEditStore = nameof(CanEditStore); public const string CanManageStores = nameof(CanManageStores); public const string CanViewInvoices = nameof(CanViewInvoices); public const string CanCreateInvoices = nameof(CanCreateInvoices); @@ -47,6 +48,7 @@ namespace BTCPayServer.Authentication AddScopePolicy(options, CanViewStores, context => context.HasScopes(BTCPayScopes.StoreManagement) || context.HasScopes(BTCPayScopes.ViewStores)); + options.AddPolicy(CanEditStore, p => p.RequireClaim(CanEditStore)); AddScopePolicy(options, CanManageStores, context => context.HasScopes(BTCPayScopes.StoreManagement)); AddScopePolicy(options, CanViewInvoices, diff --git a/BTCPayServer/Authentication/OpenId/OpenIdExtensions.cs b/BTCPayServer/Authentication/OpenId/OpenIdExtensions.cs index c9d3ccf7f..e2778e15f 100644 --- a/BTCPayServer/Authentication/OpenId/OpenIdExtensions.cs +++ b/BTCPayServer/Authentication/OpenId/OpenIdExtensions.cs @@ -26,8 +26,6 @@ namespace BTCPayServer.Authentication.OpenId ApplicationUser user, AuthenticationProperties properties = null) { - // Create a new ClaimsPrincipal containing the claims that - // will be used to create an id_token, a token or a code. var principal = await signInManager.CreateUserPrincipalAsync(user); // Create a new authentication ticket holding the user identity. diff --git a/BTCPayServer/Controllers/RestApi/TestController.cs b/BTCPayServer/Controllers/RestApi/TestController.cs index 7c94a3797..5d7f3c246 100644 --- a/BTCPayServer/Controllers/RestApi/TestController.cs +++ b/BTCPayServer/Controllers/RestApi/TestController.cs @@ -57,7 +57,7 @@ namespace BTCPayServer.Controllers.RestApi [HttpGet("me/stores/{storeId}/can-edit")] - [Authorize(Policy = Policies.CanModifyStoreSettings.Key, + [Authorize(Policy = RestAPIPolicies.CanEditStore, AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)] public bool CanEdit(string storeId) { diff --git a/BTCPayServer/Data/StoreDataExtensions.cs b/BTCPayServer/Data/StoreDataExtensions.cs index 2179726ab..23ab118ea 100644 --- a/BTCPayServer/Data/StoreDataExtensions.cs +++ b/BTCPayServer/Data/StoreDataExtensions.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Security.Claims; using System.Text; using System.Threading.Tasks; +using BTCPayServer.Authentication; using BTCPayServer.Payments; using BTCPayServer.Security; using BTCPayServer.Services.Rates; diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index deb60f121..2ec0e495b 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -53,6 +53,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authentication; namespace BTCPayServer.Hosting { @@ -175,6 +176,7 @@ namespace BTCPayServer.Hosting return htmlSanitizer; }); + services.AddTransient(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); @@ -285,43 +287,6 @@ namespace BTCPayServer.Hosting options.TokenValidationParameters.IssuerSigningKey = OpenIddictExtensions.GetSigningKey(configuration); options.IncludeErrorDetails = true; - options.Events = new JwtBearerEvents() - { - OnTokenValidated = async context => - { - var routeData = context.HttpContext.GetRouteData(); - var identity = ((ClaimsIdentity)context.Principal.Identity); - if (context.Principal.IsInRole(Roles.ServerAdmin)) - { - identity.AddClaim(new Claim(Policies.CanModifyServerSettings.Key, "true")); - } - - if (context.HttpContext.GetStoreData() != null || - !routeData.Values.TryGetValue("storeId", out var storeId)) - { - return; - } - var userManager = context.HttpContext.RequestServices - .GetService>(); - var storeRepository = context.HttpContext.RequestServices - .GetService(); - var userid = userManager.GetUserId(context.Principal); - - if (!string.IsNullOrEmpty(userid)) - { - var store = await storeRepository.FindStore((string)storeId, userid); - if (store == null) - { - context.Fail("Could not authorize you against store access"); - } - else - { - context.HttpContext.SetStoreData(store); - identity.AddClaims(store.GetClaims()); - } - } - } - }; }) .AddCookie() .AddBitpayAuthentication(); diff --git a/BTCPayServer/Security/ClaimTransformer.cs b/BTCPayServer/Security/ClaimTransformer.cs new file mode 100644 index 000000000..2397c9bb5 --- /dev/null +++ b/BTCPayServer/Security/ClaimTransformer.cs @@ -0,0 +1,66 @@ +using Microsoft.Extensions.DependencyInjection; +using System.Linq; +using Microsoft.AspNetCore.Http; +using BTCPayServer.Data; +using BTCPayServer.Services.Stores; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authentication; +using BTCPayServer.Authentication; + +namespace BTCPayServer.Security +{ + public class ClaimTransformer : IClaimsTransformation + { + private readonly HttpContext _HttpContext; + private readonly UserManager _userManager; + private readonly StoreRepository _storeRepository; + + public ClaimTransformer(IHttpContextAccessor httpContextAccessor, + UserManager userManager, + StoreRepository storeRepository) + { + _HttpContext = httpContextAccessor.HttpContext; + _userManager = userManager; + _storeRepository = storeRepository; + } + public async Task TransformAsync(ClaimsPrincipal principal) + { + var routeData = _HttpContext.GetRouteData(); + if (routeData == null) + return principal; + var identity = ((ClaimsIdentity)principal.Identity); + // A ClaimTransform can be called several time, we prevent dups by removing all the + // claims this transform might add. + var claims = new[] { RestAPIPolicies.CanEditStore }; + foreach (var claim in identity.Claims.Where(c => claims.Contains(c.Type)).ToList()) + { + identity.RemoveClaim(claim); + } + + if (!routeData.Values.TryGetValue("storeId", out var storeId)) + { + return principal; + } + var userid = _userManager.GetUserId(principal); + if (!string.IsNullOrEmpty(userid)) + { + var store = await _storeRepository.FindStore((string)storeId, userid); + if (store != null) + { + _HttpContext.SetStoreData(store); + foreach (var claim in store.GetClaims()) + { + if (claim.Type.Equals(Policies.CanModifyStoreSettings.Key, System.StringComparison.OrdinalIgnoreCase)) + { + identity.AddClaim(new Claim(RestAPIPolicies.CanEditStore, store.Id)); + } + } + } + } + return principal; + } + } +}