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

update to .net8 and is7 #3

Merged
merged 2 commits into from
Mar 12, 2024
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading