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.")},
};
}