diff --git a/src/VirtoCommerce.ProfileExperienceApiModule.Data/Authorization/ProfileAuthorizationHandler.cs b/src/VirtoCommerce.ProfileExperienceApiModule.Data/Authorization/ProfileAuthorizationHandler.cs index c639b019..866fce1e 100644 --- a/src/VirtoCommerce.ProfileExperienceApiModule.Data/Authorization/ProfileAuthorizationHandler.cs +++ b/src/VirtoCommerce.ProfileExperienceApiModule.Data/Authorization/ProfileAuthorizationHandler.cs @@ -166,7 +166,15 @@ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext { var currentUser = await userManager.FindByIdAsync(currentUserId); result = currentContact.Organizations.Contains(inviteUserCommand.OrganizationId) && currentUser.StoreId.EqualsInvariant(inviteUserCommand.StoreId); - } + } + else if (context.Resource is LockOrganizationContactCommand lockOrganizationContact) + { + result = await HasSameOrganizationAsync(currentContact, lockOrganizationContact.UserId, userManager); + } + else if (context.Resource is UnlockOrganizationContactCommand unlockOrganizationContact) + { + result = await HasSameOrganizationAsync(currentContact, unlockOrganizationContact.UserId, userManager); + } if (result) { context.Succeed(requirement); diff --git a/src/VirtoCommerce.ProfileExperienceApiModule.Data/Commands/LockOrganizationContactCommand.cs b/src/VirtoCommerce.ProfileExperienceApiModule.Data/Commands/LockOrganizationContactCommand.cs new file mode 100644 index 00000000..bef55dee --- /dev/null +++ b/src/VirtoCommerce.ProfileExperienceApiModule.Data/Commands/LockOrganizationContactCommand.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using VirtoCommerce.ExperienceApiModule.Core.Infrastructure; +using VirtoCommerce.ProfileExperienceApiModule.Data.Aggregates.Contact; + +namespace VirtoCommerce.ProfileExperienceApiModule.Data.Commands +{ + public class LockOrganizationContactCommand : ICommand + { + public string UserId { get; set; } + } +} diff --git a/src/VirtoCommerce.ProfileExperienceApiModule.Data/Commands/LockOrganizationContactCommandHandler.cs b/src/VirtoCommerce.ProfileExperienceApiModule.Data/Commands/LockOrganizationContactCommandHandler.cs new file mode 100644 index 00000000..55e95040 --- /dev/null +++ b/src/VirtoCommerce.ProfileExperienceApiModule.Data/Commands/LockOrganizationContactCommandHandler.cs @@ -0,0 +1,28 @@ +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using VirtoCommerce.ProfileExperienceApiModule.Data.Aggregates.Contact; + +namespace VirtoCommerce.ProfileExperienceApiModule.Data.Commands +{ + public class LockOrganizationContactCommandHandler : IRequestHandler + { + private readonly IContactAggregateRepository _contactAggregateRepository; + + public LockOrganizationContactCommandHandler(IContactAggregateRepository contactAggregateRepository) + { + _contactAggregateRepository = contactAggregateRepository; + } + + public async Task Handle(LockOrganizationContactCommand request, CancellationToken cancellationToken) + { + var contactAggregate = await _contactAggregateRepository.GetMemberAggregateRootByIdAsync(request.UserId); + + contactAggregate.Contact.Status = ModuleConstants.ContactStatuses.Locked; + + await _contactAggregateRepository.SaveAsync(contactAggregate); + + return contactAggregate; + } + } +} diff --git a/src/VirtoCommerce.ProfileExperienceApiModule.Data/Commands/RegisterRequestCommandHandler.cs b/src/VirtoCommerce.ProfileExperienceApiModule.Data/Commands/RegisterRequestCommandHandler.cs index 16c03e29..a41477c3 100644 --- a/src/VirtoCommerce.ProfileExperienceApiModule.Data/Commands/RegisterRequestCommandHandler.cs +++ b/src/VirtoCommerce.ProfileExperienceApiModule.Data/Commands/RegisterRequestCommandHandler.cs @@ -7,7 +7,7 @@ using FluentValidation.Results; using MediatR; using Microsoft.AspNetCore.Identity; -using VirtoCommerce.CustomerModule.Core; +using CustomerCore = VirtoCommerce.CustomerModule.Core; using VirtoCommerce.CustomerModule.Core.Model; using VirtoCommerce.CustomerModule.Core.Services; using VirtoCommerce.ExperienceApiModule.Core.Models; @@ -145,7 +145,7 @@ private async Task ProcessRequestAsync(RegisterReque await SetDynamicPropertiesAsync(request.Organization.DynamicProperties, organization); var organizationStatus = store .Settings - .GetSettingValue(ModuleConstants.Settings.General.OrganizationDefaultStatus.Name, null); + .GetSettingValue(CustomerCore.ModuleConstants.Settings.General.OrganizationDefaultStatus.Name, null); organization.CreatedBy = Creator; organization.Status = organizationStatus; organization.OwnerId = contact.Id; @@ -157,7 +157,7 @@ private async Task ProcessRequestAsync(RegisterReque } var contactStatus = store.Settings - .GetSettingValue(ModuleConstants.Settings.General.ContactDefaultStatus.Name, null); + .GetSettingValue(CustomerCore.ModuleConstants.Settings.General.ContactDefaultStatus.Name, null); contact.Status = contactStatus; contact.CreatedBy = Creator; diff --git a/src/VirtoCommerce.ProfileExperienceApiModule.Data/Commands/UnlockOrganizationContactCommand.cs b/src/VirtoCommerce.ProfileExperienceApiModule.Data/Commands/UnlockOrganizationContactCommand.cs new file mode 100644 index 00000000..48b20111 --- /dev/null +++ b/src/VirtoCommerce.ProfileExperienceApiModule.Data/Commands/UnlockOrganizationContactCommand.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using VirtoCommerce.ExperienceApiModule.Core.Infrastructure; +using VirtoCommerce.ProfileExperienceApiModule.Data.Aggregates.Contact; + +namespace VirtoCommerce.ProfileExperienceApiModule.Data.Commands +{ + public class UnlockOrganizationContactCommand : ICommand + { + public string UserId { get; set; } + } +} diff --git a/src/VirtoCommerce.ProfileExperienceApiModule.Data/Commands/UnlockOrganizationContactCommandHandler.cs b/src/VirtoCommerce.ProfileExperienceApiModule.Data/Commands/UnlockOrganizationContactCommandHandler.cs new file mode 100644 index 00000000..18c74b23 --- /dev/null +++ b/src/VirtoCommerce.ProfileExperienceApiModule.Data/Commands/UnlockOrganizationContactCommandHandler.cs @@ -0,0 +1,28 @@ +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using VirtoCommerce.ProfileExperienceApiModule.Data.Aggregates.Contact; + +namespace VirtoCommerce.ProfileExperienceApiModule.Data.Commands +{ + public class UnlockOrganizationContactCommandHandler : IRequestHandler + { + private readonly IContactAggregateRepository _contactAggregateRepository; + + public UnlockOrganizationContactCommandHandler(IContactAggregateRepository contactAggregateRepository) + { + _contactAggregateRepository = contactAggregateRepository; + } + + public async Task Handle(UnlockOrganizationContactCommand request, CancellationToken cancellationToken) + { + var contactAggregate = await _contactAggregateRepository.GetMemberAggregateRootByIdAsync(request.UserId); + + contactAggregate.Contact.Status = ModuleConstants.ContactStatuses.Approved; + + await _contactAggregateRepository.SaveAsync(contactAggregate); + + return contactAggregate; + } + } +} diff --git a/src/VirtoCommerce.ProfileExperienceApiModule.Data/ModuleConstants.cs b/src/VirtoCommerce.ProfileExperienceApiModule.Data/ModuleConstants.cs new file mode 100644 index 00000000..2748deb5 --- /dev/null +++ b/src/VirtoCommerce.ProfileExperienceApiModule.Data/ModuleConstants.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VirtoCommerce.ProfileExperienceApiModule.Data +{ + public static class ModuleConstants + { + public static class Security + { + public static class Permissions + { + public const string MyOrganizationEdit = "xapi:my_organization:edit"; + + public static string[] AllPermissions { get; } = { MyOrganizationEdit }; + } + } + + public static class ContactStatuses + { + public const string Locked = "Locked"; + public const string Approved = "Approved"; + } + } +} diff --git a/src/VirtoCommerce.ProfileExperienceApiModule.Data/Schemas/InputLockContactType.cs b/src/VirtoCommerce.ProfileExperienceApiModule.Data/Schemas/InputLockContactType.cs new file mode 100644 index 00000000..89d32ff4 --- /dev/null +++ b/src/VirtoCommerce.ProfileExperienceApiModule.Data/Schemas/InputLockContactType.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using GraphQL.Types; + +namespace VirtoCommerce.ProfileExperienceApiModule.Data.Schemas +{ + public class InputLockUnlockOrganizationContactType : InputObjectGraphType + { + public InputLockUnlockOrganizationContactType() + { + Field("UserId"); + } + } +} diff --git a/src/VirtoCommerce.ProfileExperienceApiModule.Data/Schemas/ProfileSchema.cs b/src/VirtoCommerce.ProfileExperienceApiModule.Data/Schemas/ProfileSchema.cs index 9190215f..59278678 100644 --- a/src/VirtoCommerce.ProfileExperienceApiModule.Data/Schemas/ProfileSchema.cs +++ b/src/VirtoCommerce.ProfileExperienceApiModule.Data/Schemas/ProfileSchema.cs @@ -9,8 +9,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; -using VirtoCommerce.CustomerModule.Core.Model; -using VirtoCommerce.CustomerModule.Core.Services; using VirtoCommerce.ExperienceApiModule.Core.Extensions; using VirtoCommerce.ExperienceApiModule.Core.Helpers; using VirtoCommerce.ExperienceApiModule.Core.Infrastructure; @@ -40,7 +38,6 @@ public class ProfileSchema : ISchemaBuilder private readonly IAuthorizationService _authorizationService; private readonly Func> _signInManagerFactory; private readonly IMemberAggregateFactory _factory; - private readonly IMemberService _memberService; private readonly ILogger _logger; public ProfileSchema( @@ -48,14 +45,12 @@ public ProfileSchema( IAuthorizationService authorizationService, Func> signInManagerFactory, IMemberAggregateFactory factory, - IMemberService memberService, ILogger logger) { _mediator = mediator; _authorizationService = authorizationService; _signInManagerFactory = signInManagerFactory; _factory = factory; - _memberService = memberService; _logger = logger; } @@ -133,8 +128,8 @@ totalCount items {id firstName} return new PagedConnection(response.Results.Select(x => _factory.Create(x)), query.Skip, query.Take, response.TotalCount); }); - schema.Query.AddField(organizationsConnectionBuilder.FieldType); - + schema.Query.AddField(organizationsConnectionBuilder.FieldType); + #region contact query /// #pragma warning disable S125 // Sections of code should not be commented out @@ -145,10 +140,10 @@ firstName memberType organizationIds organizations { id businessCategory descrip addresses { line1 phone } } } - */ + */ #pragma warning restore S125 // Sections of code should not be commented out /// - + #endregion schema.Query.AddField(new FieldType { @@ -286,10 +281,10 @@ firstName memberType organizationIds organizations { id businessCategory descrip return result; }) - }); - + }); + #region updateAddressMutation - + /// sample code for updating addresses: #pragma warning disable S125 // Sections of code should not be commented out /* @@ -308,9 +303,9 @@ mutation updateMemberAddresses($command: UpdateMemberAddressesCommand!){ }] } } - */ + */ #pragma warning restore S125 // Sections of code should not be commented out - + #endregion _ = schema.Mutation.AddField(FieldBuilder.Create(GraphTypeExtenstionHelper.GetActualType()) .Name("updateMemberAddresses") @@ -343,7 +338,8 @@ mutation updateMemberAddresses($command: UpdateMemberAddressesCommand!){ { var type = GenericTypeHelper.GetActualType(); var command = (UpdateOrganizationCommand)context.GetArgument(type, _commandName); - await CheckAuthAsync(context.GetCurrentUserId(), command, CustomerModule.Core.ModuleConstants.Security.Permissions.Update); + await CheckAuthAsync(context.GetCurrentUserId(), command, + ModuleConstants.Security.Permissions.MyOrganizationEdit); return await _mediator.Send(command); }) .FieldType); @@ -463,7 +459,32 @@ mutation updateMemberAddresses($command: UpdateMemberAddressesCommand!){ return await _mediator.Send(command); }) - .FieldType); + .FieldType); + + + _ = schema.Mutation.AddField(FieldBuilder.Create(GraphTypeExtenstionHelper.GetActualType()) + .Name("lockOrganizationContact") + .Argument(GraphTypeExtenstionHelper.GetActualComplexType>(), _commandName) + .ResolveAsync(async context => + { + var type = GenericTypeHelper.GetActualType(); + var command = (LockOrganizationContactCommand)context.GetArgument(type, _commandName); + await CheckAuthAsync(context.GetCurrentUserId(), command, ModuleConstants.Security.Permissions.MyOrganizationEdit); + return await _mediator.Send(command); + }) + .FieldType); + + _ = schema.Mutation.AddField(FieldBuilder.Create(GraphTypeExtenstionHelper.GetActualType()) + .Name("unlockOrganizationContact") + .Argument(GraphTypeExtenstionHelper.GetActualComplexType>(), _commandName) + .ResolveAsync(async context => + { + var type = GenericTypeHelper.GetActualType(); + var command = (UnlockOrganizationContactCommand)context.GetArgument(type, _commandName); + await CheckAuthAsync(context.GetCurrentUserId(), command, ModuleConstants.Security.Permissions.MyOrganizationEdit); + return await _mediator.Send(command); + }) + .FieldType); // Security API fields @@ -734,12 +755,13 @@ private async Task CheckAuthAsync(string userId, object resource, params string[ { var user = await signInManager.UserManager.FindByIdAsync(userId) ?? new ApplicationUser { - Id = userId, UserName = ExperienceApiModule.Core.AnonymousUser.UserName, + Id = userId, + UserName = ExperienceApiModule.Core.AnonymousUser.UserName, }; var userPrincipal = await signInManager.CreateUserPrincipalAsync(user); - if (!await CanExecuteWithoutPermissionAsync(user, resource) && !permissions.IsNullOrEmpty()) + if (!CanExecuteWithoutPermissionAsync(user, resource) && !permissions.IsNullOrEmpty()) { foreach (var permission in permissions) { @@ -773,7 +795,7 @@ private async Task CheckAuthAsync(string userId, object resource, params string[ } } - private async Task CanExecuteWithoutPermissionAsync(ApplicationUser user, object resource) + private bool CanExecuteWithoutPermissionAsync(ApplicationUser user, object resource) { var result = false; @@ -781,11 +803,6 @@ private async Task CanExecuteWithoutPermissionAsync(ApplicationUser user, { result = updateMemberDynamicPropertiesCommand.MemberId == user.MemberId; } - else if (resource is UpdateOrganizationCommand updateOrganizationCommand && !string.IsNullOrEmpty(user.MemberId)) - { - var member = await _memberService.GetByIdAsync(user.MemberId) as Contact; - result = member?.Organizations.Any(x => x.EqualsInvariant(updateOrganizationCommand.Id)) ?? false; - } return result; } diff --git a/src/VirtoCommerce.ProfileExperienceApiModule.Web/Module.cs b/src/VirtoCommerce.ProfileExperienceApiModule.Web/Module.cs index 96296c75..a1dac422 100644 --- a/src/VirtoCommerce.ProfileExperienceApiModule.Web/Module.cs +++ b/src/VirtoCommerce.ProfileExperienceApiModule.Web/Module.cs @@ -1,3 +1,4 @@ +using System.Linq; using AutoMapper; using GraphQL.Server; using MediatR; @@ -10,6 +11,7 @@ using VirtoCommerce.ExperienceApiModule.Core.Pipelines; using VirtoCommerce.MarketingModule.Core.Model.Promotions; using VirtoCommerce.Platform.Core.Modularity; +using VirtoCommerce.Platform.Core.Security; using VirtoCommerce.PricingModule.Core.Model; using VirtoCommerce.ProfileExperienceApiModule.Data; using VirtoCommerce.ProfileExperienceApiModule.Data.Aggregates; @@ -66,12 +68,15 @@ public void Initialize(IServiceCollection serviceCollection) } public void PostInitialize(IApplicationBuilder appBuilder) - { + { + var permissionsProvider = appBuilder.ApplicationServices.GetRequiredService(); + permissionsProvider.RegisterPermissions(ModuleConstants.Security.Permissions.AllPermissions.Select(x => + new Permission() { GroupName = "Xapi", Name = x }).ToArray()); } public void Uninstall() { - // do nothing in here + // Nothing to do there } } } diff --git a/tests/VirtoCommerce.ProfileExperienceApiModule.Tests/VirtoCommerce.ProfileExperienceApiModule.Tests.csproj b/tests/VirtoCommerce.ProfileExperienceApiModule.Tests/VirtoCommerce.ProfileExperienceApiModule.Tests.csproj index 0741f4e4..421d000b 100644 --- a/tests/VirtoCommerce.ProfileExperienceApiModule.Tests/VirtoCommerce.ProfileExperienceApiModule.Tests.csproj +++ b/tests/VirtoCommerce.ProfileExperienceApiModule.Tests/VirtoCommerce.ProfileExperienceApiModule.Tests.csproj @@ -15,7 +15,6 @@ -