mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 06:24:24 +01:00
Move directories, rename controllers
This commit is contained in:
@@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Identity;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using BTCPayServer.Security.GreenField;
|
using BTCPayServer.Security.GreenField;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers.RestApi
|
namespace BTCPayServer.Controllers.GreenField
|
||||||
{
|
{
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
@@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Authorization;
|
|||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers.RestApi
|
namespace BTCPayServer.Controllers.GreenField
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// this controller serves as a testing endpoint for our api key unit tests
|
/// this controller serves as a testing endpoint for our api key unit tests
|
||||||
@@ -17,7 +17,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|||||||
using NicolasDorier.RateLimits;
|
using NicolasDorier.RateLimits;
|
||||||
using BTCPayServer.Client;
|
using BTCPayServer.Client;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers.RestApi
|
namespace BTCPayServer.Controllers.GreenField
|
||||||
{
|
{
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
64
BTCPayServer/Security/GreenField/APIKeyExtensions.cs
Normal file
64
BTCPayServer/Security/GreenField/APIKeyExtensions.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using BTCPayServer.Client;
|
||||||
|
using BTCPayServer.Security.Bitpay;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Security.GreenField
|
||||||
|
{
|
||||||
|
public static class APIKeyExtensions
|
||||||
|
{
|
||||||
|
public static bool GetAPIKey(this HttpContext httpContext, out StringValues apiKey)
|
||||||
|
{
|
||||||
|
if (httpContext.Request.Headers.TryGetValue("Authorization", out var value) &&
|
||||||
|
value.ToString().StartsWith("token ", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
apiKey = value.ToString().Substring("token ".Length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AuthenticationBuilder AddAPIKeyAuthentication(this AuthenticationBuilder builder)
|
||||||
|
{
|
||||||
|
builder.AddScheme<GreenFieldAuthenticationOptions, GreenFieldAuthenticationHandler>(AuthenticationSchemes.Greenfield,
|
||||||
|
o => { });
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddAPIKeyAuthentication(this IServiceCollection serviceCollection)
|
||||||
|
{
|
||||||
|
serviceCollection.AddSingleton<APIKeyRepository>();
|
||||||
|
serviceCollection.AddScoped<IAuthorizationHandler, GreenFieldAuthorizationHandler>();
|
||||||
|
return serviceCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string[] GetPermissions(this AuthorizationHandlerContext context)
|
||||||
|
{
|
||||||
|
return context.User.Claims.Where(c =>
|
||||||
|
c.Type.Equals(GreenFieldConstants.ClaimTypes.Permission, StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
.Select(claim => claim.Value).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool HasPermission(this AuthorizationHandlerContext context, Permission permission)
|
||||||
|
{
|
||||||
|
foreach (var claim in context.User.Claims.Where(c =>
|
||||||
|
c.Type.Equals(GreenFieldConstants.ClaimTypes.Permission, StringComparison.InvariantCultureIgnoreCase)))
|
||||||
|
{
|
||||||
|
if (Permission.TryParse(claim.Value, out var claimPermission))
|
||||||
|
{
|
||||||
|
if (claimPermission.Contains(permission))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
72
BTCPayServer/Security/GreenField/APIKeyRepository.cs
Normal file
72
BTCPayServer/Security/GreenField/APIKeyRepository.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Security.GreenField
|
||||||
|
{
|
||||||
|
public class APIKeyRepository
|
||||||
|
{
|
||||||
|
private readonly ApplicationDbContextFactory _applicationDbContextFactory;
|
||||||
|
|
||||||
|
public APIKeyRepository(ApplicationDbContextFactory applicationDbContextFactory)
|
||||||
|
{
|
||||||
|
_applicationDbContextFactory = applicationDbContextFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<APIKeyData> GetKey(string apiKey)
|
||||||
|
{
|
||||||
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
|
{
|
||||||
|
return await EntityFrameworkQueryableExtensions.SingleOrDefaultAsync(context.ApiKeys,
|
||||||
|
data => data.Id == apiKey && data.Type != APIKeyType.Legacy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<APIKeyData>> GetKeys(APIKeyQuery query)
|
||||||
|
{
|
||||||
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
|
{
|
||||||
|
var queryable = context.ApiKeys.AsQueryable();
|
||||||
|
if (query?.UserId != null && query.UserId.Any())
|
||||||
|
{
|
||||||
|
queryable = queryable.Where(data => query.UserId.Contains(data.UserId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return await queryable.ToListAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateKey(APIKeyData key)
|
||||||
|
{
|
||||||
|
if (key.Type == APIKeyType.Legacy || !string.IsNullOrEmpty(key.StoreId) || string.IsNullOrEmpty(key.UserId))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("cannot save a bitpay legacy api key with this repository");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
|
{
|
||||||
|
await context.ApiKeys.AddAsync(key);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Remove(string id, string getUserId)
|
||||||
|
{
|
||||||
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
|
{
|
||||||
|
var key = await EntityFrameworkQueryableExtensions.SingleOrDefaultAsync(context.ApiKeys,
|
||||||
|
data => data.Id == id && data.UserId == getUserId);
|
||||||
|
context.ApiKeys.Remove(key);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class APIKeyQuery
|
||||||
|
{
|
||||||
|
public string[] UserId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
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.Bitpay;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Security.GreenField
|
||||||
|
{
|
||||||
|
public class GreenFieldAuthenticationHandler : AuthenticationHandler<GreenFieldAuthenticationOptions>
|
||||||
|
{
|
||||||
|
private readonly APIKeyRepository _apiKeyRepository;
|
||||||
|
private readonly IOptionsMonitor<IdentityOptions> _identityOptions;
|
||||||
|
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||||
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
|
||||||
|
public GreenFieldAuthenticationHandler(
|
||||||
|
APIKeyRepository apiKeyRepository,
|
||||||
|
IOptionsMonitor<IdentityOptions> identityOptions,
|
||||||
|
IOptionsMonitor<GreenFieldAuthenticationOptions> options,
|
||||||
|
ILoggerFactory logger,
|
||||||
|
UrlEncoder encoder,
|
||||||
|
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();
|
||||||
|
|
||||||
|
var key = await _apiKeyRepository.GetKey(apiKey);
|
||||||
|
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
return AuthenticateResult.Fail("ApiKey authentication failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
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(GreenFieldConstants.ClaimTypes.Permission, permission.ToString())));
|
||||||
|
return AuthenticateResult.Success(new AuthenticationTicket(
|
||||||
|
new ClaimsPrincipal(new ClaimsIdentity(claims, GreenFieldConstants.AuthenticationType)),
|
||||||
|
GreenFieldConstants.AuthenticationType));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<AuthenticateResult> HandleBasicAuthenticateAsync()
|
||||||
|
{
|
||||||
|
string authHeader = Context.Request.Headers["Authorization"];
|
||||||
|
|
||||||
|
if (authHeader == null || !authHeader.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase)) 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(GreenFieldConstants.ClaimTypes.Permission,
|
||||||
|
Permission.Create(Policies.Unrestricted).ToString())
|
||||||
|
};
|
||||||
|
|
||||||
|
return AuthenticateResult.Success(new AuthenticationTicket(
|
||||||
|
new ClaimsPrincipal(new ClaimsIdentity(claims, GreenFieldConstants.AuthenticationType)),
|
||||||
|
GreenFieldConstants.AuthenticationType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Security.GreenField
|
||||||
|
{
|
||||||
|
public class GreenFieldAuthenticationOptions : AuthenticationSchemeOptions
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Services.Stores;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Security.GreenField
|
||||||
|
{
|
||||||
|
public class GreenFieldAuthorizationHandler : AuthorizationHandler<PolicyRequirement>
|
||||||
|
|
||||||
|
{
|
||||||
|
private readonly HttpContext _HttpContext;
|
||||||
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
private readonly StoreRepository _storeRepository;
|
||||||
|
|
||||||
|
public GreenFieldAuthorizationHandler(IHttpContextAccessor httpContextAccessor,
|
||||||
|
UserManager<ApplicationUser> userManager,
|
||||||
|
StoreRepository storeRepository)
|
||||||
|
{
|
||||||
|
_HttpContext = httpContextAccessor.HttpContext;
|
||||||
|
_userManager = userManager;
|
||||||
|
_storeRepository = storeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
|
||||||
|
PolicyRequirement requirement)
|
||||||
|
{
|
||||||
|
if (context.User.Identity.AuthenticationType != GreenFieldConstants.AuthenticationType)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
switch (requirement.Policy)
|
||||||
|
{
|
||||||
|
case Policies.CanModifyProfile:
|
||||||
|
case Policies.CanViewProfile:
|
||||||
|
case Policies.Unrestricted:
|
||||||
|
success = context.HasPermission(Permission.Create(requirement.Policy));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Policies.CanViewStoreSettings:
|
||||||
|
case Policies.CanModifyStoreSettings:
|
||||||
|
var storeId = _HttpContext.GetImplicitStoreId();
|
||||||
|
var userid = _userManager.GetUserId(context.User);
|
||||||
|
// Specific store action
|
||||||
|
if (storeId != null)
|
||||||
|
{
|
||||||
|
if (context.HasPermission(Permission.Create(requirement.Policy, storeId)))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(userid))
|
||||||
|
break;
|
||||||
|
var store = await _storeRepository.FindStore((string)storeId, userid);
|
||||||
|
if (store == null)
|
||||||
|
break;
|
||||||
|
success = true;
|
||||||
|
_HttpContext.SetStoreData(store);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var stores = await _storeRepository.GetStoresByUserId(userid);
|
||||||
|
List<StoreData> permissionedStores = new List<StoreData>();
|
||||||
|
foreach (var store in stores)
|
||||||
|
{
|
||||||
|
if (context.HasPermission(Permission.Create(requirement.Policy, store.Id)))
|
||||||
|
permissionedStores.Add(store);
|
||||||
|
}
|
||||||
|
_HttpContext.SetStoresData(stores.ToArray());
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Policies.CanCreateUser:
|
||||||
|
case Policies.CanModifyServerSettings:
|
||||||
|
if (context.HasPermission(Permission.Create(requirement.Policy)))
|
||||||
|
{
|
||||||
|
var user = await _userManager.GetUserAsync(context.User);
|
||||||
|
if (user == null)
|
||||||
|
break;
|
||||||
|
if (!await _userManager.IsInRoleAsync(user, Roles.ServerAdmin))
|
||||||
|
break;
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
context.Succeed(requirement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
BTCPayServer/Security/GreenField/GreenFieldConstants.cs
Normal file
15
BTCPayServer/Security/GreenField/GreenFieldConstants.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using BTCPayServer.Client;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Security.GreenField
|
||||||
|
{
|
||||||
|
public static class GreenFieldConstants
|
||||||
|
{
|
||||||
|
public const string AuthenticationType = "GreenField";
|
||||||
|
|
||||||
|
public static class ClaimTypes
|
||||||
|
{
|
||||||
|
public const string Permission = "APIKey.Permission";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user