mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-19 15:04:19 +01:00
Greenfield: Expand user data (#6649)
This commit is contained in:
@@ -50,6 +50,11 @@ namespace BTCPayServer.Client.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string[] Roles { get; set; }
|
public string[] Roles { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// the invitation url of the user
|
||||||
|
/// </summary>
|
||||||
|
public string InvitationUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// the date the user was created. Null if created before v1.0.5.6.
|
/// the date the user was created. Null if created before v1.0.5.6.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace BTCPayServer.Client.Models
|
namespace BTCPayServer.Client.Models
|
||||||
{
|
{
|
||||||
@@ -10,32 +12,24 @@ namespace BTCPayServer.Client.Models
|
|||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class StoreUserData
|
public class StoreUserData : ApplicationUserData
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// the id of the user
|
/// the id of the user
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Use Id instead")]
|
||||||
public string UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// the store role of the user
|
/// the store role of the user
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
public string StoreRole { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// the store role of the user
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use StoreRole instead")]
|
||||||
public string Role { get; set; }
|
public string Role { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// the email AND username of the user
|
|
||||||
/// </summary>
|
|
||||||
public string Email { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// the name of the user
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// the image url of the user
|
|
||||||
/// </summary>
|
|
||||||
public string ImageUrl { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RoleData
|
public class RoleData
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ namespace BTCPayServer.Tests
|
|||||||
await Assert.ThrowsAsync<GreenfieldAPIException>(() => newUserClient.GetInvoices(store.Id));
|
await Assert.ThrowsAsync<GreenfieldAPIException>(() => newUserClient.GetInvoices(store.Id));
|
||||||
|
|
||||||
// if user is a guest or owner, then it should be ok
|
// if user is a guest or owner, then it should be ok
|
||||||
await unrestricted.AddStoreUser(store.Id, new StoreUserData() { UserId = newUser.Id });
|
await unrestricted.AddStoreUser(store.Id, new StoreUserData() { Id = newUser.Id });
|
||||||
await newUserClient.GetInvoices(store.Id);
|
await newUserClient.GetInvoices(store.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4017,8 +4017,8 @@ namespace BTCPayServer.Tests
|
|||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
var users = await client.GetStoreUsers(user.StoreId);
|
var users = await client.GetStoreUsers(user.StoreId);
|
||||||
var storeUser = Assert.Single(users);
|
var storeUser = Assert.Single(users);
|
||||||
Assert.Equal(user.UserId, storeUser.UserId);
|
Assert.Equal(user.UserId, storeUser.Id);
|
||||||
Assert.Equal(ownerRole.Id, storeUser.Role);
|
Assert.Equal(ownerRole.Id, storeUser.StoreRole);
|
||||||
Assert.Equal(user.Email, storeUser.Email);
|
Assert.Equal(user.Email, storeUser.Email);
|
||||||
Assert.Equal("The Admin", storeUser.Name);
|
Assert.Equal("The Admin", storeUser.Name);
|
||||||
Assert.Equal("avatar.jpg", storeUser.ImageUrl);
|
Assert.Equal("avatar.jpg", storeUser.ImageUrl);
|
||||||
@@ -4050,15 +4050,15 @@ namespace BTCPayServer.Tests
|
|||||||
await AssertPermissionError(Policies.CanModifyStoreSettings, async () => await guestClient.RemoveStoreUser(user.StoreId, user.UserId));
|
await AssertPermissionError(Policies.CanModifyStoreSettings, async () => await guestClient.RemoveStoreUser(user.StoreId, user.UserId));
|
||||||
|
|
||||||
// add users to store
|
// add users to store
|
||||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = managerRole.Id, UserId = manager.UserId });
|
await client.AddStoreUser(user.StoreId, new StoreUserData { StoreRole = managerRole.Id, Id = manager.UserId });
|
||||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = employeeRole.Id, UserId = employee.UserId });
|
await client.AddStoreUser(user.StoreId, new StoreUserData { StoreRole = employeeRole.Id, Id = employee.UserId });
|
||||||
|
|
||||||
// add with email
|
// add with email
|
||||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = guestRole.Id, UserId = guest.Email });
|
await client.AddStoreUser(user.StoreId, new StoreUserData { StoreRole = guestRole.Id, Id = guest.Email });
|
||||||
|
|
||||||
// test unknown user
|
// test unknown user
|
||||||
await AssertAPIError("user-not-found", async () => await client.AddStoreUser(user.StoreId, new StoreUserData { Role = managerRole.Id, UserId = "unknown" }));
|
await AssertAPIError("user-not-found", async () => await client.AddStoreUser(user.StoreId, new StoreUserData { StoreRole = managerRole.Id, Id = "unknown" }));
|
||||||
await AssertAPIError("user-not-found", async () => await client.UpdateStoreUser(user.StoreId, "unknown", new StoreUserData { Role = ownerRole.Id }));
|
await AssertAPIError("user-not-found", async () => await client.UpdateStoreUser(user.StoreId, "unknown", new StoreUserData { StoreRole = ownerRole.Id }));
|
||||||
await AssertAPIError("user-not-found", async () => await client.RemoveStoreUser(user.StoreId, "unknown"));
|
await AssertAPIError("user-not-found", async () => await client.RemoveStoreUser(user.StoreId, "unknown"));
|
||||||
|
|
||||||
//test no access to api for employee
|
//test no access to api for employee
|
||||||
@@ -4080,7 +4080,7 @@ namespace BTCPayServer.Tests
|
|||||||
await AssertPermissionError(Policies.CanModifyStoreSettings, async () => await managerClient.RemoveStoreUser(user.StoreId, user.UserId));
|
await AssertPermissionError(Policies.CanModifyStoreSettings, async () => await managerClient.RemoveStoreUser(user.StoreId, user.UserId));
|
||||||
|
|
||||||
// updates
|
// updates
|
||||||
await client.UpdateStoreUser(user.StoreId, employee.UserId, new StoreUserData { Role = ownerRole.Id });
|
await client.UpdateStoreUser(user.StoreId, employee.UserId, new StoreUserData { StoreRole = ownerRole.Id });
|
||||||
await employeeClient.GetStore(user.StoreId);
|
await employeeClient.GetStore(user.StoreId);
|
||||||
|
|
||||||
// remove
|
// remove
|
||||||
@@ -4088,9 +4088,9 @@ namespace BTCPayServer.Tests
|
|||||||
await AssertHttpError(403, async () => await employeeClient.GetStore(user.StoreId));
|
await AssertHttpError(403, async () => await employeeClient.GetStore(user.StoreId));
|
||||||
|
|
||||||
// test duplicate add
|
// test duplicate add
|
||||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = ownerRole.Id, UserId = employee.UserId });
|
await client.AddStoreUser(user.StoreId, new StoreUserData { StoreRole = ownerRole.Id, Id = employee.UserId });
|
||||||
await AssertAPIError("duplicate-store-user-role", async () =>
|
await AssertAPIError("duplicate-store-user-role", async () =>
|
||||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = ownerRole.Id, UserId = employee.UserId }));
|
await client.AddStoreUser(user.StoreId, new StoreUserData { StoreRole = ownerRole.Id, Id = employee.UserId }));
|
||||||
await employeeClient.RemoveStoreUser(user.StoreId, user.UserId);
|
await employeeClient.RemoveStoreUser(user.StoreId, user.UserId);
|
||||||
|
|
||||||
//test no access to api when unrelated to store at all
|
//test no access to api when unrelated to store at all
|
||||||
|
|||||||
@@ -194,5 +194,5 @@
|
|||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ProjectExtensions><VisualStudio><UserProperties wwwroot_4swagger_4v1_4swagger_1template_1invoices_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1misc_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1pull-payments_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1serverinfo_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1stores_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1stores-payment-methods_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1users_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1webhooks_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" /></VisualStudio></ProjectExtensions>
|
<ProjectExtensions><VisualStudio><UserProperties wwwroot_4swagger_4v1_4swagger_1template_1invoices_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1misc_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1pull-payments_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1serverinfo_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1stores_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1stores-payment-methods_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1stores-users_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1users_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1webhooks_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" /></VisualStudio></ProjectExtensions>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Reflection.Metadata;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Abstractions.Constants;
|
using BTCPayServer.Abstractions.Constants;
|
||||||
using BTCPayServer.Abstractions.Extensions;
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
@@ -11,6 +14,7 @@ using Microsoft.AspNetCore.Authorization;
|
|||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NicolasDorier.RateLimits;
|
||||||
using StoreData = BTCPayServer.Data.StoreData;
|
using StoreData = BTCPayServer.Data.StoreData;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers.Greenfield
|
namespace BTCPayServer.Controllers.Greenfield
|
||||||
@@ -22,15 +26,18 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
{
|
{
|
||||||
private readonly StoreRepository _storeRepository;
|
private readonly StoreRepository _storeRepository;
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
private readonly CallbackGenerator _callbackGenerator;
|
||||||
private readonly UriResolver _uriResolver;
|
private readonly UriResolver _uriResolver;
|
||||||
|
|
||||||
public GreenfieldStoreUsersController(
|
public GreenfieldStoreUsersController(
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
|
CallbackGenerator callbackGenerator,
|
||||||
UriResolver uriResolver)
|
UriResolver uriResolver)
|
||||||
{
|
{
|
||||||
_storeRepository = storeRepository;
|
_storeRepository = storeRepository;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
|
_callbackGenerator = callbackGenerator;
|
||||||
_uriResolver = uriResolver;
|
_uriResolver = uriResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,10 +54,12 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
public async Task<IActionResult> RemoveStoreUser(string storeId, string idOrEmail)
|
public async Task<IActionResult> RemoveStoreUser(string storeId, string idOrEmail)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
var store = HttpContext.GetStoreData();
|
||||||
if (store == null) return StoreNotFound();
|
if (store == null)
|
||||||
|
return StoreNotFound();
|
||||||
|
|
||||||
var user = await _userManager.FindByIdOrEmail(idOrEmail);
|
var user = await _userManager.FindByIdOrEmail(idOrEmail);
|
||||||
if (user == null) return UserNotFound();
|
if (user == null)
|
||||||
|
return UserNotFound();
|
||||||
|
|
||||||
return await _storeRepository.RemoveStoreUser(storeId, user.Id)
|
return await _storeRepository.RemoveStoreUser(storeId, user.Id)
|
||||||
? Ok()
|
? Ok()
|
||||||
@@ -63,17 +72,23 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
public async Task<IActionResult> AddOrUpdateStoreUser(string storeId, StoreUserData request, string idOrEmail = null)
|
public async Task<IActionResult> AddOrUpdateStoreUser(string storeId, StoreUserData request, string idOrEmail = null)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
var store = HttpContext.GetStoreData();
|
||||||
if (store == null) return StoreNotFound();
|
if (store == null)
|
||||||
|
return StoreNotFound();
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
|
request.StoreRole ??= request.Role;
|
||||||
|
request.Id ??= request.UserId;
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
|
||||||
var user = await _userManager.FindByIdOrEmail(idOrEmail ?? request.UserId);
|
var user = await _userManager.FindByIdOrEmail(idOrEmail ?? request.Id);
|
||||||
if (user == null) return UserNotFound();
|
if (user == null)
|
||||||
|
return UserNotFound();
|
||||||
|
|
||||||
StoreRoleId roleId = null;
|
StoreRoleId roleId = null;
|
||||||
if (request.Role is not null)
|
if (request.StoreRole is not null)
|
||||||
{
|
{
|
||||||
roleId = await _storeRepository.ResolveStoreRoleId(storeId, request.Role);
|
roleId = await _storeRepository.ResolveStoreRoleId(storeId, request.StoreRole);
|
||||||
if (roleId is null)
|
if (roleId is null)
|
||||||
ModelState.AddModelError(nameof(request.Role), "The role id provided does not exist");
|
ModelState.AddModelError(nameof(request.StoreRole), "The role id provided does not exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
@@ -87,21 +102,21 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
: this.CreateAPIError(409, "duplicate-store-user-role", "The user is already added to the store");
|
: this.CreateAPIError(409, "duplicate-store-user-role", "The user is already added to the store");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IEnumerable<StoreUserData>> FromModel(StoreData data)
|
private async Task<IEnumerable<StoreUserData>> FromModel(StoreData store)
|
||||||
{
|
{
|
||||||
var storeUsers = new List<StoreUserData>();
|
var storeUsers = new List<StoreUserData>();
|
||||||
foreach (var storeUser in data.UserStores)
|
foreach (var storeUser in store.UserStores)
|
||||||
{
|
{
|
||||||
var user = await _userManager.FindByIdOrEmail(storeUser.ApplicationUserId);
|
var user = await _userManager.FindByIdOrEmail(storeUser.ApplicationUserId);
|
||||||
var blob = user?.GetBlob();
|
if (user == null)
|
||||||
storeUsers.Add(new StoreUserData
|
continue;
|
||||||
{
|
var data = await UserService.ForAPI<StoreUserData>(user, [], _callbackGenerator, _uriResolver, Request);
|
||||||
UserId = storeUser.ApplicationUserId,
|
data.StoreRole = storeUser.StoreRoleId;
|
||||||
Role = storeUser.StoreRoleId,
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
Email = user?.Email,
|
data.UserId = storeUser.ApplicationUserId;
|
||||||
Name = blob?.Name,
|
data.Role = storeUser.StoreRoleId;
|
||||||
ImageUrl = blob?.ImageUrl == null ? null : await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), UnresolvedUri.Create(blob.ImageUrl))
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
});
|
storeUsers.Add(data);
|
||||||
}
|
}
|
||||||
return storeUsers;
|
return storeUsers;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -84,7 +85,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
var user = await _userManager.FindByIdOrEmail(idOrEmail);
|
var user = await _userManager.FindByIdOrEmail(idOrEmail);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
return Ok(await FromModel(user));
|
return Ok(await ForAPI(user));
|
||||||
}
|
}
|
||||||
return this.UserNotFound();
|
return this.UserNotFound();
|
||||||
}
|
}
|
||||||
@@ -128,7 +129,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
[HttpGet("~/api/v1/users/")]
|
[HttpGet("~/api/v1/users/")]
|
||||||
public async Task<ActionResult<ApplicationUserData[]>> GetUsers()
|
public async Task<ActionResult<ApplicationUserData[]>> GetUsers()
|
||||||
{
|
{
|
||||||
return Ok(await _userService.GetUsersWithRoles());
|
var usersWithRoles = await _userService.GetUsersWithRoles();
|
||||||
|
List<ApplicationUserData> users = [];
|
||||||
|
foreach (var user in usersWithRoles)
|
||||||
|
{
|
||||||
|
users.Add(await UserService.ForAPI<ApplicationUserData>(user.User, user.Roles, _callbackGenerator, _uriResolver, Request));
|
||||||
|
}
|
||||||
|
return Ok(users);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(Policy = Policies.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
@@ -136,7 +143,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
public async Task<ActionResult<ApplicationUserData>> GetCurrentUser()
|
public async Task<ActionResult<ApplicationUserData>> GetCurrentUser()
|
||||||
{
|
{
|
||||||
var user = await _userManager.GetUserAsync(User);
|
var user = await _userManager.GetUserAsync(User);
|
||||||
return await FromModel(user!);
|
return await ForAPI(user!);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanModifyProfile, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(Policy = Policies.CanModifyProfile, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
@@ -232,7 +239,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
return this.CreateValidationError(ModelState);
|
return this.CreateValidationError(ModelState);
|
||||||
|
|
||||||
var model = await FromModel(user);
|
var model = await ForAPI(user);
|
||||||
return Ok(model);
|
return Ok(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +271,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
user.SetBlob(blob);
|
user.SetBlob(blob);
|
||||||
await _userManager.UpdateAsync(user);
|
await _userManager.UpdateAsync(user);
|
||||||
_eventAggregator.Publish(new UserEvent.Updated(user));
|
_eventAggregator.Publish(new UserEvent.Updated(user));
|
||||||
var model = await FromModel(user);
|
var model = await ForAPI(user);
|
||||||
return Ok(model);
|
return Ok(model);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -414,7 +421,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
};
|
};
|
||||||
_eventAggregator.Publish(userEvent);
|
_eventAggregator.Publish(userEvent);
|
||||||
|
|
||||||
var model = await FromModel(user);
|
var model = await ForAPI(user);
|
||||||
return CreatedAtAction(string.Empty, model);
|
return CreatedAtAction(string.Empty, model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,14 +456,11 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ApplicationUserData> FromModel(ApplicationUser data)
|
private async Task<ApplicationUserData> ForAPI(ApplicationUser data)
|
||||||
{
|
{
|
||||||
var roles = (await _userManager.GetRolesAsync(data)).ToArray();
|
var roles = (await _userManager.GetRolesAsync(data)).ToArray();
|
||||||
var model = UserService.FromModel(data, roles);
|
var blob = data.GetBlob();
|
||||||
model.ImageUrl = string.IsNullOrEmpty(model.ImageUrl)
|
return await UserService.ForAPI<ApplicationUserData>(data, roles, _callbackGenerator, _uriResolver, Request);
|
||||||
? null
|
|
||||||
: await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), UnresolvedUri.Create(model.ImageUrl));
|
|
||||||
return model;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ namespace BTCPayServer.Controllers
|
|||||||
InvitationUrl =
|
InvitationUrl =
|
||||||
string.IsNullOrEmpty(blob?.InvitationToken)
|
string.IsNullOrEmpty(blob?.InvitationToken)
|
||||||
? null
|
? null
|
||||||
: _callbackGenerator.ForInvitation(u, blob.InvitationToken, Request),
|
: _callbackGenerator.ForInvitation(u.Id, blob.InvitationToken, Request),
|
||||||
EmailConfirmed = u.RequiresEmailConfirmation ? u.EmailConfirmed : null,
|
EmailConfirmed = u.RequiresEmailConfirmation ? u.EmailConfirmed : null,
|
||||||
Approved = u.RequiresApproval ? u.Approved : null,
|
Approved = u.RequiresApproval ? u.Approved : null,
|
||||||
Created = u.Created,
|
Created = u.Created,
|
||||||
@@ -97,7 +97,7 @@ namespace BTCPayServer.Controllers
|
|||||||
Id = user.Id,
|
Id = user.Id,
|
||||||
Email = user.Email,
|
Email = user.Email,
|
||||||
Name = blob?.Name,
|
Name = blob?.Name,
|
||||||
InvitationUrl = string.IsNullOrEmpty(blob?.InvitationToken) ? null : _callbackGenerator.ForInvitation(user, blob.InvitationToken, Request),
|
InvitationUrl = string.IsNullOrEmpty(blob?.InvitationToken) ? null : _callbackGenerator.ForInvitation(user.Id, blob.InvitationToken, Request),
|
||||||
ImageUrl = string.IsNullOrEmpty(blob?.ImageUrl) ? null : await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), UnresolvedUri.Create(blob.ImageUrl)),
|
ImageUrl = string.IsNullOrEmpty(blob?.ImageUrl) ? null : await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), UnresolvedUri.Create(blob.ImageUrl)),
|
||||||
EmailConfirmed = user.RequiresEmailConfirmation ? user.EmailConfirmed : null,
|
EmailConfirmed = user.RequiresEmailConfirmation ? user.EmailConfirmed : null,
|
||||||
Approved = user.RequiresApproval ? user.Approved : null,
|
Approved = user.RequiresApproval ? user.Approved : null,
|
||||||
|
|||||||
@@ -49,12 +49,12 @@ namespace BTCPayServer.Services
|
|||||||
public async Task<string> ForInvitation(ApplicationUser user, HttpRequest request)
|
public async Task<string> ForInvitation(ApplicationUser user, HttpRequest request)
|
||||||
{
|
{
|
||||||
var code = await UserManager.GenerateInvitationTokenAsync<ApplicationUser>(user.Id) ?? throw Bug();
|
var code = await UserManager.GenerateInvitationTokenAsync<ApplicationUser>(user.Id) ?? throw Bug();
|
||||||
return ForInvitation(user, code, request);
|
return ForInvitation(user.Id, code, request);
|
||||||
}
|
}
|
||||||
public string ForInvitation(ApplicationUser user, string code, HttpRequest request)
|
public string ForInvitation(string userId, string code, HttpRequest request)
|
||||||
{
|
{
|
||||||
return LinkGenerator.GetUriByAction(nameof(UIAccountController.AcceptInvite), "UIAccount",
|
return LinkGenerator.GetUriByAction(nameof(UIAccountController.AcceptInvite), "UIAccount",
|
||||||
new { userId = user.Id, code }, request.Scheme, request.Host, request.PathBase) ?? throw Bug();
|
new { userId, code }, request.Scheme, request.Host, request.PathBase) ?? throw Bug();
|
||||||
}
|
}
|
||||||
public async Task<string> ForPasswordReset(ApplicationUser user, HttpRequest request)
|
public async Task<string> ForPasswordReset(ApplicationUser user, HttpRequest request)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
using BTCPayServer.Storage.Services;
|
using BTCPayServer.Storage.Services;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -40,17 +42,30 @@ namespace BTCPayServer.Services
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<ApplicationUserData>> GetUsersWithRoles()
|
public record ApplicationUserWithRoles(ApplicationUser User, string[] Roles);
|
||||||
|
public async Task<List<ApplicationUserWithRoles>> GetUsersWithRoles()
|
||||||
{
|
{
|
||||||
await using var context = _applicationDbContextFactory.CreateContext();
|
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,
|
var res = await context.Users.Select(p =>
|
||||||
(userRole, role) => role.Name).ToArray()))).ToListAsync();
|
new
|
||||||
|
{
|
||||||
|
User = p,
|
||||||
|
Roles = p.UserRoles.Join(context.Roles, userRole => userRole.RoleId,
|
||||||
|
role => role.Id, (userRole, role) => role.Name).ToArray()
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
return res.Select(p => new ApplicationUserWithRoles(p.User, (p.Roles ?? [])!)).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ApplicationUserData FromModel(ApplicationUser data, string?[] roles)
|
public static async Task<T> ForAPI<T>(
|
||||||
|
ApplicationUser data,
|
||||||
|
string?[] roles,
|
||||||
|
CallbackGenerator callbackGenerator,
|
||||||
|
UriResolver uriResolver,
|
||||||
|
HttpRequest request) where T : ApplicationUserData, new()
|
||||||
{
|
{
|
||||||
var blob = data.GetBlob() ?? new();
|
var blob = data.GetBlob() ?? new UserBlob();
|
||||||
return new ApplicationUserData
|
return new T
|
||||||
{
|
{
|
||||||
Id = data.Id,
|
Id = data.Id,
|
||||||
Email = data.Email,
|
Email = data.Email,
|
||||||
@@ -60,9 +75,13 @@ namespace BTCPayServer.Services
|
|||||||
RequiresApproval = data.RequiresApproval,
|
RequiresApproval = data.RequiresApproval,
|
||||||
Created = data.Created,
|
Created = data.Created,
|
||||||
Name = blob.Name,
|
Name = blob.Name,
|
||||||
ImageUrl = blob.ImageUrl,
|
|
||||||
Roles = roles,
|
Roles = roles,
|
||||||
Disabled = data.LockoutEnabled && data.LockoutEnd is not null && DateTimeOffset.UtcNow < data.LockoutEnd.Value.UtcDateTime
|
Disabled = IsDisabled(data),
|
||||||
|
ImageUrl = string.IsNullOrEmpty(blob.ImageUrl)
|
||||||
|
? null
|
||||||
|
: await uriResolver.Resolve(request.GetAbsoluteRootUri(), UnresolvedUri.Create(blob.ImageUrl)),
|
||||||
|
InvitationUrl = string.IsNullOrEmpty(blob.InvitationToken) ? null
|
||||||
|
: callbackGenerator.ForInvitation(data.Id, blob.InvitationToken, request)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,8 +97,8 @@ namespace BTCPayServer.Services
|
|||||||
|
|
||||||
private static bool IsDisabled(ApplicationUser user)
|
private static bool IsDisabled(ApplicationUser user)
|
||||||
{
|
{
|
||||||
return user.LockoutEnabled && user.LockoutEnd is not null &&
|
return user is { LockoutEnabled: true, LockoutEnd: {} lockoutEnd } &&
|
||||||
DateTimeOffset.UtcNow < user.LockoutEnd.Value.UtcDateTime;
|
DateTimeOffset.UtcNow < lockoutEnd.UtcDateTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryCanLogin([NotNullWhen(true)] ApplicationUser? user, [MaybeNullWhen(true)] out string error)
|
public static bool TryCanLogin([NotNullWhen(true)] ApplicationUser? user, [MaybeNullWhen(true)] out string error)
|
||||||
|
|||||||
@@ -235,30 +235,25 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"userId": {
|
"userId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The id of the user",
|
"description": "The id of the user (Deprecated, use `id` instead)",
|
||||||
"nullable": false
|
"nullable": false,
|
||||||
|
"deprecated": true
|
||||||
},
|
},
|
||||||
"role": {
|
"role": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The role of the user. Default roles are `Owner`, `Manager`, `Employee` and `Guest` (Deprecated, use `storeRole` instead)",
|
||||||
|
"nullable": false,
|
||||||
|
"deprecated": true
|
||||||
|
},
|
||||||
|
"storeRole": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The role of the user. Default roles are `Owner`, `Manager`, `Employee` and `Guest`",
|
"description": "The role of the user. Default roles are `Owner`, `Manager`, `Employee` and `Guest`",
|
||||||
"nullable": false
|
"nullable": false
|
||||||
},
|
|
||||||
"email": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The email of the user",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The name of the user",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"imageUrl": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The profile picture URL of the user",
|
|
||||||
"nullable": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/ApplicationUserData"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -520,6 +520,11 @@
|
|||||||
"description": "The profile picture URL of the user",
|
"description": "The profile picture URL of the user",
|
||||||
"nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
|
"invitationUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The pending invitation URL of the user",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
"emailConfirmed": {
|
"emailConfirmed": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "True if the email has been confirmed by the user"
|
"description": "True if the email has been confirmed by the user"
|
||||||
@@ -546,6 +551,10 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"disabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "True if an admin has disabled the user"
|
||||||
|
},
|
||||||
"roles": {
|
"roles": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
|
|||||||
Reference in New Issue
Block a user