mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-01-18 21:32:27 +01:00
New API endpoint: Find 1 user by ID or by email, or list all users. (#3176)
Co-authored-by: Kukks <evilkukka@gmail.com>
This commit is contained in:
parent
03bc91fd1e
commit
288fbda54f
@ -27,6 +27,18 @@ namespace BTCPayServer.Client
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<ApplicationUserData> GetUserByIdOrEmail(string idOrEmail, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/users/{idOrEmail}", null, HttpMethod.Get), token);
|
||||
return await HandleResponse<ApplicationUserData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<ApplicationUserData[]> GetUsers( CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/users/", null, HttpMethod.Get), token);
|
||||
return await HandleResponse<ApplicationUserData[]>(response);
|
||||
}
|
||||
|
||||
public virtual async Task DeleteCurrentUser(CancellationToken token = default)
|
||||
{
|
||||
await DeleteUser("me", token);
|
||||
|
@ -24,6 +24,7 @@ namespace BTCPayServer.Client
|
||||
public const string CanViewProfile = "btcpay.user.canviewprofile";
|
||||
public const string CanManageNotificationsForUser = "btcpay.user.canmanagenotificationsforuser";
|
||||
public const string CanViewNotificationsForUser = "btcpay.user.canviewnotificationsforuser";
|
||||
public const string CanViewUsers = "btcpay.server.canviewusers";
|
||||
public const string CanCreateUser = "btcpay.server.cancreateuser";
|
||||
public const string CanDeleteUser = "btcpay.user.candeleteuser";
|
||||
public const string CanManagePullPayments = "btcpay.store.canmanagepullpayments";
|
||||
@ -43,6 +44,7 @@ namespace BTCPayServer.Client
|
||||
yield return CanModifyPaymentRequests;
|
||||
yield return CanModifyProfile;
|
||||
yield return CanViewProfile;
|
||||
yield return CanViewUsers;
|
||||
yield return CanCreateUser;
|
||||
yield return CanDeleteUser;
|
||||
yield return CanManageNotificationsForUser;
|
||||
|
@ -212,6 +212,100 @@ namespace BTCPayServer.Tests
|
||||
|
||||
tester.Stores.Remove(user.StoreId);
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanViewUsersViaApi()
|
||||
{
|
||||
using var tester = CreateServerTester(newDb: true);
|
||||
await tester.StartAsync();
|
||||
|
||||
var unauthClient = new BTCPayServerClient(tester.PayTester.ServerUri);
|
||||
|
||||
// Should be 401 for all calls because we don't have permission
|
||||
await AssertHttpError(401, async () => await unauthClient.GetUsers());
|
||||
await AssertHttpError(401, async () => await unauthClient.GetUserByIdOrEmail("non_existing_id"));
|
||||
await AssertHttpError(401, async () => await unauthClient.GetUserByIdOrEmail("someone@example.com"));
|
||||
|
||||
|
||||
var adminUser = tester.NewAccount();
|
||||
await adminUser.GrantAccessAsync();
|
||||
await adminUser.MakeAdmin();
|
||||
var adminClient = await adminUser.CreateClient(Policies.Unrestricted);
|
||||
|
||||
// Should be 404 if user doesn't exist
|
||||
await AssertHttpError(404,async () => await adminClient.GetUserByIdOrEmail("non_existing_id"));
|
||||
await AssertHttpError(404,async () => await adminClient.GetUserByIdOrEmail("doesnotexist@example.com"));
|
||||
|
||||
// Try listing all users, should be fine
|
||||
await adminClient.GetUsers();
|
||||
|
||||
// Try loading 1 user by ID. Loading myself.
|
||||
await adminClient.GetUserByIdOrEmail(adminUser.UserId);
|
||||
|
||||
// Try loading 1 user by email. Loading myself.
|
||||
await adminClient.GetUserByIdOrEmail(adminUser.Email);
|
||||
|
||||
|
||||
// var badClient = await user.CreateClient(Policies.CanCreateInvoice);
|
||||
// await AssertHttpError(403,
|
||||
// async () => await badClient.DeleteCurrentUser());
|
||||
|
||||
var goodUser = tester.NewAccount();
|
||||
await goodUser.GrantAccessAsync();
|
||||
await goodUser.MakeAdmin();
|
||||
var goodClient = await goodUser.CreateClient(Policies.CanViewUsers);
|
||||
|
||||
// Try listing all users, should be fine
|
||||
await goodClient.GetUsers();
|
||||
|
||||
// Should be 404 if user doesn't exist
|
||||
await AssertHttpError(404,async () => await goodClient.GetUserByIdOrEmail("non_existing_id"));
|
||||
await AssertHttpError(404,async () => await goodClient.GetUserByIdOrEmail("doesnotexist@example.com"));
|
||||
|
||||
// Try listing all users, should be fine
|
||||
await goodClient.GetUsers();
|
||||
|
||||
// Try loading 1 user by ID. Loading myself.
|
||||
await goodClient.GetUserByIdOrEmail(goodUser.UserId);
|
||||
|
||||
// Try loading 1 user by email. Loading myself.
|
||||
await goodClient.GetUserByIdOrEmail(goodUser.Email);
|
||||
|
||||
|
||||
|
||||
|
||||
var badUser = tester.NewAccount();
|
||||
await badUser.GrantAccessAsync();
|
||||
await badUser.MakeAdmin();
|
||||
|
||||
// Bad user has a permission, but it's the wrong one.
|
||||
var badClient = await goodUser.CreateClient(Policies.CanCreateInvoice);
|
||||
|
||||
// Try listing all users, should be fine
|
||||
await AssertHttpError(403,async () => await badClient.GetUsers());
|
||||
|
||||
// Should be 404 if user doesn't exist
|
||||
await AssertHttpError(403,async () => await badClient.GetUserByIdOrEmail("non_existing_id"));
|
||||
await AssertHttpError(403,async () => await badClient.GetUserByIdOrEmail("doesnotexist@example.com"));
|
||||
|
||||
// Try listing all users, should be fine
|
||||
await AssertHttpError(403,async () => await badClient.GetUsers());
|
||||
|
||||
// Try loading 1 user by ID. Loading myself.
|
||||
await AssertHttpError(403,async () => await badClient.GetUserByIdOrEmail(badUser.UserId));
|
||||
|
||||
// Try loading 1 user by email. Loading myself.
|
||||
await AssertHttpError(403,async () => await badClient.GetUserByIdOrEmail(badUser.Email));
|
||||
|
||||
|
||||
|
||||
|
||||
// Why is this line needed? I saw it in "CanDeleteUsersViaApi" as well. Is this part of the cleanup?
|
||||
tester.Stores.Remove(adminUser.StoreId);
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
|
@ -236,6 +236,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
UserId = account.RegisteredUserId;
|
||||
Email = RegisterDetails.Email;
|
||||
IsAdmin = account.RegisteredAdmin;
|
||||
}
|
||||
|
||||
@ -252,6 +253,12 @@ namespace BTCPayServer.Tests
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string Email
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string StoreId
|
||||
{
|
||||
|
@ -9,13 +9,10 @@ using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Security.Greenfield;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Storage.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@ -64,6 +61,25 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewUsers, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/users/{idOrEmail}")]
|
||||
public async Task<IActionResult> GetUser(string idOrEmail)
|
||||
{
|
||||
var user = (await _userManager.FindByIdAsync(idOrEmail) ) ?? await _userManager.FindByEmailAsync(idOrEmail);
|
||||
if (user != null)
|
||||
{
|
||||
return Ok(await FromModel(user));
|
||||
}
|
||||
return UserNotFound();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewUsers, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/users/")]
|
||||
public async Task<ActionResult<ApplicationUserData[]>> GetUsers()
|
||||
{
|
||||
return Ok(await _userService.GetUsersWithRoles());
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/users/me")]
|
||||
public async Task<ActionResult<ApplicationUserData>> GetCurrentUser()
|
||||
@ -216,15 +232,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private async Task<ApplicationUserData> FromModel(ApplicationUser data)
|
||||
{
|
||||
var roles = (await _userManager.GetRolesAsync(data)).ToArray();
|
||||
return new ApplicationUserData()
|
||||
{
|
||||
Id = data.Id,
|
||||
Email = data.Email,
|
||||
EmailConfirmed = data.EmailConfirmed,
|
||||
RequiresEmailConfirmation = data.RequiresEmailConfirmation,
|
||||
Roles = roles,
|
||||
Created = data.Created
|
||||
};
|
||||
return UserService.FromModel(data, roles);
|
||||
}
|
||||
|
||||
private async Task<bool> IsUserTheOnlyOneAdmin()
|
||||
|
@ -505,6 +505,7 @@ namespace BTCPayServer.Controllers
|
||||
public static readonly Dictionary<string, (string Title, string Description)> PermissionDescriptions = new Dictionary<string, (string Title, string Description)>()
|
||||
{
|
||||
{BTCPayServer.Client.Policies.Unrestricted, ("Unrestricted access", "The app will have unrestricted access to your account.")},
|
||||
{BTCPayServer.Client.Policies.CanViewUsers, ("View users", "The app will be able to see all users on this server.")},
|
||||
{BTCPayServer.Client.Policies.CanCreateUser, ("Create new users", "The app will be able to create new users on this server.")},
|
||||
{BTCPayServer.Client.Policies.CanDeleteUser, ("Delete user", "The app will be able to delete the user to whom it is assigned. Admin users can delete any user without this permission.")},
|
||||
{BTCPayServer.Client.Policies.CanModifyStoreSettings, ("Modify your stores", "The app will be able to manage invoices on all your stores and modify their settings.")},
|
||||
|
@ -3,11 +3,13 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Storage.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Services
|
||||
{
|
||||
@ -18,13 +20,16 @@ namespace BTCPayServer.Services
|
||||
private readonly StoredFileRepository _storedFileRepository;
|
||||
private readonly FileService _fileService;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly ApplicationDbContextFactory _applicationDbContextFactory;
|
||||
|
||||
public UserService(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
IAuthorizationService authorizationService,
|
||||
StoredFileRepository storedFileRepository,
|
||||
FileService fileService,
|
||||
StoreRepository storeRepository
|
||||
StoreRepository storeRepository,
|
||||
ApplicationDbContextFactory applicationDbContextFactory
|
||||
|
||||
)
|
||||
{
|
||||
_userManager = userManager;
|
||||
@ -32,6 +37,28 @@ namespace BTCPayServer.Services
|
||||
_storedFileRepository = storedFileRepository;
|
||||
_fileService = fileService;
|
||||
_storeRepository = storeRepository;
|
||||
_applicationDbContextFactory = applicationDbContextFactory;
|
||||
}
|
||||
|
||||
public async Task<List<ApplicationUserData>> GetUsersWithRoles()
|
||||
{
|
||||
await using var context = _applicationDbContextFactory.CreateContext();
|
||||
return await (context.Users.Select(p => FromModel(p, p.UserRoles.Join(context.Roles, userRole => userRole.RoleId, role => role.Id,
|
||||
(userRole, role) => role.Name).ToArray()))).ToListAsync();
|
||||
}
|
||||
|
||||
|
||||
public static ApplicationUserData FromModel(ApplicationUser data, string[] roles)
|
||||
{
|
||||
return new ApplicationUserData()
|
||||
{
|
||||
Id = data.Id,
|
||||
Email = data.Email,
|
||||
EmailConfirmed = data.EmailConfirmed,
|
||||
RequiresEmailConfirmation = data.RequiresEmailConfirmation,
|
||||
Created = data.Created,
|
||||
Roles = roles
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<bool> IsAdminUser(string userId)
|
||||
|
@ -76,7 +76,7 @@
|
||||
"securitySchemes": {
|
||||
"API_Key": {
|
||||
"type": "apiKey",
|
||||
"description": "BTCPay Server supports authenticating and authorizing users through an API Key that is generated by them. Send the API Key as a header value to Authorization with the format: `token {token}`. For a smoother experience, you can generate a url that redirects users to an API key creation screen.\n\n The following permissions are available to the context of the user creating the API Key:\n\n* `unrestricted`: Unrestricted access\n* `btcpay.user.candeleteuser`: Delete user\n* `btcpay.user.canviewprofile`: View your profile\n* `btcpay.user.canmodifyprofile`: Manage your profile\n* `btcpay.user.canmanagenotificationsforuser`: Manage your notifications\n* `btcpay.user.canviewnotificationsforuser`: View your notifications\n\nThe following permissions are available if the user is an administrator:\n\n* `btcpay.server.cancreateuser`: Create new users\n* `btcpay.server.canmodifyserversettings`: Manage your server\n* `btcpay.server.canuseinternallightningnode`: Use the internal lightning node\n* `btcpay.server.cancreatelightninginvoiceinternalnode`: Create invoices with internal lightning node\n\nThe following permissions applies to all stores of the user, you can limit to a specific store with the following format: `btcpay.store.cancreateinvoice:6HSHAEU4iYWtjxtyRs9KyPjM9GAQp8kw2T9VWbGG1FnZ`:\n\n* `btcpay.store.canmodifystoresettings`: Modify your stores\n* `btcpay.store.webhooks.canmodifywebhooks`: Modify stores webhooks\n* `btcpay.store.canviewstoresettings`: View your stores\n* `btcpay.store.cancreateinvoice`: Create an invoice\n* `btcpay.store.canviewinvoices`: View invoices\n* `btcpay.store.canmodifyinvoices`: Modify invoices\n* `btcpay.store.canmodifypaymentrequests`: Modify your payment requests\n* `btcpay.store.canviewpaymentrequests`: View your payment requests\n* `btcpay.store.canuselightningnode`: Use the lightning nodes associated with your stores\n* `btcpay.store.cancreatelightninginvoice`: Create invoices the lightning nodes associated with your stores\n\nNote that API Keys only limits permission of a user and can never expand it. If an API Key has the permission `btcpay.server.canmodifyserversettings` but that the user account creating this API Key is not administrator, the API Key will not be able to modify the server settings.\nSome permissions may include other permissions, see [this operation](#operation/permissionsMetadata).\n",
|
||||
"description": "BTCPay Server supports authenticating and authorizing users through an API Key that is generated by them. Send the API Key as a header value to Authorization with the format: `token {token}`. For a smoother experience, you can generate a url that redirects users to an API key creation screen.\n\n The following permissions are available to the context of the user creating the API Key:\n\n* `unrestricted`: Unrestricted access\n* `btcpay.user.candeleteuser`: Delete user\n* `btcpay.user.canviewprofile`: View your profile\n* `btcpay.user.canmodifyprofile`: Manage your profile\n* `btcpay.user.canmanagenotificationsforuser`: Manage your notifications\n* `btcpay.user.canviewnotificationsforuser`: View your notifications\n\nThe following permissions are available if the user is an administrator:\n\n* `btcpay.server.canviewusers`: View users\n* `btcpay.server.cancreateuser`: Create new users\n* `btcpay.server.canmodifyserversettings`: Manage your server\n* `btcpay.server.canuseinternallightningnode`: Use the internal lightning node\n* `btcpay.server.cancreatelightninginvoiceinternalnode`: Create invoices with internal lightning node\n\nThe following permissions applies to all stores of the user, you can limit to a specific store with the following format: `btcpay.store.cancreateinvoice:6HSHAEU4iYWtjxtyRs9KyPjM9GAQp8kw2T9VWbGG1FnZ`:\n\n* `btcpay.store.canmodifystoresettings`: Modify your stores\n* `btcpay.store.webhooks.canmodifywebhooks`: Modify stores webhooks\n* `btcpay.store.canviewstoresettings`: View your stores\n* `btcpay.store.cancreateinvoice`: Create an invoice\n* `btcpay.store.canviewinvoices`: View invoices\n* `btcpay.store.canmodifyinvoices`: Modify invoices\n* `btcpay.store.canmodifypaymentrequests`: Modify your payment requests\n* `btcpay.store.canviewpaymentrequests`: View your payment requests\n* `btcpay.store.canuselightningnode`: Use the lightning nodes associated with your stores\n* `btcpay.store.cancreatelightninginvoice`: Create invoices the lightning nodes associated with your stores\n\nNote that API Keys only limits permission of a user and can never expand it. If an API Key has the permission `btcpay.server.canmodifyserversettings` but that the user account creating this API Key is not administrator, the API Key will not be able to modify the server settings.\nSome permissions may include other permissions, see [this operation](#operation/permissionsMetadata).\n",
|
||||
"name": "Authorization",
|
||||
"in": "header"
|
||||
},
|
||||
|
@ -52,6 +52,33 @@
|
||||
}
|
||||
},
|
||||
"/api/v1/users": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Get all users",
|
||||
"description": "Load all users that exist.",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Users found"
|
||||
},
|
||||
"401": {
|
||||
"description": "Missing authorization for loading the users"
|
||||
},
|
||||
"403": {
|
||||
"description": "Authorized but forbidden to load the users. You have the wrong API permissions."
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"API_Key": [
|
||||
"btcpay.server.canviewusers"
|
||||
],
|
||||
"Basic": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"Users"
|
||||
@ -129,7 +156,47 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/users/{userId}": {
|
||||
"/api/v1/users/{idOrEmail}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Get user by ID or Email",
|
||||
"description": "Get 1 user by ID or Email.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "idOrEmail",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The ID or email of the user to load",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "User found"
|
||||
},
|
||||
"401": {
|
||||
"description": "Missing authorization for loading the user"
|
||||
},
|
||||
"403": {
|
||||
"description": "Authorized but forbidden to load the user. You have the wrong API permissions."
|
||||
},
|
||||
"404": {
|
||||
"description": "No user found with this ID or email"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"API_Key": [
|
||||
"btcpay.server.canviewusers"
|
||||
],
|
||||
"Basic": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"Users"
|
||||
@ -161,7 +228,14 @@
|
||||
"description": "User with provided ID was not found"
|
||||
}
|
||||
},
|
||||
"security": []
|
||||
"security": [
|
||||
{
|
||||
"API_Key": [
|
||||
"btcpay.user.candeleteuser"
|
||||
],
|
||||
"Basic": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -192,7 +266,11 @@
|
||||
"created": {
|
||||
"nullable": true,
|
||||
"description": "The creation date of the user as a unix timestamp. Null if created before v1.0.5.6",
|
||||
"allOf": [ {"$ref": "#/components/schemas/UnixTimestamp"}]
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/UnixTimestamp"
|
||||
}
|
||||
]
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
|
Loading…
Reference in New Issue
Block a user