From 56ba834ca28581788127a08cd19f5255a8ec3854 Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 23 Mar 2020 14:23:23 +0100 Subject: [PATCH] Consolidate auth into one --- .../Controllers/RestApi/ApiKeysController.cs | 4 +- .../RestApi/TestApiKeyController.cs | 14 ++-- .../Controllers/RestApi/UsersController.cs | 10 +-- BTCPayServer/Hosting/BTCPayServerServices.cs | 3 +- .../APIKeys/APIKeyAuthenticationHandler.cs | 54 +++++++++++++-- .../Security/APIKeys/APIKeyExtensions.cs | 2 +- .../Security/AuthenticationSchemes.cs | 4 +- .../Basic/BasicAuthenticationHandler.cs | 65 ------------------- .../Basic/BasicAuthenticationOptions.cs | 8 --- .../Security/Basic/BasicExtensions.cs | 17 ----- 10 files changed, 67 insertions(+), 114 deletions(-) delete mode 100644 BTCPayServer/Security/Basic/BasicAuthenticationHandler.cs delete mode 100644 BTCPayServer/Security/Basic/BasicAuthenticationOptions.cs delete mode 100644 BTCPayServer/Security/Basic/BasicExtensions.cs diff --git a/BTCPayServer/Controllers/RestApi/ApiKeysController.cs b/BTCPayServer/Controllers/RestApi/ApiKeysController.cs index 95324ebf9..3fd79e781 100644 --- a/BTCPayServer/Controllers/RestApi/ApiKeysController.cs +++ b/BTCPayServer/Controllers/RestApi/ApiKeysController.cs @@ -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> RevokeKey() { ControllerContext.HttpContext.GetAPIKey(out var apiKey); diff --git a/BTCPayServer/Controllers/RestApi/TestApiKeyController.cs b/BTCPayServer/Controllers/RestApi/TestApiKeyController.cs index 3673b4fb9..a3c89c67c 100644 --- a/BTCPayServer/Controllers/RestApi/TestApiKeyController.cs +++ b/BTCPayServer/Controllers/RestApi/TestApiKeyController.cs @@ -14,7 +14,7 @@ namespace BTCPayServer.Controllers.RestApi /// [Route("api/test/apikey")] [ApiController] - [Authorize(AuthenticationSchemes = AuthenticationSchemes.ApiKeyOrBasic)] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)] public class TestApiKeyController : ControllerBase { private readonly UserManager _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 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; diff --git a/BTCPayServer/Controllers/RestApi/UsersController.cs b/BTCPayServer/Controllers/RestApi/UsersController.cs index 25711540a..cd2140963 100644 --- a/BTCPayServer/Controllers/RestApi/UsersController.cs +++ b/BTCPayServer/Controllers/RestApi/UsersController.cs @@ -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 _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> 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 diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index c32ea9907..07d1282e7 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -286,8 +286,7 @@ namespace BTCPayServer.Hosting services.AddAuthentication() .AddCookie() .AddBitpayAuthentication() - .AddAPIKeyAuthentication() - .AddBasicAuthentication(); + .AddAPIKeyAuthentication(); } public static IApplicationBuilder UsePayServer(this IApplicationBuilder app) diff --git a/BTCPayServer/Security/APIKeys/APIKeyAuthenticationHandler.cs b/BTCPayServer/Security/APIKeys/APIKeyAuthenticationHandler.cs index 07eff055d..1ef14a1f3 100644 --- a/BTCPayServer/Security/APIKeys/APIKeyAuthenticationHandler.cs +++ b/BTCPayServer/Security/APIKeys/APIKeyAuthenticationHandler.cs @@ -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; + private readonly SignInManager _signInManager; + private readonly UserManager _userManager; public APIKeyAuthenticationHandler( APIKeyRepository apiKeyRepository, @@ -26,13 +28,28 @@ namespace BTCPayServer.Security.APIKeys IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, - ISystemClock clock) : base(options, logger, encoder, clock) + ISystemClock clock, + SignInManager signInManager, + UserManager userManager) : base(options, logger, encoder, clock) { _apiKeyRepository = apiKeyRepository; _identityOptions = identityOptions; + _signInManager = signInManager; + _userManager = userManager; } protected override async Task HandleAuthenticateAsync() + { + var res = await HandleApiKeyAuthenticateResult(); + if (res.None) + { + return await HandleBasicAuthenticateAsync(); + } + + return res; + } + + private async Task HandleApiKeyAuthenticateResult() { if (!Context.Request.HttpContext.GetAPIKey(out var apiKey) || string.IsNullOrEmpty(apiKey)) return AuthenticateResult.NoResult(); @@ -46,9 +63,38 @@ namespace BTCPayServer.Security.APIKeys List claims = new List(); 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 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() + { + 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)); } } } diff --git a/BTCPayServer/Security/APIKeys/APIKeyExtensions.cs b/BTCPayServer/Security/APIKeys/APIKeyExtensions.cs index d1b6a391a..8671b6992 100644 --- a/BTCPayServer/Security/APIKeys/APIKeyExtensions.cs +++ b/BTCPayServer/Security/APIKeys/APIKeyExtensions.cs @@ -26,7 +26,7 @@ namespace BTCPayServer.Security.APIKeys public static AuthenticationBuilder AddAPIKeyAuthentication(this AuthenticationBuilder builder) { - builder.AddScheme(AuthenticationSchemes.ApiKey, + builder.AddScheme(AuthenticationSchemes.Greenfield, o => { }); return builder; } diff --git a/BTCPayServer/Security/AuthenticationSchemes.cs b/BTCPayServer/Security/AuthenticationSchemes.cs index 5f34a7caa..de87c4116 100644 --- a/BTCPayServer/Security/AuthenticationSchemes.cs +++ b/BTCPayServer/Security/AuthenticationSchemes.cs @@ -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"; } } diff --git a/BTCPayServer/Security/Basic/BasicAuthenticationHandler.cs b/BTCPayServer/Security/Basic/BasicAuthenticationHandler.cs deleted file mode 100644 index 34be18070..000000000 --- a/BTCPayServer/Security/Basic/BasicAuthenticationHandler.cs +++ /dev/null @@ -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 - { - private readonly IOptionsMonitor _identityOptions; - private readonly SignInManager _signInManager; - private readonly UserManager _userManager; - - public BasicAuthenticationHandler( - - IOptionsMonitor identityOptions, - SignInManager signInManager, - UserManager userManager, - IOptionsMonitor options, - ILoggerFactory logger, - UrlEncoder encoder, - ISystemClock clock) : base(options, logger, encoder, clock) - { - _identityOptions = identityOptions; - _signInManager = signInManager; - _userManager = userManager; - } - - protected override async Task 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() - { - 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)); - - } - } -} diff --git a/BTCPayServer/Security/Basic/BasicAuthenticationOptions.cs b/BTCPayServer/Security/Basic/BasicAuthenticationOptions.cs deleted file mode 100644 index f326d66e8..000000000 --- a/BTCPayServer/Security/Basic/BasicAuthenticationOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Authentication; - -namespace BTCPayServer.Security.Basic -{ - public class BasicAuthenticationOptions : AuthenticationSchemeOptions - { - } -} diff --git a/BTCPayServer/Security/Basic/BasicExtensions.cs b/BTCPayServer/Security/Basic/BasicExtensions.cs deleted file mode 100644 index fd51395b8..000000000 --- a/BTCPayServer/Security/Basic/BasicExtensions.cs +++ /dev/null @@ -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(AuthenticationSchemes.Basic, - o => { }); - return builder; - } - - } -}