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 BTCPayServer.Security.GreenField;
|
||||
|
||||
namespace BTCPayServer.Controllers.RestApi
|
||||
namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
@@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Controllers.RestApi
|
||||
namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
/// <summary>
|
||||
/// 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 BTCPayServer.Client;
|
||||
|
||||
namespace BTCPayServer.Controllers.RestApi
|
||||
namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
[ApiController]
|
||||
[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