Skip to content

Commit dc7d6d4

Browse files
committed
Use new extensibility points for federated authentication
1 parent 4802e0e commit dc7d6d4

17 files changed

+144
-414
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ jobs:
3737

3838
- name: Package
3939
run: dotnet pack src/Dfe.Analytics/ --configuration Release --no-build
40+
env:
41+
MinVerVersionOverride: 0.3.0-alpha.0.${{ github.run_number }}
4042

4143
- name: Publish package artifact
4244
uses: actions/upload-artifact@v4

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
Overhauls authentication to use new extensibility points in the Google SDK and simplify setup by auto-detecting authentication mechanism to use.
6+
57
Removes the option to pseudonymize the user ID.
68

79
## 0.2.6

src/Dfe.Analytics/AspNetCore/DfeAnalyticsAspNetCoreConfigureOptions.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,13 @@
33

44
namespace Dfe.Analytics.AspNetCore;
55

6-
#pragma warning disable CA1812
76
internal class DfeAnalyticsAspNetCoreConfigureOptions(IConfiguration configuration) : IConfigureOptions<DfeAnalyticsAspNetCoreOptions>
8-
#pragma warning restore CA1812
97
{
10-
private readonly IConfiguration _configuration = configuration;
11-
128
public void Configure(DfeAnalyticsAspNetCoreOptions options)
139
{
1410
ArgumentNullException.ThrowIfNull(options);
1511

16-
var section = _configuration.GetSection(Constants.RootConfigurationSectionName).GetSection("AspNetCore");
12+
var section = configuration.GetSection(Constants.ConfigurationSectionName).GetSection("AspNetCore");
1713

1814
section.AssignConfigurationValueIfNotEmpty("UserIdClaimType", v => options.UserIdClaimType = v);
1915
section.AssignConfigurationValueIfNotEmpty("RestoreOriginalPathAndQueryString", v => options.RestoreOriginalPathAndQueryString = bool.Parse(v));

src/Dfe.Analytics/AspNetCore/DfeAnalyticsAspNetCorePostConfigureOptions.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33

44
namespace Dfe.Analytics.AspNetCore;
55

6-
#pragma warning disable CA1812
76
internal class DfeAnalyticsAspNetCorePostConfigureOptions : IPostConfigureOptions<DfeAnalyticsAspNetCoreOptions>
8-
#pragma warning restore CA1812
97
{
108
public void PostConfigure(string? name, DfeAnalyticsAspNetCoreOptions options)
119
{

src/Dfe.Analytics/AspNetCore/DfeAnalyticsMiddleware.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,34 @@ namespace Dfe.Analytics.AspNetCore;
1515
public class DfeAnalyticsMiddleware
1616
{
1717
private readonly RequestDelegate _next;
18-
private readonly IBigQueryClientProvider _bigQueryClientProvider;
1918
private readonly IEnumerable<IWebRequestEventEnricher> _webRequestEventEnrichers;
2019
private readonly ILogger<DfeAnalyticsMiddleware> _logger;
2120

2221
/// <summary>
2322
/// Creates a new <see cref="DfeAnalyticsMiddleware"/>.
2423
/// </summary>
2524
/// <param name="next">The <see cref="RequestDelegate"/> representing the next middleware in the pipeline.</param>
26-
/// <param name="bigQueryClientProvider">The <see cref="IBigQueryClientProvider"/>.</param>
2725
/// <param name="timeProvider">The <see cref="TimeProvider"/>.</param>
2826
/// <param name="optionsAccessor">The configuration options.</param>
2927
/// <param name="aspNetCoreOptionsAccessor">The middleware configuration options.</param>
3028
/// <param name="webRequestEventEnrichers">The collection of <see cref="IWebRequestEventEnricher"/>.</param>
3129
/// <param name="logger">The logger instance.</param>
3230
public DfeAnalyticsMiddleware(
3331
RequestDelegate next,
34-
IBigQueryClientProvider bigQueryClientProvider,
3532
TimeProvider timeProvider,
3633
IOptions<DfeAnalyticsOptions> optionsAccessor,
3734
IOptions<DfeAnalyticsAspNetCoreOptions> aspNetCoreOptionsAccessor,
3835
IEnumerable<IWebRequestEventEnricher> webRequestEventEnrichers,
3936
ILogger<DfeAnalyticsMiddleware> logger)
4037
{
4138
ArgumentNullException.ThrowIfNull(next);
42-
ArgumentNullException.ThrowIfNull(bigQueryClientProvider);
4339
ArgumentNullException.ThrowIfNull(timeProvider);
4440
ArgumentNullException.ThrowIfNull(optionsAccessor);
4541
ArgumentNullException.ThrowIfNull(aspNetCoreOptionsAccessor);
4642
ArgumentNullException.ThrowIfNull(webRequestEventEnrichers);
4743
ArgumentNullException.ThrowIfNull(logger);
4844

4945
_next = next;
50-
_bigQueryClientProvider = bigQueryClientProvider;
5146
TimeProvider = timeProvider;
5247
_webRequestEventEnrichers = webRequestEventEnrichers;
5348
Options = optionsAccessor.Value;
@@ -123,7 +118,7 @@ public async Task InvokeAsync(HttpContext context)
123118
}
124119
}
125120

126-
var bigQueryClient = await _bigQueryClientProvider.GetBigQueryClientAsync();
121+
var bigQueryClient = Options.BigQueryClient;
127122

128123
var row = @event.ToBigQueryInsertRow();
129124

src/Dfe.Analytics/Constants.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ namespace Dfe.Analytics;
22

33
internal static class Constants
44
{
5-
public const string RootConfigurationSectionName = "DfeAnalytics";
5+
public const string ConfigurationSectionName = "DfeAnalytics";
66
}

src/Dfe.Analytics/Dfe.Analytics.csproj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@
1111
<RepositoryType>git</RepositoryType>
1212
<Description>A port of the DfE Analytics gem for .NET.</Description>
1313
<MinVerTagPrefix>v</MinVerTagPrefix>
14-
<MinVerMinimumMajorMinor>0.2</MinVerMinimumMajorMinor>
14+
<MinVerMinimumMajorMinor>0.3</MinVerMinimumMajorMinor>
1515
<IncludeSymbols>true</IncludeSymbols>
1616
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
1717
<EmbedUntrackedSources>true</EmbedUntrackedSources>
1818
<AnalysisMode>All</AnalysisMode>
19-
<NoWarn>$(NoWarn);CA1716;CA1852;CA1848</NoWarn>
19+
<NoWarn>$(NoWarn);CA1716;CA1852;CA1848;CA1812</NoWarn>
2020
</PropertyGroup>
2121

2222
<ItemGroup>
2323
<FrameworkReference Include="Microsoft.AspNetCore.App" PrivateAssets="all" />
24+
<PackageReference Include="Google.Apis.Auth" Version="1.71.0" />
2425
<PackageReference Include="Google.Cloud.BigQuery.V2" Version="[3.0,4.0)" />
2526
<PackageReference Include="Microsoft.Extensions.Configuration" Version="[8.0,)" />
2627
<PackageReference Include="Microsoft.Extensions.Logging" Version="[8.0,)" />

src/Dfe.Analytics/DfeAnalyticsConfigureOptions.cs

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,85 @@
66

77
namespace Dfe.Analytics;
88

9-
#pragma warning disable CA1812
109
internal class DfeAnalyticsConfigureOptions(IConfiguration configuration) : IConfigureOptions<DfeAnalyticsOptions>
11-
#pragma warning restore CA1812
1210
{
13-
private readonly IConfiguration _configuration = configuration;
14-
1511
public void Configure(DfeAnalyticsOptions options)
1612
{
1713
ArgumentNullException.ThrowIfNull(options);
1814

19-
var section = _configuration.GetSection(Constants.RootConfigurationSectionName);
15+
var section = configuration.GetSection(Constants.ConfigurationSectionName);
2016

2117
section.AssignConfigurationValueIfNotEmpty("DatasetId", v => options.DatasetId = v);
2218
section.AssignConfigurationValueIfNotEmpty("Environment", v => options.Environment = v);
2319
section.AssignConfigurationValueIfNotEmpty("Namespace", v => options.Namespace = v);
2420
section.AssignConfigurationValueIfNotEmpty("TableId", v => options.TableId = v);
2521
section.AssignConfigurationValueIfNotEmpty("ProjectId", v => options.ProjectId = v);
22+
section.AssignConfigurationValueIfNotEmpty("Audience", v =>
23+
{
24+
options.FederatedAksAuthentication ??= new();
25+
options.FederatedAksAuthentication.Audience = v;
26+
});
27+
section.AssignConfigurationValueIfNotEmpty("GenerateAccessTokenUrl", v =>
28+
{
29+
options.FederatedAksAuthentication ??= new();
30+
options.FederatedAksAuthentication.ServiceAccountImpersonationUrl = v;
31+
});
2632

2733
var credentialsJson = section["CredentialsJson"];
28-
2934
if (!string.IsNullOrEmpty(credentialsJson))
3035
{
3136
using var credentialsJsonDoc = JsonDocument.Parse(credentialsJson);
37+
AssignConfigurationFromCredentialsJson(options, credentialsJsonDoc);
38+
}
39+
}
40+
41+
private void AssignConfigurationFromCredentialsJson(DfeAnalyticsOptions options, JsonDocument credentialsJson)
42+
{
43+
if (options.ProjectId is null &&
44+
credentialsJson.RootElement.TryGetProperty("project_id", out var projectIdElement))
45+
{
46+
options.ProjectId = projectIdElement.GetString();
47+
}
3248

33-
// We don't have ProjectId configured explicitly; see if it's set in the JSON credentials
34-
if (options.ProjectId is null &&
35-
credentialsJsonDoc.RootElement.TryGetProperty("project_id", out var projectIdElement) &&
36-
projectIdElement.ValueKind == JsonValueKind.String)
49+
if (options.FederatedAksAuthentication?.Audience is null &&
50+
credentialsJson.RootElement.TryGetProperty("audience", out var audienceElement))
51+
{
52+
options.FederatedAksAuthentication ??= new();
53+
options.FederatedAksAuthentication.Audience = audienceElement.GetString()!;
54+
}
55+
56+
if (options.FederatedAksAuthentication?.ServiceAccountImpersonationUrl is null &&
57+
credentialsJson.RootElement.TryGetProperty("service_account_impersonation_url", out var impersonationUrlElement))
58+
{
59+
options.FederatedAksAuthentication ??= new();
60+
options.FederatedAksAuthentication.ServiceAccountImpersonationUrl = impersonationUrlElement.GetString()!;
61+
}
62+
63+
if (options.BigQueryClient is null && options.ProjectId is { } projectId)
64+
{
65+
if (credentialsJson.RootElement.TryGetProperty("private_key", out _))
3766
{
38-
options.ProjectId = projectIdElement.GetString();
67+
options.BigQueryClient = BigQueryClient.Create(
68+
projectId,
69+
GoogleCredential.FromJson(credentialsJson.ToString()));
3970
}
40-
41-
if (credentialsJsonDoc.RootElement.TryGetProperty("private_key", out _) && options.ProjectId is string projectId)
71+
else if (Environment.GetEnvironmentVariable(FederatedAksSubjectTokenProvider.TokenPathEnvironmentVariableName) is not null &&
72+
options.FederatedAksAuthentication is { Audience: { } audience, ServiceAccountImpersonationUrl: { } serviceAccountImpersonationUrl })
4273
{
43-
var creds = GoogleCredential.FromJson(credentialsJson);
44-
options.BigQueryClient = BigQueryClient.Create(projectId, creds);
74+
options.BigQueryClient = BigQueryClient.Create(
75+
projectId,
76+
GoogleCredential.FromProgrammaticExternalAccountCredential(
77+
new ProgrammaticExternalAccountCredential(
78+
new ProgrammaticExternalAccountCredential.Initializer(
79+
tokenUrl: "https://sts.googleapis.com/v1/token",
80+
audience,
81+
FederatedAksSubjectTokenProvider.SubjectTokenType,
82+
#pragma warning disable CA2000
83+
new FederatedAksSubjectTokenProvider())
84+
{
85+
ServiceAccountImpersonationUrl = serviceAccountImpersonationUrl
86+
})));
87+
#pragma warning restore CA2000
4588
}
4689
}
4790
}

src/Dfe.Analytics/DfeAnalyticsOptions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,22 @@ public class DfeAnalyticsOptions
4747
/// </summary>
4848
public string? ProjectId { get; set; }
4949

50+
/// <summary>
51+
/// The federated AKS authentication options.
52+
/// </summary>
53+
public FederatedAksAuthenticationOptions? FederatedAksAuthentication { get; set; }
54+
55+
[MemberNotNull(nameof(BigQueryClient))]
5056
[MemberNotNull(nameof(DatasetId))]
5157
[MemberNotNull(nameof(TableId))]
5258
[MemberNotNull(nameof(Environment))]
5359
internal void ValidateOptions()
5460
{
61+
if (BigQueryClient is null)
62+
{
63+
throw new InvalidOperationException($"{nameof(BigQueryClient)} has not been configured.");
64+
}
65+
5566
if (DatasetId is null)
5667
{
5768
throw new InvalidOperationException($"{nameof(DatasetId)} has not been configured.");

src/Dfe.Analytics/DfeAnalyticsPostConfigureOptions.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33

44
namespace Dfe.Analytics;
55

6-
#pragma warning disable CA1812
76
internal class DfeAnalyticsPostConfigureOptions : IPostConfigureOptions<DfeAnalyticsOptions>
8-
#pragma warning restore CA1812
97
{
108
public void PostConfigure(string? name, DfeAnalyticsOptions options)
119
{

0 commit comments

Comments
 (0)