From 7249022cbbcbace4255cf57ce9299528b3248175 Mon Sep 17 00:00:00 2001 From: Phil Stephenson Date: Mon, 11 Oct 2021 11:00:42 -0700 Subject: [PATCH 1/3] Add configurable AzureAD ClientSecret. Needed for authenticating to the Microsoft Graph API --- .../Configuration/AzureADConfiguration.cs | 5 ++++- .../Configuration/AzureADConfigurationResource.cs | 6 ++++++ .../Configuration/AzureADConfigurationSettings.cs | 2 ++ .../Configuration/AzureADConfigurationStore.cs | 13 ++++++++++++- .../Configuration/AzureADConfigureCommands.cs | 9 +++++++++ .../Configuration/IAzureADConfigurationStore.cs | 5 ++++- 6 files changed, 37 insertions(+), 3 deletions(-) diff --git a/source/Server.AzureAD/Configuration/AzureADConfiguration.cs b/source/Server.AzureAD/Configuration/AzureADConfiguration.cs index adfa681..53ae799 100644 --- a/source/Server.AzureAD/Configuration/AzureADConfiguration.cs +++ b/source/Server.AzureAD/Configuration/AzureADConfiguration.cs @@ -1,4 +1,5 @@ -using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Configuration; +using Octopus.Data.Model; +using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Configuration; namespace Octopus.Server.Extensibility.Authentication.AzureAD.Configuration { @@ -6,6 +7,8 @@ class AzureADConfiguration : OpenIDConnectConfigurationWithRole { public static string DefaultRoleClaimType = "roles"; + public SensitiveString? ClientSecret { get; set; } + public AzureADConfiguration() : base(AzureADConfigurationStore.SingletonId, "AzureAD", "Octopus Deploy", "1.0") { RoleClaimType = DefaultRoleClaimType; diff --git a/source/Server.AzureAD/Configuration/AzureADConfigurationResource.cs b/source/Server.AzureAD/Configuration/AzureADConfigurationResource.cs index f2f32ef..b3a0ae0 100644 --- a/source/Server.AzureAD/Configuration/AzureADConfigurationResource.cs +++ b/source/Server.AzureAD/Configuration/AzureADConfigurationResource.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Configuration; +using Octopus.Server.MessageContracts; using Octopus.Server.MessageContracts.Attributes; namespace Octopus.Server.Extensibility.Authentication.AzureAD.Configuration @@ -11,5 +12,10 @@ class AzureADConfigurationResource : OpenIDConnectConfigurationResource [Description("Tell Octopus how to find the roles/groups in the security token from Azure Active Directory (usually \"roles\" or \"groups\")")] [Writeable] public string? RoleClaimType { get; set; } + + [DisplayName("ClientSecret")] + [Description("A client secret from the Octopus App Registration in AzureAD. Used for authenticating to the Microsoft Graph API when necessary")] + [Writeable] + public SensitiveValue? ClientSecret { get; set; } } } \ No newline at end of file diff --git a/source/Server.AzureAD/Configuration/AzureADConfigurationSettings.cs b/source/Server.AzureAD/Configuration/AzureADConfigurationSettings.cs index 934f728..349eaf4 100644 --- a/source/Server.AzureAD/Configuration/AzureADConfigurationSettings.cs +++ b/source/Server.AzureAD/Configuration/AzureADConfigurationSettings.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Octopus.Data.Model; using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Configuration; using Octopus.Server.Extensibility.Extensions.Infrastructure.Configuration; @@ -21,6 +22,7 @@ public override IEnumerable GetConfigurationValues() yield return configurationValue; } yield return new ConfigurationValue($"Octopus.{ConfigurationDocumentStore.ConfigurationSettingsName}.RoleClaimType", ConfigurationDocumentStore.GetRoleClaimType(), ConfigurationDocumentStore.GetIsEnabled() && ConfigurationDocumentStore.GetRoleClaimType() != AzureADConfiguration.DefaultRoleClaimType, "Role Claim Type"); + yield return new ConfigurationValue($"Octopus.{ConfigurationDocumentStore.ConfigurationSettingsName}.ClientSecret", ConfigurationDocumentStore.GetClientSecret(), ConfigurationDocumentStore.GetIsEnabled(), "ClientSecret"); } } diff --git a/source/Server.AzureAD/Configuration/AzureADConfigurationStore.cs b/source/Server.AzureAD/Configuration/AzureADConfigurationStore.cs index fc5de7d..a737c3e 100644 --- a/source/Server.AzureAD/Configuration/AzureADConfigurationStore.cs +++ b/source/Server.AzureAD/Configuration/AzureADConfigurationStore.cs @@ -1,4 +1,5 @@ -using Octopus.Data.Storage.Configuration; +using Octopus.Data.Model; +using Octopus.Data.Storage.Configuration; using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Configuration; namespace Octopus.Server.Extensibility.Authentication.AzureAD.Configuration @@ -15,5 +16,15 @@ public AzureADConfigurationStore( IConfigurationStore configurationStore) : base(configurationStore) { } + + public void SetClientSecret(SensitiveString? clientSecret) + { + SetProperty(doc => doc.ClientSecret = clientSecret); + } + + public SensitiveString? GetClientSecret() + { + return GetProperty(doc => doc.ClientSecret); + } } } \ No newline at end of file diff --git a/source/Server.AzureAD/Configuration/AzureADConfigureCommands.cs b/source/Server.AzureAD/Configuration/AzureADConfigureCommands.cs index d16971b..ee9dd4c 100644 --- a/source/Server.AzureAD/Configuration/AzureADConfigureCommands.cs +++ b/source/Server.AzureAD/Configuration/AzureADConfigureCommands.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Octopus.Data.Model; using Octopus.Diagnostics; using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Configuration; using Octopus.Server.Extensibility.Extensions.Infrastructure.Configuration; @@ -30,6 +31,14 @@ public override IEnumerable GetOptions() ConfigurationStore.Value.SetRoleClaimType(v); Log.Info($"{ConfigurationSettingsName} RoleClaimType set to: {v}"); }); + yield return new ConfigureCommandOption($"{ConfigurationSettingsName}ClientSecret=", "A client secret from the Octopus App Registration in AzureAD. Used for authenticating to the Microsoft Graph API when necessary", v => + { + if (!string.IsNullOrEmpty(v)) + { + ConfigurationStore.Value.SetClientSecret(v.ToSensitiveString()); + Log.Info($"{ConfigurationSettingsName} ClientSecret was set."); + } + }); } } } \ No newline at end of file diff --git a/source/Server.AzureAD/Configuration/IAzureADConfigurationStore.cs b/source/Server.AzureAD/Configuration/IAzureADConfigurationStore.cs index 9f1b8c5..577a2b8 100644 --- a/source/Server.AzureAD/Configuration/IAzureADConfigurationStore.cs +++ b/source/Server.AzureAD/Configuration/IAzureADConfigurationStore.cs @@ -1,8 +1,11 @@ -using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Configuration; +using Octopus.Data.Model; +using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Configuration; namespace Octopus.Server.Extensibility.Authentication.AzureAD.Configuration { interface IAzureADConfigurationStore : IOpenIDConnectConfigurationWithRoleStore { + SensitiveString? GetClientSecret(); + void SetClientSecret(SensitiveString? clientSecret); } } \ No newline at end of file From 9d3fe0286b82aa05c27a1f70862a51e624ed33e0 Mon Sep 17 00:00:00 2001 From: Phil Stephenson Date: Tue, 12 Oct 2021 15:31:45 -0700 Subject: [PATCH 2/3] Pipe through the id_token to retrieve an access token to the Graph API on the user's behalf --- .../Server.AzureAD/GraphApi/GraphApiClient.cs | 48 +++++++++++++++++++ .../Server.AzureAD/GraphApi/TokenResponse.cs | 16 +++++++ .../Tokens/AzureADAuthTokenHandler.cs | 44 +++++++++++++++++ .../Tokens/AuthTokenHandler.cs | 4 +- .../OpenIDConnectAuthTokenWithRolesHandler.cs | 2 +- 5 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 source/Server.AzureAD/GraphApi/GraphApiClient.cs create mode 100644 source/Server.AzureAD/GraphApi/TokenResponse.cs diff --git a/source/Server.AzureAD/GraphApi/GraphApiClient.cs b/source/Server.AzureAD/GraphApi/GraphApiClient.cs new file mode 100644 index 0000000..d179fcc --- /dev/null +++ b/source/Server.AzureAD/GraphApi/GraphApiClient.cs @@ -0,0 +1,48 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Octopus.Server.Extensibility.Authentication.AzureAD.GraphApi +{ + internal class GraphApiClient + { + private readonly HttpClient httpClient; + private readonly Uri tokenUri; + private readonly Guid clientId; + private readonly string clientSecret; + + private const string scope = "https://graph.microsoft.com/groupmember.read.all"; + private const string grantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"; + private const string requestedTokenUse = "on_behalf_of"; + + public GraphApiClient(HttpClient httpClient, Guid tenantId, Guid clientId, string clientSecret) + { + this.httpClient = httpClient; + tokenUri = new Uri("https://login.microsoftonline.com/" + tenantId.ToString() + "/oauth2/v2.0/token"); + this.clientId = clientId; + this.clientSecret = clientSecret; + } + + public async Task GetAccessTokenOnBehalfOfUser(string assertion) + { + var requestBody = new FormUrlEncodedContent(new[] + { + new KeyValuePair("grant_type", grantType), + new KeyValuePair("client_id", clientId.ToString()), + new KeyValuePair("client_secret", clientSecret), + new KeyValuePair("assertion", assertion), + new KeyValuePair("scope", scope), + new KeyValuePair("requested_token_use", requestedTokenUse) + }); + + var response = await httpClient.PostAsync(tokenUri, requestBody); + response.EnsureSuccessStatusCode(); + var responseBody = await response.Content.ReadAsStringAsync(); + var model = JsonConvert.DeserializeObject(responseBody); + + return model.access_token; + } + } +} diff --git a/source/Server.AzureAD/GraphApi/TokenResponse.cs b/source/Server.AzureAD/GraphApi/TokenResponse.cs new file mode 100644 index 0000000..05fe5ca --- /dev/null +++ b/source/Server.AzureAD/GraphApi/TokenResponse.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Octopus.Server.Extensibility.Authentication.AzureAD.GraphApi +{ + internal class TokenResponse + { + public string token_type { get; set; } + public string scope { get; set; } + public int expires_in { get; set; } + public int ext_expires_in { get; set; } + public string access_token { get; set; } + public string refresh_token { get; set; } + } +} diff --git a/source/Server.AzureAD/Tokens/AzureADAuthTokenHandler.cs b/source/Server.AzureAD/Tokens/AzureADAuthTokenHandler.cs index 0a48b22..df7e1e6 100644 --- a/source/Server.AzureAD/Tokens/AzureADAuthTokenHandler.cs +++ b/source/Server.AzureAD/Tokens/AzureADAuthTokenHandler.cs @@ -1,15 +1,59 @@ using Octopus.Diagnostics; using Octopus.Server.Extensibility.Authentication.AzureAD.Configuration; +using Octopus.Server.Extensibility.Authentication.AzureAD.GraphApi; using Octopus.Server.Extensibility.Authentication.AzureAD.Issuer; using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Issuer; using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Tokens; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; namespace Octopus.Server.Extensibility.Authentication.AzureAD.Tokens { class AzureADAuthTokenHandler : OpenIDConnectAuthTokenWithRolesHandler, IAzureADAuthTokenHandler { + private readonly IAzureADConfigurationStore configurationStore; + private const string graphQuerySelect = "id,displayName,onPremisesNetBiosName,onPremisesDomainName,onPremisesSamAccountNameonPremisesSecurityIdentifier"; + public AzureADAuthTokenHandler(ISystemLog log, IAzureADConfigurationStore configurationStore, IIdentityProviderConfigDiscoverer identityProviderConfigDiscoverer, IAzureADKeyRetriever keyRetriever) : base(log, configurationStore, identityProviderConfigDiscoverer, keyRetriever) { + this.configurationStore = configurationStore; + } + + protected override string[] GetProviderGroupIds(ClaimsPrincipal principal, string? assertion = null) + => (SupportsHandlingOverages() && HasOverageOccurred(principal)) + ? GetProviderGroupIdsAsync(principal, assertion).Result + : base.GetProviderGroupIds(principal); + + private async Task GetProviderGroupIdsAsync(ClaimsPrincipal principal, string? idToken) + { + var groups = new HashSet(); + using (var httpClient = new HttpClient()) + { + var graphClient = new GraphApiClient( + httpClient, + GetTenantIdFromIssuer(configurationStore.GetIssuer()), + Guid.Parse(configurationStore.GetClientId()), + configurationStore.GetClientSecret()?.Value + ); + var bearerToken = await graphClient.GetAccessTokenOnBehalfOfUser(idToken); + // todo: Call Graph API to get group membership + + return groups.ToArray(); + } + } + + private bool SupportsHandlingOverages() => !string.IsNullOrEmpty(configurationStore.GetClientSecret()?.Value); + + private static bool HasOverageOccurred(ClaimsPrincipal identity) => identity.Claims.Any(x => x.Type == "hasgroups" || (x.Type == "_claim_names" && x.Value == "{\"groups\":\"src1\"}")); + + private static Guid GetTenantIdFromIssuer(string? issuer) + { + var uri = new Uri(issuer); + return Guid.Parse(uri.Segments.Last()); } } } \ No newline at end of file diff --git a/source/Server.OpenIDConnect.Common/Tokens/AuthTokenHandler.cs b/source/Server.OpenIDConnect.Common/Tokens/AuthTokenHandler.cs index 5383a15..7b1f45c 100644 --- a/source/Server.OpenIDConnect.Common/Tokens/AuthTokenHandler.cs +++ b/source/Server.OpenIDConnect.Common/Tokens/AuthTokenHandler.cs @@ -81,7 +81,7 @@ protected async Task GetPrincipalFromToken(string? acc DoIssuerSpecificClaimsValidation(principal, out error); if (string.IsNullOrWhiteSpace(error)) - return new ClaimsPrincipleContainer(principal, GetProviderGroupIds(principal)); + return new ClaimsPrincipleContainer(principal, GetProviderGroupIds(principal, tokenToValidate)); return new ClaimsPrincipleContainer(error); } @@ -172,7 +172,7 @@ protected virtual void DoIssuerSpecificClaimsValidation(ClaimsPrincipal principa error = string.Empty; } - protected virtual string[] GetProviderGroupIds(ClaimsPrincipal principal) + protected virtual string[] GetProviderGroupIds(ClaimsPrincipal principal, string? idToken = null) { return new string[0]; } diff --git a/source/Server.OpenIDConnect.Common/Tokens/OpenIDConnectAuthTokenWithRolesHandler.cs b/source/Server.OpenIDConnect.Common/Tokens/OpenIDConnectAuthTokenWithRolesHandler.cs index d75b2a9..65df1ee 100644 --- a/source/Server.OpenIDConnect.Common/Tokens/OpenIDConnectAuthTokenWithRolesHandler.cs +++ b/source/Server.OpenIDConnect.Common/Tokens/OpenIDConnectAuthTokenWithRolesHandler.cs @@ -23,7 +23,7 @@ protected override void SetIssuerSpecificTokenValidationParameters(TokenValidati validationParameters.RoleClaimType = ConfigurationStore.GetRoleClaimType(); } - protected override string[] GetProviderGroupIds(ClaimsPrincipal principal) + protected override string[] GetProviderGroupIds(ClaimsPrincipal principal, string? idToken = null) { var roleClaimType = ConfigurationStore.GetRoleClaimType(); From 24ac8835485e66dabc945ef7cd96b3e7c169df7d Mon Sep 17 00:00:00 2001 From: Phil Stephenson Date: Wed, 13 Oct 2021 15:16:11 -0700 Subject: [PATCH 3/3] Query the Graph API for group membership. Handle paging when necessary --- .../Server.AzureAD/GraphApi/GraphApiClient.cs | 34 +++++++++++++++++-- .../Server.AzureAD/GraphApi/GraphResponse.cs | 14 ++++++++ .../GraphApi/MembershipEntity.cs | 11 ++++++ .../Server.AzureAD/GraphApi/TokenResponse.cs | 21 ++++++++---- .../Tokens/AzureADAuthTokenHandler.cs | 8 ++--- 5 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 source/Server.AzureAD/GraphApi/GraphResponse.cs create mode 100644 source/Server.AzureAD/GraphApi/MembershipEntity.cs diff --git a/source/Server.AzureAD/GraphApi/GraphApiClient.cs b/source/Server.AzureAD/GraphApi/GraphApiClient.cs index d179fcc..47d7ddb 100644 --- a/source/Server.AzureAD/GraphApi/GraphApiClient.cs +++ b/source/Server.AzureAD/GraphApi/GraphApiClient.cs @@ -1,7 +1,9 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading.Tasks; namespace Octopus.Server.Extensibility.Authentication.AzureAD.GraphApi @@ -16,13 +18,14 @@ internal class GraphApiClient private const string scope = "https://graph.microsoft.com/groupmember.read.all"; private const string grantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"; private const string requestedTokenUse = "on_behalf_of"; + private const string graphQuerySelect = "$select=id,displayName,onPremisesNetBiosName,onPremisesDomainName,onPremisesSamAccountNameonPremisesSecurityIdentifier"; - public GraphApiClient(HttpClient httpClient, Guid tenantId, Guid clientId, string clientSecret) + public GraphApiClient(HttpClient httpClient, Guid tenantId, Guid clientId, string? clientSecret) { this.httpClient = httpClient; tokenUri = new Uri("https://login.microsoftonline.com/" + tenantId.ToString() + "/oauth2/v2.0/token"); this.clientId = clientId; - this.clientSecret = clientSecret; + this.clientSecret = clientSecret ?? throw new ArgumentNullException(nameof(clientSecret)); } public async Task GetAccessTokenOnBehalfOfUser(string assertion) @@ -42,7 +45,32 @@ public async Task GetAccessTokenOnBehalfOfUser(string assertion) var responseBody = await response.Content.ReadAsStringAsync(); var model = JsonConvert.DeserializeObject(responseBody); - return model.access_token; + return model.AccessToken; + } + + public async Task GetGroupMembershipIds(string accessToken) + { + var groups = new HashSet(); + string? nextLink = null; + do + { + var uri = string.IsNullOrEmpty(nextLink) ? ("https://graph.microsoft.com/v1.0/me/memberOf/microsoft.graph.group?" + graphQuerySelect) : nextLink; + // The nextLink will contain all other query parameters from original request: https://docs.microsoft.com/en-us/graph/paging?context=graph%2Fapi%2F1.0&view=graph-rest-1.0 + using (var request = new HttpRequestMessage(HttpMethod.Get, uri)) + { + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var response = await httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); + var responseBody = await response.Content.ReadAsStringAsync(); + var graphObjects = JsonConvert.DeserializeObject(responseBody); + nextLink = graphObjects.NextLink; + + groups.UnionWith(graphObjects.Value.Select(m => m.Id)); + } + } while (!string.IsNullOrEmpty(nextLink)); + + return groups.ToArray(); } } } diff --git a/source/Server.AzureAD/GraphApi/GraphResponse.cs b/source/Server.AzureAD/GraphApi/GraphResponse.cs new file mode 100644 index 0000000..2d510c6 --- /dev/null +++ b/source/Server.AzureAD/GraphApi/GraphResponse.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Octopus.Server.Extensibility.Authentication.AzureAD.GraphApi +{ + internal class GraphResponse + { + [JsonProperty("@odata.context")] + public string? Context { get; set; } + [JsonProperty("@odata.nextLink")] + public string? NextLink { get; set; } + [JsonProperty("value")] + public MembershipEntity[]? Value { get; set; } + } +} diff --git a/source/Server.AzureAD/GraphApi/MembershipEntity.cs b/source/Server.AzureAD/GraphApi/MembershipEntity.cs new file mode 100644 index 0000000..3eafe4c --- /dev/null +++ b/source/Server.AzureAD/GraphApi/MembershipEntity.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json; +using System; + +namespace Octopus.Server.Extensibility.Authentication.AzureAD.GraphApi +{ + internal class MembershipEntity + { + [JsonProperty("id")] + public string Id { get; set; } = string.Empty; + } +} diff --git a/source/Server.AzureAD/GraphApi/TokenResponse.cs b/source/Server.AzureAD/GraphApi/TokenResponse.cs index 05fe5ca..6b51bb9 100644 --- a/source/Server.AzureAD/GraphApi/TokenResponse.cs +++ b/source/Server.AzureAD/GraphApi/TokenResponse.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json; +using System; using System.Collections.Generic; using System.Text; @@ -6,11 +7,17 @@ namespace Octopus.Server.Extensibility.Authentication.AzureAD.GraphApi { internal class TokenResponse { - public string token_type { get; set; } - public string scope { get; set; } - public int expires_in { get; set; } - public int ext_expires_in { get; set; } - public string access_token { get; set; } - public string refresh_token { get; set; } + [JsonProperty("token_type")] + public string TokenType { get; set; } = string.Empty; + [JsonProperty("scope")] + public string Scope { get; set; } = string.Empty; + [JsonProperty("expires_in")] + public int ExpiresIn { get; set; } + [JsonProperty("ext_expires_in")] + public int ExtExpiresIn { get; set; } + [JsonProperty("access_token")] + public string AccessToken { get; set; } = string.Empty; + [JsonProperty("refresh_token")] + public string RefreshToken { get; set; } = string.Empty; } } diff --git a/source/Server.AzureAD/Tokens/AzureADAuthTokenHandler.cs b/source/Server.AzureAD/Tokens/AzureADAuthTokenHandler.cs index df7e1e6..0a6d43d 100644 --- a/source/Server.AzureAD/Tokens/AzureADAuthTokenHandler.cs +++ b/source/Server.AzureAD/Tokens/AzureADAuthTokenHandler.cs @@ -5,7 +5,6 @@ using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Issuer; using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Tokens; using System; -using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Security.Claims; @@ -16,7 +15,6 @@ namespace Octopus.Server.Extensibility.Authentication.AzureAD.Tokens class AzureADAuthTokenHandler : OpenIDConnectAuthTokenWithRolesHandler, IAzureADAuthTokenHandler { private readonly IAzureADConfigurationStore configurationStore; - private const string graphQuerySelect = "id,displayName,onPremisesNetBiosName,onPremisesDomainName,onPremisesSamAccountNameonPremisesSecurityIdentifier"; public AzureADAuthTokenHandler(ISystemLog log, IAzureADConfigurationStore configurationStore, IIdentityProviderConfigDiscoverer identityProviderConfigDiscoverer, IAzureADKeyRetriever keyRetriever) : base(log, configurationStore, identityProviderConfigDiscoverer, keyRetriever) { @@ -30,7 +28,6 @@ protected override string[] GetProviderGroupIds(ClaimsPrincipal principal, strin private async Task GetProviderGroupIdsAsync(ClaimsPrincipal principal, string? idToken) { - var groups = new HashSet(); using (var httpClient = new HttpClient()) { var graphClient = new GraphApiClient( @@ -39,10 +36,9 @@ private async Task GetProviderGroupIdsAsync(ClaimsPrincipal principal, Guid.Parse(configurationStore.GetClientId()), configurationStore.GetClientSecret()?.Value ); - var bearerToken = await graphClient.GetAccessTokenOnBehalfOfUser(idToken); - // todo: Call Graph API to get group membership - return groups.ToArray(); + var bearerToken = await graphClient.GetAccessTokenOnBehalfOfUser(idToken!); + return await graphClient.GetGroupMembershipIds(bearerToken); } }