From e5c701c071bbb324686d9262a6ee86f4cf365cc3 Mon Sep 17 00:00:00 2001 From: Jasper Hedegaard Bojsen Date: Mon, 30 Oct 2023 12:53:21 +0100 Subject: [PATCH] Bump MS Graph 4.x -> 5.x --- .../workflows/admin-service-api-deploy.yml | 2 +- .github/workflows/saas-app-deploy.yml | 2 +- .../signup-administration-deploy.yml | 2 +- .../Saas.Admin.Service.csproj | 13 -- .../admin-service-api-deploy-debug.yml | 2 +- .../Saas.Application.Web.csproj | 9 -- .../act/workflows/saas-app-deploy-debug.yml | 2 +- .../Saas.Permissions.Service.csproj | 14 -- .../Services/GraphAPIService.cs | 122 ++++++++++++------ .../Services/GraphClientFactory.cs | 9 +- ...ntityConfigurationBuilderExtensions.api.cs | 2 +- .../SaasGraphClientCredentialsProvider.cs | 26 +++- .../Saas.Identity/Saas.Identity.csproj | 3 +- src/Saas.Lib/Saas.Shared/Saas.Shared.csproj | 1 - .../Controllers/AccountSignUpController.cs | 27 ---- .../Views/Shared/_Layout.cshtml | 2 +- .../signup-administration-deploy-debug.yml | 2 +- 17 files changed, 120 insertions(+), 120 deletions(-) delete mode 100644 src/Saas.SignupAdministration/Saas.SignupAdministration.Web/Controllers/AccountSignUpController.cs diff --git a/.github/workflows/admin-service-api-deploy.yml b/.github/workflows/admin-service-api-deploy.yml index d67a89f2..b96dc319 100644 --- a/.github/workflows/admin-service-api-deploy.yml +++ b/.github/workflows/admin-service-api-deploy.yml @@ -9,7 +9,7 @@ permissions: contents: read env: - AZURE_WEBAPP_NAME: 'admin-api-asdk-test-5yb3' # set this to your application's name + AZURE_WEBAPP_NAME: 'admin-api-asdk-test-83sx' # set this to your application's name AZURE_WEBAPP_PACKAGE_PATH: . # set this to the path to your web app project, defaults to the repository root DOTNET_VERSION: 7.x.x PROJECT_DIR: ./src/Saas.Admin/Saas.Admin.Service diff --git a/.github/workflows/saas-app-deploy.yml b/.github/workflows/saas-app-deploy.yml index 673da659..1e09c7ef 100644 --- a/.github/workflows/saas-app-deploy.yml +++ b/.github/workflows/saas-app-deploy.yml @@ -9,7 +9,7 @@ permissions: contents: read env: - AZURE_WEBAPP_NAME: 'saas-app-asdk-test-aiuq' # set this to your application's name + AZURE_WEBAPP_NAME: 'saas-app-asdk-test-83sx' # set this to your application's name AZURE_WEBAPP_PACKAGE_PATH: . # set this to the path to your web app project, defaults to the repository root DOTNET_VERSION: 7.x.x PROJECT_DIR: ./src/Saas.Application/Saas.Application.Web diff --git a/.github/workflows/signup-administration-deploy.yml b/.github/workflows/signup-administration-deploy.yml index 0e7ab4bc..652aa0ae 100644 --- a/.github/workflows/signup-administration-deploy.yml +++ b/.github/workflows/signup-administration-deploy.yml @@ -9,7 +9,7 @@ permissions: contents: read env: - AZURE_WEBAPP_NAME: 'signupadmin-app-asdk-test-aiuq' # set this to your application's name + AZURE_WEBAPP_NAME: 'signupadmin-app-asdk-test-83sx' # set this to your application's name AZURE_WEBAPP_PACKAGE_PATH: . # set this to the path to your web app project, defaults to the repository root DOTNET_VERSION: 7.x.x PROJECT_DIR: ./src/Saas.SignupAdministration/Saas.SignupAdministration.Web diff --git a/src/Saas.Admin/Saas.Admin.Service/Saas.Admin.Service.csproj b/src/Saas.Admin/Saas.Admin.Service/Saas.Admin.Service.csproj index 7c3e32ac..fda2d956 100644 --- a/src/Saas.Admin/Saas.Admin.Service/Saas.Admin.Service.csproj +++ b/src/Saas.Admin/Saas.Admin.Service/Saas.Admin.Service.csproj @@ -12,23 +12,10 @@ - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - diff --git a/src/Saas.Admin/deployment/act/workflows/admin-service-api-deploy-debug.yml b/src/Saas.Admin/deployment/act/workflows/admin-service-api-deploy-debug.yml index 8cd084a9..25b875d6 100644 --- a/src/Saas.Admin/deployment/act/workflows/admin-service-api-deploy-debug.yml +++ b/src/Saas.Admin/deployment/act/workflows/admin-service-api-deploy-debug.yml @@ -10,7 +10,7 @@ permissions: env: APP_NAME: admin-api - AZURE_WEBAPP_NAME: admin-api-asdk-test-aiuq # set this to your application's name + AZURE_WEBAPP_NAME: admin-api-asdk-test-83sx # set this to your application's name AZURE_WEBAPP_PACKAGE_PATH: . # set this to the path to your web app project, defaults to the repository root DOTNET_VERSION: 7.x.x PROJECT_DIR: ./src/Saas.Admin/Saas.Admin.Service diff --git a/src/Saas.Application/Saas.Application.Web/Saas.Application.Web.csproj b/src/Saas.Application/Saas.Application.Web/Saas.Application.Web.csproj index b797a9f9..e9d4abe0 100644 --- a/src/Saas.Application/Saas.Application.Web/Saas.Application.Web.csproj +++ b/src/Saas.Application/Saas.Application.Web/Saas.Application.Web.csproj @@ -9,17 +9,8 @@ - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - diff --git a/src/Saas.Application/deployment/act/workflows/saas-app-deploy-debug.yml b/src/Saas.Application/deployment/act/workflows/saas-app-deploy-debug.yml index 423a624c..9b8df7fb 100644 --- a/src/Saas.Application/deployment/act/workflows/saas-app-deploy-debug.yml +++ b/src/Saas.Application/deployment/act/workflows/saas-app-deploy-debug.yml @@ -10,7 +10,7 @@ permissions: env: APP_NAME: saas-app - AZURE_WEBAPP_NAME: saas-app-asdk-test-aiuq # set this to your application's name + AZURE_WEBAPP_NAME: saas-app-asdk-test-83sx # set this to your application's name AZURE_WEBAPP_PACKAGE_PATH: . # set this to the path to your web app project, defaults to the repository root DOTNET_VERSION: 7.x.x PROJECT_DIR: ./src/Saas.Application/Saas.Application.Web diff --git a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service_v1.1/Saas.Permissions.Service.csproj b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service_v1.1/Saas.Permissions.Service.csproj index 0f31a096..41b82966 100644 --- a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service_v1.1/Saas.Permissions.Service.csproj +++ b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service_v1.1/Saas.Permissions.Service.csproj @@ -16,24 +16,10 @@ - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - diff --git a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service_v1.1/Services/GraphAPIService.cs b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service_v1.1/Services/GraphAPIService.cs index 382627ff..e7305733 100644 --- a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service_v1.1/Services/GraphAPIService.cs +++ b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service_v1.1/Services/GraphAPIService.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Options; using Microsoft.Graph; +using Microsoft.Graph.Models; using Saas.Permissions.Service.Exceptions; using Saas.Permissions.Service.Interfaces; using Saas.Permissions.Service.Models; @@ -57,22 +58,24 @@ public class GraphAPIService : IGraphAPIService try { var graphUsers = await _graphServiceClient.Users - .Request() - .Filter($"identities/any(id: id/issuer eq '{_permissionOptions.Domain}' and id/issuerAssignedId eq '{userEmail}')") - .Select("id, identitied, displayName") - .GetAsync(); + .GetAsync(requestionConfiguration => + { + requestionConfiguration.QueryParameters.Filter = $"identities/any(id: id/issuer eq '{_permissionOptions.Domain}' and id/issuerAssignedId eq '{userEmail}')"; + requestionConfiguration.QueryParameters.Select = new string[] { "id, identities, displayName" }; + }); - if (graphUsers.Count > 1) + if (graphUsers?.Value?.Count > 1) { throw new UserNotFoundException($"More than one user with the email {userEmail} exists in the Identity provider"); } - if (graphUsers.Count == 0) + + if (graphUsers?.Value?.Count == 0 || graphUsers?.Value is null) { throw new UserNotFoundException($"The user with the email {userEmail} was not found in the Identity Provider"); } // Ok to just return first, because at this point we've verified we have exactly 1 user in the graphUsers object. - return ToUserObjects(graphUsers).First(); + return ToUserObjects(graphUsers.Value).First(); } catch (Exception ex) { @@ -84,29 +87,34 @@ public class GraphAPIService : IGraphAPIService // Enriches the user object with data from Microsoft Graph. public async Task> GetUsersByIds(ICollection userIds) { - // Build graph query: "id in ('id1', 'id2')" - // https://docs.microsoft.com/en-us/graph/aad-advanced-queries?tabs=csharp - StringBuilder filter = new(); - filter.Append("id in ("); - filter.Append(string.Join(",", userIds.Select(id => $"'{id}'"))); - filter.Append(')'); - + List userList = new(); try { var graphUsers = await _graphServiceClient.Users - .Request() - .Filter(filter.ToString()) - .GetAsync(); + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Filter = MakeUserFilter(); + }); - userList.AddRange(ToUserObjects(graphUsers)); - - while (graphUsers.NextPageRequest is not null) + if (graphUsers?.Value is null) { - graphUsers = await graphUsers.NextPageRequest.GetAsync(); - userList.AddRange(ToUserObjects(graphUsers)); - }; + return userList; + } + + PageIterator pageIterator + = PageIterator + .CreatePageIterator( + _graphServiceClient, + graphUsers, + (msg) => + { + userList.Add(ToUserObject(msg)); + return true; + }); + + await pageIterator.IterateAsync(); return userList; } @@ -115,17 +123,31 @@ public class GraphAPIService : IGraphAPIService _logError(_logger, ex); throw; } + + string MakeUserFilter () + { + // Build graph query: "id in ('id1', 'id2')" + // https://docs.microsoft.com/en-us/graph/aad-advanced-queries?tabs=csharp + StringBuilder filter = new(); + filter.Append("id in ("); + filter.Append(string.Join(",", userIds.Select(id => $"'{id}'"))); + filter.Append(')'); + + return filter.ToString(); + } } private async Task GetServicePrincipalAsync(string clientId) { try { - var servicePrincipal = await _graphServiceClient.ServicePrincipals.Request() - .Filter($"appId eq '{clientId}'") - .GetAsync(); + var servicePrincipal = await _graphServiceClient.ServicePrincipals + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Filter = $"appId eq '{clientId}'"; + }); - return servicePrincipal.SingleOrDefault(); + return servicePrincipal?.Value?.SingleOrDefault(); } catch (Exception ex) { @@ -139,17 +161,38 @@ public class GraphAPIService : IGraphAPIService try { var userAppRoleAssignments = await _graphServiceClient.Users[userId].AppRoleAssignments - .Request() - .Filter($"resourceId eq {servicePrincipal.Id}") - .GetAsync(); + .GetAsync(requestConfiguration => + { + requestConfiguration.Equals($"resourceId eq {servicePrincipal.Id}"); + }) ?? throw new ArgumentException($"App role not found for \"{servicePrincipal.AppId}\"."); - var appRoleIds = userAppRoleAssignments.Select(a => a.AppRoleId); + var appRoleIds = userAppRoleAssignments?.Value? + .Where(appRole => appRole is not null) + .Where(appRole => appRole.AppRoleId is not null) + .Select(appRole => appRole.AppRoleId); - var appRoles = servicePrincipal.AppRoles - .Where(a => appRoleIds.Contains(a.Id)) - .Select(a => a.Value); + if (appRoleIds is null || !appRoleIds.Any()) + { + throw new ArgumentException($"App role not found for \"{servicePrincipal.AppId}\"."); + } - return appRoles.ToArray(); + var appRoles = servicePrincipal.AppRoles? + .Where(appRole => appRole?.Id is not null) + .Where(appRole => appRoleIds.Contains(appRole.Id)); + + if (appRoles is null || !appRoles.Any()) + { + throw new ArgumentException($"App role not found for \"{servicePrincipal.AppId}\"."); + } + + var roleClaimsArray = appRoles + .Select(appRole => appRole.Value ?? string.Empty) + .Where(x => !string.IsNullOrEmpty(x)) + .ToArray(); + + return roleClaimsArray is not null && roleClaimsArray.Any() + ? roleClaimsArray + : throw new ArgumentException($"App role not found for \"{servicePrincipal.AppId}\"."); } catch (Exception ex) { @@ -158,12 +201,19 @@ public class GraphAPIService : IGraphAPIService } } - private static IEnumerable ToUserObjects(IGraphServiceUsersCollectionPage graphUsers) => + private static IEnumerable ToUserObjects(IEnumerable graphUsers) => graphUsers.Select(graphUser => new Models.User() { UserId = graphUser.Id, DisplayName = graphUser.DisplayName }); + private static Models.User ToUserObject(Microsoft.Graph.Models.User graphUser) => + new() + { + UserId = graphUser.Id, + DisplayName = graphUser.DisplayName + }; + } diff --git a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service_v1.1/Services/GraphClientFactory.cs b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service_v1.1/Services/GraphClientFactory.cs index 3ce127ca..3aad86d5 100644 --- a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service_v1.1/Services/GraphClientFactory.cs +++ b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service_v1.1/Services/GraphClientFactory.cs @@ -2,6 +2,7 @@ using Microsoft.Graph; using Saas.Shared.Options; using Saas.Permissions.Service.Interfaces; +using Microsoft.Kiota.Abstractions.Authentication; namespace Saas.Permissions.Service.Services; @@ -21,10 +22,6 @@ public class GraphApiClientFactory : IGraphApiClientFactory _httpClient = httpClient; } - public GraphServiceClient Create() => - new(_httpClient, _msGraphOptions.BaseUrl) - { - AuthenticationProvider = _authenticationProvider - }; - + public GraphServiceClient Create() => + new(_httpClient, _authenticationProvider, _msGraphOptions.BaseUrl); } diff --git a/src/Saas.Lib/Saas.Identity/Extensions/SaasIdentityConfigurationBuilderExtensions.api.cs b/src/Saas.Lib/Saas.Identity/Extensions/SaasIdentityConfigurationBuilderExtensions.api.cs index 7faa5086..e14d7be1 100644 --- a/src/Saas.Lib/Saas.Identity/Extensions/SaasIdentityConfigurationBuilderExtensions.api.cs +++ b/src/Saas.Lib/Saas.Identity/Extensions/SaasIdentityConfigurationBuilderExtensions.api.cs @@ -1,6 +1,6 @@  using Microsoft.Extensions.DependencyInjection; -using Microsoft.Graph; +using Microsoft.Kiota.Abstractions.Authentication; using Saas.Identity.Crypto; using Saas.Identity.Interface; using Saas.Identity.Provider; diff --git a/src/Saas.Lib/Saas.Identity/Provider/SaasGraphClientCredentialsProvider.cs b/src/Saas.Lib/Saas.Identity/Provider/SaasGraphClientCredentialsProvider.cs index 109d93e9..3d665c26 100644 --- a/src/Saas.Lib/Saas.Identity/Provider/SaasGraphClientCredentialsProvider.cs +++ b/src/Saas.Lib/Saas.Identity/Provider/SaasGraphClientCredentialsProvider.cs @@ -1,9 +1,9 @@  using Microsoft.Extensions.Logging; -using Microsoft.Graph; +using Microsoft.Kiota.Abstractions; +using Microsoft.Kiota.Abstractions.Authentication; using Saas.Shared.Interface; using Saas.Shared.Options; -using System.Net.Http.Headers; namespace Saas.Identity.Provider; public class SaasGraphClientCredentialsProvider : IAuthenticationProvider @@ -26,12 +26,28 @@ public class SaasGraphClientCredentialsProvider : IAuthenticationProvi _authProvider = authProvider; } - public async Task AuthenticateRequestAsync(HttpRequestMessage requestMessage) + //public async Task AuthenticateRequestAsync(HttpRequestMessage requestMessage) + //{ + // try + // { + // requestMessage.Headers.Authorization = + // new AuthenticationHeaderValue("bearer", await _authProvider.GetAccessTokenAsync()); + // } + // catch (Exception ex) + // { + // _logError(_logger, ex); + // throw; + // } + //} + + public async Task AuthenticateRequestAsync( + RequestInformation request, + Dictionary? additionalAuthenticationContext = null, + CancellationToken cancellationToken = default) { try { - requestMessage.Headers.Authorization = - new AuthenticationHeaderValue("bearer", await _authProvider.GetAccessTokenAsync()); + request.Headers.Add("Authorization", $"bearer { await _authProvider.GetAccessTokenAsync() }"); } catch (Exception ex) { diff --git a/src/Saas.Lib/Saas.Identity/Saas.Identity.csproj b/src/Saas.Lib/Saas.Identity/Saas.Identity.csproj index e5785ec5..7dec3667 100644 --- a/src/Saas.Lib/Saas.Identity/Saas.Identity.csproj +++ b/src/Saas.Lib/Saas.Identity/Saas.Identity.csproj @@ -9,7 +9,8 @@ - + + diff --git a/src/Saas.Lib/Saas.Shared/Saas.Shared.csproj b/src/Saas.Lib/Saas.Shared/Saas.Shared.csproj index cb9b3214..19bc24b9 100644 --- a/src/Saas.Lib/Saas.Shared/Saas.Shared.csproj +++ b/src/Saas.Lib/Saas.Shared/Saas.Shared.csproj @@ -8,7 +8,6 @@ - diff --git a/src/Saas.SignupAdministration/Saas.SignupAdministration.Web/Controllers/AccountSignUpController.cs b/src/Saas.SignupAdministration/Saas.SignupAdministration.Web/Controllers/AccountSignUpController.cs deleted file mode 100644 index cdc34653..00000000 --- a/src/Saas.SignupAdministration/Saas.SignupAdministration.Web/Controllers/AccountSignUpController.cs +++ /dev/null @@ -1,27 +0,0 @@ -//using Microsoft.AspNetCore.Authentication.OpenIdConnect; - -//namespace Microsoft.Identity.Web.UI.Areas.MicrosoftIdentity.Controllers; - -//// https://damienbod.com/2022/05/16/using-multiple-azure-b2c-user-flows-from-asp-net-core/ - -//[AllowAnonymous] -//[Route("MicrosoftIdentity/[controller]/[action]")] -//public class AccountSignUpController : Controller -//{ -// [HttpGet("{scheme?}")] -// public IActionResult SignUpPolicy( -// [FromRoute] string scheme, -// [FromQuery] string redirectUri) -// { -// scheme ??= OpenIdConnectDefaults.AuthenticationScheme; - -// string redirect = !string.IsNullOrEmpty(redirectUri) && Url.IsLocalUrl(redirectUri) -// ? redirectUri -// : Url.Content("~/"); - -// AuthenticationProperties properties = new() { RedirectUri = redirect }; - -// properties.Items[Constants.Policy] = "B2C_1A_SIGNUP_SIGNIN"; -// return Challenge(properties, scheme); -// } -//} diff --git a/src/Saas.SignupAdministration/Saas.SignupAdministration.Web/Views/Shared/_Layout.cshtml b/src/Saas.SignupAdministration/Saas.SignupAdministration.Web/Views/Shared/_Layout.cshtml index e74c0f78..7e0647f9 100644 --- a/src/Saas.SignupAdministration/Saas.SignupAdministration.Web/Views/Shared/_Layout.cshtml +++ b/src/Saas.SignupAdministration/Saas.SignupAdministration.Web/Views/Shared/_Layout.cshtml @@ -62,7 +62,7 @@
- Microsoft - © 2022 - Azure SaaS - Privacy + Microsoft - © 2023 - Azure SaaS - Privacy
diff --git a/src/Saas.SignupAdministration/deployment/act/workflows/signup-administration-deploy-debug.yml b/src/Saas.SignupAdministration/deployment/act/workflows/signup-administration-deploy-debug.yml index 78f086e6..f24f15b8 100644 --- a/src/Saas.SignupAdministration/deployment/act/workflows/signup-administration-deploy-debug.yml +++ b/src/Saas.SignupAdministration/deployment/act/workflows/signup-administration-deploy-debug.yml @@ -10,7 +10,7 @@ permissions: env: APP_NAME: signupadmin-app - AZURE_WEBAPP_NAME: signupadmin-app-asdk-test-aiuq # set this to your application's name + AZURE_WEBAPP_NAME: signupadmin-app-asdk-test-83sx # set this to your application's name AZURE_WEBAPP_PACKAGE_PATH: . # set this to the path to your web app project, defaults to the repository root DOTNET_VERSION: 7.x.x PROJECT_DIR: ./src/Saas.SignupAdministration/Saas.SignupAdministration.Web