Skip to content

Commit

Permalink
Make License public
Browse files Browse the repository at this point in the history
  • Loading branch information
josephdecock committed Dec 3, 2024
1 parent 6bc9c46 commit fb60a05
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Duende.IdentityServer.Licensing.v2;

namespace Microsoft.AspNetCore.Builder;

Expand Down Expand Up @@ -91,6 +92,29 @@ internal static void Validate(this IApplicationBuilder app)
ValidateOptions(options, logger);

ValidateAsync(serviceProvider, logger).GetAwaiter().GetResult();

ValidateLicenseServices(serviceProvider);
}
}

private static void ValidateLicenseServices(IServiceProvider serviceProvider)
{
var licenseAccessor = serviceProvider.GetRequiredService<ILicenseAccessor>();
if (licenseAccessor is not LicenseAccessor)
{
throw new InvalidOperationException("Detected a custom ILicenseAccessor implementation. The default ILicenseAccessor is required.");
}

var counter = serviceProvider.GetRequiredService<IProtocolRequestCounter>();
if (counter is not ProtocolRequestCounter)
{
throw new InvalidOperationException("Detected a custom IProtocolRequestCounter implementation. The default IProtocolRequestCounter is required.");
}

var featureManager = serviceProvider.GetRequiredService<IFeatureManager>();
if (counter is not FeatureManager)
{
throw new InvalidOperationException("Detected a custom IFeatureManager implementation. The default IFeatureManager is required.");
}
}

Expand Down
7 changes: 5 additions & 2 deletions src/IdentityServer/Licensing/v2/FeatureManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ public IEnumerable<LicenseFeature> UsedFeatures()

public void UseFeature(LicenseFeature feature)
{
if (!_license.Current.IsEnabled(feature) && !AlreadyWarned(feature))
if ( _license.Current.IsConfigured &&
!_license.Current.IsEnabled(feature) &&
!AlreadyWarned(feature))
{
lock (_lock)
{
// Two AlreadyWarned checks makes the hotest code path lock-free
// Two AlreadyWarned checks makes the hottest code path lock-free
if (!AlreadyWarned(feature))
{
// TODO - this is not a very sophisticated message (we don't say which license would include the feature)
Expand All @@ -52,6 +54,7 @@ public void UseFeature(LicenseFeature feature)
}
}
}
// TODO - refactor the feature so that its value is already the feature mask
var featureMask = feature.ToFeatureMask();
Interlocked.Or(ref _usedFeatures, featureMask);
}
Expand Down
10 changes: 8 additions & 2 deletions src/IdentityServer/Licensing/v2/ILicenseAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
#nullable enable
namespace Duende.IdentityServer.Licensing.v2;

internal interface ILicenseAccessor
/// <summary>
/// Provides access to the current License.
/// </summary>
public interface ILicenseAccessor
{
/// <summary>
/// Gets the current IdentityServer license.
/// </summary>
License Current { get; }
}
}
29 changes: 18 additions & 11 deletions src/IdentityServer/Licensing/v2/License.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace Duende.IdentityServer.Licensing.v2;
/// <summary>
/// Models a Duende commercial license.
/// </summary>
internal class License
public sealed class License
{

/// <summary>
Expand All @@ -24,6 +24,7 @@ internal class License
internal License()
{
IsConfigured = false;
Features = [];
}

/// <summary>
Expand Down Expand Up @@ -64,26 +65,26 @@ internal License(ClaimsPrincipal claims)
/// <summary>
/// The serial number
/// </summary>
public int? SerialNumber { get; set; }
public int? SerialNumber { get; init; }

/// <summary>
/// The company name
/// </summary>
public string? CompanyName { get; set; }
public string? CompanyName { get; init; }
/// <summary>
/// The company contact info
/// </summary>
public string? ContactInfo { get; set; }
public string? ContactInfo { get; init; }

/// <summary>
/// The license expiration
/// </summary>
public DateTimeOffset? Expiration { get; set; }
public DateTimeOffset? Expiration { get; init; }

/// <summary>
/// The license edition
/// </summary>
public LicenseEdition? Edition { get; set; }
public LicenseEdition? Edition { get; init; }

/// <summary>
/// True if redistribution is enabled for this license, and false otherwise.
Expand All @@ -93,12 +94,12 @@ internal License(ClaimsPrincipal claims)
/// <summary>
/// The license features
/// </summary>
public IEnumerable<string> Features { get; set; } = [];
public IEnumerable<string> Features { get; init; }

/// <summary>
/// Extras
/// </summary>
public string? Extras { get; set; }
public string? Extras { get; init; }

/// <summary>
/// True if the license was configured in options or from a file, and false otherwise.
Expand All @@ -111,11 +112,17 @@ internal License(ClaimsPrincipal claims)
nameof(Edition),
nameof(Extras))
]
public bool IsConfigured { get; set; }
public bool IsConfigured { get; init; }

internal bool IsEnabled(LicenseFeature feature)
/// <summary>
/// Checks if a LicenseFeature is enabled in the current license. If there
/// is no configured license, this always returns true.
/// </summary>
/// <param name="feature"></param>
/// <returns></returns>
public bool IsEnabled(LicenseFeature feature)
{
return IsConfigured && (AllowedFeatureMask & feature.ToFeatureMask()) != 0;
return !IsConfigured || (AllowedFeatureMask & feature.ToFeatureMask()) != 0;
}


Expand Down
7 changes: 6 additions & 1 deletion src/IdentityServer/Licensing/v2/LicenseEdition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@

namespace Duende.IdentityServer.Licensing.v2;

internal enum LicenseEdition
/// <summary>
/// The editions of our license, which give access to different features.
/// </summary>
public enum LicenseEdition
{
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
Enterprise,
Business,
Starter,
Community,
Bff
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
}
36 changes: 34 additions & 2 deletions src/IdentityServer/Licensing/v2/LicenseFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,73 @@

namespace Duende.IdentityServer.Licensing.v2;

internal enum LicenseFeature
/// <summary>
/// The features of IdentityServer that can be enabled or disabled through the License.
/// </summary>
public enum LicenseFeature
{
/// <summary>
/// Automatic Key Management
/// </summary>
[Description("key_management")]
KeyManagement,

/// <summary>
/// Pushed Authorization Requests
/// </summary>
[Description("par")]
PAR,

/// <summary>
/// Resource Isolation
/// </summary>
[Description("resource_isolation")]
ResourceIsolation,

/// <summary>
/// Dyanmic External Providers
/// </summary>
[Description("dynamic_providers")]
DynamicProviders,

/// <summary>
/// Client Initiated Backchannel Authorization
/// </summary>
[Description("ciba")]
CIBA,

/// <summary>
/// Server-Side Sessions
/// </summary>
[Description("server_side_sessions")]
ServerSideSessions,

/// <summary>
/// Demonstrating Proof of Possesion
/// </summary>
[Description("dpop")]
DPoP,

/// <summary>
/// Configuration API
/// </summary>
[Description("config_api")]
DCR,

/// <summary>
/// ISV (same as Redistribution)
/// </summary>
[Description("isv")]
ISV,

/// <summary>
/// Dedistribution
/// </summary>
[Description("redistribution")]
Redistribution,
}

internal static class LicenseFeatureExtensions
{
internal static ulong ToFeatureMask(this LicenseFeature feature) => 1UL << (int) feature;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using Duende.IdentityServer.Licensing.v2;
using IntegrationTests.Common;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace IntegrationTests.Extensibility;

public class CustomLicenseServiceTests
{
[Fact]
public void customization_of_ILicenseAccessor_is_not_allowed()
{
var mockPipeline = new IdentityServerPipeline();

mockPipeline.OnPostConfigureServices += svcs =>
{
svcs.AddTransient<ILicenseAccessor, CustomLicenseAccessor>();
};

Assert.Throws<InvalidOperationException>(() => mockPipeline.Initialize());
}

[Fact]
public void customization_of_IProtocolRequestCounter_is_not_allowed()
{
var mockPipeline = new IdentityServerPipeline();

mockPipeline.OnPostConfigureServices += svcs =>
{
svcs.AddTransient<IProtocolRequestCounter, CustomProtocolRequestCounter>();
};

Assert.Throws<InvalidOperationException>(() => mockPipeline.Initialize());
}

[Fact]
public void customization_of_IFeatureManager_is_not_allowed()
{
var mockPipeline = new IdentityServerPipeline();

mockPipeline.OnPostConfigureServices += svcs =>
{
svcs.AddTransient<IFeatureManager, CustomFeatureManager>();
};

Assert.Throws<InvalidOperationException>(() => mockPipeline.Initialize());
}
}


internal class CustomLicenseAccessor : ILicenseAccessor
{
public License Current { get; }
}

internal class CustomProtocolRequestCounter : IProtocolRequestCounter
{
public uint RequestCount { get; }

public void Increment()
{
}
}

internal class CustomFeatureManager : IFeatureManager
{
public IEnumerable<LicenseFeature> UsedFeatures() => [];

public void UseFeature(LicenseFeature feature)
{
}
}
13 changes: 10 additions & 3 deletions test/IdentityServer.UnitTests/Licensing/v2/FeatureManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// See LICENSE in the project root for license information.

using System.Linq;
using System.Security.Claims;
using Duende.IdentityServer.Licensing.v2;
using FluentAssertions;
using Microsoft.Extensions.Logging.Testing;
Expand All @@ -23,9 +24,15 @@ public FeatureManagerTests()
private readonly TestLicenseAccessor _license;
private readonly FakeLogger<FeatureManager> _logger;

[Fact]
public void used_features_are_reported()
[Theory]
[InlineData(false)]
[InlineData(true)]
public void used_features_are_reported(bool configureLicense)
{
if (configureLicense)
{
_license.Current = LicenseFactory.Create(LicenseEdition.Enterprise);
}
_featureManager.UseFeature(LicenseFeature.PAR);
_featureManager.UseFeature(LicenseFeature.DPoP);
_featureManager.UseFeature(LicenseFeature.KeyManagement);
Expand All @@ -40,11 +47,11 @@ public void used_features_are_reported()
[Fact]
public void unlicensed_features_log_warnings_exactly_once()
{
_license.Current = LicenseFactory.Create(LicenseEdition.Starter);
_featureManager.UseFeature(LicenseFeature.PAR);
_featureManager.UseFeature(LicenseFeature.PAR);

_logger.Collector.GetSnapshot().Should()
.ContainSingle(r => r.Message == "Attempt to use feature PAR, but the license does not allow it");
}

}
Loading

0 comments on commit fb60a05

Please sign in to comment.