From 3c9b58916bd8d7b584345fce9d0b2a50ce10974e Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Tue, 8 Oct 2019 15:21:30 +0900 Subject: [PATCH] Update to OpenIddict3.0 --- BTCPayServer.Data/BTCPayServer.Data.csproj | 4 +- BTCPayServer.Tests/AuthenticationTests.cs | 22 ++--- BTCPayServer.Tests/BTCPayServerTester.cs | 8 +- BTCPayServer.Tests/Dockerfile | 2 + BTCPayServer.Tests/TestUtils.cs | 5 + BTCPayServer/Authentication/BTCPayScopes.cs | 1 + .../AuthorizationCodeGrantTypeEventHandler.cs | 27 ------ .../OpenId/BaseOpenIdGrantHandler.cs | 19 ++-- .../ClientCredentialsGrantTypeEventHandler.cs | 34 ++++--- .../OpenId/LogoutEventHandler.cs | 32 +++---- .../Authentication/OpenId/OpenIdExtensions.cs | 93 +++++++------------ .../OpenIdGrantHandlerCheckCanSignIn.cs | 48 +++++----- .../OpenId/PasswordGrantTypeEventHandler.cs | 39 ++++---- .../RefreshTokenGrantTypeEventHandler.cs | 27 ------ BTCPayServer/BTCPayServer.csproj | 10 +- .../Controllers/AuthorizationController.cs | 46 ++++----- .../Controllers/RestApi/TestController.cs | 6 +- .../Extensions/OpenIddictExtensions.cs | 8 +- BTCPayServer/Hosting/BTCPayServerServices.cs | 14 --- BTCPayServer/Hosting/Startup.cs | 63 ++++--------- BTCPayServer/Program.cs | 2 +- .../Security/AuthenticationSchemes.cs | 3 +- .../Security/OpenIdAuthorizationHandler.cs | 15 ++- BTCPayServer/Security/SecurityExtensions.cs | 2 +- .../Views/Authorization/Authorize.cshtml | 4 +- Nuget.Config | 8 -- amd64.Dockerfile | 1 + arm32v7.Dockerfile | 1 + nuget.config | 13 +++ 29 files changed, 220 insertions(+), 337 deletions(-) delete mode 100644 BTCPayServer/Authentication/OpenId/AuthorizationCodeGrantTypeEventHandler.cs delete mode 100644 BTCPayServer/Authentication/OpenId/RefreshTokenGrantTypeEventHandler.cs delete mode 100644 Nuget.Config create mode 100644 nuget.config diff --git a/BTCPayServer.Data/BTCPayServer.Data.csproj b/BTCPayServer.Data/BTCPayServer.Data.csproj index 3e7c7b134..e9f49c5ab 100644 --- a/BTCPayServer.Data/BTCPayServer.Data.csproj +++ b/BTCPayServer.Data/BTCPayServer.Data.csproj @@ -6,14 +6,14 @@ - + - + diff --git a/BTCPayServer.Tests/AuthenticationTests.cs b/BTCPayServer.Tests/AuthenticationTests.cs index 450bce7f2..1fdb02629 100644 --- a/BTCPayServer.Tests/AuthenticationTests.cs +++ b/BTCPayServer.Tests/AuthenticationTests.cs @@ -4,16 +4,9 @@ using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; -#if NETCOREAPP21 -using OpenIddictRequest = AspNet.Security.OpenIdConnect.Primitives.OpenIdConnectRequest; -using OpenIddictResponse = AspNet.Security.OpenIdConnect.Primitives.OpenIdConnectResponse; -using OpenIdConnectDefaults = OpenIddict.Server.OpenIddictServerDefaults; -using AspNet.Security.OpenIdConnect.Primitives; -#else using System.Security.Claims; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; -#endif using BTCPayServer.Tests.Logging; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Xunit; using Xunit.Abstractions; @@ -31,7 +24,7 @@ namespace BTCPayServer.Tests { public class AuthenticationTests { - public const int TestTimeout = 60_000; + public const int TestTimeout = TestUtils.TestTimeout; public AuthenticationTests(ITestOutputHelper helper) { Logs.Tester = new XUnitLog(helper) {Name = "Tests"}; @@ -112,6 +105,7 @@ namespace BTCPayServer.Tests var user = tester.NewAccount(); user.GrantAccess(); + await user.MakeAdmin(); var id = Guid.NewGuid().ToString(); var redirecturi = new Uri("http://127.0.0.1/oidc-callback"); var openIdClient = await user.RegisterOpenIdClient( @@ -124,7 +118,7 @@ namespace BTCPayServer.Tests }); var implicitAuthorizeUrl = new Uri(tester.PayTester.ServerUri, - $"connect/authorize?response_type=token&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid&nonce={Guid.NewGuid().ToString()}"); + $"connect/authorize?response_type=token&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid server_management store_management&nonce={Guid.NewGuid().ToString()}"); s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl); s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password); s.Driver.FindElement(By.Id("consent-yes")).Click(); @@ -225,7 +219,7 @@ namespace BTCPayServer.Tests RedirectUris = {redirecturi} }, secret); var authorizeUrl = new Uri(tester.PayTester.ServerUri, - $"connect/authorize?response_type=code&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid offline_access&state={Guid.NewGuid().ToString()}"); + $"connect/authorize?response_type=code&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid offline_access server_management store_management&state={Guid.NewGuid().ToString()}"); s.Driver.Navigate().GoToUrl(authorizeUrl); s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password); s.Driver.FindElement(By.Id("consent-yes")).Click(); @@ -325,7 +319,8 @@ namespace BTCPayServer.Tests new KeyValuePair("grant_type", OpenIddictConstants.GrantTypes.ClientCredentials), new KeyValuePair("client_id", openIdClient.ClientId), - new KeyValuePair("client_secret", secret) + new KeyValuePair("client_secret", secret), + new KeyValuePair("scope", "server_management store_management") }) }; @@ -365,7 +360,8 @@ namespace BTCPayServer.Tests new KeyValuePair("username", user.RegisterDetails.Email), new KeyValuePair("password", user.RegisterDetails.Password), new KeyValuePair("client_id", openIdClient.ClientId), - new KeyValuePair("client_secret", secret) + new KeyValuePair("client_secret", secret), + new KeyValuePair("scope", "server_management store_management") }) }; diff --git a/BTCPayServer.Tests/BTCPayServerTester.cs b/BTCPayServer.Tests/BTCPayServerTester.cs index a49689ea2..e02ab94e2 100644 --- a/BTCPayServer.Tests/BTCPayServerTester.cs +++ b/BTCPayServer.Tests/BTCPayServerTester.cs @@ -32,11 +32,7 @@ using System.Security.Claims; using System.Security.Principal; using System.Text; using System.Threading; -#if NETCOREAPP21 -using AspNet.Security.OpenIdConnect.Primitives; -#else -using OpenIdConnectConstants = OpenIddict.Abstractions.OpenIddictConstants; -#endif +using OpenIddict.Abstractions; using Xunit; using BTCPayServer.Services; using System.Net.Http; @@ -305,7 +301,7 @@ namespace BTCPayServer.Tests if (userId != null) { List claims = new List(); - claims.Add(new Claim(OpenIdConnectConstants.Claims.Subject, userId)); + claims.Add(new Claim(OpenIddictConstants.Claims.Subject, userId)); if (isAdmin) claims.Add(new Claim(ClaimTypes.Role, Roles.ServerAdmin)); context.User = new ClaimsPrincipal(new ClaimsIdentity(claims.ToArray(), AuthenticationSchemes.Cookie)); diff --git a/BTCPayServer.Tests/Dockerfile b/BTCPayServer.Tests/Dockerfile index 2f369b9fc..c3ff88e26 100644 --- a/BTCPayServer.Tests/Dockerfile +++ b/BTCPayServer.Tests/Dockerfile @@ -5,10 +5,12 @@ ENV LC_ALL en_US.UTF-8 ENV LANG en_US.UTF-8 WORKDIR /source +COPY nuget.config nuget.config COPY Build/Common.csproj Build/Common.csproj COPY BTCPayServer/BTCPayServer.csproj BTCPayServer/BTCPayServer.csproj COPY BTCPayServer.Common/BTCPayServer.Common.csproj BTCPayServer.Common/BTCPayServer.Common.csproj COPY BTCPayServer.Rating/BTCPayServer.Rating.csproj BTCPayServer.Rating/BTCPayServer.Rating.csproj +COPY BTCPayServer.Data/BTCPayServer.Data.csproj BTCPayServer.Data/BTCPayServer.Data.csproj COPY BTCPayServer.Tests/BTCPayServer.Tests.csproj BTCPayServer.Tests/BTCPayServer.Tests.csproj RUN dotnet restore BTCPayServer.Tests/BTCPayServer.Tests.csproj diff --git a/BTCPayServer.Tests/TestUtils.cs b/BTCPayServer.Tests/TestUtils.cs index 67a159206..2f2819fd1 100644 --- a/BTCPayServer.Tests/TestUtils.cs +++ b/BTCPayServer.Tests/TestUtils.cs @@ -15,6 +15,11 @@ namespace BTCPayServer.Tests { public static class TestUtils { +#if DEBUG + public const int TestTimeout = 600_000; +#else + public const int TestTimeout = 60_000; +#endif public static DirectoryInfo TryGetSolutionDirectoryInfo(string currentPath = null) { var directory = new DirectoryInfo( diff --git a/BTCPayServer/Authentication/BTCPayScopes.cs b/BTCPayServer/Authentication/BTCPayScopes.cs index afb7d77b0..0d790530b 100644 --- a/BTCPayServer/Authentication/BTCPayScopes.cs +++ b/BTCPayServer/Authentication/BTCPayScopes.cs @@ -30,6 +30,7 @@ namespace BTCPayServer.Authentication //create and manage apps public const string AppManagement = "app_management"; public const string WalletManagement = "wallet_management"; + public const string ServerManagement = "server_management"; } public const string CanViewStores = nameof(CanViewStores); diff --git a/BTCPayServer/Authentication/OpenId/AuthorizationCodeGrantTypeEventHandler.cs b/BTCPayServer/Authentication/OpenId/AuthorizationCodeGrantTypeEventHandler.cs deleted file mode 100644 index 0352139e1..000000000 --- a/BTCPayServer/Authentication/OpenId/AuthorizationCodeGrantTypeEventHandler.cs +++ /dev/null @@ -1,27 +0,0 @@ -using AspNet.Security.OpenIdConnect.Primitives; -using BTCPayServer.Data; -using BTCPayServer.Models; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Options; -using OpenIddict.Core; - -namespace BTCPayServer.Authentication.OpenId -{ - public class AuthorizationCodeGrantTypeEventHandler : OpenIdGrantHandlerCheckCanSignIn - { - public AuthorizationCodeGrantTypeEventHandler( - OpenIddictApplicationManager applicationManager, - OpenIddictAuthorizationManager authorizationManager, - SignInManager signInManager, - IOptions identityOptions, - UserManager userManager) : base(applicationManager, authorizationManager, signInManager, - identityOptions, userManager) - { - } - - protected override bool IsValid(OpenIdConnectRequest request) - { - return request.IsAuthorizationCodeGrantType(); - } - } -} diff --git a/BTCPayServer/Authentication/OpenId/BaseOpenIdGrantHandler.cs b/BTCPayServer/Authentication/OpenId/BaseOpenIdGrantHandler.cs index 213461be8..d138fa9f7 100644 --- a/BTCPayServer/Authentication/OpenId/BaseOpenIdGrantHandler.cs +++ b/BTCPayServer/Authentication/OpenId/BaseOpenIdGrantHandler.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using AspNet.Security.OpenIdConnect.Primitives; +using OpenIdConnectRequest = OpenIddict.Abstractions.OpenIddictRequest; using BTCPayServer.Data; using BTCPayServer.Models; using Microsoft.AspNetCore.Authentication; @@ -7,11 +7,13 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using OpenIddict.Core; using OpenIddict.Server; +using System.Security.Claims; namespace BTCPayServer.Authentication.OpenId { - public abstract class BaseOpenIdGrantHandler : IOpenIddictServerEventHandler - where T : class, IOpenIddictServerEvent + public abstract class BaseOpenIdGrantHandler : + IOpenIddictServerHandler + where T : OpenIddictServerEvents.BaseContext { private readonly OpenIddictApplicationManager _applicationManager; private readonly OpenIddictAuthorizationManager _authorizationManager; @@ -31,14 +33,11 @@ namespace BTCPayServer.Authentication.OpenId } - protected async Task CreateTicketAsync( - OpenIdConnectRequest request, ApplicationUser user, - AuthenticationProperties properties = null) + protected Task CreateClaimsPrincipalAsync(OpenIdConnectRequest request, ApplicationUser user) { - return await OpenIdExtensions.CreateAuthenticationTicket(_applicationManager, _authorizationManager, - _identityOptions.Value, _signInManager, request, user, properties); + return OpenIdExtensions.CreateClaimsPrincipalAsync(_applicationManager, _authorizationManager, + _identityOptions.Value, _signInManager, request, user); } - - public abstract Task HandleAsync(T notification); + public abstract ValueTask HandleAsync(T notification); } } diff --git a/BTCPayServer/Authentication/OpenId/ClientCredentialsGrantTypeEventHandler.cs b/BTCPayServer/Authentication/OpenId/ClientCredentialsGrantTypeEventHandler.cs index a47cf9c43..9d9b87abf 100644 --- a/BTCPayServer/Authentication/OpenId/ClientCredentialsGrantTypeEventHandler.cs +++ b/BTCPayServer/Authentication/OpenId/ClientCredentialsGrantTypeEventHandler.cs @@ -1,7 +1,5 @@ using System.Security.Claims; using System.Threading.Tasks; -using AspNet.Security.OpenIdConnect.Extensions; -using AspNet.Security.OpenIdConnect.Primitives; using BTCPayServer.Data; using BTCPayServer.Models; using Microsoft.AspNetCore.Authentication; @@ -14,9 +12,13 @@ using OpenIddict.Server; namespace BTCPayServer.Authentication.OpenId { - public class - ClientCredentialsGrantTypeEventHandler : BaseOpenIdGrantHandler + public class ClientCredentialsGrantTypeEventHandler : + BaseOpenIdGrantHandler { + public static OpenIddictServerHandlerDescriptor Descriptor { get; } = + OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseScopedHandler() + .Build(); private readonly OpenIddictApplicationManager _applicationManager; private readonly UserManager _userManager; @@ -33,32 +35,28 @@ namespace BTCPayServer.Authentication.OpenId _userManager = userManager; } - public override async Task HandleAsync( - OpenIddictServerEvents.HandleTokenRequest notification) + public override async ValueTask HandleAsync( + OpenIddictServerEvents.HandleTokenRequestContext notification) { - var request = notification.Context.Request; + var request = notification.Request; + var context = notification; if (!request.IsClientCredentialsGrantType()) { - // Allow other handlers to process the event. - return OpenIddictServerEventState.Unhandled; + return; } - var application = await _applicationManager.FindByClientIdAsync(request.ClientId, - notification.Context.HttpContext.RequestAborted); + var application = await _applicationManager.FindByClientIdAsync(request.ClientId); if (application == null) { - notification.Context.Reject( + context.Reject( error: OpenIddictConstants.Errors.InvalidClient, description: "The client application was not found in the database."); - // Don't allow other handlers to process the event. - return OpenIddictServerEventState.Handled; + return; } var user = await _userManager.FindByIdAsync(application.ApplicationUserId); - - notification.Context.Validate(await CreateTicketAsync(request, user)); - // Don't allow other handlers to process the event. - return OpenIddictServerEventState.Handled; + context.Principal = await CreateClaimsPrincipalAsync(request, user); + notification.HandleAuthentication(); } } } diff --git a/BTCPayServer/Authentication/OpenId/LogoutEventHandler.cs b/BTCPayServer/Authentication/OpenId/LogoutEventHandler.cs index d348d91c1..35ce4d4ca 100644 --- a/BTCPayServer/Authentication/OpenId/LogoutEventHandler.cs +++ b/BTCPayServer/Authentication/OpenId/LogoutEventHandler.cs @@ -3,37 +3,33 @@ using System.Threading.Tasks; using BTCPayServer.Data; using BTCPayServer.Models; using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using OpenIddict.Core; using OpenIddict.Server; +using Microsoft.AspNetCore; +using OpenIddict.Server.AspNetCore; namespace BTCPayServer.Authentication.OpenId { - public class LogoutEventHandler : BaseOpenIdGrantHandler + public class LogoutEventHandler : IOpenIddictServerHandler { - public LogoutEventHandler( - OpenIddictApplicationManager applicationManager, - OpenIddictAuthorizationManager authorizationManager, - SignInManager signInManager, IOptions identityOptions) : base( - applicationManager, authorizationManager, - signInManager, identityOptions) + protected readonly SignInManager _signInManager; + public static OpenIddictServerHandlerDescriptor Descriptor { get; } = + OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseScopedHandler() + .Build(); + public LogoutEventHandler(SignInManager signInManager) { + _signInManager = signInManager; } - public override async Task HandleAsync( - OpenIddictServerEvents.HandleLogoutRequest notification) + public async ValueTask HandleAsync( + OpenIddictServerEvents.HandleLogoutRequestContext notification) { - // Ask ASP.NET Core Identity to delete the local and external cookies created - // when the user agent is redirected from the external identity provider - // after a successful authentication flow (e.g Google or Facebook). await _signInManager.SignOutAsync(); - - // Returning a SignOutResult will ask OpenIddict to redirect the user agent - // to the post_logout_redirect_uri specified by the client application. - await notification.Context.HttpContext.SignOutAsync(OpenIddictServerDefaults.AuthenticationScheme); - notification.Context.HandleResponse(); - return OpenIddictServerEventState.Handled; } } } diff --git a/BTCPayServer/Authentication/OpenId/OpenIdExtensions.cs b/BTCPayServer/Authentication/OpenId/OpenIdExtensions.cs index e2778e15f..25655e619 100644 --- a/BTCPayServer/Authentication/OpenId/OpenIdExtensions.cs +++ b/BTCPayServer/Authentication/OpenId/OpenIdExtensions.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; -using AspNet.Security.OpenIdConnect.Extensions; -using AspNet.Security.OpenIdConnect.Primitives; using BTCPayServer.Data; using BTCPayServer.Models; using Microsoft.AspNetCore.Authentication; @@ -12,108 +11,80 @@ using Microsoft.AspNetCore.Identity; using OpenIddict.Abstractions; using OpenIddict.Core; using OpenIddict.Server; +using static BTCPayServer.Authentication.RestAPIPolicies; namespace BTCPayServer.Authentication.OpenId { public static class OpenIdExtensions { - public static async Task CreateAuthenticationTicket( - OpenIddictApplicationManager applicationManager, + 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, - OpenIdConnectRequest request, - ApplicationUser user, - AuthenticationProperties properties = null) + OpenIddictRequest request, + ApplicationUser user) { var principal = await signInManager.CreateUserPrincipalAsync(user); - - // Create a new authentication ticket holding the user identity. - var ticket = new AuthenticationTicket(principal, properties, - OpenIddictServerDefaults.AuthenticationScheme); - if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType()) { - ticket.SetScopes(request.GetScopes()); + principal.SetScopes(request.GetScopes().Restrict(principal)); } else if (request.IsAuthorizationCodeGrantType() && - string.IsNullOrEmpty(ticket.GetInternalAuthorizationId())) + 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)) { - ticket.SetInternalAuthorizationId(authorizationId); + principal.SetInternalAuthorizationId(authorizationId); } } - foreach (var claim in ticket.Principal.Claims) - { - claim.SetDestinations(GetDestinations(identityOptions, claim, ticket)); - } + principal.SetDestinations(identityOptions); + return principal; + } - return ticket; + 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, - AuthenticationTicket ticket) + ClaimsPrincipal principal) { - // Note: by default, claims are NOT automatically included in the access and identity tokens. - // To allow OpenIddict to serialize them, you must attach them a destination, that specifies - // whether they should be included in access tokens, in identity tokens or in both. - - switch (claim.Type) { case OpenIddictConstants.Claims.Name: - yield return OpenIddictConstants.Destinations.AccessToken; - - if (ticket.HasScope(OpenIddictConstants.Scopes.Profile)) - yield return OpenIddictConstants.Destinations.IdentityToken; - - yield break; - case OpenIddictConstants.Claims.Email: yield return OpenIddictConstants.Destinations.AccessToken; - - if (ticket.HasScope(OpenIddictConstants.Scopes.Email)) - yield return OpenIddictConstants.Destinations.IdentityToken; - yield break; - - case OpenIddictConstants.Claims.Role: - yield return OpenIddictConstants.Destinations.AccessToken; - - if (ticket.HasScope(OpenIddictConstants.Scopes.Roles)) - yield return OpenIddictConstants.Destinations.IdentityToken; - - yield break; - default: - if (claim.Type == identityOptions.ClaimsIdentity.SecurityStampClaimType) - { - // Never include the security stamp in the access and identity tokens, as it's a secret value. - yield break; - } - else - { - yield return OpenIddictConstants.Destinations.AccessToken; - yield break; - } } } public static async Task IsUserAuthorized( OpenIddictAuthorizationManager authorizationManager, - OpenIdConnectRequest request, string userId, string applicationId) + OpenIddictRequest request, string userId, string applicationId) { - var authorizations = - await authorizationManager.ListAsync(queryable => + 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))); - + StringComparison.OrdinalIgnoreCase))).ToArrayAsync(); if (authorizations.Length > 0) { diff --git a/BTCPayServer/Authentication/OpenId/OpenIdGrantHandlerCheckCanSignIn.cs b/BTCPayServer/Authentication/OpenId/OpenIdGrantHandlerCheckCanSignIn.cs index 08cf3c5e7..e2cfc1b78 100644 --- a/BTCPayServer/Authentication/OpenId/OpenIdGrantHandlerCheckCanSignIn.cs +++ b/BTCPayServer/Authentication/OpenId/OpenIdGrantHandlerCheckCanSignIn.cs @@ -1,22 +1,29 @@ using System.Threading.Tasks; -using AspNet.Security.OpenIdConnect.Primitives; +using OpenIddict.Abstractions; using BTCPayServer.Data; using BTCPayServer.Models; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; -using OpenIddict.Abstractions; using OpenIddict.Core; using OpenIddict.Server; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using OpenIddict.Server.AspNetCore; namespace BTCPayServer.Authentication.OpenId { - public abstract class - OpenIdGrantHandlerCheckCanSignIn : BaseOpenIdGrantHandler + public class OpenIdGrantHandlerCheckCanSignIn : + BaseOpenIdGrantHandler { + public static OpenIddictServerHandlerDescriptor Descriptor { get; } = + OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseScopedHandler() + .Build(); + private readonly UserManager _userManager; - protected OpenIdGrantHandlerCheckCanSignIn( + public OpenIdGrantHandlerCheckCanSignIn( OpenIddictApplicationManager applicationManager, OpenIddictAuthorizationManager authorizationManager, SignInManager signInManager, @@ -27,44 +34,39 @@ namespace BTCPayServer.Authentication.OpenId _userManager = userManager; } - protected abstract bool IsValid(OpenIdConnectRequest request); - - public override async Task HandleAsync( - OpenIddictServerEvents.HandleTokenRequest notification) + public override async ValueTask HandleAsync( + OpenIddictServerEvents.HandleTokenRequestContext notification) { - var request = notification.Context.Request; - if (!IsValid(request)) + var request = notification.Request; + if (!request.IsRefreshTokenGrantType() && !request.IsAuthorizationCodeGrantType()) { // Allow other handlers to process the event. - return OpenIddictServerEventState.Unhandled; + return; } - var scheme = notification.Context.Scheme.Name; - var authenticateResult = (await notification.Context.HttpContext.AuthenticateAsync(scheme)); + var httpContext = notification.Transaction.GetHttpRequest().HttpContext; + var authenticateResult = (await httpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)); var user = await _userManager.GetUserAsync(authenticateResult.Principal); if (user == null) { - notification.Context.Reject( + notification.Reject( error: OpenIddictConstants.Errors.InvalidGrant, description: "The token is no longer valid."); - // Don't allow other handlers to process the event. - return OpenIddictServerEventState.Handled; + return; } // Ensure the user is still allowed to sign in. if (!await _signInManager.CanSignInAsync(user)) { - notification.Context.Reject( + notification.Reject( error: OpenIddictConstants.Errors.InvalidGrant, description: "The user is no longer allowed to sign in."); - // Don't allow other handlers to process the event. - return OpenIddictServerEventState.Handled; + return; } - notification.Context.Validate(await CreateTicketAsync(request, user)); - // Don't allow other handlers to process the event. - return OpenIddictServerEventState.Handled; + notification.Principal = await this.CreateClaimsPrincipalAsync(request, user); + notification.HandleAuthentication(); } } } diff --git a/BTCPayServer/Authentication/OpenId/PasswordGrantTypeEventHandler.cs b/BTCPayServer/Authentication/OpenId/PasswordGrantTypeEventHandler.cs index 6a157261c..002a3cd08 100644 --- a/BTCPayServer/Authentication/OpenId/PasswordGrantTypeEventHandler.cs +++ b/BTCPayServer/Authentication/OpenId/PasswordGrantTypeEventHandler.cs @@ -1,5 +1,5 @@ +using System; using System.Threading.Tasks; -using AspNet.Security.OpenIdConnect.Primitives; using BTCPayServer.Data; using BTCPayServer.Models; using BTCPayServer.U2F; @@ -8,12 +8,14 @@ using Microsoft.Extensions.Options; using OpenIddict.Abstractions; using OpenIddict.Core; using OpenIddict.Server; +using Microsoft.AspNetCore; namespace BTCPayServer.Authentication.OpenId { - public class PasswordGrantTypeEventHandler : BaseOpenIdGrantHandler + public class PasswordGrantTypeEventHandler : BaseOpenIdGrantHandler { private readonly UserManager _userManager; + private readonly NicolasDorier.RateLimits.RateLimitService _rateLimitService; private readonly U2FService _u2FService; public PasswordGrantTypeEventHandler( @@ -21,43 +23,44 @@ namespace BTCPayServer.Authentication.OpenId OpenIddictAuthorizationManager authorizationManager, SignInManager signInManager, UserManager userManager, + NicolasDorier.RateLimits.RateLimitService rateLimitService, IOptions identityOptions, U2FService u2FService) : base(applicationManager, authorizationManager, signInManager, identityOptions) { _userManager = userManager; + _rateLimitService = rateLimitService; _u2FService = u2FService; } - public override async Task HandleAsync( - OpenIddictServerEvents.HandleTokenRequest notification) + public static OpenIddictServerHandlerDescriptor Descriptor { get; } = + OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseScopedHandler() + .Build(); + + public override async ValueTask HandleAsync( + OpenIddictServerEvents.HandleTokenRequestContext notification) { - var request = notification.Context.Request; + var request = notification.Request; if (!request.IsPasswordGrantType()) { - // Allow other handlers to process the event. - return OpenIddictServerEventState.Unhandled; + return; } - // Validate the user credentials. - // Note: to mitigate brute force attacks, you SHOULD strongly consider - // applying a key derivation function like PBKDF2 to slow down - // the password validation process. You SHOULD also consider - // using a time-constant comparer to prevent timing attacks. + var httpContext = notification.Transaction.GetHttpRequest().HttpContext; + await _rateLimitService.Throttle(ZoneLimits.Login, httpContext.Connection.RemoteIpAddress.ToString(), httpContext.RequestAborted); var user = await _userManager.FindByNameAsync(request.Username); if (user == null || await _u2FService.HasDevices(user.Id) || !(await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure: true)) .Succeeded) { - notification.Context.Reject( + notification.Reject( error: OpenIddictConstants.Errors.InvalidGrant, description: "The specified credentials are invalid."); - // Don't allow other handlers to process the event. - return OpenIddictServerEventState.Handled; + return; } - notification.Context.Validate(await CreateTicketAsync(request, user)); - // Don't allow other handlers to process the event. - return OpenIddictServerEventState.Handled; + notification.Principal = await CreateClaimsPrincipalAsync(request, user); + notification.HandleAuthentication(); } } } diff --git a/BTCPayServer/Authentication/OpenId/RefreshTokenGrantTypeEventHandler.cs b/BTCPayServer/Authentication/OpenId/RefreshTokenGrantTypeEventHandler.cs deleted file mode 100644 index b15420c4a..000000000 --- a/BTCPayServer/Authentication/OpenId/RefreshTokenGrantTypeEventHandler.cs +++ /dev/null @@ -1,27 +0,0 @@ -using AspNet.Security.OpenIdConnect.Primitives; -using BTCPayServer.Data; -using BTCPayServer.Models; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Options; -using OpenIddict.Core; - -namespace BTCPayServer.Authentication.OpenId -{ - public class RefreshTokenGrantTypeEventHandler : OpenIdGrantHandlerCheckCanSignIn - { - public RefreshTokenGrantTypeEventHandler( - OpenIddictApplicationManager applicationManager, - OpenIddictAuthorizationManager authorizationManager, - SignInManager signInManager, - IOptions identityOptions, UserManager userManager) : base( - applicationManager, authorizationManager, signInManager, - identityOptions, userManager) - { - } - - protected override bool IsValid(OpenIdConnectRequest request) - { - return request.IsRefreshTokenGrantType(); - } - } -} diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index ae4a356ac..8360acd92 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -6,9 +6,7 @@ - - @@ -51,7 +49,6 @@ - @@ -71,13 +68,14 @@ + + + - - @@ -223,4 +221,4 @@ $(IncludeRazorContentInPack) - + \ No newline at end of file diff --git a/BTCPayServer/Controllers/AuthorizationController.cs b/BTCPayServer/Controllers/AuthorizationController.cs index 4df5d6646..3b2aab612 100644 --- a/BTCPayServer/Controllers/AuthorizationController.cs +++ b/BTCPayServer/Controllers/AuthorizationController.cs @@ -17,18 +17,13 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; +using Microsoft.AspNetCore; using OpenIddict.Abstractions; using OpenIddict.Core; using OpenIddict.Server; -#if NETCOREAPP21 -using OpenIddictRequest = AspNet.Security.OpenIdConnect.Primitives.OpenIdConnectRequest; -using OpenIdConnectDefaults = OpenIddict.Server.OpenIddictServerDefaults; -using AspNet.Security.OpenIdConnect.Extensions; -using AspNet.Security.OpenIdConnect.Primitives; -#else using System.Security.Claims; using Microsoft.AspNetCore.Authentication.OpenIdConnect; -#endif +using OpenIddict.Server.AspNetCore; namespace BTCPayServer.Controllers { @@ -56,10 +51,11 @@ namespace BTCPayServer.Controllers [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpGet("/connect/authorize")] - public async Task Authorize(OpenIddictRequest openIdConnectRequest) + public async Task Authorize() { + var request = HttpContext.GetOpenIddictServerRequest(); // Retrieve the application details from the database. - var application = await _applicationManager.FindByClientIdAsync(openIdConnectRequest.ClientId); + var application = await _applicationManager.FindByClientIdAsync(request.ClientId); if (application == null) { @@ -74,9 +70,9 @@ namespace BTCPayServer.Controllers var userId = _userManager.GetUserId(User); if (!string.IsNullOrEmpty( - await OpenIdExtensions.IsUserAuthorized(_authorizationManager, openIdConnectRequest, userId, application.Id))) + await OpenIdExtensions.IsUserAuthorized(_authorizationManager, request, userId, application.Id))) { - return await Authorize(openIdConnectRequest, "YES", false); + return await Authorize("YES", false); } // Flow the request_id to allow OpenIddict to restore @@ -84,16 +80,16 @@ namespace BTCPayServer.Controllers return View(new AuthorizeViewModel { ApplicationName = await _applicationManager.GetDisplayNameAsync(application), - RequestId = openIdConnectRequest.RequestId, - Scope = openIdConnectRequest.GetScopes() + RequestId = request.RequestId, + Scope = request.GetScopes() }); } [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpPost("/connect/authorize")] - public async Task Authorize(OpenIddictRequest openIdConnectRequest, - string consent, bool createAuthorization = true) + public async Task Authorize(string consent, bool createAuthorization = true) { + var request = HttpContext.GetOpenIddictServerRequest(); var user = await _userManager.GetUserAsync(User); if (user == null) { @@ -118,26 +114,24 @@ namespace BTCPayServer.Controllers default: // Notify OpenIddict that the authorization grant has been denied by the resource owner // to redirect the user agent to the client application using the appropriate response_mode. - return Forbid(OpenIdConnectDefaults.AuthenticationScheme); + return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); } - // Create a new authentication ticket. - var ticket = - await OpenIdExtensions.CreateAuthenticationTicket(_applicationManager, _authorizationManager, - _IdentityOptions.Value, _signInManager, - openIdConnectRequest, user); + var principal = await _signInManager.CreateUserPrincipalAsync(user); + principal = await _signInManager.CreateUserPrincipalAsync(user); + principal.SetScopes(request.GetScopes().Restrict(principal)); + principal.SetDestinations(_IdentityOptions.Value); if (createAuthorization) { - var application = await _applicationManager.FindByClientIdAsync(openIdConnectRequest.ClientId); + var application = await _applicationManager.FindByClientIdAsync(request.ClientId); var authorization = await _authorizationManager.CreateAsync(User, user.Id, application.Id, - type, ticket.GetScopes().ToImmutableArray(), - ticket.Properties.Items.ToImmutableDictionary()); - ticket.SetInternalAuthorizationId(authorization.Id); + type, principal.GetScopes().ToImmutableArray()); + principal.SetInternalAuthorizationId(authorization.Id); } // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. - return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme); + return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); } } } diff --git a/BTCPayServer/Controllers/RestApi/TestController.cs b/BTCPayServer/Controllers/RestApi/TestController.cs index 7fbbd6d54..d4044bf49 100644 --- a/BTCPayServer/Controllers/RestApi/TestController.cs +++ b/BTCPayServer/Controllers/RestApi/TestController.cs @@ -7,10 +7,7 @@ using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using OpenIddict.Validation; -#if !NETCOREAPP21 -using OpenIddictValidationDefaults = Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectDefaults; -#endif +using OpenIddict.Validation.AspNetCore; namespace BTCPayServer.Controllers.RestApi { @@ -111,7 +108,6 @@ namespace BTCPayServer.Controllers.RestApi [Authorize(Policy = RestAPIPolicies.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.OpenId)] - [HttpGet(nameof(ScopeCanViewProfile))] public bool ScopeCanViewProfile() { return true; } diff --git a/BTCPayServer/Extensions/OpenIddictExtensions.cs b/BTCPayServer/Extensions/OpenIddictExtensions.cs index e16f81525..14ee02ccd 100644 --- a/BTCPayServer/Extensions/OpenIddictExtensions.cs +++ b/BTCPayServer/Extensions/OpenIddictExtensions.cs @@ -10,10 +10,10 @@ namespace BTCPayServer { public static class OpenIddictExtensions { - public static SecurityKey GetSigningKey(IConfiguration configuration) + public static SecurityKey GetSigningKey(IConfiguration configuration, string fileName) { - var file = Path.Combine(configuration.GetDataDir(), "rsaparams"); + var file = Path.Combine(configuration.GetDataDir(), fileName); var rsa = new RSACryptoServiceProvider(2048); if (File.Exists(file)) { @@ -29,7 +29,9 @@ namespace BTCPayServer public static OpenIddictServerBuilder ConfigureSigningKey(this OpenIddictServerBuilder builder, IConfiguration configuration) { - return builder.AddSigningKey(GetSigningKey(configuration)); + return builder + .AddSigningKey(GetSigningKey(configuration, "signing.rsaparams")) + .AddEncryptionKey(GetSigningKey(configuration, "encrypting.rsaparams")); } } } diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index 4f70f7221..7d56ca028 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -276,21 +276,7 @@ namespace BTCPayServer.Hosting private static void AddBtcPayServerAuthenticationSchemes(this IServiceCollection services, IConfiguration configuration) { - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear(); - services.AddAuthentication() - .AddJwtBearer(options => - { - //Disabled so that Tor works witt JWT auth - options.RequireHttpsMetadata = false; - options.TokenValidationParameters.ValidateAudience = false; - //we do not validate the issuer directly because btcpay can be accessed through multiple urls that we cannot predetermine - options.TokenValidationParameters.ValidateIssuer = false; - options.TokenValidationParameters.IssuerSigningKey = - OpenIddictExtensions.GetSigningKey(configuration); - options.IncludeErrorDetails = true; - }) .AddCookie() .AddBitpayAuthentication(); } diff --git a/BTCPayServer/Hosting/Startup.cs b/BTCPayServer/Hosting/Startup.cs index 243bc2955..7b5b7f981 100644 --- a/BTCPayServer/Hosting/Startup.cs +++ b/BTCPayServer/Hosting/Startup.cs @@ -1,11 +1,11 @@ using Microsoft.AspNetCore.Hosting; #if NETCOREAPP21 using IWebHostEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment; -using AspNet.Security.OpenIdConnect.Primitives; #else using Microsoft.Extensions.Hosting; -using OpenIdConnectConstants = OpenIddict.Abstractions.OpenIddictConstants; #endif +using OpenIddict.Validation.AspNetCore; +using OpenIddict.Abstractions; using Microsoft.AspNetCore.Builder; using System; using Microsoft.Extensions.DependencyInjection; @@ -22,7 +22,6 @@ using System.IO; using Microsoft.Extensions.DependencyInjection.Extensions; using BTCPayServer.Security; using Microsoft.AspNetCore.Server.Kestrel.Core; -using OpenIddict.Abstractions; using OpenIddict.EntityFrameworkCore.Models; using System.Net; using BTCPayServer.Authentication; @@ -57,8 +56,8 @@ namespace BTCPayServer.Hosting services.AddMemoryCache(); services.AddIdentity() .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); - + .AddDefaultTokenProviders(); + ConfigureOpenIddict(services); services.AddBTCPayServer(Configuration); @@ -95,13 +94,13 @@ namespace BTCPayServer.Hosting options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); options.Lockout.MaxFailedAccessAttempts = 5; options.Lockout.AllowedForNewUsers = true; - options.Password.RequireUppercase = false; + options.Password.RequireUppercase = false; // Configure Identity to use the same JWT claims as OpenIddict instead // of the legacy WS-Federation claims it uses by default (ClaimTypes), // which saves you from doing the mapping in your authorization controller. - options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name; - options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject; - options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role; + options.ClaimsIdentity.UserNameClaimType = OpenIddictConstants.Claims.Name; + options.ClaimsIdentity.UserIdClaimType = OpenIddictConstants.Claims.Subject; + options.ClaimsIdentity.RoleClaimType = OpenIddictConstants.Claims.Role; }); // If the HTTPS certificate path is not set this logic will NOT be used and the default Kestrel binding logic will be. string httpsCertificateFilePath = Configuration.GetOrDefault("HttpsCertificateFilePath", null); @@ -123,7 +122,7 @@ namespace BTCPayServer.Hosting // Note that by design this is a fatal error condition that will cause the process to exit. throw new ConfigException($"The https certificate file could not be found at {httpsCertificateFilePath}."); } - if(hasCertPath && useDefaultCertificate) + if (hasCertPath && useDefaultCertificate) { throw new ConfigException($"Conflicting settings: if HttpsUseDefaultCertificate is true, HttpsCertificateFilePath should not be used"); } @@ -147,7 +146,7 @@ namespace BTCPayServer.Hosting private void ConfigureOpenIddict(IServiceCollection services) { -// Register the OpenIddict services. + // Register the OpenIddict services. services.AddOpenIddict() .AddCore(options => { @@ -159,15 +158,6 @@ namespace BTCPayServer.Hosting }) .AddServer(options => { -#if NETCOREAPP21 - options.EnableRequestCaching(); - //Disabled so that Tor works with OpenIddict too - options.DisableHttpsRequirement(); - // Register the ASP.NET Core MVC binder used by OpenIddict. - // Note: if you don't call this method, you won't be able to - // bind OpenIdConnectRequest or OpenIdConnectResponse parameters. - options.UseMvc(); -#else options.UseAspNetCore() .EnableStatusCodePagesIntegration() .EnableAuthorizationEndpointPassthrough() @@ -175,18 +165,11 @@ namespace BTCPayServer.Hosting .EnableTokenEndpointPassthrough() .EnableRequestCaching() .DisableTransportSecurityRequirement(); -#endif // Enable the token endpoint (required to use the password flow). -#if NETCOREAPP21 - options.EnableTokenEndpoint("/connect/token"); - options.EnableAuthorizationEndpoint("/connect/authorize"); - options.EnableLogoutEndpoint("/connect/logout"); -#else options.SetTokenEndpointUris("/connect/token"); options.SetAuthorizationEndpointUris("/connect/authorize"); options.SetLogoutEndpointUris("/connect/logout"); -#endif //we do not care about these granular controls for now options.IgnoreScopePermissions(); @@ -198,36 +181,26 @@ namespace BTCPayServer.Hosting options.AllowPasswordFlow(); options.AllowAuthorizationCodeFlow(); options.UseRollingTokens(); -#if NETCOREAPP21 - options.UseJsonWebTokens(); -#endif options.RegisterScopes( - OpenIdConnectConstants.Scopes.OpenId, - OpenIdConnectConstants.Scopes.OfflineAccess, - OpenIdConnectConstants.Scopes.Email, - OpenIdConnectConstants.Scopes.Profile, - OpenIddictConstants.Scopes.Roles, + OpenIddictConstants.Scopes.OpenId, RestAPIPolicies.BTCPayScopes.ViewStores, RestAPIPolicies.BTCPayScopes.CreateInvoices, RestAPIPolicies.BTCPayScopes.StoreManagement, RestAPIPolicies.BTCPayScopes.ViewApps, + RestAPIPolicies.BTCPayScopes.ServerManagement, RestAPIPolicies.BTCPayScopes.AppManagement ); -#if NETCOREAPP21 - options.AddEventHandler(); - options.AddEventHandler(); - options.AddEventHandler(); - options.AddEventHandler(); - options.AddEventHandler(); -#else options.AddEventHandler(PasswordGrantTypeEventHandler.Descriptor); - options.AddEventHandler(AuthorizationCodeGrantTypeEventHandler.Descriptor); - options.AddEventHandler(RefreshTokenGrantTypeEventHandler.Descriptor); + options.AddEventHandler(OpenIdGrantHandlerCheckCanSignIn.Descriptor); options.AddEventHandler(ClientCredentialsGrantTypeEventHandler.Descriptor); options.AddEventHandler(LogoutEventHandler.Descriptor); -#endif options.ConfigureSigningKey(Configuration); + }) + .AddValidation(options => + { + options.UseLocalServer(); + options.UseAspNetCore(); }); } diff --git a/BTCPayServer/Program.cs b/BTCPayServer/Program.cs index bbd69dbb4..27219daa7 100644 --- a/BTCPayServer/Program.cs +++ b/BTCPayServer/Program.cs @@ -53,7 +53,7 @@ namespace BTCPayServer l.AddFilter("Microsoft", LogLevel.Error); l.AddFilter("System.Net.Http.HttpClient", LogLevel.Critical); l.AddFilter("Microsoft.AspNetCore.Antiforgery.Internal", LogLevel.Critical); - l.AddFilter("AspNet.Security.OpenIdConnect.Server.OpenIdConnectServerHandler", LogLevel.Error); + l.AddFilter("OpenIddict.Server.OpenIddictServerProvider", LogLevel.Error); l.AddProvider(new CustomConsoleLogProvider(processor)); // Use Serilog for debug log file. diff --git a/BTCPayServer/Security/AuthenticationSchemes.cs b/BTCPayServer/Security/AuthenticationSchemes.cs index bd861ec31..0f986f4e2 100644 --- a/BTCPayServer/Security/AuthenticationSchemes.cs +++ b/BTCPayServer/Security/AuthenticationSchemes.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using OpenIddict.Validation; +using OpenIddict.Validation.AspNetCore; namespace BTCPayServer.Security { @@ -10,6 +11,6 @@ namespace BTCPayServer.Security { public const string Cookie = "Identity.Application"; public const string Bitpay = "Bitpay"; - public const string OpenId = OpenIddictValidationDefaults.AuthenticationScheme; + public const string OpenId = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme; } } diff --git a/BTCPayServer/Security/OpenIdAuthorizationHandler.cs b/BTCPayServer/Security/OpenIdAuthorizationHandler.cs index cde79318e..bd42947f5 100644 --- a/BTCPayServer/Security/OpenIdAuthorizationHandler.cs +++ b/BTCPayServer/Security/OpenIdAuthorizationHandler.cs @@ -66,6 +66,11 @@ namespace BTCPayServer.Security success = context.HasScopes(OpenIddictConstants.Scopes.Profile); break; case Policies.CanModifyStoreSettings.Key: + if (!context.HasScopes(BTCPayScopes.StoreManagement)) + break; + // TODO: It should be possible to grant permission to a specific store + // we can do this by adding saving a claim with the specific store id + // to the access_token string storeId = _HttpContext.GetImplicitStoreId(); if (storeId == null) break; @@ -79,7 +84,15 @@ namespace BTCPayServer.Security _HttpContext.SetStoreData(store); break; case Policies.CanModifyServerSettings.Key: - success = context.User.HasClaim("role", Roles.ServerAdmin); + if (!context.HasScopes(BTCPayScopes.ServerManagement)) + break; + // For this authorization, we stil check in database because it is super sensitive. + var user = await _userManager.GetUserAsync(context.User); + if (user == null) + break; + if (!await _userManager.IsInRoleAsync(user, Roles.ServerAdmin)) + break; + success = true; break; } diff --git a/BTCPayServer/Security/SecurityExtensions.cs b/BTCPayServer/Security/SecurityExtensions.cs index ddeba1575..192aa3351 100644 --- a/BTCPayServer/Security/SecurityExtensions.cs +++ b/BTCPayServer/Security/SecurityExtensions.cs @@ -16,7 +16,7 @@ namespace BTCPayServer.Security { public static bool HasScopes(this AuthorizationHandlerContext context, params string[] scopes) { - return scopes.All(s => context.User.HasClaim(OpenIddictConstants.Claims.Scope, s)); + return scopes.All(s => context.User.HasClaim(c => c.Type == OpenIddictConstants.Claims.Scope && c.Value.Split(' ').Contains(s))); } public static string GetImplicitStoreId(this HttpContext httpContext) { diff --git a/BTCPayServer/Views/Authorization/Authorize.cshtml b/BTCPayServer/Views/Authorization/Authorize.cshtml index 0a9c7f9ea..ac2923135 100644 --- a/BTCPayServer/Views/Authorization/Authorize.cshtml +++ b/BTCPayServer/Views/Authorization/Authorize.cshtml @@ -12,10 +12,8 @@ {RestAPIPolicies.BTCPayScopes.StoreManagement, ("Manage your stores", "The app will be able to create, modify and delete all your stores.")}, {RestAPIPolicies.BTCPayScopes.ViewStores, ("View your stores", "The app will be able to list and view all your stores.")}, {RestAPIPolicies.BTCPayScopes.WalletManagement, ("Manage your wallet", "The app will be able to manage your wallet associate to stores. This includes configuring it, transaction creation and signing.")}, + {RestAPIPolicies.BTCPayScopes.ServerManagement, ("Manage your server", "The app will have total control on your server")}, {RestAPIPolicies.BTCPayScopes.ViewInvoices, ("View your invoices", "The app will be able to list and view all your apps.")}, - {OpenIddictConstants.Scopes.Email, ("View your email", "The app will have access to your email.")}, - {OpenIddictConstants.Scopes.Profile, ("View your account", "The app will have access to your account details.")}, - {OpenIddictConstants.Scopes.Roles, ("View your roles", "The app will know if you are a server admin.")}, }; }
diff --git a/Nuget.Config b/Nuget.Config deleted file mode 100644 index 02ac9d1e9..000000000 --- a/Nuget.Config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/amd64.Dockerfile b/amd64.Dockerfile index b905a45e8..c45b6f197 100644 --- a/amd64.Dockerfile +++ b/amd64.Dockerfile @@ -1,5 +1,6 @@ FROM mcr.microsoft.com/dotnet/core/sdk:2.1.505-alpine3.7 AS builder WORKDIR /source +COPY nuget.config nuget.config COPY Build/Common.csproj Build/Common.csproj COPY BTCPayServer/BTCPayServer.csproj BTCPayServer/BTCPayServer.csproj COPY BTCPayServer.Common/BTCPayServer.Common.csproj BTCPayServer.Common/BTCPayServer.Common.csproj diff --git a/arm32v7.Dockerfile b/arm32v7.Dockerfile index 0ef5d088d..6fbd6cb5b 100644 --- a/arm32v7.Dockerfile +++ b/arm32v7.Dockerfile @@ -4,6 +4,7 @@ RUN apt-get update \ && apt-get install -qq --no-install-recommends qemu qemu-user-static qemu-user binfmt-support WORKDIR /source +COPY nuget.config nuget.config COPY Build/Common.csproj Build/Common.csproj COPY BTCPayServer/BTCPayServer.csproj BTCPayServer/BTCPayServer.csproj COPY BTCPayServer.Common/BTCPayServer.Common.csproj BTCPayServer.Common/BTCPayServer.Common.csproj diff --git a/nuget.config b/nuget.config new file mode 100644 index 000000000..29f81a89e --- /dev/null +++ b/nuget.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + +