Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,19 @@ Mediator.LicenseKey = "<license key here>";
> Turn off the license warning by configuring logging in your logging start configuration:
> `builder.Logging.AddFilter("LuckyPennySoftware.MediatR.License", LogLevel.None);`

#### Auto-discovery via environment variables

If no license key is set in code, MediatR looks for one in environment variables. This is convenient for containerized and cloud environments, and for enterprises that share a single key across many services without code changes:

- `MEDIATR_LICENSE_KEY` – the MediatR-specific license key.
- `LUCKYPENNY_LICENSE_KEY` – a shared key usable across Lucky Penny products (for example, [AutoMapper](https://github.com/LuckyPennySoftware/AutoMapper) reads the same variable). Because it is shared, the key must be for a license that includes MediatR (a `Bundle` or MediatR edition); an AutoMapper-only license will not validate here.

The license key is resolved in the following order of precedence, using the first value found:

1. An explicit value set in code (`cfg.LicenseKey` or `Mediator.LicenseKey`).
2. The `MEDIATR_LICENSE_KEY` environment variable.
3. The `LUCKYPENNY_LICENSE_KEY` environment variable.

No code change is required when using an environment variable—just register MediatR as usual without setting the license key.

You can register for your license key at [MediatR.io](https://mediatr.io)
19 changes: 17 additions & 2 deletions src/MediatR/Licensing/LicenseAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

internal class LicenseAccessor
{
internal const string MediatRLicenseKeyEnvVariable = "MEDIATR_LICENSE_KEY";
internal const string SharedLicenseKeyEnvVariable = "LUCKYPENNY_LICENSE_KEY";

private readonly MediatRServiceConfiguration? _configuration;
private readonly ILogger _logger;

Expand Down Expand Up @@ -41,8 +44,7 @@
return _license;
}

var key = _configuration?.LicenseKey
?? Mediator.LicenseKey;
var key = ResolveLicenseKey(_configuration?.LicenseKey, Mediator.LicenseKey);

if (string.IsNullOrWhiteSpace(key))
{
Expand All @@ -56,6 +58,19 @@
}
}

/// <summary>
/// Resolves the license key using the first non-blank value, in order of precedence: the
/// explicitly configured keys (the <see cref="MediatRServiceConfiguration.LicenseKey"/> then the
/// static <see cref="Mediator.LicenseKey"/>), the product-specific <c>MEDIATR_LICENSE_KEY</c>
/// environment variable, then the shared <c>LUCKYPENNY_LICENSE_KEY</c> environment variable
/// (usable across Lucky Penny products).
/// </summary>
internal static string? ResolveLicenseKey(params string?[] explicitKeys) =>
explicitKeys
.Append(Environment.GetEnvironmentVariable(MediatRLicenseKeyEnvVariable))

Check failure on line 70 in src/MediatR/Licensing/LicenseAccessor.cs

View workflow job for this annotation

GitHub Actions / build

'string?[]' does not contain a definition for 'Append' and no accessible extension method 'Append' accepting a first argument of type 'string?[]' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 70 in src/MediatR/Licensing/LicenseAccessor.cs

View workflow job for this annotation

GitHub Actions / build

'string?[]' does not contain a definition for 'Append' and no accessible extension method 'Append' accepting a first argument of type 'string?[]' could be found (are you missing a using directive or an assembly reference?)
.Append(Environment.GetEnvironmentVariable(SharedLicenseKeyEnvVariable))
.FirstOrDefault(key => !string.IsNullOrWhiteSpace(key));
Comment thread
jbogard marked this conversation as resolved.
Outdated

private Claim[] ValidateKey(string licenseKey)
{
var handler = new JsonWebTokenHandler();
Expand Down
96 changes: 96 additions & 0 deletions test/MediatR.Tests/Licensing/LicenseKeyEnvironmentVariableTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System;
using MediatR.Licensing;
using Shouldly;
using Xunit;

namespace MediatR.Tests.Licensing;

// Mutates process-global environment variables, so it must not run alongside
// other tests that read them. Disable parallelization for this class.
[Collection(nameof(LicenseKeyEnvironmentVariableTests))]
[CollectionDefinition(nameof(LicenseKeyEnvironmentVariableTests), DisableParallelization = true)]
public class LicenseKeyEnvironmentVariableTests
{
Comment thread
jbogard marked this conversation as resolved.
private const string MediatREnvVar = LicenseAccessor.MediatRLicenseKeyEnvVariable;
private const string SharedEnvVar = LicenseAccessor.SharedLicenseKeyEnvVariable;

[Fact]
public void ExplicitKey_TakesPrecedence_OverBothEnvironmentVariables()
{
const string explicitKey = "explicit-license-key";
WithEnvironment(mediatR: "env-mediatr-key", shared: "env-shared-key", () =>
LicenseAccessor.ResolveLicenseKey(explicitKey).ShouldBe(explicitKey));
}

[Fact]
public void ConfigurationKey_TakesPrecedence_OverStaticKey()
{
const string configKey = "config-license-key";
WithEnvironment(mediatR: null, shared: null, () =>
LicenseAccessor.ResolveLicenseKey(configKey, "static-license-key").ShouldBe(configKey));
}

[Fact]
public void StaticKey_Used_WhenConfigurationKeyIsBlank()
{
const string staticKey = "static-license-key";
WithEnvironment(mediatR: null, shared: null, () =>
LicenseAccessor.ResolveLicenseKey(" ", staticKey).ShouldBe(staticKey));
}

[Fact]
public void MediatREnvironmentVariable_Used_WhenNoExplicitKey()
{
const string mediatRKey = "env-mediatr-key";
WithEnvironment(mediatR: mediatRKey, shared: null, () =>
LicenseAccessor.ResolveLicenseKey(null, null).ShouldBe(mediatRKey));
}

[Fact]
public void SharedEnvironmentVariable_Used_WhenOnlyItIsSet()
{
const string sharedKey = "env-shared-key";
WithEnvironment(mediatR: null, shared: sharedKey, () =>
LicenseAccessor.ResolveLicenseKey(null, null).ShouldBe(sharedKey));
}

[Fact]
public void MediatREnvironmentVariable_TakesPrecedence_OverSharedEnvironmentVariable()
{
const string mediatRKey = "env-mediatr-key";
WithEnvironment(mediatR: mediatRKey, shared: "env-shared-key", () =>
LicenseAccessor.ResolveLicenseKey(null, null).ShouldBe(mediatRKey));
}

[Fact]
public void EnvironmentVariable_Used_WhenExplicitKeysAreBlank()
{
const string mediatRKey = "env-mediatr-key";
WithEnvironment(mediatR: mediatRKey, shared: null, () =>
LicenseAccessor.ResolveLicenseKey("", " ").ShouldBe(mediatRKey));
}

[Fact]
public void ReturnsNull_WhenNothingIsSet()
{
WithEnvironment(mediatR: null, shared: null, () =>
LicenseAccessor.ResolveLicenseKey(null, null).ShouldBeNull());
}

private static void WithEnvironment(string? mediatR, string? shared, Action assert)
{
var originalMediatR = Environment.GetEnvironmentVariable(MediatREnvVar);
var originalShared = Environment.GetEnvironmentVariable(SharedEnvVar);
try
{
Environment.SetEnvironmentVariable(MediatREnvVar, mediatR);
Environment.SetEnvironmentVariable(SharedEnvVar, shared);
assert();
}
finally
{
Environment.SetEnvironmentVariable(MediatREnvVar, originalMediatR);
Environment.SetEnvironmentVariable(SharedEnvVar, originalShared);
}
}
}
Loading