Skip to content

Commit

Permalink
Merge pull request #1272 from DuendeSoftware/joe/dcr-license
Browse files Browse the repository at this point in the history
Add licensing to DCR
  • Loading branch information
brockallen authored May 10, 2023
2 parents 437c84a + 7bf3c7a commit 274b79f
Show file tree
Hide file tree
Showing 8 changed files with 570 additions and 5 deletions.
1 change: 1 addition & 0 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
<PackageReference Update="Microsoft.Extensions.Options.ConfigurationExtensions" Version="$(ExtensionsVersion)"/>

<!--misc -->
<PackageReference Update="Microsoft.IdentityModel.JsonWebTokens" Version="$(WilsonVersion)"/>
<PackageReference Update="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="$(WilsonVersion)"/>
<PackageReference Update="System.IdentityModel.Tokens.Jwt" Version="$(WilsonVersion)"/>
<PackageReference Update="AutoMapper" Version="[12.0.0,13.0)"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ namespace Duende.IdentityServer.Configuration.Configuration;
/// </summary>
public class IdentityServerConfigurationOptions
{
/// <summary>
/// Gets or Sets the license key. Typically, this is the same license key as
/// used by IdentityServer.
/// </summary>
public string? LicenseKey { get; set; }

/// <summary>
/// Options for Dynamic Client Registration
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

<ItemGroup>
<PackageReference Include="IdentityModel" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
</ItemGroup>

<ItemGroup>
Expand Down
23 changes: 23 additions & 0 deletions src/Configuration/Extensions/ConfigurationEndpointExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Duende.IdentityServer.Configuration.Configuration;
using Duende.IdentityServer.Configuration.Licensing;

namespace Duende.IdentityServer.Configuration;

Expand All @@ -12,11 +17,29 @@ namespace Duende.IdentityServer.Configuration;
/// </summary>
public static class ConfigurationEndpointExtensions
{
internal static bool _licenseChecked;

/// <summary>
/// Maps the dynamic client registration endpoint.
/// </summary>
public static IEndpointConventionBuilder MapDynamicClientRegistration(this IEndpointRouteBuilder endpoints, string path = "/connect/dcr")
{
endpoints.CheckLicense();

return endpoints.MapPost(path, (DynamicClientRegistrationEndpoint endpoint, HttpContext context) => endpoint.Process(context));
}

internal static void CheckLicense(this IEndpointRouteBuilder endpoints)
{
if (_licenseChecked == false)
{
var loggerFactory = endpoints.ServiceProvider.GetRequiredService<ILoggerFactory>();
var options = endpoints.ServiceProvider.GetRequiredService<IOptions<IdentityServerConfigurationOptions>>().Value;

LicenseValidator.Initalize(loggerFactory, options);
LicenseValidator.ValidateLicense();
}

_licenseChecked = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public IdentityServerConfigurationBuilder(IServiceCollection services)
public static class ConfigurationServiceCollectionExtensions
{
/// <summary>
/// Adds IdentityServer.Configuration services
/// Adds IdentityServer.Configuration services with configuration options
/// specified by a lambda.
/// </summary>
public static IdentityServerConfigurationBuilder AddIdentityServerConfiguration(this IServiceCollection services, Action<IdentityServerConfigurationOptions> setupAction)
{
Expand All @@ -47,7 +48,8 @@ public static IdentityServerConfigurationBuilder AddIdentityServerConfiguration(
}

/// <summary>
/// Adds IdentityServer.Configuration services
/// Adds IdentityServer.Configuration services with configuration options
/// specified by an <see cref="IConfiguration" />.
/// </summary>
public static IdentityServerConfigurationBuilder AddIdentityServerConfiguration(this IServiceCollection services, IConfiguration configuration)
{
Expand Down
248 changes: 248 additions & 0 deletions src/Configuration/Licensing/License.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.


using System.Security.Claims;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Duende.IdentityServer.Configuration.Licensing;

internal class License
{
// for testing
internal License(params Claim[] claims)
: this(new ClaimsPrincipal(new ClaimsIdentity(claims)))
{
}

public License(ClaimsPrincipal claims)
{
if (Int32.TryParse(claims.FindFirst("id")?.Value, out var id))
{
Id = id;
}

CompanyName = claims.FindFirst("company_name")?.Value;
ContactInfo = claims.FindFirst("contact_info")?.Value;

if (Int64.TryParse(claims.FindFirst("exp")?.Value, out var exp))
{
var expDate = DateTimeOffset.FromUnixTimeSeconds(exp);
Expiration = expDate.UtcDateTime;
}

var edition = claims.FindFirst("edition")?.Value;
if (!Enum.TryParse<License.LicenseEdition>(edition, true, out var editionValue))
{
throw new Exception($"Invalid edition in license: '{edition}'");
}

Edition = editionValue;
RedistributionFeature = claims.HasClaim("feature", "isv") || claims.HasClaim("feature", "redistribution");
Extras = claims.FindFirst("extras")?.Value;

if (IsCommunityEdition && RedistributionFeature)
{
throw new Exception("Invalid License: Redistribution is not valid for community edition.");
}

if (IsBffEdition && RedistributionFeature)
{
throw new Exception("Invalid License: Redistribution is not valid for BFF edition.");
}

if (IsBffEdition)
{
// for BFF-only edition we set BFF flag and ignore all other features
BffFeature = true;
ClientLimit = 0;
IssuerLimit = 0;
return;
}

KeyManagementFeature = claims.HasClaim("feature", "key_management");
switch (Edition)
{
case LicenseEdition.Enterprise:
case LicenseEdition.Business:
case LicenseEdition.Community:
KeyManagementFeature = true;
break;
}

ResourceIsolationFeature = claims.HasClaim("feature", "resource_isolation");
switch (Edition)
{
case LicenseEdition.Enterprise:
case LicenseEdition.Community:
ResourceIsolationFeature = true;
break;
}

DynamicProvidersFeature = claims.HasClaim("feature", "dynamic_providers");
switch (Edition)
{
case LicenseEdition.Enterprise:
case LicenseEdition.Community:
DynamicProvidersFeature = true;
break;
}

CibaFeature = claims.HasClaim("feature", "ciba");
switch (Edition)
{
case LicenseEdition.Enterprise:
case LicenseEdition.Community:
CibaFeature = true;
break;
}

ServerSideSessionsFeature = claims.HasClaim("feature", "server_side_sessions");
switch (Edition)
{
case LicenseEdition.Enterprise:
case LicenseEdition.Business:
case LicenseEdition.Community:
ServerSideSessionsFeature = true;
break;
}

ConfigApiFeature = claims.HasClaim("feature", "config_api");
switch (Edition)
{
case LicenseEdition.Enterprise:
case LicenseEdition.Business:
case LicenseEdition.Community:
ConfigApiFeature = true;
break;
}

DPoPFeature = claims.HasClaim("feature", "dpop");
switch (Edition)
{
case LicenseEdition.Enterprise:
case LicenseEdition.Community:
DPoPFeature = true;
break;
}

BffFeature = claims.HasClaim("feature", "bff");
switch (Edition)
{
case LicenseEdition.Enterprise:
case LicenseEdition.Business:
case LicenseEdition.Community:
BffFeature = true;
break;
}


if (!claims.HasClaim("feature", "unlimited_clients"))
{
// default values
if (RedistributionFeature)
{
// default for all ISV editions
ClientLimit = 5;
}
else
{
// defaults limits for non-ISV editions
switch (Edition)
{
case LicenseEdition.Business:
ClientLimit = 15;
break;
case LicenseEdition.Starter:
ClientLimit = 5;
break;
}
}

if (Int32.TryParse(claims.FindFirst("client_limit")?.Value, out var clientLimit))
{
// explicit, so use that value
ClientLimit = clientLimit;
}

if (!RedistributionFeature)
{
// these for the non-ISV editions that always have unlimited, regardless of explicit value
switch (Edition)
{
case LicenseEdition.Enterprise:
case LicenseEdition.Community:
// unlimited
ClientLimit = null;
break;
}
}
}

if (!claims.HasClaim("feature", "unlimited_issuers"))
{
// default
IssuerLimit = 1;

if (Int32.TryParse(claims.FindFirst("issuer_limit")?.Value, out var issuerLimit))
{
IssuerLimit = issuerLimit;
}

// these for the editions that always have unlimited, regardless of explicit value
switch (Edition)
{
case LicenseEdition.Enterprise:
case LicenseEdition.Community:
// unlimited
IssuerLimit = null;
break;
}
}
}

public int Id { get; set; }

public string? CompanyName { get; set; }
public string? ContactInfo { get; set; }

public DateTime? Expiration { get; set; }

public LicenseEdition Edition { get; set; }

internal bool IsEnterpriseEdition => Edition == LicenseEdition.Enterprise;
internal bool IsBusinessEdition => Edition == LicenseEdition.Business;
internal bool IsStarterEdition => Edition == LicenseEdition.Starter;
internal bool IsCommunityEdition => Edition == LicenseEdition.Community;
internal bool IsBffEdition => Edition == LicenseEdition.Bff;

public int? ClientLimit { get; set; }
public int? IssuerLimit { get; set; }

public bool KeyManagementFeature { get; set; }
public bool ResourceIsolationFeature { get; set; }
public bool DynamicProvidersFeature { get; set; }
public bool RedistributionFeature { get; set; }
public bool BffFeature { get; set; }
public bool CibaFeature { get; set; }
public bool ServerSideSessionsFeature { get; set; }
public bool ConfigApiFeature { get; set; }
public bool DPoPFeature { get; set; }

public string? Extras { get; set; }

public enum LicenseEdition
{
Enterprise,
Business,
Starter,
Community,
Bff
}

public override string ToString()
{
return JsonSerializer.Serialize(this, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
}
}
Loading

0 comments on commit 274b79f

Please sign in to comment.