From 11b8663ef1b7e4cf1ceab344f7e0e8db279aed51 Mon Sep 17 00:00:00 2001 From: James Gunn Date: Mon, 23 Oct 2023 14:47:49 +0100 Subject: [PATCH] Add warning to Edit User page for users missing CRM account (#771) --- .../Dqt/Models/GeneratedCode.cs | 21 ++++++++++++++++ ...UserByAzureActiveDirectoryObjectIdQuery.cs | 5 ++++ ...erByAzureActiveDirectoryObjectIdHandler.cs | 22 ++++++++++++++++ .../Security/AssignUserInfoOnSignIn.cs | 1 + .../Pages/Users/AddUser/Confirm.cshtml.cs | 4 +-- .../Pages/Users/AddUser/Index.cshtml.cs | 4 +-- .../Pages/Users/EditUser.cshtml | 9 +++++++ .../Pages/Users/EditUser.cshtml.cs | 25 +++++++++++++++++-- .../{UserService.cs => AadUserService.cs} | 4 +-- .../{IUserService.cs => IAadUserService.cs} | 2 +- .../ServiceCollectionExtensions.cs | 2 +- .../TeachingRecordSystem.SupportUi/Usings.cs | 1 + .../HostFixture.cs | 2 +- .../TestBase.cs | 2 +- .../TestScopedServices.cs | 2 +- crm_attributes.json | 1 + tools/coretools/CrmSvcUtil.exe.config | 2 +- 17 files changed, 95 insertions(+), 14 deletions(-) create mode 100644 TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetSystemUserByAzureActiveDirectoryObjectIdQuery.cs create mode 100644 TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetSystemUserByAzureActiveDirectoryObjectIdHandler.cs rename TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/{UserService.cs => AadUserService.cs} (91%) rename TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/{IUserService.cs => IAadUserService.cs} (83%) diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Models/GeneratedCode.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Models/GeneratedCode.cs index ee9017cb2..ca06b9b28 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Models/GeneratedCode.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Models/GeneratedCode.cs @@ -16220,6 +16220,7 @@ public static class Fields { public const string AzureActiveDirectoryObjectId = "azureactivedirectoryobjectid"; public const string FirstName = "firstname"; + public const string IsDisabled = "isdisabled"; public const string LastName = "lastname"; public const string annotation_owning_user = "annotation_owning_user"; public const string contact_owning_user = "contact_owning_user"; @@ -16456,6 +16457,26 @@ public string FirstName } } + /// + /// Information about whether the user is enabled. + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("isdisabled")] + public System.Nullable IsDisabled + { + [System.Diagnostics.DebuggerNonUserCode()] + get + { + return this.GetAttributeValue>("isdisabled"); + } + [System.Diagnostics.DebuggerNonUserCode()] + set + { + this.OnPropertyChanging("IsDisabled"); + this.SetAttributeValue("isdisabled", value); + this.OnPropertyChanged("IsDisabled"); + } + } + /// /// Last name of the user. /// diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetSystemUserByAzureActiveDirectoryObjectIdQuery.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetSystemUserByAzureActiveDirectoryObjectIdQuery.cs new file mode 100644 index 000000000..da79cf868 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetSystemUserByAzureActiveDirectoryObjectIdQuery.cs @@ -0,0 +1,5 @@ +using Microsoft.Xrm.Sdk.Query; + +namespace TeachingRecordSystem.Core.Dqt.Queries; + +public record GetSystemUserByAzureActiveDirectoryObjectIdQuery(string AzureActiveDirectoryObjectId, ColumnSet ColumnSet) : ICrmQuery; diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetSystemUserByAzureActiveDirectoryObjectIdHandler.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetSystemUserByAzureActiveDirectoryObjectIdHandler.cs new file mode 100644 index 000000000..c07d6840b --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetSystemUserByAzureActiveDirectoryObjectIdHandler.cs @@ -0,0 +1,22 @@ +using Microsoft.PowerPlatform.Dataverse.Client; +using Microsoft.Xrm.Sdk.Query; +using TeachingRecordSystem.Core.Dqt.Queries; + +namespace TeachingRecordSystem.Core.Dqt.QueryHandlers; + +public class GetSystemUserByAzureActiveDirectoryObjectIdHandler : ICrmQueryHandler +{ + public async Task Execute(GetSystemUserByAzureActiveDirectoryObjectIdQuery query, IOrganizationServiceAsync organizationService) + { + var queryByAttribute = new QueryByAttribute() + { + EntityName = SystemUser.EntityLogicalName, + ColumnSet = query.ColumnSet + }; + queryByAttribute.AddAttributeValue(SystemUser.Fields.AzureActiveDirectoryObjectId, query.AzureActiveDirectoryObjectId); + + var response = await organizationService.RetrieveMultipleAsync(queryByAttribute); + + return response.Entities.SingleOrDefault()?.ToEntity(); + } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Infrastructure/Security/AssignUserInfoOnSignIn.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Infrastructure/Security/AssignUserInfoOnSignIn.cs index 5afe87d69..3ef647b07 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Infrastructure/Security/AssignUserInfoOnSignIn.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Infrastructure/Security/AssignUserInfoOnSignIn.cs @@ -86,6 +86,7 @@ public void Configure(string? name, OpenIdConnectOptions options) var request = new QueryByAttribute(SystemUser.EntityLogicalName); request.AddAttributeValue(SystemUser.Fields.AzureActiveDirectoryObjectId, new Guid(aadUserId)); + request.AddAttributeValue(SystemUser.Fields.IsDisabled, false); var response = await serviceClient.RetrieveMultipleAsync(request); diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/AddUser/Confirm.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/AddUser/Confirm.cshtml.cs index 4d745bd7d..f5c9b583b 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/AddUser/Confirm.cshtml.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/AddUser/Confirm.cshtml.cs @@ -11,14 +11,14 @@ namespace TeachingRecordSystem.SupportUi.Pages.Users.AddUser; public class ConfirmModel : PageModel { private readonly TrsDbContext _dbContext; - private readonly IUserService _userService; + private readonly IAadUserService _userService; private readonly IClock _clock; private readonly TrsLinkGenerator _linkGenerator; private Services.AzureActiveDirectory.User? _user; public ConfirmModel( TrsDbContext dbContext, - IUserService userService, + IAadUserService userService, IClock clock, TrsLinkGenerator linkGenerator) { diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/AddUser/Index.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/AddUser/Index.cshtml.cs index 477e6b218..96778c32c 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/AddUser/Index.cshtml.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/AddUser/Index.cshtml.cs @@ -9,10 +9,10 @@ namespace TeachingRecordSystem.SupportUi.Pages.Users.AddUser; [Authorize(Roles = UserRoles.Administrator)] public class IndexModel : PageModel { - private readonly IUserService _userService; + private readonly IAadUserService _userService; private readonly TrsLinkGenerator _trsLinkGenerator; - public IndexModel(IUserService userService, TrsLinkGenerator trsLinkGenerator) + public IndexModel(IAadUserService userService, TrsLinkGenerator trsLinkGenerator) { _userService = userService; _trsLinkGenerator = trsLinkGenerator; diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/EditUser.cshtml b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/EditUser.cshtml index 91068aa81..05111a379 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/EditUser.cshtml +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/EditUser.cshtml @@ -14,6 +14,15 @@

@ViewBag.Title

+ @if (!Model.HasCrmAccount) + { + User does not have an account in CRM. + } + else if (Model.CrmAccountIsDisabled) + { + User's CRM account is disabled. + } + diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/EditUser.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/EditUser.cshtml.cs index 53f04b905..9e1c5d469 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/EditUser.cshtml.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/EditUser.cshtml.cs @@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; using TeachingRecordSystem.Core.DataStore.Postgres; +using TeachingRecordSystem.Core.Dqt.Models; +using TeachingRecordSystem.Core.Dqt.Queries; using TeachingRecordSystem.Core.Events; namespace TeachingRecordSystem.SupportUi.Pages.Users; @@ -13,16 +15,19 @@ namespace TeachingRecordSystem.SupportUi.Pages.Users; public class EditUser : PageModel { private readonly TrsDbContext _dbContext; + private readonly ICrmQueryDispatcher _crmQueryDispatcher; private readonly IClock _clock; private readonly TrsLinkGenerator _linkGenerator; private Core.DataStore.Postgres.Models.User? _user; public EditUser( TrsDbContext dbContext, + ICrmQueryDispatcher crmQueryDispatcher, IClock clock, TrsLinkGenerator linkGenerator) { _dbContext = dbContext; + _crmQueryDispatcher = crmQueryDispatcher; _clock = clock; _linkGenerator = linkGenerator; } @@ -44,13 +49,29 @@ public EditUser( public bool IsActiveUser { get; set; } - public IActionResult OnGet() + public bool HasCrmAccount { get; set; } + + public bool CrmAccountIsDisabled { get; set; } + + public async Task OnGet() { Name = _user!.Name; IsActiveUser = _user.Active; Roles = _user.Roles; - return Page(); + if (_user.AzureAdUserId is not null) + { + var crmUser = await _crmQueryDispatcher.ExecuteQuery( + new GetSystemUserByAzureActiveDirectoryObjectIdQuery( + _user.AzureAdUserId, new ColumnSet(SystemUser.Fields.IsDisabled))); + + HasCrmAccount = crmUser is not null; + CrmAccountIsDisabled = crmUser?.IsDisabled == true; + } + else + { + HasCrmAccount = false; + } } public async Task OnPost() diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/UserService.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/AadUserService.cs similarity index 91% rename from TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/UserService.cs rename to TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/AadUserService.cs index 842ce6f99..276fb5b1a 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/UserService.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/AadUserService.cs @@ -2,11 +2,11 @@ namespace TeachingRecordSystem.SupportUi.Services.AzureActiveDirectory; -public class UserService : IUserService +public class AadUserService : IAadUserService { private readonly GraphServiceClient _graphServiceClient; - public UserService(GraphServiceClient graphServiceClient) + public AadUserService(GraphServiceClient graphServiceClient) { _graphServiceClient = graphServiceClient; } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/IUserService.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/IAadUserService.cs similarity index 83% rename from TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/IUserService.cs rename to TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/IAadUserService.cs index 99ed5bd68..ae1e07fc6 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/IUserService.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/IAadUserService.cs @@ -1,6 +1,6 @@ namespace TeachingRecordSystem.SupportUi.Services.AzureActiveDirectory; -public interface IUserService +public interface IAadUserService { Task GetUserByEmail(string email); Task GetUserById(string userId); diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/ServiceCollectionExtensions.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/ServiceCollectionExtensions.cs index 1699539d7..5abb65e5f 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/ServiceCollectionExtensions.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Services/AzureActiveDirectory/ServiceCollectionExtensions.cs @@ -8,7 +8,7 @@ public static IServiceCollection AddAzureActiveDirectory( { if (!environment.IsUnitTests()) { - services.AddTransient(); + services.AddTransient(); } return services; diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Usings.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Usings.cs index 4c527dd24..ef25288eb 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Usings.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Usings.cs @@ -1,3 +1,4 @@ global using FormFlow; global using TeachingRecordSystem.Core; global using TeachingRecordSystem.Core.Dqt; +global using ColumnSet = Microsoft.Xrm.Sdk.Query.ColumnSet; diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/HostFixture.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/HostFixture.cs index bb1d05671..5d23067bf 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/HostFixture.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/HostFixture.cs @@ -68,7 +68,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) services.AddSingleton(EventObserver); services.AddTestScoped(tss => tss.Clock); services.AddTestScoped(tss => tss.DataverseAdapterMock.Object); - services.AddTestScoped(tss => tss.AzureActiveDirectoryUserServiceMock.Object); + services.AddTestScoped(tss => tss.AzureActiveDirectoryUserServiceMock.Object); services.AddSingleton(); services.AddFakeXrm(); services.AddTransient(); diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/TestBase.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/TestBase.cs index e00ec809d..9fd7cc59b 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/TestBase.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/TestBase.cs @@ -38,7 +38,7 @@ protected TestBase(HostFixture hostFixture) public TestableClock Clock => _testServices.Clock; - public Mock AzureActiveDirectoryUserServiceMock => _testServices.AzureActiveDirectoryUserServiceMock; + public Mock AzureActiveDirectoryUserServiceMock => _testServices.AzureActiveDirectoryUserServiceMock; public HttpClient HttpClient { get; } diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/TestScopedServices.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/TestScopedServices.cs index 5f2836e2b..9c15fca6b 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/TestScopedServices.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/TestScopedServices.cs @@ -31,5 +31,5 @@ public static TestScopedServices Reset() public Mock DataverseAdapterMock { get; } - public Mock AzureActiveDirectoryUserServiceMock { get; } + public Mock AzureActiveDirectoryUserServiceMock { get; } } diff --git a/crm_attributes.json b/crm_attributes.json index 2f860d03c..a98be4606 100644 --- a/crm_attributes.json +++ b/crm_attributes.json @@ -262,6 +262,7 @@ ], "systemuser": [ "azureactivedirectoryobjectid", + "isdisabled", "firstname", "lastname" ], diff --git a/tools/coretools/CrmSvcUtil.exe.config b/tools/coretools/CrmSvcUtil.exe.config index 45dc6f28b..b97e6c075 100644 --- a/tools/coretools/CrmSvcUtil.exe.config +++ b/tools/coretools/CrmSvcUtil.exe.config @@ -20,7 +20,7 @@ - +