Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Joe/dcr-samples #132

Merged
merged 13 commits into from
Jul 20, 2023
Merged
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dotnet.defaultSolution": "Permissions.sln"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Duende.IdentityServer.Configuration" Version="6.3.0-rc.2" />
<PackageReference Include="Duende.IdentityServer.Configuration.EntityFramework" Version="6.3.0-rc.2" />
<PackageReference Include="Duende.IdentityServer.Configuration" Version="6.3.3" />
<PackageReference Include="Duende.IdentityServer.Configuration.EntityFramework" Version="6.3.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.16" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.16" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Duende.IdentityServer.Configuration;
using Duende.IdentityServer.Configuration.Configuration;
using Duende.IdentityServer.Configuration.Models;
using Duende.IdentityServer.Configuration.Models.DynamicClientRegistration;
using Duende.IdentityServer.Configuration.RequestProcessing;
using Duende.IdentityServer.Models;
using IdentityModel;

namespace Configuration;

/// <summary>
/// This request processor can set the client secret, if it is supplied as a
/// property of the dynamic client registration request document. A special
/// scope is also
/// </summary>
public class PermissionsCheckingRequestProcessor : DynamicClientRegistrationRequestProcessor
{
private readonly ILogger<PermissionsCheckingRequestProcessor> _logger;

public PermissionsCheckingRequestProcessor(IdentityServerConfigurationOptions options, IClientConfigurationStore store, ILogger<PermissionsCheckingRequestProcessor> logger)
: base(options, store)
{
_logger = logger;
}

protected override async Task<(Secret, string)> GenerateSecret(DynamicClientRegistrationContext context)
{
if (context.Request.Extensions.TryGetValue("client_secret", out var secretParam))
{
// Remove the client_secret, so that we don't echo back a duplicate
// or inconsistent value
context.Request.Extensions.Remove("client_secret");

if(!context.Caller.HasClaim("scope", "IdentityServer.Configuration:SetClientSecret"))
{
_logger.LogWarning("The dynamic client request includes a secret, but the required IdentityServer.Configuration:SetClientSecret scope is missing. The secret is ignored.");
}
else
{
var plainText = secretParam.ToString();
ArgumentNullException.ThrowIfNull(plainText);
var secret = new Secret(plainText.ToSha256());

return (secret, plainText);
}
}
return await base.GenerateSecret(context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Configuration;
using Duende.IdentityServer.Configuration;
using Duende.IdentityServer.Configuration.EntityFramework;
using Duende.IdentityServer.Configuration.RequestProcessing;
using Duende.IdentityServer.EntityFramework.DbContexts;
using Duende.IdentityServer.EntityFramework.Storage;
using Microsoft.EntityFrameworkCore;

Console.Title = "Configuration API";

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddIdentityServerConfiguration(opt => {})
.AddClientConfigurationStore();

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddConfigurationDbContext<ConfigurationDbContext>(options =>
{
options.ConfigureDbContext = b =>
b.UseSqlite(connectionString, dbOpts => dbOpts.MigrationsAssembly(typeof(Program).Assembly.FullName));
});

builder.Services.AddAuthentication("token")
.AddJwtBearer("token", options =>
{
options.Authority = "https://localhost:5001";
options.MapInboundClaims = false;

options.TokenValidationParameters.ValidateAudience = false;
options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
});

builder.Services.AddAuthorization(opt =>
{
opt.AddPolicy("DCR", policy =>
{
policy.RequireClaim("scope", "IdentityServer.Configuration");
});
});

builder.Services.AddTransient<IDynamicClientRegistrationRequestProcessor, PermissionsCheckingRequestProcessor>();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();
app.MapDynamicClientRegistration().RequireAuthorization("DCR");

app.Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Text.Json;
using IdentityModel.Client;

namespace ConsoleDcrClient;

public static class DcrResponseExtensions
{
public static void Show(this DynamicClientRegistrationResponse response)
{
Console.WriteLine(JsonSerializer.Serialize(new
{
response.ClientId,
response.ClientSecret
}, new JsonSerializerOptions { WriteIndented = true }));

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using ConsoleDcrClient;
using IdentityModel.Client;


Console.Title = "Dynamic Client Registration - Client Credentials Flow";

"Obtaining initial access token (which does not allow setting client ids)".ConsoleYellow();
var badTokenResponse = await RequestTokenAsync(scope: "IdentityServer.Configuration");
badTokenResponse.Show();
Console.ReadLine();

"\n\nAttempting to register a dynamic client with a specific client secret, but without needed scope".ConsoleYellow();
var badDcrResponse = await RegisterClient(badTokenResponse.AccessToken);
"This succeeded, but ignored our attempt to set a client secret.".ConsoleYellow();
Console.ReadLine();

$"\n\nObtaining access token for dynamic client using clientId: {badDcrResponse.ClientId} and secret {badDcrResponse.ClientSecret}".ConsoleYellow();
var badDynamicClientToken = await RequestTokenAsync(badDcrResponse.ClientId, badDcrResponse.ClientSecret);
badDynamicClientToken.Show();
Console.ReadLine();

"Obtaining a new access token (which does allow setting client ids)".ConsoleYellow();
var goodTokenResponse = await RequestTokenAsync(scope: "IdentityServer.Configuration IdentityServer.Configuration:SetClientSecret");
goodTokenResponse.Show();
Console.ReadLine();

"\n\nReattempting to register a dynamic client with a specific client secret".ConsoleYellow();
var goodDcrResponse = await RegisterClient(goodTokenResponse.AccessToken);
"This succeeded, and respected our attempt to set a client secret.".ConsoleYellow();
Console.ReadLine();

$"\n\nObtaining access token for dynamic client using clientId: {goodDcrResponse.ClientId} and secret {goodDcrResponse.ClientSecret}".ConsoleYellow();
var dynamicClientToken = await RequestTokenAsync(goodDcrResponse.ClientId, goodDcrResponse.ClientSecret);
dynamicClientToken.Show();
Console.ReadLine();

"\n\nCalling API".ConsoleYellow();
await CallServiceAsync(dynamicClientToken.AccessToken);
Console.ReadLine();

static async Task<DynamicClientRegistrationResponse> RegisterClient(string accessToken)
{
var client = new HttpClient();
client.SetBearerToken(accessToken);

var request = new DynamicClientRegistrationRequest
{
Address = "https://localhost:5002/connect/dcr",
Document = new DynamicClientRegistrationDocument
{

GrantTypes = { "client_credentials" },
Scope = "SimpleApi"
}
};

request.Document.Extensions.Add("client_secret", "hunter2");

var response = await client.RegisterClientAsync(request);

if (response.IsError)
{
Console.WriteLine(response.Error);
return null;
}

response.Show();

return response;
}

static async Task<TokenResponse> RequestTokenAsync(string clientId = "client", string clientSecret = "secret", string scope = null)
{
var client = new HttpClient();

var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001");
if (disco.IsError) throw new Exception(disco.Error);

var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,

ClientId = clientId,
ClientSecret = clientSecret,
Scope = scope
});

if (response.IsError) throw new Exception(response.Error);
return response;
}

static async Task CallServiceAsync(string token)
{
var baseAddress = Constants.SimpleApi;

var client = new HttpClient
{
BaseAddress = new Uri(baseAddress)
};

client.SetBearerToken(token);
var response = await client.GetStringAsync("identity");

"\n\nService claims:".ConsoleGreen();
Console.WriteLine(response.PrettyPrintJson());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Text;
using System.Text.Json;
using IdentityModel;
using IdentityModel.Client;

namespace ConsoleDcrClient;

public static class TokenResponseExtensions
{
public static void Show(this TokenResponse response)
{
if (!response.IsError)
{
if (response.AccessToken.Contains("."))
{
"\nAccess Token (decoded):".ConsoleGreen();

var parts = response.AccessToken.Split('.');
var header = parts[0];
var payload = parts[1];

Console.WriteLine(PrettyPrintJson(Encoding.UTF8.GetString(Base64Url.Decode(header))));
Console.WriteLine(PrettyPrintJson(Encoding.UTF8.GetString(Base64Url.Decode(payload))));
} else
{
"Token response:".ConsoleGreen();
Console.WriteLine(response.Json);
}
}
else
{
if (response.ErrorType == ResponseErrorType.Http)
{
"HTTP error: ".ConsoleGreen();
Console.WriteLine(response.Error);
"HTTP status code: ".ConsoleGreen();
Console.WriteLine(response.HttpStatusCode);
}
else
{
"Protocol error response:".ConsoleGreen();
Console.WriteLine(response.Raw);
}
}
}

public static string PrettyPrintJson(this string raw)
{
var doc = JsonDocument.Parse(raw).RootElement;
return JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Duende.IdentityServer.Models;

namespace IdentityServer;

public static class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};

public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("IdentityServer.Configuration"),
new ApiScope("IdentityServer.Configuration:SetClientSecret"),
new ApiScope("SimpleApi")
};

public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "client",
ClientName = "Client Credentials Client for DCR",

AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = { new Secret("secret".Sha256()) },

AllowedScopes = { "IdentityServer.Configuration", "IdentityServer.Configuration:SetClientSecret" }
}

};
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Duende.IdentityServer.EntityFramework" Version="6.3.0-rc.1" />
<PackageReference Include="Duende.IdentityServer.EntityFramework" Version="6.3.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="6.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using Serilog;
using System.Globalization;

Console.Title = "IdentityServer Host";

Log.Logger = new LoggerConfiguration()
.WriteTo.Console(formatProvider: CultureInfo.InvariantCulture)
.CreateBootstrapLogger();
Expand Down
40 changes: 40 additions & 0 deletions IdentityServer/v6/Configuration/Permissions/Permissions.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Configuration", "Configuration\Configuration.csproj", "{CD4E158A-9173-49A5-BF10-F5CAE6E5D3B1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleDcrClient", "ConsoleDcrClient\ConsoleDcrClient.csproj", "{D134466E-58AE-4787-984B-FB6F95EEA969}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentityServer", "IdentityServer\IdentityServer.csproj", "{C6A9607B-3D1F-4CC5-B276-48526E5CB940}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleApi", "SimpleApi\SimpleApi.csproj", "{CCAA779F-8528-4351-8333-80B60A7C4FAC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CD4E158A-9173-49A5-BF10-F5CAE6E5D3B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CD4E158A-9173-49A5-BF10-F5CAE6E5D3B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD4E158A-9173-49A5-BF10-F5CAE6E5D3B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD4E158A-9173-49A5-BF10-F5CAE6E5D3B1}.Release|Any CPU.Build.0 = Release|Any CPU
{D134466E-58AE-4787-984B-FB6F95EEA969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D134466E-58AE-4787-984B-FB6F95EEA969}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D134466E-58AE-4787-984B-FB6F95EEA969}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D134466E-58AE-4787-984B-FB6F95EEA969}.Release|Any CPU.Build.0 = Release|Any CPU
{C6A9607B-3D1F-4CC5-B276-48526E5CB940}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C6A9607B-3D1F-4CC5-B276-48526E5CB940}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C6A9607B-3D1F-4CC5-B276-48526E5CB940}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C6A9607B-3D1F-4CC5-B276-48526E5CB940}.Release|Any CPU.Build.0 = Release|Any CPU
{CCAA779F-8528-4351-8333-80B60A7C4FAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CCAA779F-8528-4351-8333-80B60A7C4FAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CCAA779F-8528-4351-8333-80B60A7C4FAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CCAA779F-8528-4351-8333-80B60A7C4FAC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
Loading