Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

Overhauls authentication to use new extensibility points in the Google SDK and simplify setup by auto-detecting authentication mechanism to use.

Removes the option to pseudonymize the user ID.

## 0.2.6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,13 @@

namespace Dfe.Analytics.AspNetCore;

#pragma warning disable CA1812
internal class DfeAnalyticsAspNetCoreConfigureOptions(IConfiguration configuration) : IConfigureOptions<DfeAnalyticsAspNetCoreOptions>
#pragma warning restore CA1812
{
private readonly IConfiguration _configuration = configuration;

public void Configure(DfeAnalyticsAspNetCoreOptions options)
{
ArgumentNullException.ThrowIfNull(options);

var section = _configuration.GetSection(Constants.RootConfigurationSectionName).GetSection("AspNetCore");
var section = configuration.GetSection(Constants.ConfigurationSectionName).GetSection("AspNetCore");

section.AssignConfigurationValueIfNotEmpty("UserIdClaimType", v => options.UserIdClaimType = v);
section.AssignConfigurationValueIfNotEmpty("RestoreOriginalPathAndQueryString", v => options.RestoreOriginalPathAndQueryString = bool.Parse(v));
Expand Down
4 changes: 2 additions & 2 deletions src/Dfe.Analytics/AspNetCore/DfeAnalyticsAspNetCoreOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ namespace Dfe.Analytics.AspNetCore;
public class DfeAnalyticsAspNetCoreOptions
{
/// <summary>
/// A delegate that returns the signed in user's ID, if any.
/// A delegate that returns the signed-in user's ID, if any.
/// </summary>
/// <remarks>
/// The default returns the value of the first <see cref="UserIdClaimType"/> claim from <see cref="HttpContext.User"/> property.
/// </remarks>
public Func<HttpContext, string?>? GetUserIdFromRequest { get; set; }

/// <summary>
/// The claim type that contains the signed in user's ID.
/// The claim type that contains the signed-in user's ID.
/// </summary>
public string? UserIdClaimType { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@

namespace Dfe.Analytics.AspNetCore;

#pragma warning disable CA1812
internal class DfeAnalyticsAspNetCorePostConfigureOptions : IPostConfigureOptions<DfeAnalyticsAspNetCoreOptions>
#pragma warning restore CA1812
{
public void PostConfigure(string? name, DfeAnalyticsAspNetCoreOptions options)
{
Expand Down
7 changes: 1 addition & 6 deletions src/Dfe.Analytics/AspNetCore/DfeAnalyticsMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,34 @@ namespace Dfe.Analytics.AspNetCore;
public class DfeAnalyticsMiddleware
{
private readonly RequestDelegate _next;
private readonly IBigQueryClientProvider _bigQueryClientProvider;
private readonly IEnumerable<IWebRequestEventEnricher> _webRequestEventEnrichers;
private readonly ILogger<DfeAnalyticsMiddleware> _logger;

/// <summary>
/// Creates a new <see cref="DfeAnalyticsMiddleware"/>.
/// </summary>
/// <param name="next">The <see cref="RequestDelegate"/> representing the next middleware in the pipeline.</param>
/// <param name="bigQueryClientProvider">The <see cref="IBigQueryClientProvider"/>.</param>
/// <param name="timeProvider">The <see cref="TimeProvider"/>.</param>
/// <param name="optionsAccessor">The configuration options.</param>
/// <param name="aspNetCoreOptionsAccessor">The middleware configuration options.</param>
/// <param name="webRequestEventEnrichers">The collection of <see cref="IWebRequestEventEnricher"/>.</param>
/// <param name="logger">The logger instance.</param>
public DfeAnalyticsMiddleware(
RequestDelegate next,
IBigQueryClientProvider bigQueryClientProvider,
TimeProvider timeProvider,
IOptions<DfeAnalyticsOptions> optionsAccessor,
IOptions<DfeAnalyticsAspNetCoreOptions> aspNetCoreOptionsAccessor,
IEnumerable<IWebRequestEventEnricher> webRequestEventEnrichers,
ILogger<DfeAnalyticsMiddleware> logger)
{
ArgumentNullException.ThrowIfNull(next);
ArgumentNullException.ThrowIfNull(bigQueryClientProvider);
ArgumentNullException.ThrowIfNull(timeProvider);
ArgumentNullException.ThrowIfNull(optionsAccessor);
ArgumentNullException.ThrowIfNull(aspNetCoreOptionsAccessor);
ArgumentNullException.ThrowIfNull(webRequestEventEnrichers);
ArgumentNullException.ThrowIfNull(logger);

_next = next;
_bigQueryClientProvider = bigQueryClientProvider;
TimeProvider = timeProvider;
_webRequestEventEnrichers = webRequestEventEnrichers;
Options = optionsAccessor.Value;
Expand Down Expand Up @@ -123,7 +118,7 @@ public async Task InvokeAsync(HttpContext context)
}
}

var bigQueryClient = await _bigQueryClientProvider.GetBigQueryClientAsync();
var bigQueryClient = Options.BigQueryClient;

var row = @event.ToBigQueryInsertRow();

Expand Down
2 changes: 1 addition & 1 deletion src/Dfe.Analytics/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ namespace Dfe.Analytics;

internal static class Constants
{
public const string RootConfigurationSectionName = "DfeAnalytics";
public const string ConfigurationSectionName = "DfeAnalytics";
}
5 changes: 3 additions & 2 deletions src/Dfe.Analytics/Dfe.Analytics.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@
<RepositoryType>git</RepositoryType>
<Description>A port of the DfE Analytics gem for .NET.</Description>
<MinVerTagPrefix>v</MinVerTagPrefix>
<MinVerMinimumMajorMinor>0.2</MinVerMinimumMajorMinor>
<MinVerMinimumMajorMinor>0.3</MinVerMinimumMajorMinor>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AnalysisMode>All</AnalysisMode>
<NoWarn>$(NoWarn);CA1716;CA1852;CA1848</NoWarn>
<NoWarn>$(NoWarn);CA1716;CA1852;CA1848;CA1812</NoWarn>
</PropertyGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" PrivateAssets="all" />
<PackageReference Include="Google.Apis.Auth" Version="1.71.0" />
<PackageReference Include="Google.Cloud.BigQuery.V2" Version="[3.0,4.0)" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="[8.0,)" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="[8.0,)" />
Expand Down
73 changes: 58 additions & 15 deletions src/Dfe.Analytics/DfeAnalyticsConfigureOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,85 @@

namespace Dfe.Analytics;

#pragma warning disable CA1812
internal class DfeAnalyticsConfigureOptions(IConfiguration configuration) : IConfigureOptions<DfeAnalyticsOptions>
#pragma warning restore CA1812
{
private readonly IConfiguration _configuration = configuration;

public void Configure(DfeAnalyticsOptions options)
{
ArgumentNullException.ThrowIfNull(options);

var section = _configuration.GetSection(Constants.RootConfigurationSectionName);
var section = configuration.GetSection(Constants.ConfigurationSectionName);

section.AssignConfigurationValueIfNotEmpty("DatasetId", v => options.DatasetId = v);
section.AssignConfigurationValueIfNotEmpty("Environment", v => options.Environment = v);
section.AssignConfigurationValueIfNotEmpty("Namespace", v => options.Namespace = v);
section.AssignConfigurationValueIfNotEmpty("TableId", v => options.TableId = v);
section.AssignConfigurationValueIfNotEmpty("ProjectId", v => options.ProjectId = v);
section.AssignConfigurationValueIfNotEmpty("Audience", v =>
{
options.FederatedAksAuthentication ??= new();
options.FederatedAksAuthentication.Audience = v;
});
section.AssignConfigurationValueIfNotEmpty("GenerateAccessTokenUrl", v =>
{
options.FederatedAksAuthentication ??= new();
options.FederatedAksAuthentication.ServiceAccountImpersonationUrl = v;
});

var credentialsJson = section["CredentialsJson"];

if (!string.IsNullOrEmpty(credentialsJson))
{
using var credentialsJsonDoc = JsonDocument.Parse(credentialsJson);
AssignConfigurationFromCredentialsJson(options, credentialsJsonDoc);
}
}

private void AssignConfigurationFromCredentialsJson(DfeAnalyticsOptions options, JsonDocument credentialsJson)
{
if (options.ProjectId is null &&
credentialsJson.RootElement.TryGetProperty("project_id", out var projectIdElement))
{
options.ProjectId = projectIdElement.GetString();
}

// We don't have ProjectId configured explicitly; see if it's set in the JSON credentials
if (options.ProjectId is null &&
credentialsJsonDoc.RootElement.TryGetProperty("project_id", out var projectIdElement) &&
projectIdElement.ValueKind == JsonValueKind.String)
if (options.FederatedAksAuthentication?.Audience is null &&
credentialsJson.RootElement.TryGetProperty("audience", out var audienceElement))
{
options.FederatedAksAuthentication ??= new();
options.FederatedAksAuthentication.Audience = audienceElement.GetString()!;
}

if (options.FederatedAksAuthentication?.ServiceAccountImpersonationUrl is null &&
credentialsJson.RootElement.TryGetProperty("service_account_impersonation_url", out var impersonationUrlElement))
{
options.FederatedAksAuthentication ??= new();
options.FederatedAksAuthentication.ServiceAccountImpersonationUrl = impersonationUrlElement.GetString()!;
}

if (options.BigQueryClient is null && options.ProjectId is { } projectId)
{
if (credentialsJson.RootElement.TryGetProperty("private_key", out _))
{
options.ProjectId = projectIdElement.GetString();
options.BigQueryClient = BigQueryClient.Create(
projectId,
GoogleCredential.FromJson(credentialsJson.ToString()));
}

if (credentialsJsonDoc.RootElement.TryGetProperty("private_key", out _) && options.ProjectId is string projectId)
else if (Environment.GetEnvironmentVariable(FederatedAksSubjectTokenProvider.TokenPathEnvironmentVariableName) is not null &&
options.FederatedAksAuthentication is { Audience: { } audience, ServiceAccountImpersonationUrl: { } serviceAccountImpersonationUrl })
{
var creds = GoogleCredential.FromJson(credentialsJson);
options.BigQueryClient = BigQueryClient.Create(projectId, creds);
options.BigQueryClient = BigQueryClient.Create(
projectId,
GoogleCredential.FromProgrammaticExternalAccountCredential(
new ProgrammaticExternalAccountCredential(
new ProgrammaticExternalAccountCredential.Initializer(
tokenUrl: "https://sts.googleapis.com/v1/token",
audience,
FederatedAksSubjectTokenProvider.SubjectTokenType,
#pragma warning disable CA2000
new FederatedAksSubjectTokenProvider())
{
ServiceAccountImpersonationUrl = serviceAccountImpersonationUrl
})));
#pragma warning restore CA2000
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/Dfe.Analytics/DfeAnalyticsOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,22 @@ public class DfeAnalyticsOptions
/// </summary>
public string? ProjectId { get; set; }

/// <summary>
/// The federated AKS authentication options.
/// </summary>
public FederatedAksAuthenticationOptions? FederatedAksAuthentication { get; set; }

[MemberNotNull(nameof(BigQueryClient))]
[MemberNotNull(nameof(DatasetId))]
[MemberNotNull(nameof(TableId))]
[MemberNotNull(nameof(Environment))]
internal void ValidateOptions()
{
if (BigQueryClient is null)
{
throw new InvalidOperationException($"{nameof(BigQueryClient)} has not been configured.");
}

if (DatasetId is null)
{
throw new InvalidOperationException($"{nameof(DatasetId)} has not been configured.");
Expand Down
2 changes: 0 additions & 2 deletions src/Dfe.Analytics/DfeAnalyticsPostConfigureOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@

namespace Dfe.Analytics;

#pragma warning disable CA1812
internal class DfeAnalyticsPostConfigureOptions : IPostConfigureOptions<DfeAnalyticsOptions>
#pragma warning restore CA1812
{
public void PostConfigure(string? name, DfeAnalyticsOptions options)
{
Expand Down
33 changes: 0 additions & 33 deletions src/Dfe.Analytics/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,40 +40,7 @@ public static DfeAnalyticsBuilder AddDfeAnalytics(this IServiceCollection servic
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<DfeAnalyticsOptions>, DfeAnalyticsPostConfigureOptions>());
services.TryAddSingleton(_ => TimeProvider.System);
services.Configure(setupAction);
services.TryAddSingleton<IBigQueryClientProvider, OptionsBigQueryClientProvider>();

return new DfeAnalyticsBuilder(services);
}

/// <summary>
/// Registers <see cref="AksFederatedBigQueryClientProvider"/> as the <see cref="IBigQueryClientProvider"/>.
/// </summary>
/// <param name="builder">The <see cref="DfeAnalyticsBuilder"/>.</param>
/// <returns>The <see cref="DfeAnalyticsBuilder"/> so that additional calls can be chained.</returns>
public static DfeAnalyticsBuilder UseFederatedAksBigQueryClientProvider(this DfeAnalyticsBuilder builder)
{
ArgumentNullException.ThrowIfNull(builder);

return builder.UseFederatedAksBigQueryClientProvider(_ => { });
}

/// <summary>
/// Registers <see cref="AksFederatedBigQueryClientProvider"/> and configures as the <see cref="IBigQueryClientProvider"/>.
/// </summary>
/// <param name="builder">The <see cref="DfeAnalyticsBuilder"/>.</param>
/// <param name="setupAction">
/// An <see cref="Action{FederatedAksAuthenticationOptions}"/> to configure the provided <see cref="FederatedAksAuthenticationOptions"/>.
/// </param>
/// <returns>The <see cref="DfeAnalyticsBuilder"/> so that additional calls can be chained.</returns>
public static DfeAnalyticsBuilder UseFederatedAksBigQueryClientProvider(this DfeAnalyticsBuilder builder, Action<FederatedAksAuthenticationOptions> setupAction)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(setupAction);

builder.Services.AddSingleton<IBigQueryClientProvider, AksFederatedBigQueryClientProvider>();
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<FederatedAksAuthenticationOptions>, FederatedAksAuthenticationConfigureOptions>());
builder.Services.Configure(setupAction);

return builder;
}
}
59 changes: 0 additions & 59 deletions src/Dfe.Analytics/FederatedAksAuthenticationConfigureOptions.cs

This file was deleted.

Loading