mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-03 17:36:59 +01:00
Consolidate auth into one
This commit is contained in:
parent
e48e8c34d9
commit
56ba834ca2
10 changed files with 67 additions and 114 deletions
|
@ -12,7 +12,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||
namespace BTCPayServer.Controllers.RestApi
|
||||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public class ApiKeysController : ControllerBase
|
||||
{
|
||||
private readonly APIKeyRepository _apiKeyRepository;
|
||||
|
@ -33,7 +33,7 @@ namespace BTCPayServer.Controllers.RestApi
|
|||
}
|
||||
|
||||
[HttpDelete("~/api/v1/api-keys/current")]
|
||||
[Authorize(Policy = Policies.Unrestricted, AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
||||
[Authorize(Policy = Policies.Unrestricted, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<ActionResult<ApiKeyData>> RevokeKey()
|
||||
{
|
||||
ControllerContext.HttpContext.GetAPIKey(out var apiKey);
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace BTCPayServer.Controllers.RestApi
|
|||
/// </summary>
|
||||
[Route("api/test/apikey")]
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.ApiKeyOrBasic)]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public class TestApiKeyController : ControllerBase
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
@ -27,28 +27,28 @@ namespace BTCPayServer.Controllers.RestApi
|
|||
}
|
||||
|
||||
[HttpGet("me/id")]
|
||||
[Authorize(Policy = Policies.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.ApiKeyOrBasic)]
|
||||
[Authorize(Policy = Policies.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public string GetCurrentUserId()
|
||||
{
|
||||
return _userManager.GetUserId(User);
|
||||
}
|
||||
|
||||
[HttpGet("me")]
|
||||
[Authorize(Policy = Policies.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.ApiKeyOrBasic)]
|
||||
[Authorize(Policy = Policies.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<ApplicationUser> GetCurrentUser()
|
||||
{
|
||||
return await _userManager.GetUserAsync(User);
|
||||
}
|
||||
|
||||
[HttpGet("me/is-admin")]
|
||||
[Authorize(Policy = Policies.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.ApiKeyOrBasic)]
|
||||
[Authorize(Policy = Policies.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public bool AmIAnAdmin()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
[HttpGet("me/stores")]
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.ApiKeyOrBasic)]
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public StoreData[] GetCurrentUserStores()
|
||||
{
|
||||
return this.HttpContext.GetStoresData();
|
||||
|
@ -56,7 +56,7 @@ namespace BTCPayServer.Controllers.RestApi
|
|||
|
||||
[HttpGet("me/stores/{storeId}/can-view")]
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings,
|
||||
AuthenticationSchemes = AuthenticationSchemes.ApiKeyOrBasic)]
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public bool CanViewStore(string storeId)
|
||||
{
|
||||
return true;
|
||||
|
@ -64,7 +64,7 @@ namespace BTCPayServer.Controllers.RestApi
|
|||
|
||||
[HttpGet("me/stores/{storeId}/can-edit")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings,
|
||||
AuthenticationSchemes = AuthenticationSchemes.ApiKeyOrBasic)]
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public bool CanEditStore(string storeId)
|
||||
{
|
||||
return true;
|
||||
|
|
|
@ -20,7 +20,7 @@ using BTCPayServer.Client;
|
|||
namespace BTCPayServer.Controllers.RestApi
|
||||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.ApiKeyOrBasic)]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public class UsersController : ControllerBase
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
@ -52,7 +52,7 @@ namespace BTCPayServer.Controllers.RestApi
|
|||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.ApiKeyOrBasic)]
|
||||
[Authorize(Policy = Policies.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/users/me")]
|
||||
public async Task<ActionResult<ApplicationUserData>> GetCurrentUser()
|
||||
{
|
||||
|
@ -82,7 +82,7 @@ namespace BTCPayServer.Controllers.RestApi
|
|||
|
||||
// Even if subscription are unlocked, it is forbidden to create admin unauthenticated
|
||||
if (anyAdmin && request.IsAdministrator is true && !isAuth)
|
||||
return Forbid(AuthenticationSchemes.ApiKey, AuthenticationSchemes.Basic);
|
||||
return Forbid(AuthenticationSchemes.Greenfield);
|
||||
// You are de-facto admin if there is no other admin, else you need to be auth and pass policy requirements
|
||||
bool isAdmin = anyAdmin ? (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.CanModifyServerSettings))).Succeeded
|
||||
&& (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.Unrestricted))).Succeeded
|
||||
|
@ -90,14 +90,14 @@ namespace BTCPayServer.Controllers.RestApi
|
|||
: true;
|
||||
// You need to be admin to create an admin
|
||||
if (request.IsAdministrator is true && !isAdmin)
|
||||
return Forbid(AuthenticationSchemes.ApiKey, AuthenticationSchemes.Basic);
|
||||
return Forbid(AuthenticationSchemes.Greenfield);
|
||||
|
||||
if (!isAdmin && policies.LockSubscription)
|
||||
{
|
||||
// If we are not admin and subscriptions are locked, we need to check the Policies.CanCreateUser.Key permission
|
||||
var canCreateUser = (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.CanCreateUser))).Succeeded;
|
||||
if (!isAuth || !canCreateUser)
|
||||
return Forbid(AuthenticationSchemes.ApiKey, AuthenticationSchemes.Basic);
|
||||
return Forbid(AuthenticationSchemes.Greenfield);
|
||||
}
|
||||
|
||||
var user = new ApplicationUser
|
||||
|
|
|
@ -286,8 +286,7 @@ namespace BTCPayServer.Hosting
|
|||
services.AddAuthentication()
|
||||
.AddCookie()
|
||||
.AddBitpayAuthentication()
|
||||
.AddAPIKeyAuthentication()
|
||||
.AddBasicAuthentication();
|
||||
.AddAPIKeyAuthentication();
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UsePayServer(this IApplicationBuilder app)
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -19,6 +19,8 @@ namespace BTCPayServer.Security.APIKeys
|
|||
{
|
||||
private readonly APIKeyRepository _apiKeyRepository;
|
||||
private readonly IOptionsMonitor<IdentityOptions> _identityOptions;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
||||
public APIKeyAuthenticationHandler(
|
||||
APIKeyRepository apiKeyRepository,
|
||||
|
@ -26,13 +28,28 @@ namespace BTCPayServer.Security.APIKeys
|
|||
IOptionsMonitor<APIKeyAuthenticationOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock) : base(options, logger, encoder, clock)
|
||||
ISystemClock clock,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
UserManager<ApplicationUser> userManager) : base(options, logger, encoder, clock)
|
||||
{
|
||||
_apiKeyRepository = apiKeyRepository;
|
||||
_identityOptions = identityOptions;
|
||||
_signInManager = signInManager;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
var res = await HandleApiKeyAuthenticateResult();
|
||||
if (res.None)
|
||||
{
|
||||
return await HandleBasicAuthenticateAsync();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private async Task<AuthenticateResult> HandleApiKeyAuthenticateResult()
|
||||
{
|
||||
if (!Context.Request.HttpContext.GetAPIKey(out var apiKey) || string.IsNullOrEmpty(apiKey))
|
||||
return AuthenticateResult.NoResult();
|
||||
|
@ -46,9 +63,38 @@ namespace BTCPayServer.Security.APIKeys
|
|||
|
||||
List<Claim> claims = new List<Claim>();
|
||||
claims.Add(new Claim(_identityOptions.CurrentValue.ClaimsIdentity.UserIdClaimType, key.UserId));
|
||||
claims.AddRange(Permission.ToPermissions(key.Permissions).Select(permission => new Claim(APIKeyConstants.ClaimTypes.Permission, permission.ToString())));
|
||||
claims.AddRange(Permission.ToPermissions(key.Permissions).Select(permission =>
|
||||
new Claim(APIKeyConstants.ClaimTypes.Permission, permission.ToString())));
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(
|
||||
new ClaimsPrincipal(new ClaimsIdentity(claims, APIKeyConstants.AuthenticationType)), APIKeyConstants.AuthenticationType));
|
||||
new ClaimsPrincipal(new ClaimsIdentity(claims, APIKeyConstants.AuthenticationType)),
|
||||
APIKeyConstants.AuthenticationType));
|
||||
}
|
||||
|
||||
private async Task<AuthenticateResult> HandleBasicAuthenticateAsync()
|
||||
{
|
||||
string authHeader = Context.Request.Headers["Authorization"];
|
||||
|
||||
if (authHeader == null || !authHeader.StartsWith("Basic ")) return AuthenticateResult.NoResult();
|
||||
var encodedUsernamePassword = authHeader.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[1]?.Trim();
|
||||
var decodedUsernamePassword =
|
||||
Encoding.UTF8.GetString(Convert.FromBase64String(encodedUsernamePassword)).Split(':');
|
||||
var username = decodedUsernamePassword[0];
|
||||
var password = decodedUsernamePassword[1];
|
||||
|
||||
var result = await _signInManager.PasswordSignInAsync(username, password, true, true);
|
||||
if (!result.Succeeded) return AuthenticateResult.Fail(result.ToString());
|
||||
|
||||
var user = await _userManager.FindByNameAsync(username);
|
||||
var claims = new List<Claim>()
|
||||
{
|
||||
new Claim(_identityOptions.CurrentValue.ClaimsIdentity.UserIdClaimType, user.Id),
|
||||
new Claim(APIKeyConstants.ClaimTypes.Permission,
|
||||
Permission.Create(Policies.Unrestricted).ToString())
|
||||
};
|
||||
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(
|
||||
new ClaimsPrincipal(new ClaimsIdentity(claims, APIKeyConstants.AuthenticationType)),
|
||||
APIKeyConstants.AuthenticationType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace BTCPayServer.Security.APIKeys
|
|||
|
||||
public static AuthenticationBuilder AddAPIKeyAuthentication(this AuthenticationBuilder builder)
|
||||
{
|
||||
builder.AddScheme<APIKeyAuthenticationOptions, APIKeyAuthenticationHandler>(AuthenticationSchemes.ApiKey,
|
||||
builder.AddScheme<APIKeyAuthenticationOptions, APIKeyAuthenticationHandler>(AuthenticationSchemes.Greenfield,
|
||||
o => { });
|
||||
return builder;
|
||||
}
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
{
|
||||
public const string Cookie = "Identity.Application";
|
||||
public const string Bitpay = "Bitpay";
|
||||
public const string ApiKey = "GreenfieldApiKey";
|
||||
public const string Basic = "Basic";
|
||||
public const string ApiKeyOrBasic = ApiKey + "," + Basic;
|
||||
public const string Greenfield = "Greenfield";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Security.APIKeys;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace BTCPayServer.Security.Basic
|
||||
{
|
||||
public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions>
|
||||
{
|
||||
private readonly IOptionsMonitor<IdentityOptions> _identityOptions;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
||||
public BasicAuthenticationHandler(
|
||||
|
||||
IOptionsMonitor<IdentityOptions> identityOptions,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
IOptionsMonitor<BasicAuthenticationOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock) : base(options, logger, encoder, clock)
|
||||
{
|
||||
_identityOptions = identityOptions;
|
||||
_signInManager = signInManager;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
|
||||
string authHeader = Context.Request.Headers["Authorization"];
|
||||
|
||||
if (authHeader == null || !authHeader.StartsWith("Basic ")) return AuthenticateResult.NoResult();
|
||||
var encodedUsernamePassword = authHeader.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[1]?.Trim();
|
||||
var decodedUsernamePassword = Encoding.UTF8.GetString(Convert.FromBase64String(encodedUsernamePassword)).Split(':');
|
||||
var username = decodedUsernamePassword[0];
|
||||
var password = decodedUsernamePassword[1];
|
||||
|
||||
var result = await _signInManager.PasswordSignInAsync(username, password, true, true);
|
||||
if (!result.Succeeded) return AuthenticateResult.Fail(result.ToString());
|
||||
|
||||
var user = await _userManager.FindByNameAsync(username);
|
||||
var claims = new List<Claim>()
|
||||
{
|
||||
new Claim(_identityOptions.CurrentValue.ClaimsIdentity.UserIdClaimType, user.Id),
|
||||
new Claim(APIKeyConstants.ClaimTypes.Permission, Permission.Create(Policies.Unrestricted).ToString())
|
||||
};
|
||||
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(
|
||||
new ClaimsPrincipal(new ClaimsIdentity(claims, APIKeyConstants.AuthenticationType)), APIKeyConstants.AuthenticationType));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace BTCPayServer.Security.Basic
|
||||
{
|
||||
public class BasicAuthenticationOptions : AuthenticationSchemeOptions
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
using BTCPayServer.Security.Basic;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace BTCPayServer.Security.APIKeys
|
||||
{
|
||||
public static class BasicExtensions
|
||||
{
|
||||
|
||||
public static AuthenticationBuilder AddBasicAuthentication(this AuthenticationBuilder builder)
|
||||
{
|
||||
builder.AddScheme<BasicAuthenticationOptions, BasicAuthenticationHandler>(AuthenticationSchemes.Basic,
|
||||
o => { });
|
||||
return builder;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue