-
Notifications
You must be signed in to change notification settings - Fork 62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add store owner #81
Add store owner #81
Conversation
add product tag plugin and store approval plugin. add store owner and store owner permission check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add product tag plugin and open store approval plugin.
Hi @RayMMond, I did not see any new plugin in this PR. I noticed that the code relate to the ProductTag is added to the Products module directly, what if I do not install the ProductTag plugin?
...p.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderAppService.cs
Show resolved
Hide resolved
await AuthorizationService.CheckAsync(OrdersPermissions.Orders.Manage); | ||
|
||
// Todo: Check if current user is an admin of the store. | ||
await AuthorizationService.CheckStoreOwnerAsync(order.StoreId, OrdersPermissions.Orders.Manage); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A store side AuthorizationRequirement may be better, we can define a StoreOwnerAuthorizationRequirement
to check if the current user is a owner of the store (it does not care about the permission requirement). Please refer to the practice of https://github.com/EasyAbp/FileManagement/blob/master/src/EasyAbp.FileManagement.Application/EasyAbp/FileManagement/Files/FileOperationAuthorizationHandler.cs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see an AuthorizationHandler below, but the idea of this comment still has reference significance. (keep the PR lightweight since a huge PR is really hard to review)
} | ||
else | ||
{ | ||
await AuthorizationService.CheckAsync(PaymentsPermissions.Payments.Manage); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should also have PaymentsPermissions.Payments.CrossStore
permission. I think there may be many similar problems, I did not check all the code in this PR, please check it, thank you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👌
var userId = context.User?.FindUserId(); | ||
if (userId.HasValue) | ||
{ | ||
var isStoreOwner = await _storeOwnerAppService.IsStoreOwnerAsync(requirement.StoreId, userId.Value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should use IStoreOwenerStore.IsOwnerAsync()
public class StoreOwnerAppService : ReadOnlyAppService<StoreOwner, StoreOwnerDto, Guid, GetStoreOwnerListDto>, | ||
IStoreOwnerAppService | ||
{ | ||
protected override string GetListPolicyName { get; set; } = StoresPermissions.Stores.Default; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- What about the "get" policy?
- Should people who have
StoresPermissions.Stores.Default
be allowed to get other stores' and users' StoreOwner?
|
||
MapToEntity(input, entity); | ||
|
||
entity = await _storeManager.UpdateAsync(entity, input.OwnerIds); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The best practice of domain services is only creating/updating data of entities and use the repositories to persist the data in application services.
StoreOwners = | ||
(await _userAppService.GetListAsync(new GetIdentityUsersInput | ||
{ MaxResultCount = LimitedResultRequestDto.MaxMaxResultCount })).Items | ||
.Select(x => new SelectListItem(x.UserName, x.Id.ToString())).ToList(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if there are more than 1000 users?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gdlcf88 I just follow the implementation of product categories, see this:
Lines 56 to 64 in ac750c4
ProductTypes = | |
(await _productTypeAppService.GetListAsync(new PagedAndSortedResultRequestDto | |
{MaxResultCount = LimitedResultRequestDto.MaxMaxResultCount})).Items | |
.Select(dto => new SelectListItem(dto.DisplayName, dto.Id.ToString())).ToList(); | |
Categories = | |
(await _categoryAppService.GetListAsync(new GetCategoryListDto | |
{MaxResultCount = LimitedResultRequestDto.MaxMaxResultCount}))?.Items | |
.Select(dto => new SelectListItem(dto.DisplayName, dto.Id.ToString())).ToList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The count of ProductType and Category is expected to be less than 1000, but the count of User is easy to be more than 1000.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the best practice should be selecting from a modal with filtered user list, but I have no clue how to implement filtered list in abp, do you have any examples?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe using UserId directly before we have a better way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed.
return store; | ||
} | ||
|
||
protected virtual async Task UpdateStoreOwnersAsync(Guid storeId, IEnumerable<Guid> ownerIds) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be easier to manage if the StoreOwner has its own application service and management pages (remember it is an aggregate root). Then the StoreManager
can be removed.
protected virtual async Task CheckMultiStorePolicyAsync(Guid storeId, string policyName) | ||
{ | ||
if (!await AuthorizationService.IsStoreOwnerGrantedAsync(storeId, policyName)) | ||
{ | ||
await CheckPolicyAsync(CrossStorePolicyName); | ||
await CheckPolicyAsync(policyName); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The storeId
can be nullable and check the cross store permission if it is null.
if (input.StoreId.HasValue) | ||
{ | ||
// Todo: Check if current user is an admin of the store. | ||
await CheckMultiStorePolicyAsync(input.StoreId.Value, GetListPolicyName); | ||
} | ||
else | ||
{ | ||
await AuthorizationService.CheckAsync(OrdersPermissions.Orders.CrossStore); | ||
throw new AbpAuthorizationException("Authorization failed! Given policy has not granted: " + | ||
GetListPolicyName); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use input.StoreId
directly as the param of CheckMultiStorePolicyAsync()
method.
...p.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderAppService.cs
Show resolved
Hide resolved
...p.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderAppService.cs
Show resolved
Hide resolved
{ | ||
var isStoreOwner = await _storeOwnerStore.IsStoreOwnerAsync(requirement.StoreId, userId.Value); | ||
|
||
if (isStoreOwner) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the current user is a cross store manager?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Every domain can have their own cross-store permission definition, so it should be implemented separately. And I have created base classes which contains store-owner permission and cross-store permission check, here:https://github.com/RayMMond/EShop/blob/29080d62532c90d330077e50b85ca7d7031fc174/modules/EasyAbp.EShop.Stores/src/EasyAbp.EShop.Stores.Application/EasyAbp/EShop/Stores/Stores/MultiStoreAbstractKeyCrudAppService.cs
context.Succeed(requirement); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe set failed result here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A requirement can have multiple AuthorizationHandler, and module user may register their own Handers, so I think we should not set failed here.
{ | ||
public static class StoreOwnerAuthorizationExtensions | ||
{ | ||
public static Task<bool> IsStoreOwnerGrantedAsync(this IAuthorizationService authorizationService, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is store owner? Does it include cross store managers?
b.ConfigureByConvention(); | ||
/* Configure more properties here */ | ||
|
||
b.HasIndex(x => new {x.StoreId, x.OwnerId}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be new {x.OwnerId, x.StoreId}
.
And it is better to change all the usage of x.StoreId == storeId && x.OwnerId == ownerId
to x.OwnerId == ownerId && x.StoreId == storeId
.
<DependentUpon>Index.cshtml</DependentUpon> | ||
</Compile> | ||
</ItemGroup> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused items in ItemGroup should be cleaned.
|
||
public virtual Guid StoreId { get; protected set; } | ||
|
||
public virtual Guid OwnerId { get; protected set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OwnerUserId
is a more accurate name.
<ProjectReference Include="..\..\..\EasyAbp.EShop.Products\src\EasyAbp.EShop.Products.Application.Contracts\EasyAbp.EShop.Products.Application.Contracts.csproj" /> | ||
<ProjectReference Include="..\..\..\EasyAbp.EShop.Stores\src\EasyAbp.EShop.Stores.Application\EasyAbp.EShop.Stores.Application.csproj" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should not do that since the entities in Stores module can be distributed.
…ication.Shared 2. minor refactoring
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should remove the related cache after a user's store ownership is changed.
...res.Application.Shared/EasyAbp/EShop/Stores/Permissions/StoreOwnerAuthorizationExtensions.cs
Outdated
Show resolved
Hide resolved
...res.Application.Shared/EasyAbp/EShop/Stores/Permissions/StoreOwnerAuthorizationExtensions.cs
Outdated
Show resolved
Hide resolved
...res.Application.Shared/EasyAbp/EShop/Stores/Permissions/StoreOwnerAuthorizationExtensions.cs
Outdated
Show resolved
Hide resolved
...Stores.Application.Shared/EasyAbp/EShop/Stores/Stores/MultiStoreAbstractKeyCrudAppService.cs
Outdated
Show resolved
Hide resolved
...res.Application.Shared/EasyAbp/EShop/Stores/Permissions/StoreOwnerAuthorizationExtensions.cs
Outdated
Show resolved
Hide resolved
...res.Application.Shared/EasyAbp/EShop/Stores/Permissions/StoreOwnerAuthorizationExtensions.cs
Outdated
Show resolved
Hide resolved
...Products.Application/EasyAbp/EShop/Products/ProductInventories/ProductInventoryAppService.cs
Outdated
Show resolved
Hide resolved
...es.Application.Shared/EasyAbp/EShop/Stores/Stores/MultiStoreAbstractKeyReadOnlyAppService.cs
Outdated
Show resolved
Hide resolved
{ | ||
_repository = repository; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, please remove the IStoreOwnerStore injection.
remove the related cache after a user's store ownership is changed.
...res.Application.Shared/EasyAbp/EShop/Stores/Permissions/StoreOwnerAuthorizationExtensions.cs
Outdated
Show resolved
Hide resolved
...Stores.Application.Shared/EasyAbp/EShop/Stores/Stores/MultiStoreAbstractKeyCrudAppService.cs
Outdated
Show resolved
Hide resolved
...es.Application.Shared/EasyAbp/EShop/Stores/Stores/MultiStoreAbstractKeyReadOnlyAppService.cs
Outdated
Show resolved
Hide resolved
...Products.Application/EasyAbp/EShop/Products/ProductInventories/ProductInventoryAppService.cs
Outdated
Show resolved
Hide resolved
using Volo.Abp; | ||
using Volo.Abp.Application.Dtos; | ||
using Volo.Abp.Application.Services; | ||
using Volo.Abp.Domain.Entities; | ||
|
||
namespace EasyAbp.EShop.Products.Products | ||
{ | ||
public class ProductAppService : CrudAppService<Product, ProductDto, Guid, GetProductListDto, CreateUpdateProductDto, CreateUpdateProductDto>, | ||
public class ProductAppService : CrudAppService<Product, ProductDto, Guid, GetProductListDto, CreateUpdateProductDto |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can ProductAppService
inherit MultiStoreCrudAppService
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Product don't inherit IMultiStore, so answer is no.
Thank you @RayMMond for your great work. |
This PR resolve #68 and :