Skip to content

Commit

Permalink
Merge pull request #3 from DuendeSoftware/brock/update-is-7.0
Browse files Browse the repository at this point in the history
update to .net8 and is7
  • Loading branch information
brockallen authored Mar 12, 2024
2 parents 1649f98 + 2ab37df commit 56136a5
Show file tree
Hide file tree
Showing 56 changed files with 1,116 additions and 428 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ on:
env:
AZURE_WEBAPP_NAME: DuendeSoftware-Demo # set this to your application's name
AZURE_WEBAPP_PACKAGE_PATH: './publish' # set this to the path to your web app project, defaults to the repository root
DOTNET_VERSION: '8.0.x' # set this to the dot net version to use

jobs:
build-and-deploy:
Expand All @@ -28,6 +29,8 @@ jobs:
# Setup .NET Core SDK
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

# Run dotnet build and publish
- name: dotnet build and publish
Expand Down
4 changes: 2 additions & 2 deletions src/DPoP/DPoPJwtBearerEvents.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using ApiHost;
using IdentityModel;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using System.Text;
Expand Down Expand Up @@ -131,7 +131,7 @@ public override Task Challenge(JwtBearerChallengeContext context)
}
}

context.Response.Headers.Add(HeaderNames.WWWAuthenticate, sb.ToString());
context.Response.Headers.Append(HeaderNames.WWWAuthenticate, sb.ToString());


if (context.HttpContext.Items.ContainsKey("DPoP-Nonce"))
Expand Down
2 changes: 0 additions & 2 deletions src/DPoP/DPoPProofValidatonContext.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Collections.Generic;
using System.Security.Claims;

namespace DPoPApi;

Expand Down
34 changes: 17 additions & 17 deletions src/DPoP/DPoPProofValidator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using DPoPApi;
using IdentityModel;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Logging;
Expand All @@ -13,7 +12,7 @@
using System.Text.Json;
using System.Threading.Tasks;

namespace ApiHost;
namespace DPoPApi;

public class DPoPProofValidator
{
Expand Down Expand Up @@ -83,12 +82,12 @@ public async Task<DPoPProofValidatonResult> ValidateAsync(DPoPProofValidatonCont
return result;
}

Logger.LogDebug("Successfully validated DPoP proof token");
Logger.LogDebug("Successfully validated DPoP proof token with thumbprint: {jkt}", result.JsonWebKeyThumbprint);
result.IsError = false;
}
finally
{
if (result.IsError)
if (result.IsError && String.IsNullOrWhiteSpace(result.Error))
{
result.Error = OidcConstants.TokenErrors.InvalidDPoPProof;
}
Expand Down Expand Up @@ -117,21 +116,21 @@ protected virtual Task ValidateHeaderAsync(DPoPProofValidatonContext context, DP
return Task.CompletedTask;
}

if (!token.TryGetHeaderValue<string>("typ", out var typ) || typ != JwtClaimTypes.JwtTypes.DPoPProofToken)
if (!token.TryGetHeaderValue<string>(JwtClaimTypes.TokenType, out var typ) || typ != JwtClaimTypes.JwtTypes.DPoPProofToken)
{
result.IsError = true;
result.ErrorDescription = "Invalid 'typ' value.";
return Task.CompletedTask;
}

if (!token.TryGetHeaderValue<string>("alg", out var alg) || !SupportedDPoPSigningAlgorithms.Contains(alg))
if (!token.TryGetHeaderValue<string>(JwtClaimTypes.Algorithm, out var alg) || !SupportedDPoPSigningAlgorithms.Contains(alg))
{
result.IsError = true;
result.ErrorDescription = "Invalid 'alg' value.";
return Task.CompletedTask;
}

if (!token.TryGetHeaderValue<IDictionary<string, object>>(JwtClaimTypes.JsonWebKey, out var jwkValues))
if (!token.TryGetHeaderValue<JsonElement>(JwtClaimTypes.JsonWebKey, out var jwkValues))
{
result.IsError = true;
result.ErrorDescription = "Invalid 'jwk' value.";
Expand Down Expand Up @@ -170,7 +169,7 @@ protected virtual Task ValidateHeaderAsync(DPoPProofValidatonContext context, DP
/// <summary>
/// Validates the signature.
/// </summary>
protected virtual Task ValidateSignatureAsync(DPoPProofValidatonContext context, DPoPProofValidatonResult result)
protected virtual async Task ValidateSignatureAsync(DPoPProofValidatonContext context, DPoPProofValidatonResult result)
{
TokenValidationResult tokenValidationResult;

Expand All @@ -186,27 +185,25 @@ protected virtual Task ValidateSignatureAsync(DPoPProofValidatonContext context,
};

var handler = new JsonWebTokenHandler();
tokenValidationResult = handler.ValidateToken(context.ProofToken, tvp);
tokenValidationResult = await handler.ValidateTokenAsync(context.ProofToken, tvp);
}
catch (Exception ex)
{
Logger.LogDebug("Error parsing DPoP token: {error}", ex.Message);
result.IsError = true;
result.ErrorDescription = "Invalid signature on DPoP token.";
return Task.CompletedTask;
return;
}

if (tokenValidationResult.Exception != null)
{
Logger.LogDebug("Error parsing DPoP token: {error}", tokenValidationResult.Exception.Message);
result.IsError = true;
result.ErrorDescription = "Invalid signature on DPoP token.";
return Task.CompletedTask;
return;
}

result.Payload = tokenValidationResult.Claims;

return Task.CompletedTask;
}

/// <summary>
Expand Down Expand Up @@ -270,11 +267,11 @@ protected virtual async Task ValidatePayloadAsync(DPoPProofValidatonContext cont
{
if (iat is int)
{
result.IssuedAt = (int) iat;
result.IssuedAt = (int)iat;
}
if (iat is long)
{
result.IssuedAt = (long) iat;
result.IssuedAt = (long)iat;
}
}

Expand Down Expand Up @@ -337,6 +334,9 @@ protected virtual async Task ValidateReplayAsync(DPoPProofValidatonContext conte
// longer than the likelyhood of proof token expiration, which is done before replay
skew *= 2;
var cacheDuration = dpopOptions.ProofTokenValidityDuration + skew;

Logger.LogDebug("Adding proof token with jti {jti} to replay cache for duration {cacheDuration}", result.TokenId, cacheDuration);

await ReplayCache.AddAsync(ReplayCachePurpose, result.TokenId, DateTimeOffset.UtcNow.Add(cacheDuration));
}

Expand Down Expand Up @@ -447,11 +447,11 @@ protected virtual ValueTask<long> GetUnixTimeFromNonceAsync(DPoPProofValidatonCo
return ValueTask.FromResult(iat);
}
}
catch (Exception ex)
catch(Exception ex)
{
Logger.LogDebug("Error parsing DPoP 'nonce' value: {error}", ex.ToString());
}

return ValueTask.FromResult<long>(0);
}

Expand Down
1 change: 0 additions & 1 deletion src/DPoP/DPoPServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using ApiHost;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
Expand Down
8 changes: 4 additions & 4 deletions src/Duende.IdentityServer.Demo.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Duende.IdentityServer" Version="6.3.5" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="6.0.0" />
<PackageReference Include="Duende.IdentityServer" Version="7.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.2" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
</ItemGroup>
</Project>
6 changes: 4 additions & 2 deletions src/Pages/Account/AccessDenied.cshtml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Microsoft.AspNetCore.Mvc;
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

using Microsoft.AspNetCore.Mvc.RazorPages;

namespace IdentityServerHost.Pages.Account;
Expand All @@ -8,4 +10,4 @@ public class AccessDeniedModel : PageModel
public void OnGet()
{
}
}
}
40 changes: 40 additions & 0 deletions src/Pages/Account/Create/Index.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@page
@model IdentityServerHost.Pages.Create.Index

<div class="login-page">
<div class="lead">
<h1>Create Account</h1>
</div>

<partial name="_ValidationSummary" />

<div class="row">

<div class="col-sm-6">
<form asp-page="/Account/Create/Index">
<input type="hidden" asp-for="Input.ReturnUrl" />

<div class="form-group">
<label asp-for="Input.Username"></label>
<input class="form-control" placeholder="Username" asp-for="Input.Username" autofocus>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input type="password" class="form-control" placeholder="Password" asp-for="Input.Password" autocomplete="off">
</div>
<div class="form-group">
<label asp-for="Input.Name"></label>
<input type="text" class="form-control" placeholder="Name" asp-for="Input.Name">
</div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input type="email" class="form-control" placeholder="Email" asp-for="Input.Email" >
</div>

<button class="btn btn-primary" name="Input.Button" value="create">Create</button>
<button class="btn btn-secondary" name="Input.Button" value="cancel">Cancel</button>
</form>
</div>

</div>
</div>
121 changes: 121 additions & 0 deletions src/Pages/Account/Create/Index.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

using Duende.IdentityServer;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;
using Duende.IdentityServer.Test;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace IdentityServerHost.Pages.Create;

[SecurityHeaders]
[AllowAnonymous]
public class Index : PageModel
{
private readonly TestUserStore _users;
private readonly IIdentityServerInteractionService _interaction;

[BindProperty]
public InputModel Input { get; set; } = default!;

public Index(
IIdentityServerInteractionService interaction,
TestUserStore? users = null)

Check warning on line 27 in src/Pages/Account/Create/Index.cshtml.cs

View workflow job for this annotation

GitHub Actions / build-and-deploy

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
// this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity)
_users = users ?? throw new InvalidOperationException("Please call 'AddTestUsers(TestUsers.Users)' on the IIdentityServerBuilder in Startup or remove the TestUserStore from the AccountController.");

_interaction = interaction;
}

public IActionResult OnGet(string? returnUrl)

Check warning on line 35 in src/Pages/Account/Create/Index.cshtml.cs

View workflow job for this annotation

GitHub Actions / build-and-deploy

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
Input = new InputModel { ReturnUrl = returnUrl };
return Page();
}

public async Task<IActionResult> OnPost()
{
// check if we are in the context of an authorization request
var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl);

// the user clicked the "cancel" button
if (Input.Button != "create")
{
if (context != null)
{
// if the user cancels, send a result back into IdentityServer as if they
// denied the consent (even if this client does not require consent).
// this will send back an access denied OIDC error response to the client.
await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied);

// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
if (context.IsNativeClient())
{
// The client is native, so this change in how to
// return the response is for better UX for the end user.
return this.LoadingPage(Input.ReturnUrl);
}

return Redirect(Input.ReturnUrl ?? "~/");
}
else
{
// since we don't have a valid context, then we just go back to the home page
return Redirect("~/");
}
}

if (_users.FindByUsername(Input.Username) != null)
{
ModelState.AddModelError("Input.Username", "Invalid username");
}

if (ModelState.IsValid)
{
var user = _users.CreateUser(Input.Username, Input.Password, Input.Name, Input.Email);

// issue authentication cookie with subject ID and username
var isuser = new IdentityServerUser(user.SubjectId)
{
DisplayName = user.Username
};

await HttpContext.SignInAsync(isuser);

if (context != null)
{
if (context.IsNativeClient())
{
// The client is native, so this change in how to
// return the response is for better UX for the end user.
return this.LoadingPage(Input.ReturnUrl);
}

// we can trust Input.ReturnUrl since GetAuthorizationContextAsync returned non-null
return Redirect(Input.ReturnUrl ?? "~/");
}

// request for a local page
if (Url.IsLocalUrl(Input.ReturnUrl))
{
return Redirect(Input.ReturnUrl);
}
else if (string.IsNullOrEmpty(Input.ReturnUrl))
{
return Redirect("~/");
}
else
{
// user might have clicked on a malicious link - should be logged
throw new ArgumentException("invalid return URL");
}
}

return Page();
}
}
22 changes: 22 additions & 0 deletions src/Pages/Account/Create/InputModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

using System.ComponentModel.DataAnnotations;

namespace IdentityServerHost.Pages.Create;

public class InputModel
{
[Required]
public string? Username { get; set; }

Check warning on line 11 in src/Pages/Account/Create/InputModel.cs

View workflow job for this annotation

GitHub Actions / build-and-deploy

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

[Required]
public string? Password { get; set; }

Check warning on line 14 in src/Pages/Account/Create/InputModel.cs

View workflow job for this annotation

GitHub Actions / build-and-deploy

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

public string? Name { get; set; }

Check warning on line 16 in src/Pages/Account/Create/InputModel.cs

View workflow job for this annotation

GitHub Actions / build-and-deploy

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public string? Email { get; set; }

Check warning on line 17 in src/Pages/Account/Create/InputModel.cs

View workflow job for this annotation

GitHub Actions / build-and-deploy

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

public string? ReturnUrl { get; set; }

Check warning on line 19 in src/Pages/Account/Create/InputModel.cs

View workflow job for this annotation

GitHub Actions / build-and-deploy

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

public string? Button { get; set; }

Check warning on line 21 in src/Pages/Account/Create/InputModel.cs

View workflow job for this annotation

GitHub Actions / build-and-deploy

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
}
Loading

0 comments on commit 56136a5

Please sign in to comment.