Skip to content

Commit 2c2d89f

Browse files
authored
fix: avoid false ProjectApiKey deprecation warning (#265)
* fix: avoid false ProjectApiKey deprecation warning * ci: remove invalid workflow control characters * docs: explain configuration restore approach
1 parent 53e00c6 commit 2c2d89f

6 files changed

Lines changed: 97 additions & 10 deletions

File tree

.changeset/calm-badgers-bind.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"PostHog": patch
3+
"PostHog.AspNetCore": patch
4+
---
5+
6+
Prevent `ProjectToken`-only configuration from logging the deprecated `ProjectApiKey` warning.

.github/workflows/call-flags-project-board.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SECURITY PLACEHOLDER - DO NOT USE THIS WORKFLOW NAME
1+
# SECURITY PLACEHOLDER - DO NOT USE THIS WORKFLOW NAME
22
#
33
# This workflow previously existed and was compromised. This placeholder file
44
# exists to allow blocking this workflow name in GitHub's branch protection rules.
@@ -21,5 +21,5 @@ jobs:
2121
steps:
2222
- name: This workflow is blocked
2323
run: |
24-
echo "A workflow with this name was previously compromised and is now blocked."
24+
echo "A workflow with this name was previously compromised and is now blocked."
2525
exit 1

src/PostHog/Config/PostHogConfigurationBuilderExtensions.cs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.Extensions.Configuration;
22
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.Options;
34
using static PostHog.Library.Ensure;
45

56
namespace PostHog.Config;
@@ -31,12 +32,24 @@ public static IPostHogConfigurationBuilder PostConfigure(
3132
public static IPostHogConfigurationBuilder UseConfigurationSection(
3233
this IPostHogConfigurationBuilder builder,
3334
IConfigurationSection section) =>
34-
NotNull(builder).Use(services
35-
=> services.Configure<PostHogOptions>(section));
36-
37-
#if NETSTANDARD2_0 || NETSTANDARD2_1
38-
static IServiceCollection Configure<T>(this IServiceCollection services, IConfigurationSection section)
39-
where T : class =>
40-
services.Configure<T>(section.Bind);
35+
NotNull(builder).Use(services =>
36+
{
37+
#if !(NETSTANDARD2_0 || NETSTANDARD2_1)
38+
services.AddSingleton<IOptionsChangeTokenSource<PostHogOptions>>(
39+
new ConfigurationChangeTokenSource<PostHogOptions>(section));
4140
#endif
41+
services.Configure<PostHogOptions>(options =>
42+
{
43+
var projectKeyState = options.GetProjectKeyState();
44+
section.Bind(options);
45+
46+
// ConfigurationBinder writes alias getter values back when their keys are absent. A raw
47+
// ProjectApiKey getter would avoid this but break its public alias behavior, so restore raw values
48+
// for absent keys instead.
49+
options.RestoreProjectKeyState(
50+
projectKeyState,
51+
restoreProjectToken: section["ProjectToken"] is null,
52+
restoreProjectApiKey: section["ProjectApiKey"] is null);
53+
});
54+
});
4255
}

src/PostHog/Config/PostHogOptions.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,24 @@ public string? ProjectToken
3030

3131
internal bool HasLegacyProjectApiKey => _projectApiKey is not null;
3232

33+
internal (string? ProjectToken, string? ProjectApiKey) GetProjectKeyState() =>
34+
(_projectToken, _projectApiKey);
35+
36+
internal void RestoreProjectKeyState(
37+
(string? ProjectToken, string? ProjectApiKey) state,
38+
bool restoreProjectToken,
39+
bool restoreProjectApiKey)
40+
{
41+
if (restoreProjectToken)
42+
{
43+
_projectToken = state.ProjectToken;
44+
}
45+
if (restoreProjectApiKey)
46+
{
47+
_projectApiKey = state.ProjectApiKey;
48+
}
49+
}
50+
3351
internal void Normalize()
3452
{
3553
_projectToken = _projectToken.NullIfEmpty();

tests/UnitTests.AspNetCore/RegistrationTests.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.Extensions.Configuration;
44
using Microsoft.Extensions.DependencyInjection;
55
using Microsoft.Extensions.Hosting;
6+
using Microsoft.Extensions.Logging;
67
using Microsoft.Extensions.Options;
78
using PostHog;
89
using PostHog.Config;
@@ -41,6 +42,26 @@ public void ReadsSettingsFromPostHogConfigurationSection()
4142
Assert.Equal(TimeSpan.FromSeconds(10), options.FeatureFlagPollInterval);
4243
}
4344

45+
[Fact]
46+
public void DoesNotLogLegacyWarningWhenOnlyProjectTokenIsConfigured()
47+
{
48+
var builder = WebApplication.CreateSlimBuilder();
49+
builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
50+
{
51+
["PostHog:ProjectToken"] = "fake-not-so-secret",
52+
});
53+
using var logger = new FakeLoggerProvider();
54+
builder.Services.AddSingleton<ILoggerFactory>(logger);
55+
56+
builder.AddPostHog();
57+
58+
using var provider = builder.Services.BuildServiceProvider();
59+
Assert.NotNull(provider.GetRequiredService<IPostHogClient>());
60+
Assert.DoesNotContain(
61+
logger.GetAllEvents(minimumLevel: LogLevel.Warning),
62+
log => log.Message?.Contains("ProjectApiKey is deprecated", StringComparison.Ordinal) == true);
63+
}
64+
4465
[Fact]
4566
public void ReadsSecretKeyFromPostHogConfigurationSection()
4667
{
@@ -81,12 +102,18 @@ public void ReadsLegacyProjectApiKeyFromPostHogConfigurationSection()
81102
{
82103
["PostHog:ProjectApiKey"] = "fake-not-so-secret",
83104
});
105+
using var logger = new FakeLoggerProvider();
106+
services.AddSingleton<ILoggerFactory>(logger);
84107

85108
builder.AddPostHog();
86109

87-
var provider = services.BuildServiceProvider();
110+
using var provider = services.BuildServiceProvider();
111+
Assert.NotNull(provider.GetRequiredService<IPostHogClient>());
88112
var options = provider.GetRequiredService<IOptions<PostHogOptions>>().Value;
89113
Assert.Equal("fake-not-so-secret", options.ProjectToken);
114+
Assert.Contains(
115+
logger.GetAllEvents(minimumLevel: LogLevel.Warning),
116+
log => log.Message?.Contains("ProjectApiKey is deprecated", StringComparison.Ordinal) == true);
90117
}
91118

92119
[Fact]

tests/UnitTests/Config/RegistrationTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,29 @@ public void CanReadLegacyProjectApiKeyConfiguration()
155155
Assert.Equal("fake-public-project-api-key", options.ProjectToken);
156156
}
157157

158+
[Fact]
159+
public void LaterConfigurationSectionOverridesLegacyProjectApiKey()
160+
{
161+
var services = new ServiceCollection();
162+
var configuration = new ConfigurationBuilder()
163+
.AddInMemoryCollection(new Dictionary<string, string?>
164+
{
165+
["First:ProjectApiKey"] = "first-project-api-key",
166+
["Second:ProjectApiKey"] = "second-project-api-key",
167+
})
168+
.Build();
169+
services.AddPostHog(options =>
170+
{
171+
options.UseConfigurationSection(configuration.GetSection("First"));
172+
options.UseConfigurationSection(configuration.GetSection("Second"));
173+
});
174+
175+
var provider = services.BuildServiceProvider();
176+
var options = provider.GetRequiredService<IOptions<PostHogOptions>>().Value;
177+
178+
Assert.Equal("second-project-api-key", options.ProjectToken);
179+
}
180+
158181
[Fact]
159182
public void ProjectApiKeyAliasMirrorsProjectToken()
160183
{

0 commit comments

Comments
 (0)