diff --git a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Controllers/HomeController.cs b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Controllers/HomeController.cs index c1991d2..166b31c 100644 --- a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Controllers/HomeController.cs +++ b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Controllers/HomeController.cs @@ -14,134 +14,133 @@ using ScottBrady.IdentityModel.Tokens.Branca; using ScottBrady.IdentityModel.Tokens.Paseto; -namespace ScottBrady.IdentityModel.Samples.AspNetCore.Controllers +namespace ScottBrady.IdentityModel.Samples.AspNetCore.Controllers; + +public class HomeController : Controller { - public class HomeController : Controller - { - private readonly SampleOptions options; - private readonly UserManager userManager; + private readonly SampleOptions options; + private readonly UserManager userManager; - public HomeController(SampleOptions options, UserManager userManager) - { - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - } + public HomeController(SampleOptions options, UserManager userManager) + { + this.options = options ?? throw new ArgumentNullException(nameof(options)); + this.userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); + } - public IActionResult Index() - { - return View(); - } - - [HttpGet] - public IActionResult Branca() - { - var handler = new BrancaTokenHandler(); + public IActionResult Index() + { + return View(); + } - var token = handler.CreateToken(new SecurityTokenDescriptor - { - Issuer = "me", - Audience = "you", - EncryptingCredentials = options.BrancaEncryptingCredentials - }); + [HttpGet] + public IActionResult Branca() + { + var handler = new BrancaTokenHandler(); - var parsedToken = handler.DecryptToken(token, ((SymmetricSecurityKey) options.BrancaEncryptingCredentials.Key).Key); + var token = handler.CreateToken(new SecurityTokenDescriptor + { + Issuer = "me", + Audience = "you", + EncryptingCredentials = options.BrancaEncryptingCredentials + }); - return View("Index", new TokenModel - { - Type = "Branca", - Token = token, - Payload = Encoding.UTF8.GetString(parsedToken.Payload) - }); - } + var parsedToken = handler.DecryptToken(token, ((SymmetricSecurityKey) options.BrancaEncryptingCredentials.Key).Key); - [HttpGet] - public IActionResult Paseto(string version) + return View("Index", new TokenModel { - var handler = new PasetoTokenHandler(); - - SigningCredentials signingCredentials; - if (version == PasetoConstants.Versions.V1) - signingCredentials = new SigningCredentials(options.PasetoV1PrivateKey, SecurityAlgorithms.RsaSsaPssSha384); - else if (version == PasetoConstants.Versions.V2) - signingCredentials = new SigningCredentials(options.EdDsaPrivateKey, ExtendedSecurityAlgorithms.EdDsa); - else - throw new NotSupportedException("Unsupported version"); + Type = "Branca", + Token = token, + Payload = Encoding.UTF8.GetString(parsedToken.Payload) + }); + } + + [HttpGet] + public IActionResult Paseto(string version) + { + var handler = new PasetoTokenHandler(); + + SigningCredentials signingCredentials; + if (version == PasetoConstants.Versions.V1) + signingCredentials = new SigningCredentials(options.PasetoV1PrivateKey, SecurityAlgorithms.RsaSsaPssSha384); + else if (version == PasetoConstants.Versions.V2) + signingCredentials = new SigningCredentials(options.EdDsaPrivateKey, ExtendedSecurityAlgorithms.EdDsa); + else + throw new NotSupportedException("Unsupported version"); - var descriptor = new PasetoSecurityTokenDescriptor(version, PasetoConstants.Purposes.Public) - { - Issuer = "me", - Audience = "you", - SigningCredentials = signingCredentials - }; + var descriptor = new PasetoSecurityTokenDescriptor(version, PasetoConstants.Purposes.Public) + { + Issuer = "me", + Audience = "you", + SigningCredentials = signingCredentials + }; - var token = handler.CreateToken(descriptor); - var payload = descriptor.ToJwtPayload(JwtDateTimeFormat.Iso); + var token = handler.CreateToken(descriptor); + var payload = descriptor.ToJwtPayload(JwtDateTimeFormat.Iso); - return View("Index", new TokenModel - { - Type = "PASETO", - Token = token, - Payload = payload - }); - } + return View("Index", new TokenModel + { + Type = "PASETO", + Token = token, + Payload = payload + }); + } - [HttpGet] - public IActionResult EdDsaJwt() + [HttpGet] + public IActionResult EdDsaJwt() + { + var handler = new JsonWebTokenHandler(); + + var descriptor = new SecurityTokenDescriptor { - var handler = new JsonWebTokenHandler(); + Issuer = "me", + Audience = "you", + SigningCredentials = new SigningCredentials(options.EdDsaPrivateKey, ExtendedSecurityAlgorithms.EdDsa) + }; - var descriptor = new SecurityTokenDescriptor - { - Issuer = "me", - Audience = "you", - SigningCredentials = new SigningCredentials(options.EdDsaPrivateKey, ExtendedSecurityAlgorithms.EdDsa) - }; + var token = handler.CreateToken(descriptor); + var payload = descriptor.ToJwtPayload(JwtDateTimeFormat.Iso); - var token = handler.CreateToken(descriptor); - var payload = descriptor.ToJwtPayload(JwtDateTimeFormat.Iso); + return View("Index", new TokenModel + { + Type = "EdDSA JWT", + Token = token, + Payload = payload + }); + } - return View("Index", new TokenModel - { - Type = "EdDSA JWT", - Token = token, - Payload = payload - }); - } + [HttpGet] + [Authorize(AuthenticationSchemes = "branca-bearer,paseto-bearer-v1,paseto-bearer-v2,eddsa")] + public IActionResult CallApi() + { + return Ok(); + } - [HttpGet] - [Authorize(AuthenticationSchemes = "branca-bearer,paseto-bearer-v1,paseto-bearer-v2,eddsa")] - public IActionResult CallApi() - { - return Ok(); - } + [HttpGet] + public IActionResult PasswordRules() + { + return View(new PasswordRulesModel()); + } - [HttpGet] - public IActionResult PasswordRules() - { - return View(new PasswordRulesModel()); - } + [HttpPost] + public async Task PasswordRules(PasswordRulesModel model) + { + if (!ModelState.IsValid) return View(model); - [HttpPost] - public async Task PasswordRules(PasswordRulesModel model) + var errors = new List(); + foreach (var validator in userManager.PasswordValidators) { - if (!ModelState.IsValid) return View(model); - - var errors = new List(); - foreach (var validator in userManager.PasswordValidators) + var result = await validator.ValidateAsync(userManager, new IdentityUser(), model.Password); + if (!result.Succeeded) { - var result = await validator.ValidateAsync(userManager, new IdentityUser(), model.Password); - if (!result.Succeeded) + if (result.Errors.Any()) { - if (result.Errors.Any()) - { - errors.AddRange(result.Errors.Select(x => x.Description)); - } + errors.AddRange(result.Errors.Select(x => x.Description)); } } - - model.Errors = errors; - model.Message = errors.Any() ? "Password failed server-side validation" : "Password passed server-side validation"; - return View(model); } + + model.Errors = errors; + model.Message = errors.Any() ? "Password failed server-side validation" : "Password passed server-side validation"; + return View(model); } -} +} \ No newline at end of file diff --git a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Models/PasswordRulesModel.cs b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Models/PasswordRulesModel.cs index 79f0ec9..d9de0ee 100644 --- a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Models/PasswordRulesModel.cs +++ b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Models/PasswordRulesModel.cs @@ -1,11 +1,10 @@ using System.Collections.Generic; -namespace ScottBrady.IdentityModel.Samples.AspNetCore.Models +namespace ScottBrady.IdentityModel.Samples.AspNetCore.Models; + +public class PasswordRulesModel { - public class PasswordRulesModel - { - public string Message { get; set; } - public IEnumerable Errors { get; set; } = new List(); - public string Password { get; set; } - } + public string Message { get; set; } + public IEnumerable Errors { get; set; } = new List(); + public string Password { get; set; } } \ No newline at end of file diff --git a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Models/TokenModel.cs b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Models/TokenModel.cs index 78a9597..e147320 100644 --- a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Models/TokenModel.cs +++ b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Models/TokenModel.cs @@ -1,9 +1,8 @@ -namespace ScottBrady.IdentityModel.Samples.AspNetCore.Models +namespace ScottBrady.IdentityModel.Samples.AspNetCore.Models; + +public class TokenModel { - public class TokenModel - { - public string Type { get; set; } - public string Token { get; set; } - public string Payload { get; set; } - } + public string Type { get; set; } + public string Token { get; set; } + public string Payload { get; set; } } \ No newline at end of file diff --git a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Program.cs b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Program.cs index 59e8264..25f058e 100644 --- a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Program.cs +++ b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Program.cs @@ -1,20 +1,19 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; -namespace ScottBrady.IdentityModel.Samples.AspNetCore +namespace ScottBrady.IdentityModel.Samples.AspNetCore; + +public class Program { - public class Program + public static void Main(string[] args) { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); + CreateHostBuilder(args).Build().Run(); } -} + + private static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); +} \ No newline at end of file diff --git a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/SampleOptions.cs b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/SampleOptions.cs index b6f7c1f..930ef5e 100644 --- a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/SampleOptions.cs +++ b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/SampleOptions.cs @@ -4,37 +4,36 @@ using ScottBrady.IdentityModel.Crypto; using ScottBrady.IdentityModel.Tokens; -namespace ScottBrady.IdentityModel.Samples.AspNetCore +namespace ScottBrady.IdentityModel.Samples.AspNetCore; + +public class SampleOptions { - public class SampleOptions - { - private EncryptingCredentials encryptingCredentials; + private EncryptingCredentials encryptingCredentials; - public EncryptingCredentials BrancaEncryptingCredentials + public EncryptingCredentials BrancaEncryptingCredentials + { + get { - get + if (encryptingCredentials == null) { - if (encryptingCredentials == null) - { - var key = new byte[32]; - RandomNumberGenerator.Create().GetBytes(key); + var key = new byte[32]; + RandomNumberGenerator.Create().GetBytes(key); - encryptingCredentials = new EncryptingCredentials( - new SymmetricSecurityKey(key), - ExtendedSecurityAlgorithms.XChaCha20Poly1305); - } - - return encryptingCredentials; + encryptingCredentials = new EncryptingCredentials( + new SymmetricSecurityKey(key), + ExtendedSecurityAlgorithms.XChaCha20Poly1305); } + + return encryptingCredentials; } + } - public RsaSecurityKey PasetoV1PrivateKey = new RsaSecurityKey(RSA.Create()); - public RsaSecurityKey PasetoV1PublicKey => new RsaSecurityKey(RSA.Create(PasetoV1PrivateKey.Rsa.ExportParameters(false))); + public RsaSecurityKey PasetoV1PrivateKey = new RsaSecurityKey(RSA.Create()); + public RsaSecurityKey PasetoV1PublicKey => new RsaSecurityKey(RSA.Create(PasetoV1PrivateKey.Rsa.ExportParameters(false))); - public readonly EdDsaSecurityKey EdDsaPublicKey = new EdDsaSecurityKey( - EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {X =Convert.FromBase64String("doaS7QILHBdnPULlgs1fX0MWpd1wak14r1yT6ae/b4M=")})); + public readonly EdDsaSecurityKey EdDsaPublicKey = new EdDsaSecurityKey( + EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {X =Convert.FromBase64String("doaS7QILHBdnPULlgs1fX0MWpd1wak14r1yT6ae/b4M=")})); - public readonly EdDsaSecurityKey EdDsaPrivateKey= new EdDsaSecurityKey( - EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {D =Convert.FromBase64String("TYXei5+8Qd2ZqKIlEuJJ3S50WYuocFTrqK+3/gHVH9B2hpLtAgscF2c9QuWCzV9fQxal3XBqTXivXJPpp79vgw==")})); - } + public readonly EdDsaSecurityKey EdDsaPrivateKey= new EdDsaSecurityKey( + EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {D =Convert.FromBase64String("TYXei5+8Qd2ZqKIlEuJJ3S50WYuocFTrqK+3/gHVH9B2hpLtAgscF2c9QuWCzV9fQxal3XBqTXivXJPpp79vgw==")})); } \ No newline at end of file diff --git a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Startup.cs b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Startup.cs index 1900384..6862804 100644 --- a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Startup.cs +++ b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Startup.cs @@ -9,91 +9,90 @@ using ScottBrady.IdentityModel.Tokens.Branca; using ScottBrady.IdentityModel.Tokens.Paseto; -namespace ScottBrady.IdentityModel.Samples.AspNetCore +namespace ScottBrady.IdentityModel.Samples.AspNetCore; + +public class Startup { - public class Startup + public void ConfigureServices(IServiceCollection services) { - public void ConfigureServices(IServiceCollection services) - { - IdentityModelEventSource.ShowPII = true; + IdentityModelEventSource.ShowPII = true; - services.AddControllersWithViews() - .AddRazorRuntimeCompilation(); + services.AddControllersWithViews() + .AddRazorRuntimeCompilation(); - var sampleOptions = new SampleOptions(); - services.AddSingleton(sampleOptions); + var sampleOptions = new SampleOptions(); + services.AddSingleton(sampleOptions); - services.AddAuthentication() - .AddJwtBearer("branca-bearer", options => - { - options.SecurityTokenValidators.Clear(); - options.SecurityTokenValidators.Add(new BrancaTokenHandler()); - options.TokenValidationParameters.TokenDecryptionKey = sampleOptions.BrancaEncryptingCredentials.Key; - options.TokenValidationParameters.ValidIssuer = "me"; - options.TokenValidationParameters.ValidAudience = "you"; - }) - .AddJwtBearer("paseto-bearer-v1", options => - { - options.SecurityTokenValidators.Clear(); - options.SecurityTokenValidators.Add(new PasetoTokenHandler( - new Dictionary {{PasetoConstants.Versions.V1, new PasetoVersion1()}})); + services.AddAuthentication() + .AddJwtBearer("branca-bearer", options => + { + options.SecurityTokenValidators.Clear(); + options.SecurityTokenValidators.Add(new BrancaTokenHandler()); + options.TokenValidationParameters.TokenDecryptionKey = sampleOptions.BrancaEncryptingCredentials.Key; + options.TokenValidationParameters.ValidIssuer = "me"; + options.TokenValidationParameters.ValidAudience = "you"; + }) + .AddJwtBearer("paseto-bearer-v1", options => + { + options.SecurityTokenValidators.Clear(); + options.SecurityTokenValidators.Add(new PasetoTokenHandler( + new Dictionary {{PasetoConstants.Versions.V1, new PasetoVersion1()}})); - options.TokenValidationParameters.IssuerSigningKey = sampleOptions.PasetoV1PublicKey; - options.TokenValidationParameters.ValidIssuer = "me"; - options.TokenValidationParameters.ValidAudience = "you"; - }) - .AddJwtBearer("paseto-bearer-v2", options => - { - options.SecurityTokenValidators.Clear(); - options.SecurityTokenValidators.Add(new PasetoTokenHandler( - new Dictionary {{PasetoConstants.Versions.V2, new PasetoVersion2()}})); + options.TokenValidationParameters.IssuerSigningKey = sampleOptions.PasetoV1PublicKey; + options.TokenValidationParameters.ValidIssuer = "me"; + options.TokenValidationParameters.ValidAudience = "you"; + }) + .AddJwtBearer("paseto-bearer-v2", options => + { + options.SecurityTokenValidators.Clear(); + options.SecurityTokenValidators.Add(new PasetoTokenHandler( + new Dictionary {{PasetoConstants.Versions.V2, new PasetoVersion2()}})); - options.TokenValidationParameters.IssuerSigningKey = sampleOptions.EdDsaPublicKey; - options.TokenValidationParameters.ValidIssuer = "me"; - options.TokenValidationParameters.ValidAudience = "you"; - }) - .AddJwtBearer("eddsa", options => - { - options.TokenValidationParameters.IssuerSigningKey = sampleOptions.EdDsaPublicKey; - options.TokenValidationParameters.ValidIssuer = "me"; - options.TokenValidationParameters.ValidAudience = "you"; - }); + options.TokenValidationParameters.IssuerSigningKey = sampleOptions.EdDsaPublicKey; + options.TokenValidationParameters.ValidIssuer = "me"; + options.TokenValidationParameters.ValidAudience = "you"; + }) + .AddJwtBearer("eddsa", options => + { + options.TokenValidationParameters.IssuerSigningKey = sampleOptions.EdDsaPublicKey; + options.TokenValidationParameters.ValidIssuer = "me"; + options.TokenValidationParameters.ValidAudience = "you"; + }); - services.AddIdentityCore(options => + services.AddIdentityCore(options => + { + options.Password = new ExtendedPasswordOptions { - options.Password = new ExtendedPasswordOptions - { - RequiredLength = 15, - RequireDigit = true, - RequireLowercase = true, - RequireUppercase = true, - RequireNonAlphanumeric = true, + RequiredLength = 15, + RequireDigit = true, + RequireLowercase = true, + RequireUppercase = true, + RequireNonAlphanumeric = true, - // extended options - MaxLength = 64, - MaxConsecutiveChars = 3 - }; - }) - .AddPasswordValidator>() // Required for max length and consecutive character checks - .AddEntityFrameworkStores(); + // extended options + MaxLength = 64, + MaxConsecutiveChars = 3 + }; + }) + .AddPasswordValidator>() // Required for max length and consecutive character checks + .AddEntityFrameworkStores(); - services.AddDbContext(options => options.UseInMemoryDatabase("test")); - } + services.AddDbContext(options => options.UseInMemoryDatabase("test")); + } - public void Configure(IApplicationBuilder app) - { - app.UseDeveloperExceptionPage(); + public void Configure(IApplicationBuilder app) + { + app.UseDeveloperExceptionPage(); - app.UseHttpsRedirection(); + app.UseHttpsRedirection(); - app.UseStaticFiles(); + app.UseStaticFiles(); - app.UseRouting(); + app.UseRouting(); - app.UseAuthentication(); - app.UseAuthorization(); + app.UseAuthentication(); + app.UseAuthorization(); - app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute()); - } + app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute()); } -} +} \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.AspNetCore/Identity/ExtendedPasswordOptions.cs b/src/ScottBrady.IdentityModel.AspNetCore/Identity/ExtendedPasswordOptions.cs index ee96bc5..ae22022 100644 --- a/src/ScottBrady.IdentityModel.AspNetCore/Identity/ExtendedPasswordOptions.cs +++ b/src/ScottBrady.IdentityModel.AspNetCore/Identity/ExtendedPasswordOptions.cs @@ -1,20 +1,19 @@ using Microsoft.AspNetCore.Identity; -namespace ScottBrady.IdentityModel.AspNetCore.Identity +namespace ScottBrady.IdentityModel.AspNetCore.Identity; + +/// +/// Extends to support all passwordrules attribute values. +/// +public class ExtendedPasswordOptions : PasswordOptions { /// - /// Extends to support all passwordrules attribute values. + /// The maximum length of the password. /// - public class ExtendedPasswordOptions : PasswordOptions - { - /// - /// The maximum length of the password. - /// - public int? MaxLength { get; set; } + public int? MaxLength { get; set; } - /// - /// The maximum number of consecutive identical characters allowed in the password. - /// - public int? MaxConsecutiveChars { get; set; } - } + /// + /// The maximum number of consecutive identical characters allowed in the password. + /// + public int? MaxConsecutiveChars { get; set; } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.AspNetCore/Identity/ExtendedPasswordValidator.cs b/src/ScottBrady.IdentityModel.AspNetCore/Identity/ExtendedPasswordValidator.cs index be236e5..35cb032 100644 --- a/src/ScottBrady.IdentityModel.AspNetCore/Identity/ExtendedPasswordValidator.cs +++ b/src/ScottBrady.IdentityModel.AspNetCore/Identity/ExtendedPasswordValidator.cs @@ -4,62 +4,61 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; -namespace ScottBrady.IdentityModel.AspNetCore.Identity +namespace ScottBrady.IdentityModel.AspNetCore.Identity; + +/// +/// ASP.NET Core Identity PasswordValidator for max length and max consecutive character checks. +/// +public class ExtendedPasswordValidator : IPasswordValidator where TUser : class { /// - /// ASP.NET Core Identity PasswordValidator for max length and max consecutive character checks. + /// Validates the password for max length and max consecutive characters. /// - public class ExtendedPasswordValidator : IPasswordValidator where TUser : class + public Task ValidateAsync(UserManager manager, TUser user, string password) { - /// - /// Validates the password for max length and max consecutive characters. - /// - public Task ValidateAsync(UserManager manager, TUser user, string password) - { - if (manager == null) throw new ArgumentNullException(nameof(manager)); - if (password == null) throw new ArgumentNullException(nameof(password)); + if (manager == null) throw new ArgumentNullException(nameof(manager)); + if (password == null) throw new ArgumentNullException(nameof(password)); - var errors = new List(); + var errors = new List(); - if (manager.Options.Password is ExtendedPasswordOptions options) + if (manager.Options.Password is ExtendedPasswordOptions options) + { + if (options.MaxLength.HasValue && 0 < options.MaxLength && options.MaxLength < password.Length) { - if (options.MaxLength.HasValue && 0 < options.MaxLength && options.MaxLength < password.Length) + errors.Add(new IdentityError { - errors.Add(new IdentityError - { - Code = "PasswordTooLong", - Description = $"Passwords must be no longer than {options.MaxLength} characters" - }); - } + Code = "PasswordTooLong", + Description = $"Passwords must be no longer than {options.MaxLength} characters" + }); + } - if (options.MaxConsecutiveChars.HasValue - && 0 <= options.MaxConsecutiveChars - && HasConsecutiveCharacters(password, options.MaxConsecutiveChars.Value)) + if (options.MaxConsecutiveChars.HasValue + && 0 <= options.MaxConsecutiveChars + && HasConsecutiveCharacters(password, options.MaxConsecutiveChars.Value)) + { + errors.Add(new IdentityError { - errors.Add(new IdentityError - { - Code = "TooManyConsecutiveCharacters", - Description = $"Password must not contain more than {options.MaxConsecutiveChars} consecutive characters" - }); - } + Code = "TooManyConsecutiveCharacters", + Description = $"Password must not contain more than {options.MaxConsecutiveChars} consecutive characters" + }); } - - return Task.FromResult(errors.Count == 0 ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray())); } + + return Task.FromResult(errors.Count == 0 ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray())); + } - /// - /// Checks for consecutive characters using Regex. - /// Does not account for UTF16 surrogate pairs. - /// Adapted from https://codereview.stackexchange.com/questions/102568/checking-if-a-text-contains-n-consecutive-repeating-characters - /// - public virtual bool HasConsecutiveCharacters(string password, int maxConsecutive) - { - if (string.IsNullOrWhiteSpace(password)) throw new ArgumentNullException(nameof(password)); + /// + /// Checks for consecutive characters using Regex. + /// Does not account for UTF16 surrogate pairs. + /// Adapted from https://codereview.stackexchange.com/questions/102568/checking-if-a-text-contains-n-consecutive-repeating-characters + /// + public virtual bool HasConsecutiveCharacters(string password, int maxConsecutive) + { + if (string.IsNullOrWhiteSpace(password)) throw new ArgumentNullException(nameof(password)); - var invalidAmount = 1; - if (1 < maxConsecutive) invalidAmount = maxConsecutive; + var invalidAmount = 1; + if (1 < maxConsecutive) invalidAmount = maxConsecutive; - return Regex.IsMatch(password,"(.)\\1{"+ invalidAmount + "}"); - } + return Regex.IsMatch(password,"(.)\\1{"+ invalidAmount + "}"); } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.AspNetCore/TagHelpers/NewPasswordTagHelper.cs b/src/ScottBrady.IdentityModel.AspNetCore/TagHelpers/NewPasswordTagHelper.cs index ca0c0b5..700d5b3 100644 --- a/src/ScottBrady.IdentityModel.AspNetCore/TagHelpers/NewPasswordTagHelper.cs +++ b/src/ScottBrady.IdentityModel.AspNetCore/TagHelpers/NewPasswordTagHelper.cs @@ -8,75 +8,74 @@ using Microsoft.Extensions.Options; using ScottBrady.IdentityModel.AspNetCore.Identity; -namespace ScottBrady.IdentityModel.AspNetCore.TagHelpers +namespace ScottBrady.IdentityModel.AspNetCore.TagHelpers; + +/// +/// Creates an input element with a type of "password", autocomplete of "new-password", +/// and transforms ASP.NET Identity password validation rules into the passwordrule attribute. +/// +[HtmlTargetElement("newpassword", TagStructure = TagStructure.WithoutEndTag)] +public class NewPasswordTagHelper : InputTagHelper { + internal IdentityOptions Options { get; } + /// - /// Creates an input element with a type of "password", autocomplete of "new-password", - /// and transforms ASP.NET Identity password validation rules into the passwordrule attribute. + /// Creates a new . /// - [HtmlTargetElement("newpassword", TagStructure = TagStructure.WithoutEndTag)] - public class NewPasswordTagHelper : InputTagHelper + /// The . + /// The . + public NewPasswordTagHelper( + IHtmlGenerator generator, + IOptions optionsAccessor) : base(generator) { - internal IdentityOptions Options { get; } - - /// - /// Creates a new . - /// - /// The . - /// The . - public NewPasswordTagHelper( - IHtmlGenerator generator, - IOptions optionsAccessor) : base(generator) - { - Options = optionsAccessor?.Value; - } + Options = optionsAccessor?.Value; + } - /// - public override void Process(TagHelperContext context, TagHelperOutput output) - { - if (context == null) throw new ArgumentNullException(nameof(context)); - if (output == null) throw new ArgumentNullException(nameof(output)); + /// + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (context == null) throw new ArgumentNullException(nameof(context)); + if (output == null) throw new ArgumentNullException(nameof(output)); - ProcessInputTag(context, output); + ProcessInputTag(context, output); - output.TagName = "input"; - output.Attributes.SetAttribute("type", "password"); - output.Attributes.SetAttribute("autocomplete", "new-password"); - output.Attributes.SetAttribute("autocorrect", "off"); - output.Attributes.SetAttribute("autocapitalize", "off"); + output.TagName = "input"; + output.Attributes.SetAttribute("type", "password"); + output.Attributes.SetAttribute("autocomplete", "new-password"); + output.Attributes.SetAttribute("autocorrect", "off"); + output.Attributes.SetAttribute("autocapitalize", "off"); - ProcessIdentityPasswordRules(Options.Password, output); - } + ProcessIdentityPasswordRules(Options.Password, output); + } - internal virtual void ProcessIdentityPasswordRules(PasswordOptions options, TagHelperOutput output) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - if (output == null) throw new ArgumentNullException(nameof(output)); + internal virtual void ProcessIdentityPasswordRules(PasswordOptions options, TagHelperOutput output) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + if (output == null) throw new ArgumentNullException(nameof(output)); - var passwordRules = new StringBuilder(); - passwordRules.AppendFormat("minlength: {0};", options.RequiredLength); + var passwordRules = new StringBuilder(); + passwordRules.AppendFormat("minlength: {0};", options.RequiredLength); - if (options.RequireLowercase) passwordRules.Append(" required: lower;"); - if (options.RequireUppercase) passwordRules.Append(" required: upper;"); - if (options.RequireDigit) passwordRules.Append(" required: digit;"); - if (options.RequireNonAlphanumeric) passwordRules.Append(" required: special;"); + if (options.RequireLowercase) passwordRules.Append(" required: lower;"); + if (options.RequireUppercase) passwordRules.Append(" required: upper;"); + if (options.RequireDigit) passwordRules.Append(" required: digit;"); + if (options.RequireNonAlphanumeric) passwordRules.Append(" required: special;"); - if (options is ExtendedPasswordOptions extendedOptions) + if (options is ExtendedPasswordOptions extendedOptions) + { + if (extendedOptions.MaxLength.HasValue && 0 < extendedOptions.MaxLength) { - if (extendedOptions.MaxLength.HasValue && 0 < extendedOptions.MaxLength) - { - passwordRules.AppendFormat(" maxlength: {0};", extendedOptions.MaxLength); - output.Attributes.SetAttribute("maxlength", extendedOptions.MaxLength); - } - if (extendedOptions.MaxConsecutiveChars.HasValue && 0 <= extendedOptions.MaxConsecutiveChars) - passwordRules.AppendFormat(" max-consecutive: {0};", extendedOptions.MaxConsecutiveChars); + passwordRules.AppendFormat(" maxlength: {0};", extendedOptions.MaxLength); + output.Attributes.SetAttribute("maxlength", extendedOptions.MaxLength); } - - output.Attributes.SetAttribute("passwordrules", passwordRules.ToString()); - output.Attributes.SetAttribute("minlength", options.RequiredLength); + if (extendedOptions.MaxConsecutiveChars.HasValue && 0 <= extendedOptions.MaxConsecutiveChars) + passwordRules.AppendFormat(" max-consecutive: {0};", extendedOptions.MaxConsecutiveChars); } - - internal virtual void ProcessInputTag(TagHelperContext context, TagHelperOutput output) - => base.Process(context, output); + + output.Attributes.SetAttribute("passwordrules", passwordRules.ToString()); + output.Attributes.SetAttribute("minlength", options.RequiredLength); } + + internal virtual void ProcessInputTag(TagHelperContext context, TagHelperOutput output) + => base.Process(context, output); } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaSecurityToken.cs b/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaSecurityToken.cs index 7c5ff3f..2dc1a38 100644 --- a/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaSecurityToken.cs +++ b/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaSecurityToken.cs @@ -2,23 +2,22 @@ using System.Text; using Microsoft.IdentityModel.Tokens; -namespace ScottBrady.IdentityModel.Tokens.Branca +namespace ScottBrady.IdentityModel.Tokens.Branca; + +[Obsolete("Branca support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] +public class BrancaSecurityToken : JwtPayloadSecurityToken { - [Obsolete("Branca support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] - public class BrancaSecurityToken : JwtPayloadSecurityToken + public BrancaSecurityToken(BrancaToken token) : base(Encoding.UTF8.GetString(token.Payload)) { - public BrancaSecurityToken(BrancaToken token) : base(Encoding.UTF8.GetString(token.Payload)) - { - IssuedAt = token.Timestamp; - } + IssuedAt = token.Timestamp; + } - public override DateTime IssuedAt { get; } + public override DateTime IssuedAt { get; } - public override SecurityKey SecurityKey => throw new NotSupportedException(); - public override SecurityKey SigningKey - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } + public override SecurityKey SecurityKey => throw new NotSupportedException(); + public override SecurityKey SigningKey + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaToken.cs b/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaToken.cs index 5d566c7..3655925 100644 --- a/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaToken.cs +++ b/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaToken.cs @@ -1,35 +1,34 @@ using System; -namespace ScottBrady.IdentityModel.Tokens.Branca +namespace ScottBrady.IdentityModel.Tokens.Branca; + +[Obsolete("Branca support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] +public class BrancaToken { - [Obsolete("Branca support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] - public class BrancaToken - { - private static readonly DateTime MinDateTime = new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc); - private static readonly DateTime MaxDateTime = new DateTime(2106, 02, 07, 06, 28, 15, DateTimeKind.Utc); + private static readonly DateTime MinDateTime = new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc); + private static readonly DateTime MaxDateTime = new DateTime(2106, 02, 07, 06, 28, 15, DateTimeKind.Utc); - public BrancaToken(byte[] payload, uint timestamp) - { - Payload = payload ?? throw new ArgumentNullException(nameof(payload)); - Timestamp = GetDateTime(timestamp); - BrancaFormatTimestamp = timestamp; - } + public BrancaToken(byte[] payload, uint timestamp) + { + Payload = payload ?? throw new ArgumentNullException(nameof(payload)); + Timestamp = GetDateTime(timestamp); + BrancaFormatTimestamp = timestamp; + } - public byte[] Payload { get; } - public DateTime Timestamp { get; } - public uint BrancaFormatTimestamp { get; } + public byte[] Payload { get; } + public DateTime Timestamp { get; } + public uint BrancaFormatTimestamp { get; } - public static DateTime GetDateTime(uint timestamp) - { - return DateTimeOffset.FromUnixTimeSeconds(timestamp).UtcDateTime; - } + public static DateTime GetDateTime(uint timestamp) + { + return DateTimeOffset.FromUnixTimeSeconds(timestamp).UtcDateTime; + } - public static uint GetBrancaTimestamp(DateTimeOffset dateTime) - { - if (dateTime < MinDateTime || MaxDateTime < dateTime) - throw new InvalidOperationException("Timestamp cannot be before 1970 or after 2106 (uint max)"); + public static uint GetBrancaTimestamp(DateTimeOffset dateTime) + { + if (dateTime < MinDateTime || MaxDateTime < dateTime) + throw new InvalidOperationException("Timestamp cannot be before 1970 or after 2106 (uint max)"); - return Convert.ToUInt32(dateTime.ToUniversalTime().ToUnixTimeSeconds()); - } + return Convert.ToUInt32(dateTime.ToUniversalTime().ToUnixTimeSeconds()); } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaTokenHandler.cs b/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaTokenHandler.cs index 5de5caf..836d21a 100644 --- a/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaTokenHandler.cs +++ b/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaTokenHandler.cs @@ -10,257 +10,256 @@ using NaCl.Core; using ScottBrady.IdentityModel.Crypto; -namespace ScottBrady.IdentityModel.Tokens.Branca +namespace ScottBrady.IdentityModel.Tokens.Branca; + +[Obsolete("Branca support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] +public class BrancaTokenHandler : JwtPayloadTokenHandler { - [Obsolete("Branca support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] - public class BrancaTokenHandler : JwtPayloadTokenHandler - { - private const int TagLength = 16; + private const int TagLength = 16; - public override bool CanReadToken(string token) - { - if (string.IsNullOrWhiteSpace(token)) return false; - if (token.Length > MaximumTokenSizeInBytes) return false; - if (token.Any(x => !Base62.CharacterSet.Contains(x))) return false; + public override bool CanReadToken(string token) + { + if (string.IsNullOrWhiteSpace(token)) return false; + if (token.Length > MaximumTokenSizeInBytes) return false; + if (token.Any(x => !Base62.CharacterSet.Contains(x))) return false; - return true; - } + return true; + } - /// - /// Branca specification-level token generation. - /// Timestamp set to UtcNow - /// - /// The utf-8 payload to be encrypted into the Branca token - /// 32-byte private key used to encrypt and decrypt the Branca token - /// Base62 encoded Branca Token - public virtual string CreateToken(string payload, byte[] key) - => CreateToken(payload, DateTime.UtcNow, key); - - /// - /// Branca specification-level token generation - /// - /// The utf-8 payload to be encrypted into the Branca token - /// The timestamp included in the Branca token (iat: issued at) - /// 32-byte private key used to encrypt and decrypt the Branca token - /// Base62 encoded Branca Token - public virtual string CreateToken(string payload, DateTimeOffset timestamp, byte[] key) - => CreateToken(payload, BrancaToken.GetBrancaTimestamp(timestamp), key); - - /// - /// Branca specification-level token generation - /// - /// The utf-8 payload to be encrypted into the Branca token - /// The timestamp included in the Branca token (iat: issued at) - /// 32-byte private key used to encrypt and decrypt the Branca token - /// Base62 encoded Branca Token - public virtual string CreateToken(string payload, uint timestamp, byte[] key) - => CreateToken(Encoding.UTF8.GetBytes(payload), timestamp, key); + /// + /// Branca specification-level token generation. + /// Timestamp set to UtcNow + /// + /// The utf-8 payload to be encrypted into the Branca token + /// 32-byte private key used to encrypt and decrypt the Branca token + /// Base62 encoded Branca Token + public virtual string CreateToken(string payload, byte[] key) + => CreateToken(payload, DateTime.UtcNow, key); + + /// + /// Branca specification-level token generation + /// + /// The utf-8 payload to be encrypted into the Branca token + /// The timestamp included in the Branca token (iat: issued at) + /// 32-byte private key used to encrypt and decrypt the Branca token + /// Base62 encoded Branca Token + public virtual string CreateToken(string payload, DateTimeOffset timestamp, byte[] key) + => CreateToken(payload, BrancaToken.GetBrancaTimestamp(timestamp), key); + + /// + /// Branca specification-level token generation + /// + /// The utf-8 payload to be encrypted into the Branca token + /// The timestamp included in the Branca token (iat: issued at) + /// 32-byte private key used to encrypt and decrypt the Branca token + /// Base62 encoded Branca Token + public virtual string CreateToken(string payload, uint timestamp, byte[] key) + => CreateToken(Encoding.UTF8.GetBytes(payload), timestamp, key); - /// - /// Branca specification-level token generation - /// - /// The payload bytes to be encrypted into the Branca token - /// The timestamp included in the Branca token (iat: issued at) - /// 32-byte private key used to encrypt and decrypt the Branca token - /// Base62 encoded Branca Token - public virtual string CreateToken(byte[] payload, uint timestamp, byte[] key) - { - if (payload == null) throw new ArgumentNullException(nameof(payload)); - if (!IsValidKey(key)) throw new InvalidOperationException("Invalid encryption key"); + /// + /// Branca specification-level token generation + /// + /// The payload bytes to be encrypted into the Branca token + /// The timestamp included in the Branca token (iat: issued at) + /// 32-byte private key used to encrypt and decrypt the Branca token + /// Base62 encoded Branca Token + public virtual string CreateToken(byte[] payload, uint timestamp, byte[] key) + { + if (payload == null) throw new ArgumentNullException(nameof(payload)); + if (!IsValidKey(key)) throw new InvalidOperationException("Invalid encryption key"); - var nonce = GenerateNonce(); + var nonce = GenerateNonce(); - // header - var header = new byte[29]; - using (var stream = new MemoryStream(header)) - { - // version - stream.WriteByte(0xBA); + // header + var header = new byte[29]; + using (var stream = new MemoryStream(header)) + { + // version + stream.WriteByte(0xBA); - // timestamp (big endian uint32) - stream.Write(BitConverter.GetBytes(timestamp).Reverse().ToArray(), 0, 4); + // timestamp (big endian uint32) + stream.Write(BitConverter.GetBytes(timestamp).Reverse().ToArray(), 0, 4); - // nonce - stream.Write(nonce, 0, nonce.Length); - } + // nonce + stream.Write(nonce, 0, nonce.Length); + } - var ciphertext = new byte[payload.Length]; - var tag = new byte[TagLength]; + var ciphertext = new byte[payload.Length]; + var tag = new byte[TagLength]; - new XChaCha20Poly1305(key).Encrypt(nonce, payload, ciphertext, tag, header); + new XChaCha20Poly1305(key).Encrypt(nonce, payload, ciphertext, tag, header); - var tokenBytes = new byte[header.Length + ciphertext.Length + TagLength]; - Buffer.BlockCopy(header, 0, tokenBytes, 0, header.Length); - Buffer.BlockCopy(ciphertext, 0, tokenBytes, header.Length, ciphertext.Length); - Buffer.BlockCopy(tag, 0, tokenBytes, tokenBytes.Length - TagLength, tag.Length); + var tokenBytes = new byte[header.Length + ciphertext.Length + TagLength]; + Buffer.BlockCopy(header, 0, tokenBytes, 0, header.Length); + Buffer.BlockCopy(ciphertext, 0, tokenBytes, header.Length, ciphertext.Length); + Buffer.BlockCopy(tag, 0, tokenBytes, tokenBytes.Length - TagLength, tag.Length); - return Base62.Encode(tokenBytes); - } + return Base62.Encode(tokenBytes); + } - /// - /// Creates Branca token using JWT rules - /// - /// Token descriptor - /// Base62 encoded Branca Token - public virtual string CreateToken(SecurityTokenDescriptor tokenDescriptor) - { - if (tokenDescriptor == null) throw new ArgumentNullException(nameof(tokenDescriptor)); + /// + /// Creates Branca token using JWT rules + /// + /// Token descriptor + /// Base62 encoded Branca Token + public virtual string CreateToken(SecurityTokenDescriptor tokenDescriptor) + { + if (tokenDescriptor == null) throw new ArgumentNullException(nameof(tokenDescriptor)); - if (!IsValidKey(tokenDescriptor.EncryptingCredentials)) - throw new SecurityTokenEncryptionFailedException( - "Invalid encrypting credentials. Branca tokens require a symmetric key using the XC20P algorithm and no key wrapping"); + if (!IsValidKey(tokenDescriptor.EncryptingCredentials)) + throw new SecurityTokenEncryptionFailedException( + "Invalid encrypting credentials. Branca tokens require a symmetric key using the XC20P algorithm and no key wrapping"); - var jwtStylePayload = tokenDescriptor.ToJwtPayload(); + var jwtStylePayload = tokenDescriptor.ToJwtPayload(); - // Remove iat claim in favour of timestamp - var jObject = JsonSerializer.Deserialize>(jwtStylePayload); - jObject.Remove(JwtRegisteredClaimNames.Iat); + // Remove iat claim in favour of timestamp + var jObject = JsonSerializer.Deserialize>(jwtStylePayload); + jObject.Remove(JwtRegisteredClaimNames.Iat); - var symmetricKey = (SymmetricSecurityKey) tokenDescriptor.EncryptingCredentials.Key; + var symmetricKey = (SymmetricSecurityKey) tokenDescriptor.EncryptingCredentials.Key; - return CreateToken(JsonSerializer.Serialize(jObject), symmetricKey.Key); - } + return CreateToken(JsonSerializer.Serialize(jObject), symmetricKey.Key); + } - /// - /// Branca specification level token decryption. - /// - /// Base62 encoded Branca token - /// 32-byte private key used to encrypt and decrypt the Branca token - /// Pared and decrypted Branca Token - public virtual BrancaToken DecryptToken(string token, byte[] key) - { - if (string.IsNullOrWhiteSpace(token)) throw new ArgumentNullException(nameof(token)); - if (!CanReadToken(token)) throw new InvalidCastException("Unable to read token"); - if (!IsValidKey(key)) throw new InvalidOperationException("Invalid decryption key"); + /// + /// Branca specification level token decryption. + /// + /// Base62 encoded Branca token + /// 32-byte private key used to encrypt and decrypt the Branca token + /// Pared and decrypted Branca Token + public virtual BrancaToken DecryptToken(string token, byte[] key) + { + if (string.IsNullOrWhiteSpace(token)) throw new ArgumentNullException(nameof(token)); + if (!CanReadToken(token)) throw new InvalidCastException("Unable to read token"); + if (!IsValidKey(key)) throw new InvalidOperationException("Invalid decryption key"); + + var tokenBytes = Base62.Decode(token); - var tokenBytes = Base62.Decode(token); + using (var stream = new MemoryStream(tokenBytes, false)) + { + // header + var header = GuaranteedRead(stream, 29); - using (var stream = new MemoryStream(tokenBytes, false)) + byte[] nonce; + uint timestamp; + using (var headerStream = new MemoryStream(header)) { - // header - var header = GuaranteedRead(stream, 29); - - byte[] nonce; - uint timestamp; - using (var headerStream = new MemoryStream(header)) - { - // version - var version = headerStream.ReadByte(); - if (version != 0xBA) throw new SecurityTokenException("Unsupported Branca version"); - - // timestamp (big endian uint32) - var timestampBytes = GuaranteedRead(headerStream, 4).Reverse().ToArray(); - timestamp = BitConverter.ToUInt32(timestampBytes, 0); - - // nonce - nonce = GuaranteedRead(headerStream, 24); - } - - // ciphertext - var ciphertextLength = stream.Length - stream.Position - TagLength; - var ciphertext = GuaranteedRead(stream, (int) ciphertextLength); - var tag = GuaranteedRead(stream, TagLength); - - var plaintext = new byte[ciphertextLength]; - new XChaCha20Poly1305(key).Decrypt(nonce, ciphertext, tag, plaintext, header); - - return new BrancaToken( - plaintext, - timestamp); + // version + var version = headerStream.ReadByte(); + if (version != 0xBA) throw new SecurityTokenException("Unsupported Branca version"); + + // timestamp (big endian uint32) + var timestampBytes = GuaranteedRead(headerStream, 4).Reverse().ToArray(); + timestamp = BitConverter.ToUInt32(timestampBytes, 0); + + // nonce + nonce = GuaranteedRead(headerStream, 24); } - } - public override TokenValidationResult ValidateToken(string token, TokenValidationParameters validationParameters) - { - if (string.IsNullOrWhiteSpace(token)) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(token))}; - if (validationParameters == null) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(validationParameters))}; - if (!CanReadToken(token)) return new TokenValidationResult {Exception = new SecurityTokenException("Unable to read token")}; + // ciphertext + var ciphertextLength = stream.Length - stream.Position - TagLength; + var ciphertext = GuaranteedRead(stream, (int) ciphertextLength); + var tag = GuaranteedRead(stream, TagLength); - // get decryption keys - var securityKeys = GetBrancaDecryptionKeys(token, validationParameters); + var plaintext = new byte[ciphertextLength]; + new XChaCha20Poly1305(key).Decrypt(nonce, ciphertext, tag, plaintext, header); - BrancaToken decryptedToken = null; + return new BrancaToken( + plaintext, + timestamp); + } + } - foreach (var securityKey in securityKeys) - { - try - { - decryptedToken = DecryptToken(token, securityKey.Key); - if (decryptedToken != null) break; - } - catch (Exception) - { - // ignored - } - } + public override TokenValidationResult ValidateToken(string token, TokenValidationParameters validationParameters) + { + if (string.IsNullOrWhiteSpace(token)) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(token))}; + if (validationParameters == null) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(validationParameters))}; + if (!CanReadToken(token)) return new TokenValidationResult {Exception = new SecurityTokenException("Unable to read token")}; + + // get decryption keys + var securityKeys = GetBrancaDecryptionKeys(token, validationParameters); - if (decryptedToken == null) - return new TokenValidationResult {Exception = new SecurityTokenDecryptionFailedException("Unable to decrypt token")}; + BrancaToken decryptedToken = null; - BrancaSecurityToken brancaToken; + foreach (var securityKey in securityKeys) + { try { - brancaToken = new BrancaSecurityToken(decryptedToken); + decryptedToken = DecryptToken(token, securityKey.Key); + if (decryptedToken != null) break; } - catch (Exception e) + catch (Exception) { - return new TokenValidationResult {Exception = e}; + // ignored } + } - var innerValidationResult = ValidateTokenPayload(brancaToken, validationParameters); - if (!innerValidationResult.IsValid) return innerValidationResult; - - var identity = innerValidationResult.ClaimsIdentity; - if (validationParameters.SaveSigninToken) identity.BootstrapContext = token; + if (decryptedToken == null) + return new TokenValidationResult {Exception = new SecurityTokenDecryptionFailedException("Unable to decrypt token")}; - return new TokenValidationResult - { - SecurityToken = brancaToken, - ClaimsIdentity = identity, - IsValid = true - }; + BrancaSecurityToken brancaToken; + try + { + brancaToken = new BrancaSecurityToken(decryptedToken); } - - protected virtual IEnumerable GetBrancaDecryptionKeys(string token, TokenValidationParameters validationParameters) + catch (Exception e) { - var keys = base.GetDecryptionKeys(token, validationParameters); - - return keys.Where(IsValidKey).Select(x => (SymmetricSecurityKey) x).ToList(); + return new TokenValidationResult {Exception = e}; } - protected virtual bool IsValidKey(byte[] key) => key?.Length == 32; + var innerValidationResult = ValidateTokenPayload(brancaToken, validationParameters); + if (!innerValidationResult.IsValid) return innerValidationResult; + + var identity = innerValidationResult.ClaimsIdentity; + if (validationParameters.SaveSigninToken) identity.BootstrapContext = token; - protected virtual bool IsValidKey(SecurityKey securityKey) + return new TokenValidationResult { - if (securityKey == null) return false; - if (!(securityKey is SymmetricSecurityKey symmetricKey)) return false; + SecurityToken = brancaToken, + ClaimsIdentity = identity, + IsValid = true + }; + } - return IsValidKey(symmetricKey.Key); - } + protected virtual IEnumerable GetBrancaDecryptionKeys(string token, TokenValidationParameters validationParameters) + { + var keys = base.GetDecryptionKeys(token, validationParameters); + + return keys.Where(IsValidKey).Select(x => (SymmetricSecurityKey) x).ToList(); + } - protected virtual bool IsValidKey(EncryptingCredentials credentials) - { - if (credentials == null) return false; - if (credentials.Enc != ExtendedSecurityAlgorithms.XChaCha20Poly1305) return false; - if (string.IsNullOrWhiteSpace(credentials.Alg) || credentials.Alg != SecurityAlgorithms.None) - { - return false; - } + protected virtual bool IsValidKey(byte[] key) => key?.Length == 32; - return IsValidKey(credentials.Key); - } + protected virtual bool IsValidKey(SecurityKey securityKey) + { + if (securityKey == null) return false; + if (!(securityKey is SymmetricSecurityKey symmetricKey)) return false; - protected virtual byte[] GenerateNonce() - { - var nonce = new byte[24]; - RandomNumberGenerator.Fill(nonce); - return nonce; - } + return IsValidKey(symmetricKey.Key); + } - private static byte[] GuaranteedRead(Stream stream, int length) + protected virtual bool IsValidKey(EncryptingCredentials credentials) + { + if (credentials == null) return false; + if (credentials.Enc != ExtendedSecurityAlgorithms.XChaCha20Poly1305) return false; + if (string.IsNullOrWhiteSpace(credentials.Alg) || credentials.Alg != SecurityAlgorithms.None) { - if (!stream.TryRead(length, out var bytes)) throw new SecurityTokenException(""); - return bytes; + return false; } + + return IsValidKey(credentials.Key); + } + + protected virtual byte[] GenerateNonce() + { + var nonce = new byte[24]; + RandomNumberGenerator.Fill(nonce); + return nonce; + } + + private static byte[] GuaranteedRead(Stream stream, int length) + { + if (!stream.TryRead(length, out var bytes)) throw new SecurityTokenException(""); + return bytes; } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoConstants.cs b/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoConstants.cs index f620670..8d94fae 100644 --- a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoConstants.cs +++ b/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoConstants.cs @@ -1,22 +1,21 @@ using System; -namespace ScottBrady.IdentityModel.Tokens.Paseto +namespace ScottBrady.IdentityModel.Tokens.Paseto; + +[Obsolete("PASETO support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] +public class PasetoConstants { - [Obsolete("PASETO support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] - public class PasetoConstants - { - public const int MaxPasetoSegmentCount = 4; + public const int MaxPasetoSegmentCount = 4; - public class Versions - { - public const string V1 = "v1"; - public const string V2 = "v2"; - } + public class Versions + { + public const string V1 = "v1"; + public const string V2 = "v2"; + } - public class Purposes - { - public const string Local = "local"; - public const string Public = "public"; - } + public class Purposes + { + public const string Local = "local"; + public const string Public = "public"; } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoSecurityToken.cs b/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoSecurityToken.cs index c51e103..bf5b794 100644 --- a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoSecurityToken.cs +++ b/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoSecurityToken.cs @@ -3,53 +3,52 @@ using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; -namespace ScottBrady.IdentityModel.Tokens.Paseto +namespace ScottBrady.IdentityModel.Tokens.Paseto; + +[Obsolete("PASETO support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] +public class PasetoSecurityToken : JwtPayloadSecurityToken { - [Obsolete("PASETO support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] - public class PasetoSecurityToken : JwtPayloadSecurityToken - { - protected PasetoSecurityToken() { } + protected PasetoSecurityToken() { } - public PasetoSecurityToken(PasetoToken token) : base(token.Payload) - { - Version = token.Version; - Purpose = token.Purpose; + public PasetoSecurityToken(PasetoToken token) : base(token.Payload) + { + Version = token.Version; + Purpose = token.Purpose; - EncodedFooter = token.EncodedFooter; - Footer = token.Footer; + EncodedFooter = token.EncodedFooter; + Footer = token.Footer; - RawToken = token.RawToken; - } + RawToken = token.RawToken; + } - public virtual string Version { get; } - public virtual string Purpose { get; } + public virtual string Version { get; } + public virtual string Purpose { get; } - public virtual string EncodedFooter { get; } - public virtual string Footer { get; } + public virtual string EncodedFooter { get; } + public virtual string Footer { get; } - public virtual string RawToken { get; } + public virtual string RawToken { get; } - public override DateTime IssuedAt => ParsePasetoDateTimeClaim(JwtRegisteredClaimNames.Iat); - public override DateTime ValidFrom => ParsePasetoDateTimeClaim(JwtRegisteredClaimNames.Nbf); - public override DateTime ValidTo => ParsePasetoDateTimeClaim(JwtRegisteredClaimNames.Exp); + public override DateTime IssuedAt => ParsePasetoDateTimeClaim(JwtRegisteredClaimNames.Iat); + public override DateTime ValidFrom => ParsePasetoDateTimeClaim(JwtRegisteredClaimNames.Nbf); + public override DateTime ValidTo => ParsePasetoDateTimeClaim(JwtRegisteredClaimNames.Exp); - public override SecurityKey SecurityKey => throw new NotSupportedException(); - public override SecurityKey SigningKey { get; set; } + public override SecurityKey SecurityKey => throw new NotSupportedException(); + public override SecurityKey SigningKey { get; set; } - protected virtual DateTime ParsePasetoDateTimeClaim(string claimType) + protected virtual DateTime ParsePasetoDateTimeClaim(string claimType) + { + if (InnerToken.TryGetPayloadValue(claimType, out var claimValue)) { - if (InnerToken.TryGetPayloadValue(claimType, out var claimValue)) + // ISO 8601 format + if (DateTime.TryParse(claimValue, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.RoundtripKind, out var dateTime)) { - // ISO 8601 format - if (DateTime.TryParse(claimValue, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.RoundtripKind, out var dateTime)) - { - return dateTime.ToUniversalTime(); - } - - throw new SecurityTokenInvalidLifetimeException($"Unable to parse date time from '{claimType}'. Failing value: '{claimValue}'"); + return dateTime.ToUniversalTime(); } - - return DateTime.MinValue; + + throw new SecurityTokenInvalidLifetimeException($"Unable to parse date time from '{claimType}'. Failing value: '{claimValue}'"); } + + return DateTime.MinValue; } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoSecurityTokenDescriptor.cs b/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoSecurityTokenDescriptor.cs index 1fd9702..63bb39c 100644 --- a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoSecurityTokenDescriptor.cs +++ b/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoSecurityTokenDescriptor.cs @@ -1,21 +1,20 @@ using System; using Microsoft.IdentityModel.Tokens; -namespace ScottBrady.IdentityModel.Tokens.Paseto +namespace ScottBrady.IdentityModel.Tokens.Paseto; + +[Obsolete("PASETO support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] +public class PasetoSecurityTokenDescriptor : SecurityTokenDescriptor { - [Obsolete("PASETO support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] - public class PasetoSecurityTokenDescriptor : SecurityTokenDescriptor + public PasetoSecurityTokenDescriptor(string version, string purpose) { - public PasetoSecurityTokenDescriptor(string version, string purpose) - { - if (string.IsNullOrWhiteSpace(version)) throw new ArgumentNullException(nameof(version)); - if (string.IsNullOrWhiteSpace(purpose)) throw new ArgumentNullException(nameof(purpose)); + if (string.IsNullOrWhiteSpace(version)) throw new ArgumentNullException(nameof(version)); + if (string.IsNullOrWhiteSpace(purpose)) throw new ArgumentNullException(nameof(purpose)); - Version = version; - Purpose = purpose; - } - - public string Version { get; } - public string Purpose { get; } + Version = version; + Purpose = purpose; } + + public string Version { get; } + public string Purpose { get; } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoToken.cs b/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoToken.cs index a422311..b90ef1b 100644 --- a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoToken.cs +++ b/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoToken.cs @@ -3,56 +3,55 @@ using System.Text.Json; using Microsoft.IdentityModel.Tokens; -namespace ScottBrady.IdentityModel.Tokens.Paseto +namespace ScottBrady.IdentityModel.Tokens.Paseto; + +[Obsolete("PASETO support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] +public class PasetoToken { - [Obsolete("PASETO support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] - public class PasetoToken - { - protected PasetoToken() { } + protected PasetoToken() { } - public PasetoToken(string token) - { - if (string.IsNullOrWhiteSpace(token)) throw new ArgumentNullException(nameof(token)); + public PasetoToken(string token) + { + if (string.IsNullOrWhiteSpace(token)) throw new ArgumentNullException(nameof(token)); - var tokenParts = token.Split(new[] {'.'}, PasetoConstants.MaxPasetoSegmentCount + 1); - if (tokenParts.Length != 3 && tokenParts.Length != 4) throw new ArgumentException("Invalid number of token segments"); + var tokenParts = token.Split(new[] {'.'}, PasetoConstants.MaxPasetoSegmentCount + 1); + if (tokenParts.Length != 3 && tokenParts.Length != 4) throw new ArgumentException("Invalid number of token segments"); - RawToken = token; + RawToken = token; - Version = tokenParts[0]; - Purpose = tokenParts[1]; - EncodedPayload = tokenParts[2]; - if (tokenParts.Length == 4) - { - EncodedFooter = tokenParts[3]; - Footer = Base64UrlEncoder.Decode(EncodedFooter); - } + Version = tokenParts[0]; + Purpose = tokenParts[1]; + EncodedPayload = tokenParts[2]; + if (tokenParts.Length == 4) + { + EncodedFooter = tokenParts[3]; + Footer = Base64UrlEncoder.Decode(EncodedFooter); } + } - public string RawToken { get; } + public string RawToken { get; } - public string Version { get; } - public string Purpose { get; } + public string Version { get; } + public string Purpose { get; } - public string EncodedPayload { get; } - public string Payload { get; protected set; } + public string EncodedPayload { get; } + public string Payload { get; protected set; } - public string EncodedFooter { get; } - public string Footer { get; } + public string EncodedFooter { get; } + public string Footer { get; } - public void SetPayload(string payload) - { - if (string.IsNullOrWhiteSpace(payload)) throw new ArgumentNullException(nameof(payload)); + public void SetPayload(string payload) + { + if (string.IsNullOrWhiteSpace(payload)) throw new ArgumentNullException(nameof(payload)); - try - { - JsonSerializer.Deserialize>(payload); - Payload = payload; - } - catch (Exception e) - { - throw new ArgumentException("Token does contain valid JSON", e); - } + try + { + JsonSerializer.Deserialize>(payload); + Payload = payload; + } + catch (Exception e) + { + throw new ArgumentException("Token does contain valid JSON", e); } } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoTokenHandler.cs b/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoTokenHandler.cs index be18a07..c2f799d 100644 --- a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoTokenHandler.cs +++ b/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoTokenHandler.cs @@ -2,118 +2,117 @@ using System.Collections.Generic; using Microsoft.IdentityModel.Tokens; -namespace ScottBrady.IdentityModel.Tokens.Paseto +namespace ScottBrady.IdentityModel.Tokens.Paseto; + +[Obsolete("PASETO support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] +public class PasetoTokenHandler : JwtPayloadTokenHandler { - [Obsolete("PASETO support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] - public class PasetoTokenHandler : JwtPayloadTokenHandler - { - private readonly Dictionary supportedVersions; + private readonly Dictionary supportedVersions; - public PasetoTokenHandler(Dictionary supportedVersions = null) + public PasetoTokenHandler(Dictionary supportedVersions = null) + { + this.supportedVersions = supportedVersions ?? new Dictionary { - this.supportedVersions = supportedVersions ?? new Dictionary - { - {PasetoConstants.Versions.V1, new PasetoVersion1()}, - {PasetoConstants.Versions.V2, new PasetoVersion2()} - }; - } + {PasetoConstants.Versions.V1, new PasetoVersion1()}, + {PasetoConstants.Versions.V2, new PasetoVersion2()} + }; + } - public override bool CanReadToken(string token) - { - if (string.IsNullOrWhiteSpace(token)) return false; - if (token.Length > MaximumTokenSizeInBytes) return false; + public override bool CanReadToken(string token) + { + if (string.IsNullOrWhiteSpace(token)) return false; + if (token.Length > MaximumTokenSizeInBytes) return false; + + var tokenParts = token.Split(new[] {'.'}, PasetoConstants.MaxPasetoSegmentCount + 1); + if (tokenParts.Length != 3 && tokenParts.Length != 4) return false; - var tokenParts = token.Split(new[] {'.'}, PasetoConstants.MaxPasetoSegmentCount + 1); - if (tokenParts.Length != 3 && tokenParts.Length != 4) return false; + return true; + } + + public virtual string CreateToken(PasetoSecurityTokenDescriptor tokenDescriptor) + { + if (tokenDescriptor == null) throw new ArgumentNullException(nameof(tokenDescriptor)); + if (!(tokenDescriptor is PasetoSecurityTokenDescriptor pasetoSecurityTokenDescriptor)) + throw new ArgumentException($"Token descriptor must be of type '{typeof(PasetoSecurityTokenDescriptor)}'", nameof(tokenDescriptor)); - return true; + // get strategy for version + purpose + if (!supportedVersions.TryGetValue(pasetoSecurityTokenDescriptor.Version, out var strategy)) + { + throw new SecurityTokenException("Unsupported PASETO version"); } + + // create payload + var payload = tokenDescriptor.ToJwtPayload(JwtDateTimeFormat.Iso); - public virtual string CreateToken(PasetoSecurityTokenDescriptor tokenDescriptor) + // generate token + string token; + if (pasetoSecurityTokenDescriptor.Purpose == "local") + { + token = strategy.Encrypt(payload, null, pasetoSecurityTokenDescriptor.EncryptingCredentials); + } + else if (pasetoSecurityTokenDescriptor.Purpose == "public") + { + token = strategy.Sign(payload, null, pasetoSecurityTokenDescriptor.SigningCredentials); + } + else { - if (tokenDescriptor == null) throw new ArgumentNullException(nameof(tokenDescriptor)); - if (!(tokenDescriptor is PasetoSecurityTokenDescriptor pasetoSecurityTokenDescriptor)) - throw new ArgumentException($"Token descriptor must be of type '{typeof(PasetoSecurityTokenDescriptor)}'", nameof(tokenDescriptor)); + throw new SecurityTokenException("Unsupported PASETO purpose"); + } - // get strategy for version + purpose - if (!supportedVersions.TryGetValue(pasetoSecurityTokenDescriptor.Version, out var strategy)) - { - throw new SecurityTokenException("Unsupported PASETO version"); - } - - // create payload - var payload = tokenDescriptor.ToJwtPayload(JwtDateTimeFormat.Iso); + return token; + } - // generate token - string token; - if (pasetoSecurityTokenDescriptor.Purpose == "local") - { - token = strategy.Encrypt(payload, null, pasetoSecurityTokenDescriptor.EncryptingCredentials); - } - else if (pasetoSecurityTokenDescriptor.Purpose == "public") - { - token = strategy.Sign(payload, null, pasetoSecurityTokenDescriptor.SigningCredentials); - } - else - { - throw new SecurityTokenException("Unsupported PASETO purpose"); - } + public override TokenValidationResult ValidateToken(string token, TokenValidationParameters validationParameters) + { + if (string.IsNullOrWhiteSpace(token)) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(token))}; + if (validationParameters == null) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(validationParameters))}; + if (!CanReadToken(token)) return new TokenValidationResult {Exception = new SecurityTokenException("Unable to read token")}; - return token; - } + var pasetoToken = new PasetoToken(token); - public override TokenValidationResult ValidateToken(string token, TokenValidationParameters validationParameters) + // get strategy for version + purpose + if (!supportedVersions.TryGetValue(pasetoToken.Version, out var strategy)) { - if (string.IsNullOrWhiteSpace(token)) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(token))}; - if (validationParameters == null) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(validationParameters))}; - if (!CanReadToken(token)) return new TokenValidationResult {Exception = new SecurityTokenException("Unable to read token")}; - - var pasetoToken = new PasetoToken(token); + return new TokenValidationResult {Exception = new SecurityTokenException("Unsupported PASETO version")}; + } - // get strategy for version + purpose - if (!supportedVersions.TryGetValue(pasetoToken.Version, out var strategy)) + PasetoSecurityToken pasetoSecurityToken; + try + { + if (pasetoToken.Purpose == "local") { - return new TokenValidationResult {Exception = new SecurityTokenException("Unsupported PASETO version")}; + var keys = GetDecryptionKeys(token, validationParameters); + pasetoSecurityToken = strategy.Decrypt(pasetoToken, keys); } - - PasetoSecurityToken pasetoSecurityToken; - try + else if (pasetoToken.Purpose == "public") { - if (pasetoToken.Purpose == "local") - { - var keys = GetDecryptionKeys(token, validationParameters); - pasetoSecurityToken = strategy.Decrypt(pasetoToken, keys); - } - else if (pasetoToken.Purpose == "public") - { - var keys = GetSigningKeys(token, validationParameters); - - // TODO: kid handling (footer?) - - pasetoSecurityToken = strategy.Verify(pasetoToken, keys); - } - else - { - return new TokenValidationResult {Exception = new SecurityTokenException("Unsupported PASETO purpose")}; - } + var keys = GetSigningKeys(token, validationParameters); + + // TODO: kid handling (footer?) + + pasetoSecurityToken = strategy.Verify(pasetoToken, keys); } - catch (Exception e) + else { - return new TokenValidationResult {Exception = e}; + return new TokenValidationResult {Exception = new SecurityTokenException("Unsupported PASETO purpose")}; } + } + catch (Exception e) + { + return new TokenValidationResult {Exception = e}; + } - var innerValidationResult = ValidateTokenPayload(pasetoSecurityToken, validationParameters); - if (!innerValidationResult.IsValid) return innerValidationResult; + var innerValidationResult = ValidateTokenPayload(pasetoSecurityToken, validationParameters); + if (!innerValidationResult.IsValid) return innerValidationResult; - var identity = innerValidationResult.ClaimsIdentity; - if (validationParameters.SaveSigninToken) identity.BootstrapContext = token; + var identity = innerValidationResult.ClaimsIdentity; + if (validationParameters.SaveSigninToken) identity.BootstrapContext = token; - return new TokenValidationResult - { - SecurityToken = pasetoSecurityToken, - ClaimsIdentity = identity, - IsValid = true - }; - } + return new TokenValidationResult + { + SecurityToken = pasetoSecurityToken, + ClaimsIdentity = identity, + IsValid = true + }; } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersion1.cs b/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersion1.cs index 05621da..f4d66fd 100644 --- a/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersion1.cs +++ b/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersion1.cs @@ -5,19 +5,19 @@ using System.Text; using Microsoft.IdentityModel.Tokens; -namespace ScottBrady.IdentityModel.Tokens.Paseto +namespace ScottBrady.IdentityModel.Tokens.Paseto; + +public class PasetoVersion1 : PasetoVersionStrategy { - public class PasetoVersion1 : PasetoVersionStrategy - { - private const string PublicHeader = "v1.public."; + private const string PublicHeader = "v1.public."; - public override string Encrypt(string payload, string footer, EncryptingCredentials encryptingCredentials) - { + public override string Encrypt(string payload, string footer, EncryptingCredentials encryptingCredentials) + { throw new NotSupportedException("v1.local not supported"); } - public override string Sign(string payload, string footer, SigningCredentials signingCredentials) - { + public override string Sign(string payload, string footer, SigningCredentials signingCredentials) + { if (payload == null) throw new ArgumentNullException(nameof(payload)); if (signingCredentials == null) throw new ArgumentNullException(nameof(signingCredentials)); @@ -47,13 +47,13 @@ public override string Sign(string payload, string footer, SigningCredentials si return token; } - public override PasetoSecurityToken Decrypt(PasetoToken token, IEnumerable decryptionKeys) - { + public override PasetoSecurityToken Decrypt(PasetoToken token, IEnumerable decryptionKeys) + { throw new NotSupportedException("v1.local not supported"); } - public override PasetoSecurityToken Verify(PasetoToken token, IEnumerable signingKeys) - { + public override PasetoSecurityToken Verify(PasetoToken token, IEnumerable signingKeys) + { if (token == null) throw new ArgumentNullException(nameof(token)); if (signingKeys == null || !signingKeys.Any()) throw new ArgumentNullException(nameof(signingKeys)); @@ -100,5 +100,4 @@ public override PasetoSecurityToken Verify(PasetoToken token, IEnumerable decryptionKeys) - { - throw new NotSupportedException("v2.local not supported"); - } + public override PasetoSecurityToken Decrypt(PasetoToken token, IEnumerable decryptionKeys) + { + throw new NotSupportedException("v2.local not supported"); + } - public override PasetoSecurityToken Verify(PasetoToken token, IEnumerable signingKeys) - { - if (token == null) throw new ArgumentNullException(nameof(token)); - if (signingKeys == null || !signingKeys.Any()) throw new ArgumentNullException(nameof(signingKeys)); + public override PasetoSecurityToken Verify(PasetoToken token, IEnumerable signingKeys) + { + if (token == null) throw new ArgumentNullException(nameof(token)); + if (signingKeys == null || !signingKeys.Any()) throw new ArgumentNullException(nameof(signingKeys)); - var keys = signingKeys.OfType().ToList(); - if (!keys.Any()) throw new SecurityTokenInvalidSigningKeyException($"PASETO v2 requires key of type {typeof(EdDsaSecurityKey)}"); + var keys = signingKeys.OfType().ToList(); + if (!keys.Any()) throw new SecurityTokenInvalidSigningKeyException($"PASETO v2 requires key of type {typeof(EdDsaSecurityKey)}"); - if (token.Version != PasetoConstants.Versions.V2) throw new ArgumentException("Invalid PASETO version"); - if (token.Purpose != PasetoConstants.Purposes.Public) throw new ArgumentException("Invalid PASETO purpose"); + if (token.Version != PasetoConstants.Versions.V2) throw new ArgumentException("Invalid PASETO version"); + if (token.Purpose != PasetoConstants.Purposes.Public) throw new ArgumentException("Invalid PASETO purpose"); - // decode payload - var payload = Base64UrlEncoder.DecodeBytes(token.EncodedPayload); - if (payload.Length < 64) throw new SecurityTokenInvalidSignatureException("Payload does not contain signature"); + // decode payload + var payload = Base64UrlEncoder.DecodeBytes(token.EncodedPayload); + if (payload.Length < 64) throw new SecurityTokenInvalidSignatureException("Payload does not contain signature"); - // extract signature from payload (rightmost 64 bytes) - var signature = new byte[64]; - Buffer.BlockCopy(payload, payload.Length - 64, signature, 0, 64); + // extract signature from payload (rightmost 64 bytes) + var signature = new byte[64]; + Buffer.BlockCopy(payload, payload.Length - 64, signature, 0, 64); - // decode payload JSON - var message = new byte[payload.Length - 64]; - Buffer.BlockCopy(payload, 0, message, 0, payload.Length - 64); - token.SetPayload(Encoding.UTF8.GetString(message)); + // decode payload JSON + var message = new byte[payload.Length - 64]; + Buffer.BlockCopy(payload, 0, message, 0, payload.Length - 64); + token.SetPayload(Encoding.UTF8.GetString(message)); - // pack - var signedMessage = PreAuthEncode(new[] - { - Encoding.UTF8.GetBytes(PublicHeader), - message, - Base64UrlEncoder.DecodeBytes(token.EncodedFooter ?? string.Empty) - }); - - // verify signature using valid keys - foreach (var publicKey in keys) - { - var isValidSignature = publicKey.EdDsa.Verify(signedMessage, signature); - if (isValidSignature) return new PasetoSecurityToken(token); - } + // pack + var signedMessage = PreAuthEncode(new[] + { + Encoding.UTF8.GetBytes(PublicHeader), + message, + Base64UrlEncoder.DecodeBytes(token.EncodedFooter ?? string.Empty) + }); - throw new SecurityTokenInvalidSignatureException("Invalid PASETO signature"); + // verify signature using valid keys + foreach (var publicKey in keys) + { + var isValidSignature = publicKey.EdDsa.Verify(signedMessage, signature); + if (isValidSignature) return new PasetoSecurityToken(token); } + + throw new SecurityTokenInvalidSignatureException("Invalid PASETO signature"); } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersionStrategy.cs b/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersionStrategy.cs index c5ed91e..6394906 100644 --- a/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersionStrategy.cs +++ b/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersionStrategy.cs @@ -3,32 +3,32 @@ using System.Linq; using Microsoft.IdentityModel.Tokens; -namespace ScottBrady.IdentityModel.Tokens.Paseto +namespace ScottBrady.IdentityModel.Tokens.Paseto; + +public abstract class PasetoVersionStrategy { - public abstract class PasetoVersionStrategy - { - /// - /// Creates an encrypted local token - /// - public abstract string Encrypt(string payload, string footer, EncryptingCredentials encryptingCredentials); + /// + /// Creates an encrypted local token + /// + public abstract string Encrypt(string payload, string footer, EncryptingCredentials encryptingCredentials); - /// - /// Creates a signed public token - /// - public abstract string Sign(string payload, string footer, SigningCredentials signingCredentials); + /// + /// Creates a signed public token + /// + public abstract string Sign(string payload, string footer, SigningCredentials signingCredentials); - /// - /// Decrypts a local token - /// - public abstract PasetoSecurityToken Decrypt(PasetoToken token, IEnumerable decryptionKeys); + /// + /// Decrypts a local token + /// + public abstract PasetoSecurityToken Decrypt(PasetoToken token, IEnumerable decryptionKeys); - /// - /// Verifies the a public token's signature - /// - public abstract PasetoSecurityToken Verify(PasetoToken token, IEnumerable signingKeys); + /// + /// Verifies the a public token's signature + /// + public abstract PasetoSecurityToken Verify(PasetoToken token, IEnumerable signingKeys); - protected static byte[] PreAuthEncode(IReadOnlyList pieces) - { + protected static byte[] PreAuthEncode(IReadOnlyList pieces) + { if (pieces == null) throw new ArgumentNullException(nameof(pieces)); var output = BitConverter.GetBytes((long) pieces.Count); @@ -40,5 +40,4 @@ protected static byte[] PreAuthEncode(IReadOnlyList pieces) return output.ToArray(); } - } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel/Crypto/ExtendedSecurityAlgorithms.cs b/src/ScottBrady.IdentityModel/Crypto/ExtendedSecurityAlgorithms.cs index f420d06..dddd5d8 100644 --- a/src/ScottBrady.IdentityModel/Crypto/ExtendedSecurityAlgorithms.cs +++ b/src/ScottBrady.IdentityModel/Crypto/ExtendedSecurityAlgorithms.cs @@ -1,25 +1,24 @@ -namespace ScottBrady.IdentityModel.Crypto +namespace ScottBrady.IdentityModel.Crypto; + +public static class ExtendedSecurityAlgorithms { - public static class ExtendedSecurityAlgorithms - { - // https://tools.ietf.org/html/draft-amringer-jose-chacha-02#section-4.1 - public const string ChaCha20Poly1305 = "C20P"; - public const string XChaCha20Poly1305 = "XC20P"; - public const string ChaCha20Poly1305KeyWrap = "C20PKW"; - public const string XChaCha20Poly1305KeyWrap = "XC20PKW"; - public const string EchdEsWithChaCha20Poly1305 = "ECDH-ES+C20PKW"; - public const string EchdEsWithXChaCha20Poly1305 = "ECDH-ES+XC20PKW"; + // https://tools.ietf.org/html/draft-amringer-jose-chacha-02#section-4.1 + public const string ChaCha20Poly1305 = "C20P"; + public const string XChaCha20Poly1305 = "XC20P"; + public const string ChaCha20Poly1305KeyWrap = "C20PKW"; + public const string XChaCha20Poly1305KeyWrap = "XC20PKW"; + public const string EchdEsWithChaCha20Poly1305 = "ECDH-ES+C20PKW"; + public const string EchdEsWithXChaCha20Poly1305 = "ECDH-ES+XC20PKW"; - // https://tools.ietf.org/html/rfc8037#section-5 - public const string EdDsa = "EdDSA"; + // https://tools.ietf.org/html/rfc8037#section-5 + public const string EdDsa = "EdDSA"; - public class Curves - { - // https://tools.ietf.org/html/rfc8037#section-5 - public const string Ed25519 = "Ed25519"; - public const string Ed448 = "Ed448"; - public const string X25519 = "X25519"; - public const string X448 = "X448"; - } + public class Curves + { + // https://tools.ietf.org/html/rfc8037#section-5 + public const string Ed25519 = "Ed25519"; + public const string Ed448 = "Ed448"; + public const string X25519 = "X25519"; + public const string X448 = "X448"; } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel/Encoding/Base16.cs b/src/ScottBrady.IdentityModel/Encoding/Base16.cs index e89dfdb..e2bcd50 100644 --- a/src/ScottBrady.IdentityModel/Encoding/Base16.cs +++ b/src/ScottBrady.IdentityModel/Encoding/Base16.cs @@ -1,17 +1,17 @@ using System; using System.Text; -namespace ScottBrady.IdentityModel +namespace ScottBrady.IdentityModel; + +/// +/// Base16 (hex) encoder. +/// Encode adapted from https://docs.microsoft.com/en-us/archive/blogs/blambert/blambertcodesnip-fast-byte-array-to-hex-string-conversion. +/// Decode adapted from https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa. +/// Faster alternatives are available. +/// +public static class Base16 { - /// - /// Base16 (hex) encoder. - /// Encode adapted from https://docs.microsoft.com/en-us/archive/blogs/blambert/blambertcodesnip-fast-byte-array-to-hex-string-conversion. - /// Decode adapted from https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa. - /// Faster alternatives are available. - /// - public static class Base16 - { - private static readonly string[] LookupTable = + private static readonly string[] LookupTable = { "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e", "1f", @@ -31,8 +31,8 @@ public static class Base16 "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff" }; - public static string Encode(byte[] value) - { + public static string Encode(byte[] value) + { if (value == null) throw new ArgumentNullException(nameof(value)); var stringBuilder = new StringBuilder(); @@ -41,8 +41,8 @@ public static string Encode(byte[] value) return stringBuilder.ToString(); } - public static byte[] Decode(string value) - { + public static byte[] Decode(string value) + { if (string.IsNullOrWhiteSpace(value)) throw new ArgumentNullException(nameof(value)); var bytes = new byte[value.Length / 2]; @@ -53,5 +53,4 @@ public static byte[] Decode(string value) return bytes; } - } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel/Encoding/Base62.cs b/src/ScottBrady.IdentityModel/Encoding/Base62.cs index 463c856..c8aecd3 100644 --- a/src/ScottBrady.IdentityModel/Encoding/Base62.cs +++ b/src/ScottBrady.IdentityModel/Encoding/Base62.cs @@ -3,17 +3,17 @@ using System.Linq; using System.Text; -namespace ScottBrady.IdentityModel +namespace ScottBrady.IdentityModel; + +/// +/// Adapted from https://github.com/ghost1face/base62 +/// +public static class Base62 { - /// - /// Adapted from https://github.com/ghost1face/base62 - /// - public static class Base62 - { - public const string CharacterSet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + public const string CharacterSet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - public static string Encode(byte[] value) - { + public static string Encode(byte[] value) + { var convertedBytes = BaseConvert(value, 256, 62); var builder = new StringBuilder(); @@ -25,8 +25,8 @@ public static string Encode(byte[] value) return builder.ToString(); } - public static byte[] Decode(string value) - { + public static byte[] Decode(string value) + { var arr = new byte[value.Length]; for (var i = 0; i < arr.Length; i++) { @@ -36,8 +36,8 @@ public static byte[] Decode(string value) return BaseConvert(arr, 62, 256); } - private static byte[] BaseConvert(byte[] source, int sourceBase, int targetBase) - { + private static byte[] BaseConvert(byte[] source, int sourceBase, int targetBase) + { if (source == null) throw new ArgumentNullException(nameof(source)); int count; @@ -62,5 +62,4 @@ private static byte[] BaseConvert(byte[] source, int sourceBase, int targetBase) return result.Select(x => (byte) x).ToArray(); } - } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel/Extensions/ByteArrayExtensions.cs b/src/ScottBrady.IdentityModel/Extensions/ByteArrayExtensions.cs index a9371c5..7992720 100644 --- a/src/ScottBrady.IdentityModel/Extensions/ByteArrayExtensions.cs +++ b/src/ScottBrady.IdentityModel/Extensions/ByteArrayExtensions.cs @@ -1,16 +1,16 @@ using System; using System.Linq; -namespace ScottBrady.IdentityModel +namespace ScottBrady.IdentityModel; + +public static class ByteArrayExtensions { - public static class ByteArrayExtensions + /// + /// Combines multiple byte arrays. + /// https://stackoverflow.com/questions/415291/best-way-to-combine-two-or-more-byte-arrays-in-c-sharp + /// + public static byte[] Combine(this byte[] source, params byte[][] arrays) { - /// - /// Combines multiple byte arrays. - /// https://stackoverflow.com/questions/415291/best-way-to-combine-two-or-more-byte-arrays-in-c-sharp - /// - public static byte[] Combine(this byte[] source, params byte[][] arrays) - { var output = new byte[source.Length + arrays.Sum(a => a.Length)]; Buffer.BlockCopy(source, 0, output, 0, source.Length); @@ -22,5 +22,4 @@ public static byte[] Combine(this byte[] source, params byte[][] arrays) return output; } - } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel/Extensions/StreamExtensions.cs b/src/ScottBrady.IdentityModel/Extensions/StreamExtensions.cs index fb2752e..722be27 100644 --- a/src/ScottBrady.IdentityModel/Extensions/StreamExtensions.cs +++ b/src/ScottBrady.IdentityModel/Extensions/StreamExtensions.cs @@ -1,16 +1,15 @@ using System.IO; -namespace ScottBrady.IdentityModel +namespace ScottBrady.IdentityModel; + +public static class StreamExtensions { - public static class StreamExtensions + public static bool TryRead(this Stream stream, int length, out byte[] bytes) { - public static bool TryRead(this Stream stream, int length, out byte[] bytes) - { - bytes = new byte[length]; - var bytesRead = stream.Read(bytes, 0, length); + bytes = new byte[length]; + var bytesRead = stream.Read(bytes, 0, length); - if (bytesRead != length) return false; - return true; - } + if (bytesRead != length) return false; + return true; } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel/Tokens/EdDsaSecurityKey.cs b/src/ScottBrady.IdentityModel/Tokens/EdDsaSecurityKey.cs index 9ed940b..80b6299 100644 --- a/src/ScottBrady.IdentityModel/Tokens/EdDsaSecurityKey.cs +++ b/src/ScottBrady.IdentityModel/Tokens/EdDsaSecurityKey.cs @@ -3,45 +3,44 @@ using Org.BouncyCastle.Crypto.Parameters; using ScottBrady.IdentityModel.Crypto; -namespace ScottBrady.IdentityModel.Tokens +namespace ScottBrady.IdentityModel.Tokens; + +/// +/// A Microsoft.IdentityModel security key for EdDSA. +/// +public class EdDsaSecurityKey : AsymmetricSecurityKey { - /// - /// A Microsoft.IdentityModel security key for EdDSA. - /// - public class EdDsaSecurityKey : AsymmetricSecurityKey - { - public EdDsa EdDsa { get; } + public EdDsa EdDsa { get; } - private EdDsaSecurityKey() - { - CryptoProviderFactory.CustomCryptoProvider = new ExtendedCryptoProvider(); - } + private EdDsaSecurityKey() + { + CryptoProviderFactory.CustomCryptoProvider = new ExtendedCryptoProvider(); + } - public EdDsaSecurityKey(EdDsa edDsa) : this() - { - EdDsa = edDsa ?? throw new ArgumentNullException(nameof(edDsa)); - } + public EdDsaSecurityKey(EdDsa edDsa) : this() + { + EdDsa = edDsa ?? throw new ArgumentNullException(nameof(edDsa)); + } - [Obsolete("Deprecated in favor of EdDsa constructor")] - public EdDsaSecurityKey(Ed25519PrivateKeyParameters keyParameters) : this() - { - if (keyParameters == null) throw new ArgumentNullException(nameof(keyParameters)); - EdDsa = EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {D = keyParameters.GetEncoded()}); - } + [Obsolete("Deprecated in favor of EdDsa constructor")] + public EdDsaSecurityKey(Ed25519PrivateKeyParameters keyParameters) : this() + { + if (keyParameters == null) throw new ArgumentNullException(nameof(keyParameters)); + EdDsa = EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {D = keyParameters.GetEncoded()}); + } - [Obsolete("Deprecated in favor of EdDsa constructor")] - public EdDsaSecurityKey(Ed25519PublicKeyParameters keyParameters) : this() - { - if (keyParameters == null) throw new ArgumentNullException(nameof(keyParameters)); - EdDsa = EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {X = keyParameters.GetEncoded()}); - } + [Obsolete("Deprecated in favor of EdDsa constructor")] + public EdDsaSecurityKey(Ed25519PublicKeyParameters keyParameters) : this() + { + if (keyParameters == null) throw new ArgumentNullException(nameof(keyParameters)); + EdDsa = EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {X = keyParameters.GetEncoded()}); + } - public override int KeySize => throw new NotImplementedException(); + public override int KeySize => throw new NotImplementedException(); - [Obsolete("HasPrivateKey method is deprecated, please use PrivateKeyStatus.")] - public override bool HasPrivateKey => EdDsa.Parameters.D != null; + [Obsolete("HasPrivateKey method is deprecated, please use PrivateKeyStatus.")] + public override bool HasPrivateKey => EdDsa.Parameters.D != null; - public override PrivateKeyStatus PrivateKeyStatus - => EdDsa.Parameters.D != null ? PrivateKeyStatus.Exists : PrivateKeyStatus.DoesNotExist; - } + public override PrivateKeyStatus PrivateKeyStatus + => EdDsa.Parameters.D != null ? PrivateKeyStatus.Exists : PrivateKeyStatus.DoesNotExist; } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel/Tokens/EdDsaSignatureProvider.cs b/src/ScottBrady.IdentityModel/Tokens/EdDsaSignatureProvider.cs index 76d4865..a0e74fc 100644 --- a/src/ScottBrady.IdentityModel/Tokens/EdDsaSignatureProvider.cs +++ b/src/ScottBrady.IdentityModel/Tokens/EdDsaSignatureProvider.cs @@ -2,22 +2,21 @@ using System.Linq; using Microsoft.IdentityModel.Tokens; -namespace ScottBrady.IdentityModel.Tokens +namespace ScottBrady.IdentityModel.Tokens; + +internal class EdDsaSignatureProvider : SignatureProvider { - internal class EdDsaSignatureProvider : SignatureProvider - { - private readonly EdDsaSecurityKey edDsaKey; + private readonly EdDsaSecurityKey edDsaKey; - public EdDsaSignatureProvider(EdDsaSecurityKey key, string algorithm) - : base(key, algorithm) - { + public EdDsaSignatureProvider(EdDsaSecurityKey key, string algorithm) + : base(key, algorithm) + { edDsaKey = key; } - protected override void Dispose(bool disposing) { } - public override byte[] Sign(byte[] input) => edDsaKey.EdDsa.Sign(input); - public override bool Verify(byte[] input, byte[] signature) => edDsaKey.EdDsa.Verify(input, signature); - public override bool Verify(byte[] input, int inputOffset, int inputLength, byte[] signature, int signatureOffset, int signatureLength) - => edDsaKey.EdDsa.Verify(input, inputOffset, inputLength, signature, signatureOffset, signatureLength); - } + protected override void Dispose(bool disposing) { } + public override byte[] Sign(byte[] input) => edDsaKey.EdDsa.Sign(input); + public override bool Verify(byte[] input, byte[] signature) => edDsaKey.EdDsa.Verify(input, signature); + public override bool Verify(byte[] input, int inputOffset, int inputLength, byte[] signature, int signatureOffset, int signatureLength) + => edDsaKey.EdDsa.Verify(input, inputOffset, inputLength, signature, signatureOffset, signatureLength); } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel/Tokens/ExtendedCryptoProvider.cs b/src/ScottBrady.IdentityModel/Tokens/ExtendedCryptoProvider.cs index d8820fa..154cc3d 100644 --- a/src/ScottBrady.IdentityModel/Tokens/ExtendedCryptoProvider.cs +++ b/src/ScottBrady.IdentityModel/Tokens/ExtendedCryptoProvider.cs @@ -2,27 +2,26 @@ using Microsoft.IdentityModel.Tokens; using ScottBrady.IdentityModel.Tokens; -namespace ScottBrady.IdentityModel.Crypto +namespace ScottBrady.IdentityModel.Crypto; + +internal class ExtendedCryptoProvider : ICryptoProvider { - internal class ExtendedCryptoProvider : ICryptoProvider - { - public bool IsSupportedAlgorithm(string algorithm, params object[] args) - => algorithm == ExtendedSecurityAlgorithms.EdDsa; + public bool IsSupportedAlgorithm(string algorithm, params object[] args) + => algorithm == ExtendedSecurityAlgorithms.EdDsa; - public object Create(string algorithm, params object[] args) + public object Create(string algorithm, params object[] args) + { + if (algorithm == ExtendedSecurityAlgorithms.EdDsa && args[0] is EdDsaSecurityKey key) { - if (algorithm == ExtendedSecurityAlgorithms.EdDsa && args[0] is EdDsaSecurityKey key) - { - return new EdDsaSignatureProvider(key, algorithm); - } - - throw new NotSupportedException(); + return new EdDsaSignatureProvider(key, algorithm); } - public void Release(object cryptoInstance) - { - if (cryptoInstance is IDisposable disposableObject) - disposableObject.Dispose(); - } + throw new NotSupportedException(); + } + + public void Release(object cryptoInstance) + { + if (cryptoInstance is IDisposable disposableObject) + disposableObject.Dispose(); } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel/Tokens/JwtPayloadExtensions.cs b/src/ScottBrady.IdentityModel/Tokens/JwtPayloadExtensions.cs index b010f80..fcffa59 100644 --- a/src/ScottBrady.IdentityModel/Tokens/JwtPayloadExtensions.cs +++ b/src/ScottBrady.IdentityModel/Tokens/JwtPayloadExtensions.cs @@ -6,107 +6,106 @@ using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; -namespace ScottBrady.IdentityModel.Tokens +namespace ScottBrady.IdentityModel.Tokens; + +public static class JwtPayloadExtensions { - public static class JwtPayloadExtensions + /// + /// Creates a JWT payload from a SecurityTokenDescriptor. + /// Inspired by logic found in Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler + /// + public static string ToJwtPayload(this SecurityTokenDescriptor tokenDescriptor, JwtDateTimeFormat dateTimeFormat = JwtDateTimeFormat.Unix) { - /// - /// Creates a JWT payload from a SecurityTokenDescriptor. - /// Inspired by logic found in Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler - /// - public static string ToJwtPayload(this SecurityTokenDescriptor tokenDescriptor, JwtDateTimeFormat dateTimeFormat = JwtDateTimeFormat.Unix) - { - if (tokenDescriptor == null) throw new ArgumentNullException(nameof(tokenDescriptor)); + if (tokenDescriptor == null) throw new ArgumentNullException(nameof(tokenDescriptor)); - Dictionary payload; - if (tokenDescriptor.Subject != null) - { - payload = ToJwtClaimDictionary(tokenDescriptor.Subject.Claims); - } - else - { - payload = new Dictionary(); - } + Dictionary payload; + if (tokenDescriptor.Subject != null) + { + payload = ToJwtClaimDictionary(tokenDescriptor.Subject.Claims); + } + else + { + payload = new Dictionary(); + } - if (tokenDescriptor.Claims != null && tokenDescriptor.Claims.Count > 0) - { - foreach (var pair in tokenDescriptor.Claims) - payload[pair.Key] = pair.Value; - } + if (tokenDescriptor.Claims != null && tokenDescriptor.Claims.Count > 0) + { + foreach (var pair in tokenDescriptor.Claims) + payload[pair.Key] = pair.Value; + } - if (tokenDescriptor.Issuer != null) - payload.AddClaimIfNotPresent(JwtRegisteredClaimNames.Iss, tokenDescriptor.Issuer); - if (tokenDescriptor.Audience != null) - payload.AddClaimIfNotPresent(JwtRegisteredClaimNames.Aud, tokenDescriptor.Audience); + if (tokenDescriptor.Issuer != null) + payload.AddClaimIfNotPresent(JwtRegisteredClaimNames.Iss, tokenDescriptor.Issuer); + if (tokenDescriptor.Audience != null) + payload.AddClaimIfNotPresent(JwtRegisteredClaimNames.Aud, tokenDescriptor.Audience); - Func dateTimeFormatFunc = null; - if (dateTimeFormat == JwtDateTimeFormat.Unix) dateTimeFormatFunc = GetUnixClaimValueOrDefault; - if (dateTimeFormat == JwtDateTimeFormat.Iso) dateTimeFormatFunc = GetIsoClaimValueOrDefault; - if (dateTimeFormatFunc == null) throw new NotSupportedException("Unsupported DateTime formatting type"); + Func dateTimeFormatFunc = null; + if (dateTimeFormat == JwtDateTimeFormat.Unix) dateTimeFormatFunc = GetUnixClaimValueOrDefault; + if (dateTimeFormat == JwtDateTimeFormat.Iso) dateTimeFormatFunc = GetIsoClaimValueOrDefault; + if (dateTimeFormatFunc == null) throw new NotSupportedException("Unsupported DateTime formatting type"); - var now = DateTime.UtcNow; + var now = DateTime.UtcNow; - payload.AddClaimIfNotPresent(JwtRegisteredClaimNames.Exp, dateTimeFormatFunc(tokenDescriptor.Expires, now.AddMinutes(60))); - payload.AddClaimIfNotPresent(JwtRegisteredClaimNames.Iat, dateTimeFormatFunc(tokenDescriptor.IssuedAt, now)); - payload.AddClaimIfNotPresent(JwtRegisteredClaimNames.Nbf, dateTimeFormatFunc(tokenDescriptor.NotBefore, now)); + payload.AddClaimIfNotPresent(JwtRegisteredClaimNames.Exp, dateTimeFormatFunc(tokenDescriptor.Expires, now.AddMinutes(60))); + payload.AddClaimIfNotPresent(JwtRegisteredClaimNames.Iat, dateTimeFormatFunc(tokenDescriptor.IssuedAt, now)); + payload.AddClaimIfNotPresent(JwtRegisteredClaimNames.Nbf, dateTimeFormatFunc(tokenDescriptor.NotBefore, now)); - return JsonSerializer.Serialize(payload, new JsonSerializerOptions{Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping}); - } + return JsonSerializer.Serialize(payload, new JsonSerializerOptions{Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping}); + } + + /// + /// Handling for serializing claims in a ClaimsIdentity. + /// Adapted from Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.CreateDictionaryFromClaims + /// + public static Dictionary ToJwtClaimDictionary(IEnumerable claims) + { + var payload = new Dictionary(); - /// - /// Handling for serializing claims in a ClaimsIdentity. - /// Adapted from Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.CreateDictionaryFromClaims - /// - public static Dictionary ToJwtClaimDictionary(IEnumerable claims) + foreach (var claim in claims) { - var payload = new Dictionary(); + if (claim == null) continue; - foreach (var claim in claims) + if (payload.TryGetValue(claim.Type, out var existingValue)) { - if (claim == null) continue; - - if (payload.TryGetValue(claim.Type, out var existingValue)) - { - var existingValues = existingValue as IList; - - if (existingValues == null) - { - existingValues = new List(); - existingValues.Add(existingValue); - } + var existingValues = existingValue as IList; - existingValues.Add(claim.Value); - payload[claim.Type] = existingValues; - } - else + if (existingValues == null) { - payload[claim.Type] = claim.Value; + existingValues = new List(); + existingValues.Add(existingValue); } + + existingValues.Add(claim.Value); + payload[claim.Type] = existingValues; + } + else + { + payload[claim.Type] = claim.Value; } - - return payload; - } - - private static void AddClaimIfNotPresent(this Dictionary payload, string type, object value) - { - if (payload.TryGetValue(type, out _)) return; - payload[type] = value; } - private static Func GetUnixClaimValueOrDefault - => (value, defaultValue) => value.HasValue - ? EpochTime.GetIntDate(value.Value) - : EpochTime.GetIntDate(defaultValue); - - private static Func GetIsoClaimValueOrDefault - => (value, defaultValue) => value.HasValue - ? value.Value.ToString("yyyy-MM-ddTHH:mm:sszzz") - : defaultValue.ToString("yyyy-MM-ddTHH:mm:sszzz"); + return payload; } - public enum JwtDateTimeFormat + private static void AddClaimIfNotPresent(this Dictionary payload, string type, object value) { - Unix, - Iso - } + if (payload.TryGetValue(type, out _)) return; + payload[type] = value; + } + + private static Func GetUnixClaimValueOrDefault + => (value, defaultValue) => value.HasValue + ? EpochTime.GetIntDate(value.Value) + : EpochTime.GetIntDate(defaultValue); + + private static Func GetIsoClaimValueOrDefault + => (value, defaultValue) => value.HasValue + ? value.Value.ToString("yyyy-MM-ddTHH:mm:sszzz") + : defaultValue.ToString("yyyy-MM-ddTHH:mm:sszzz"); +} + +public enum JwtDateTimeFormat +{ + Unix, + Iso } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel/Tokens/JwtPayloadSecurityToken.cs b/src/ScottBrady.IdentityModel/Tokens/JwtPayloadSecurityToken.cs index e314bd3..776a98c 100644 --- a/src/ScottBrady.IdentityModel/Tokens/JwtPayloadSecurityToken.cs +++ b/src/ScottBrady.IdentityModel/Tokens/JwtPayloadSecurityToken.cs @@ -6,42 +6,41 @@ using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; -namespace ScottBrady.IdentityModel.Tokens +namespace ScottBrady.IdentityModel.Tokens; + +public abstract class JwtPayloadSecurityToken : SecurityToken { - public abstract class JwtPayloadSecurityToken : SecurityToken - { - protected JwtPayloadSecurityToken() { } + protected JwtPayloadSecurityToken() { } - public JwtPayloadSecurityToken(string payload) + public JwtPayloadSecurityToken(string payload) + { + try { - try - { - InnerToken = new JsonWebToken("{}", payload); + InnerToken = new JsonWebToken("{}", payload); - using (var hasher = SHA256.Create()) - { - var hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(payload)); - TokenHash = Convert.ToBase64String(hash); - } - } - catch (Exception e) + using (var hasher = SHA256.Create()) { - throw new ArgumentException("Token does not contain valid JSON", e); - } + var hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(payload)); + TokenHash = Convert.ToBase64String(hash); + } } + catch (Exception e) + { + throw new ArgumentException("Token does not contain valid JSON", e); + } + } - public override string Id => InnerToken.Id; - public override string Issuer => InnerToken.Issuer; - public virtual IEnumerable Audiences => InnerToken.Audiences; - public virtual string Subject => InnerToken.Subject; - public virtual string Actor => InnerToken.Actor; - public virtual IEnumerable Claims => InnerToken.Claims; + public override string Id => InnerToken.Id; + public override string Issuer => InnerToken.Issuer; + public virtual IEnumerable Audiences => InnerToken.Audiences; + public virtual string Subject => InnerToken.Subject; + public virtual string Actor => InnerToken.Actor; + public virtual IEnumerable Claims => InnerToken.Claims; - public virtual DateTime IssuedAt => InnerToken.IssuedAt; - public override DateTime ValidFrom => InnerToken.ValidFrom; - public override DateTime ValidTo => InnerToken.ValidTo; + public virtual DateTime IssuedAt => InnerToken.IssuedAt; + public override DateTime ValidFrom => InnerToken.ValidFrom; + public override DateTime ValidTo => InnerToken.ValidTo; - protected JsonWebToken InnerToken { get; } - public virtual string TokenHash { get; } - } + protected JsonWebToken InnerToken { get; } + public virtual string TokenHash { get; } } \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel/Tokens/JwtPayloadTokenHandler.cs b/src/ScottBrady.IdentityModel/Tokens/JwtPayloadTokenHandler.cs index 3e4af4d..f6729a3 100644 --- a/src/ScottBrady.IdentityModel/Tokens/JwtPayloadTokenHandler.cs +++ b/src/ScottBrady.IdentityModel/Tokens/JwtPayloadTokenHandler.cs @@ -5,144 +5,143 @@ using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; -namespace ScottBrady.IdentityModel.Tokens +namespace ScottBrady.IdentityModel.Tokens; + +public abstract class JwtPayloadTokenHandler : TokenHandler, ISecurityTokenValidator { - public abstract class JwtPayloadTokenHandler : TokenHandler, ISecurityTokenValidator + public abstract bool CanReadToken(string securityToken); + public bool CanValidateToken => true; + + public virtual ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters, out SecurityToken validatedToken) { - public abstract bool CanReadToken(string securityToken); - public bool CanValidateToken => true; + var result = ValidateToken(token, validationParameters); - public virtual ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters, out SecurityToken validatedToken) + if (result.IsValid) { - var result = ValidateToken(token, validationParameters); - - if (result.IsValid) - { - validatedToken = result.SecurityToken; - return new ClaimsPrincipal(result.ClaimsIdentity); - } - - throw result.Exception; + validatedToken = result.SecurityToken; + return new ClaimsPrincipal(result.ClaimsIdentity); } - public abstract TokenValidationResult ValidateToken(string token, TokenValidationParameters validationParameters); + throw result.Exception; + } + + public abstract TokenValidationResult ValidateToken(string token, TokenValidationParameters validationParameters); - /// - /// Validates a tokens lifetime, audience, and issuer using JWT payload validation rules. - /// Also checks for token replay - /// - protected virtual TokenValidationResult ValidateTokenPayload(JwtPayloadSecurityToken token, TokenValidationParameters validationParameters) - { - if (token == null) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(token))}; - if (validationParameters == null) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(validationParameters))}; + /// + /// Validates a tokens lifetime, audience, and issuer using JWT payload validation rules. + /// Also checks for token replay + /// + protected virtual TokenValidationResult ValidateTokenPayload(JwtPayloadSecurityToken token, TokenValidationParameters validationParameters) + { + if (token == null) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(token))}; + if (validationParameters == null) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(validationParameters))}; - var expires = token.ValidTo == DateTime.MinValue ? null : new DateTime?(token.ValidTo); - var notBefore = token.ValidFrom == DateTime.MinValue ? null : new DateTime?(token.ValidFrom); + var expires = token.ValidTo == DateTime.MinValue ? null : new DateTime?(token.ValidTo); + var notBefore = token.ValidFrom == DateTime.MinValue ? null : new DateTime?(token.ValidFrom); - try - { - ValidateLifetime(notBefore, expires, token, validationParameters); - ValidateAudience(token.Audiences, token, validationParameters); - ValidateIssuer(token.Issuer, token, validationParameters); - ValidateTokenReplay(expires, token.TokenHash, validationParameters); - } - catch (Exception e) - { - return new TokenValidationResult {Exception = e}; - } - - return new TokenValidationResult - { - SecurityToken = token, - ClaimsIdentity = CreateClaimsIdentity(token, validationParameters), - IsValid = true - }; + try + { + ValidateLifetime(notBefore, expires, token, validationParameters); + ValidateAudience(token.Audiences, token, validationParameters); + ValidateIssuer(token.Issuer, token, validationParameters); + ValidateTokenReplay(expires, token.TokenHash, validationParameters); } + catch (Exception e) + { + return new TokenValidationResult {Exception = e}; + } + + return new TokenValidationResult + { + SecurityToken = token, + ClaimsIdentity = CreateClaimsIdentity(token, validationParameters), + IsValid = true + }; + } - protected virtual void ValidateLifetime(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters) - => Validators.ValidateLifetime(notBefore, expires, securityToken, validationParameters); + protected virtual void ValidateLifetime(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters) + => Validators.ValidateLifetime(notBefore, expires, securityToken, validationParameters); - protected virtual void ValidateAudience(IEnumerable audiences, SecurityToken securityToken, TokenValidationParameters validationParameters) - => Validators.ValidateAudience(audiences, securityToken, validationParameters); + protected virtual void ValidateAudience(IEnumerable audiences, SecurityToken securityToken, TokenValidationParameters validationParameters) + => Validators.ValidateAudience(audiences, securityToken, validationParameters); - protected virtual string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters) - => Validators.ValidateIssuer(issuer, securityToken, validationParameters); + protected virtual string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters) + => Validators.ValidateIssuer(issuer, securityToken, validationParameters); - protected virtual void ValidateTokenReplay(DateTime? expirationTime, string securityToken, TokenValidationParameters validationParameters) - => Validators.ValidateTokenReplay(expirationTime, securityToken, validationParameters); + protected virtual void ValidateTokenReplay(DateTime? expirationTime, string securityToken, TokenValidationParameters validationParameters) + => Validators.ValidateTokenReplay(expirationTime, securityToken, validationParameters); - protected virtual void ValidateIssuerSecurityKey(SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters) - => Validators.ValidateIssuerSecurityKey(securityKey, securityToken, validationParameters); + protected virtual void ValidateIssuerSecurityKey(SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters) + => Validators.ValidateIssuerSecurityKey(securityKey, securityToken, validationParameters); - protected virtual ClaimsIdentity CreateClaimsIdentity(JwtPayloadSecurityToken token, TokenValidationParameters validationParameters) - { - if (token == null) throw LogHelper.LogArgumentNullException(nameof(token)); - if (validationParameters == null) throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + protected virtual ClaimsIdentity CreateClaimsIdentity(JwtPayloadSecurityToken token, TokenValidationParameters validationParameters) + { + if (token == null) throw LogHelper.LogArgumentNullException(nameof(token)); + if (validationParameters == null) throw LogHelper.LogArgumentNullException(nameof(validationParameters)); - var issuer = token.Issuer; - if (string.IsNullOrWhiteSpace(issuer)) issuer = ClaimsIdentity.DefaultIssuer; + var issuer = token.Issuer; + if (string.IsNullOrWhiteSpace(issuer)) issuer = ClaimsIdentity.DefaultIssuer; - var identity = validationParameters.CreateClaimsIdentity(token, issuer); - foreach (var claim in token.Claims) + var identity = validationParameters.CreateClaimsIdentity(token, issuer); + foreach (var claim in token.Claims) + { + if (claim.Properties.Count == 0) { - if (claim.Properties.Count == 0) - { - identity.AddClaim(new Claim(claim.Type, claim.Value, claim.ValueType, issuer, issuer, identity)); - } - else - { - var mappedClaim = new Claim(claim.Type, claim.Value, claim.ValueType, issuer, issuer, identity); - - foreach (var kv in claim.Properties) - mappedClaim.Properties[kv.Key] = kv.Value; - - identity.AddClaim(mappedClaim); - } + identity.AddClaim(new Claim(claim.Type, claim.Value, claim.ValueType, issuer, issuer, identity)); } + else + { + var mappedClaim = new Claim(claim.Type, claim.Value, claim.ValueType, issuer, issuer, identity); - return identity; - } - - protected virtual IEnumerable GetDecryptionKeys(string token, TokenValidationParameters validationParameters) - { - List keys = null; + foreach (var kv in claim.Properties) + mappedClaim.Properties[kv.Key] = kv.Value; - if (validationParameters.TokenDecryptionKeyResolver != null) - { - keys = validationParameters.TokenDecryptionKeyResolver(token, null, null, validationParameters)?.ToList(); + identity.AddClaim(mappedClaim); } + } - if (keys == null || !keys.Any()) - { - keys = new List(); - if (validationParameters.TokenDecryptionKey != null) - keys.Add(validationParameters.TokenDecryptionKey); - if (validationParameters.TokenDecryptionKeys != null && validationParameters.TokenDecryptionKeys.Any()) - keys.AddRange(validationParameters.TokenDecryptionKeys); - } + return identity; + } + + protected virtual IEnumerable GetDecryptionKeys(string token, TokenValidationParameters validationParameters) + { + List keys = null; - return keys; + if (validationParameters.TokenDecryptionKeyResolver != null) + { + keys = validationParameters.TokenDecryptionKeyResolver(token, null, null, validationParameters)?.ToList(); } - protected virtual IEnumerable GetSigningKeys(string token, TokenValidationParameters validationParameters) + if (keys == null || !keys.Any()) { - List keys = null; + keys = new List(); + if (validationParameters.TokenDecryptionKey != null) + keys.Add(validationParameters.TokenDecryptionKey); + if (validationParameters.TokenDecryptionKeys != null && validationParameters.TokenDecryptionKeys.Any()) + keys.AddRange(validationParameters.TokenDecryptionKeys); + } + + return keys; + } + + protected virtual IEnumerable GetSigningKeys(string token, TokenValidationParameters validationParameters) + { + List keys = null; - if (validationParameters.IssuerSigningKeyResolver != null) - { - keys = validationParameters.IssuerSigningKeyResolver(token, null, null, validationParameters)?.ToList(); - } + if (validationParameters.IssuerSigningKeyResolver != null) + { + keys = validationParameters.IssuerSigningKeyResolver(token, null, null, validationParameters)?.ToList(); + } - if (keys == null || !keys.Any()) - { - keys = new List(); - if (validationParameters.IssuerSigningKey != null) - keys.Add(validationParameters.IssuerSigningKey); - if (validationParameters.IssuerSigningKeys != null && validationParameters.IssuerSigningKeys.Any()) - keys.AddRange(validationParameters.IssuerSigningKeys); - } - - return keys; + if (keys == null || !keys.Any()) + { + keys = new List(); + if (validationParameters.IssuerSigningKey != null) + keys.Add(validationParameters.IssuerSigningKey); + if (validationParameters.IssuerSigningKeys != null && validationParameters.IssuerSigningKeys.Any()) + keys.AddRange(validationParameters.IssuerSigningKeys); } + + return keys; } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/AspNetCore/Identity/ExtendedPasswordValidatorTests.cs b/test/ScottBrady.IdentityModel.Tests/AspNetCore/Identity/ExtendedPasswordValidatorTests.cs index 67e254c..e8ba5c4 100644 --- a/test/ScottBrady.IdentityModel.Tests/AspNetCore/Identity/ExtendedPasswordValidatorTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/AspNetCore/Identity/ExtendedPasswordValidatorTests.cs @@ -7,36 +7,36 @@ using ScottBrady.IdentityModel.AspNetCore.Identity; using Xunit; -namespace ScottBrady.IdentityModel.Tests.AspNetCore.Identity +namespace ScottBrady.IdentityModel.Tests.AspNetCore.Identity; + +public class ExtendedPasswordValidatorTests { - public class ExtendedPasswordValidatorTests - { - private ExtendedPasswordValidator CreateSut() => new ExtendedPasswordValidator(); + private ExtendedPasswordValidator CreateSut() => new ExtendedPasswordValidator(); - private Mock> CreateMockedSut() - { + private Mock> CreateMockedSut() + { var sut = new Mock>() {CallBase = true}; sut.Setup(x => x.HasConsecutiveCharacters(It.IsAny(), It.IsAny())).Returns(false); return sut; } - [Fact] - public async Task ValidateAsync_WhenUserManagerIsNull_ExpectArgumentNullException() - { + [Fact] + public async Task ValidateAsync_WhenUserManagerIsNull_ExpectArgumentNullException() + { var sut = CreateMockedSut(); await Assert.ThrowsAsync(() => sut.Object.ValidateAsync(null, new IdentityUser(), "password")); } - [Fact] - public async Task ValidateAsync_WhenPasswordIsNull_ExpectArgumentNullException() - { + [Fact] + public async Task ValidateAsync_WhenPasswordIsNull_ExpectArgumentNullException() + { var sut = CreateMockedSut(); await Assert.ThrowsAsync(() => sut.Object.ValidateAsync(CreateMockUserManager().Object, new IdentityUser(), null)); } - [Fact] - public async Task ValidateAsync_WhenPasswordOptionsAreNotExtendedPasswordOptions_ExpectSuccess() - { + [Fact] + public async Task ValidateAsync_WhenPasswordOptionsAreNotExtendedPasswordOptions_ExpectSuccess() + { var options = new PasswordOptions(); var sut = CreateMockedSut(); @@ -45,9 +45,9 @@ public async Task ValidateAsync_WhenPasswordOptionsAreNotExtendedPasswordOptions result.Succeeded.Should().BeTrue(); } - [Fact] - public async Task ValidateAsync_WhenPasswordOptionsAreExtendedPasswordOptionsButNotSet_ExpectSuccess() - { + [Fact] + public async Task ValidateAsync_WhenPasswordOptionsAreExtendedPasswordOptionsButNotSet_ExpectSuccess() + { var options = new ExtendedPasswordOptions(); var sut = CreateMockedSut(); @@ -56,11 +56,11 @@ public async Task ValidateAsync_WhenPasswordOptionsAreExtendedPasswordOptionsBut result.Succeeded.Should().BeTrue(); } - [Theory] - [InlineData(1, "123")] - [InlineData(3, "1234")] - public async Task ValidateAsync_WhenExtendedPasswordOptionsAndPasswordIsTooLong_ExpectError(int maxLength, string password) - { + [Theory] + [InlineData(1, "123")] + [InlineData(3, "1234")] + public async Task ValidateAsync_WhenExtendedPasswordOptionsAndPasswordIsTooLong_ExpectError(int maxLength, string password) + { var options = new ExtendedPasswordOptions{MaxLength = maxLength}; var sut = CreateMockedSut(); @@ -70,13 +70,13 @@ public async Task ValidateAsync_WhenExtendedPasswordOptionsAndPasswordIsTooLong_ result.Errors.Should().Contain(x => x.Code == "PasswordTooLong" && x.Description.Contains(options.MaxLength.ToString())); } - [Theory] - [InlineData(-1, "123")] - [InlineData(0, "123")] - [InlineData(4, "123")] - [InlineData(3, "123")] - public async Task ValidateAsync_WhenExtendedPasswordOptionsAndPasswordIsNotTooLong_ExpectSuccess(int maxLength, string password) - { + [Theory] + [InlineData(-1, "123")] + [InlineData(0, "123")] + [InlineData(4, "123")] + [InlineData(3, "123")] + public async Task ValidateAsync_WhenExtendedPasswordOptionsAndPasswordIsNotTooLong_ExpectSuccess(int maxLength, string password) + { var options = new ExtendedPasswordOptions{MaxLength = maxLength}; var sut = CreateMockedSut(); @@ -85,12 +85,12 @@ public async Task ValidateAsync_WhenExtendedPasswordOptionsAndPasswordIsNotTooLo result.Succeeded.Should().BeTrue(); } - [Theory] - [InlineData(-1, "123")] - [InlineData(0, "123")] - [InlineData(3, "123")] - public async Task ValidateAsync_WhenExtendedPasswordOptionsAndMaxConsecutiveCharactersValid_ExpectSuccess(int maxConsecutive, string password) - { + [Theory] + [InlineData(-1, "123")] + [InlineData(0, "123")] + [InlineData(3, "123")] + public async Task ValidateAsync_WhenExtendedPasswordOptionsAndMaxConsecutiveCharactersValid_ExpectSuccess(int maxConsecutive, string password) + { var options = new ExtendedPasswordOptions {MaxConsecutiveChars = maxConsecutive}; var sut = CreateMockedSut(); @@ -101,9 +101,9 @@ public async Task ValidateAsync_WhenExtendedPasswordOptionsAndMaxConsecutiveChar result.Succeeded.Should().BeTrue(); } - [Fact] - public async Task ValidateAsync_WhenExtendedPasswordOptionsAndTooManyMaxConsecutiveCharacters_ExpectError() - { + [Fact] + public async Task ValidateAsync_WhenExtendedPasswordOptionsAndTooManyMaxConsecutiveCharacters_ExpectError() + { const string password = "Password123!"; var options = new ExtendedPasswordOptions {MaxConsecutiveChars = 2}; @@ -116,33 +116,33 @@ public async Task ValidateAsync_WhenExtendedPasswordOptionsAndTooManyMaxConsecut result.Errors.Should().Contain(x => x.Code == "TooManyConsecutiveCharacters" && x.Description.Contains(options.MaxConsecutiveChars.ToString())); } - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void HasConsecutiveCharacters_WhenPasswordIsNullOrWhitespace_ExpectArgumentNullException(string password) - { + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void HasConsecutiveCharacters_WhenPasswordIsNullOrWhitespace_ExpectArgumentNullException(string password) + { var sut = CreateSut(); Assert.Throws(() => sut.HasConsecutiveCharacters(password, 42)); } - [Theory] - [InlineData(0, "qwerty")] - [InlineData(1, "qwerty")] - [InlineData(2, "qwerty")] - [InlineData(2, "qqwerty")] - [InlineData(1, "qwertyuiopasdfghjklzxcvbnm")] - public void HasConsecutiveCharacters_WhenNoConsecutiveCharacters_ExpectFalse(int max, string password) - { + [Theory] + [InlineData(0, "qwerty")] + [InlineData(1, "qwerty")] + [InlineData(2, "qwerty")] + [InlineData(2, "qqwerty")] + [InlineData(1, "qwertyuiopasdfghjklzxcvbnm")] + public void HasConsecutiveCharacters_WhenNoConsecutiveCharacters_ExpectFalse(int max, string password) + { var sut = CreateSut(); var hasConsecutiveCharacters = sut.HasConsecutiveCharacters(password, max); hasConsecutiveCharacters.Should().BeFalse(); } - [Fact] - public void HasConsecutiveCharacters_WhenConsecutiveCharactersButUnderLimit_ExpectFalse() - { + [Fact] + public void HasConsecutiveCharacters_WhenConsecutiveCharactersButUnderLimit_ExpectFalse() + { const int maxConsecutiveCharacters = 2; const string password = "qqwweerrttyy"; @@ -152,9 +152,9 @@ public void HasConsecutiveCharacters_WhenConsecutiveCharactersButUnderLimit_Expe hasConsecutiveCharacters.Should().BeFalse(); } - [Fact] - public void HasConsecutiveCharacters_WhenConsecutiveCharactersButDifferentCasing_ExpectFalse() - { + [Fact] + public void HasConsecutiveCharacters_WhenConsecutiveCharactersButDifferentCasing_ExpectFalse() + { const int maxConsecutiveCharacters = 2; const string password = "QqQwerty"; @@ -164,21 +164,20 @@ public void HasConsecutiveCharacters_WhenConsecutiveCharactersButDifferentCasing hasConsecutiveCharacters.Should().BeFalse(); } - [Theory] - [InlineData(1, "qqqwertyy")] - [InlineData(2, "qqqwertyy")] - [InlineData(2, "qwertyyy")] - [InlineData(3, "qwertyyyy")] - public void HasConsecutiveCharacters_WhenConsecutiveCharactersAndOverLimit_ExpectTrue(int max, string password) - { + [Theory] + [InlineData(1, "qqqwertyy")] + [InlineData(2, "qqqwertyy")] + [InlineData(2, "qwertyyy")] + [InlineData(3, "qwertyyyy")] + public void HasConsecutiveCharacters_WhenConsecutiveCharactersAndOverLimit_ExpectTrue(int max, string password) + { var sut = CreateSut(); var hasConsecutiveCharacters = sut.HasConsecutiveCharacters(password, max); hasConsecutiveCharacters.Should().BeTrue(); } - private static Mock> CreateMockUserManager(PasswordOptions options = null) - => new Mock>(new Mock>().Object, - new OptionsWrapper(new IdentityOptions {Password = options ?? new PasswordOptions()}), null, null, null, null, null, null, null); - } + private static Mock> CreateMockUserManager(PasswordOptions options = null) + => new Mock>(new Mock>().Object, + new OptionsWrapper(new IdentityOptions {Password = options ?? new PasswordOptions()}), null, null, null, null, null, null, null); } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/AspNetCore/TagHelpers/NewPasswordTagHelperTests.cs b/test/ScottBrady.IdentityModel.Tests/AspNetCore/TagHelpers/NewPasswordTagHelperTests.cs index c3940c0..a50d70f 100644 --- a/test/ScottBrady.IdentityModel.Tests/AspNetCore/TagHelpers/NewPasswordTagHelperTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/AspNetCore/TagHelpers/NewPasswordTagHelperTests.cs @@ -11,301 +11,300 @@ using ScottBrady.IdentityModel.AspNetCore.TagHelpers; using Xunit; -namespace ScottBrady.IdentityModel.Tests.AspNetCore.TagHelpers +namespace ScottBrady.IdentityModel.Tests.AspNetCore.TagHelpers; + +public class NewPasswordTagHelperTests { - public class NewPasswordTagHelperTests - { - private readonly Mock mockHtmlGenerator = new Mock(); - private Mock> mockOptionsAccessor = new Mock>(); + private readonly Mock mockHtmlGenerator = new Mock(); + private Mock> mockOptionsAccessor = new Mock>(); - private readonly TagHelperContext testContext = new TagHelperContext("newpassword", new TagHelperAttributeList(), new Dictionary(), "123"); - private readonly TagHelperOutput testOutput = new TagHelperOutput("newpassword", new TagHelperAttributeList(), (b, encoder) => Task.FromResult(new DefaultTagHelperContent())); + private readonly TagHelperContext testContext = new TagHelperContext("newpassword", new TagHelperAttributeList(), new Dictionary(), "123"); + private readonly TagHelperOutput testOutput = new TagHelperOutput("newpassword", new TagHelperAttributeList(), (b, encoder) => Task.FromResult(new DefaultTagHelperContent())); - public NewPasswordTagHelperTests() - { - mockOptionsAccessor.Setup(x => x.Value).Returns(new IdentityOptions()); + public NewPasswordTagHelperTests() + { + mockOptionsAccessor.Setup(x => x.Value).Returns(new IdentityOptions()); - } + } - private NewPasswordTagHelper CreateSut() - { - return new NewPasswordTagHelper(mockHtmlGenerator?.Object, mockOptionsAccessor?.Object); - } + private NewPasswordTagHelper CreateSut() + { + return new NewPasswordTagHelper(mockHtmlGenerator?.Object, mockOptionsAccessor?.Object); + } - private Mock CreateMockedSut() - { - var sut = new Mock(mockHtmlGenerator?.Object, mockOptionsAccessor?.Object) {CallBase = true}; - sut.Setup(x => x.ProcessInputTag(It.IsAny(), It.IsAny())); - sut.Setup(x => x.ProcessIdentityPasswordRules(It.IsAny(), It.IsAny())); - return sut; - } - - [Fact] - public void ctor_WhenOptionsAccessorIsNull_ExpectNullOptions() - { - mockOptionsAccessor = null; - var sut = CreateSut(); - sut.Options.Should().BeNull(); - } - - [Fact] - public void ctor_WhenOptionsAccessorReturnsNullIsNull_ExpectNullOptions() - { - mockOptionsAccessor.Setup(x => x.Value).Returns(null); - var sut = CreateSut(); - sut.Options.Should().BeNull(); - } + private Mock CreateMockedSut() + { + var sut = new Mock(mockHtmlGenerator?.Object, mockOptionsAccessor?.Object) {CallBase = true}; + sut.Setup(x => x.ProcessInputTag(It.IsAny(), It.IsAny())); + sut.Setup(x => x.ProcessIdentityPasswordRules(It.IsAny(), It.IsAny())); + return sut; + } + + [Fact] + public void ctor_WhenOptionsAccessorIsNull_ExpectNullOptions() + { + mockOptionsAccessor = null; + var sut = CreateSut(); + sut.Options.Should().BeNull(); + } + + [Fact] + public void ctor_WhenOptionsAccessorReturnsNullIsNull_ExpectNullOptions() + { + mockOptionsAccessor.Setup(x => x.Value).Returns(null); + var sut = CreateSut(); + sut.Options.Should().BeNull(); + } - [Fact] - public void Process_WhenTagHelperContextIsNull_ExpectArgumentNullException() - { - var sut = CreateMockedSut(); - Assert.Throws(() => sut.Object.Process(null, testOutput)); - } - [Fact] - public void Process_WhenTagHelperOutputIsNull_ExpectArgumentNullException() - { - var sut = CreateMockedSut(); - Assert.Throws(() => sut.Object.Process(testContext, null)); - } - - [Fact] - public void Process_ExpectExistingAttributesUnmodified() - { - testOutput.Attributes.SetAttribute("name", "password"); - testOutput.Attributes.SetAttribute("aria-label", "Password"); - testOutput.Attributes.SetAttribute("required", "required"); + [Fact] + public void Process_WhenTagHelperContextIsNull_ExpectArgumentNullException() + { + var sut = CreateMockedSut(); + Assert.Throws(() => sut.Object.Process(null, testOutput)); + } + [Fact] + public void Process_WhenTagHelperOutputIsNull_ExpectArgumentNullException() + { + var sut = CreateMockedSut(); + Assert.Throws(() => sut.Object.Process(testContext, null)); + } + + [Fact] + public void Process_ExpectExistingAttributesUnmodified() + { + testOutput.Attributes.SetAttribute("name", "password"); + testOutput.Attributes.SetAttribute("aria-label", "Password"); + testOutput.Attributes.SetAttribute("required", "required"); - var sut = CreateMockedSut(); - sut.Object.Process(testContext, testOutput); + var sut = CreateMockedSut(); + sut.Object.Process(testContext, testOutput); - testOutput.Attributes["name"].Value.Should().Be("password"); - testOutput.Attributes["aria-label"].Value.Should().Be("Password"); - testOutput.Attributes["required"].Value.Should().Be("required"); - } - - [Fact] - public void Process_ExpectInputTag() - { - var sut = CreateMockedSut(); - sut.Object.Process(testContext, testOutput); - testOutput.TagName.Should().Be("input"); - } - - [Fact] - public void Process_ExpectTypeOfPassword() - { - var sut = CreateMockedSut(); - sut.Object.Process(testContext, testOutput); - testOutput.Attributes["type"].Value.Should().Be("password"); - } - - [Fact] - public void Process_WithExistingType_ExpectTypeOfPassword() - { - testOutput.Attributes.SetAttribute("type", "text"); + testOutput.Attributes["name"].Value.Should().Be("password"); + testOutput.Attributes["aria-label"].Value.Should().Be("Password"); + testOutput.Attributes["required"].Value.Should().Be("required"); + } + + [Fact] + public void Process_ExpectInputTag() + { + var sut = CreateMockedSut(); + sut.Object.Process(testContext, testOutput); + testOutput.TagName.Should().Be("input"); + } + + [Fact] + public void Process_ExpectTypeOfPassword() + { + var sut = CreateMockedSut(); + sut.Object.Process(testContext, testOutput); + testOutput.Attributes["type"].Value.Should().Be("password"); + } + + [Fact] + public void Process_WithExistingType_ExpectTypeOfPassword() + { + testOutput.Attributes.SetAttribute("type", "text"); - var sut = CreateMockedSut(); - sut.Object.Process(testContext, testOutput); + var sut = CreateMockedSut(); + sut.Object.Process(testContext, testOutput); - testOutput.Attributes["type"].Value.Should().Be("password"); - } - - [Fact] - public void Process_ExpectAutoCompleteOfNewPassword() - { - var sut = CreateMockedSut(); - sut.Object.Process(testContext, testOutput); - testOutput.Attributes["autocomplete"].Value.Should().Be("new-password"); - } - - [Fact] - public void Process_ExpectAutoCorrectDisabled() - { - var sut = CreateMockedSut(); - sut.Object.Process(testContext, testOutput); - testOutput.Attributes["autocorrect"].Value.Should().Be("off"); - } - - [Fact] - public void Process_ExpectAutoCapitalizeDisabled() - { - var sut = CreateMockedSut(); - sut.Object.Process(testContext, testOutput); - testOutput.Attributes["autocapitalize"].Value.Should().Be("off"); - } - - [Fact] - public void ProcessIdentityPasswordRules_WhenOptionsAreNull_ExpectArgumentNullException() - { - var sut = CreateSut(); - Assert.Throws(() => sut.ProcessIdentityPasswordRules(null, testOutput)); - } - - [Fact] - public void ProcessIdentityPasswordRules_WhenTagHelperOutputIsNull_ExpectArgumentNullException() - { - var sut = CreateSut(); - Assert.Throws(() => sut.ProcessIdentityPasswordRules(new PasswordOptions(), null)); - } - - [Theory] - [InlineData(1)] - [InlineData(6)] - [InlineData(42)] - [InlineData(255)] - public void ProcessIdentityPasswordRules_ExpectCorrectRequiredLengthAttributes(int expectedMinLength) - { - var sut = CreateSut(); - var options = new PasswordOptions {RequiredLength = expectedMinLength}; + testOutput.Attributes["type"].Value.Should().Be("password"); + } + + [Fact] + public void Process_ExpectAutoCompleteOfNewPassword() + { + var sut = CreateMockedSut(); + sut.Object.Process(testContext, testOutput); + testOutput.Attributes["autocomplete"].Value.Should().Be("new-password"); + } + + [Fact] + public void Process_ExpectAutoCorrectDisabled() + { + var sut = CreateMockedSut(); + sut.Object.Process(testContext, testOutput); + testOutput.Attributes["autocorrect"].Value.Should().Be("off"); + } + + [Fact] + public void Process_ExpectAutoCapitalizeDisabled() + { + var sut = CreateMockedSut(); + sut.Object.Process(testContext, testOutput); + testOutput.Attributes["autocapitalize"].Value.Should().Be("off"); + } + + [Fact] + public void ProcessIdentityPasswordRules_WhenOptionsAreNull_ExpectArgumentNullException() + { + var sut = CreateSut(); + Assert.Throws(() => sut.ProcessIdentityPasswordRules(null, testOutput)); + } + + [Fact] + public void ProcessIdentityPasswordRules_WhenTagHelperOutputIsNull_ExpectArgumentNullException() + { + var sut = CreateSut(); + Assert.Throws(() => sut.ProcessIdentityPasswordRules(new PasswordOptions(), null)); + } + + [Theory] + [InlineData(1)] + [InlineData(6)] + [InlineData(42)] + [InlineData(255)] + public void ProcessIdentityPasswordRules_ExpectCorrectRequiredLengthAttributes(int expectedMinLength) + { + var sut = CreateSut(); + var options = new PasswordOptions {RequiredLength = expectedMinLength}; - sut.ProcessIdentityPasswordRules(options, testOutput); + sut.ProcessIdentityPasswordRules(options, testOutput); - testOutput.Attributes["passwordrules"].Value.As().Should().Contain($"minlength: {expectedMinLength};"); - testOutput.Attributes["minlength"].Value.Should().Be(expectedMinLength); - } + testOutput.Attributes["passwordrules"].Value.As().Should().Contain($"minlength: {expectedMinLength};"); + testOutput.Attributes["minlength"].Value.Should().Be(expectedMinLength); + } - [Fact] - public void ProcessIdentityPasswordRules_WhenRequireLowercaseIsTrue_ExpectRequiredLowerAttribute() - { - var sut = CreateSut(); - var options = new PasswordOptions {RequireLowercase = true}; + [Fact] + public void ProcessIdentityPasswordRules_WhenRequireLowercaseIsTrue_ExpectRequiredLowerAttribute() + { + var sut = CreateSut(); + var options = new PasswordOptions {RequireLowercase = true}; - sut.ProcessIdentityPasswordRules(options, testOutput); + sut.ProcessIdentityPasswordRules(options, testOutput); - testOutput.Attributes["passwordrules"].Value.As().Should().Contain("required: lower;"); - } + testOutput.Attributes["passwordrules"].Value.As().Should().Contain("required: lower;"); + } - [Fact] - public void ProcessIdentityPasswordRules_WhenRequireLowercaseIsFalse_ExpectNoRequiredLowerAttribute() - { - var sut = CreateSut(); - var options = new PasswordOptions {RequireLowercase = false}; + [Fact] + public void ProcessIdentityPasswordRules_WhenRequireLowercaseIsFalse_ExpectNoRequiredLowerAttribute() + { + var sut = CreateSut(); + var options = new PasswordOptions {RequireLowercase = false}; - sut.ProcessIdentityPasswordRules(options, testOutput); + sut.ProcessIdentityPasswordRules(options, testOutput); - testOutput.Attributes["passwordrules"].Value.As().Should().NotContain("required: lower;"); - } + testOutput.Attributes["passwordrules"].Value.As().Should().NotContain("required: lower;"); + } - [Fact] - public void ProcessIdentityPasswordRules_WhenRequireUppercaseIsTrue_ExpectRequiredUpperAttribute() - { - var sut = CreateSut(); - var options = new PasswordOptions {RequireUppercase = true}; + [Fact] + public void ProcessIdentityPasswordRules_WhenRequireUppercaseIsTrue_ExpectRequiredUpperAttribute() + { + var sut = CreateSut(); + var options = new PasswordOptions {RequireUppercase = true}; - sut.ProcessIdentityPasswordRules(options, testOutput); + sut.ProcessIdentityPasswordRules(options, testOutput); - testOutput.Attributes["passwordrules"].Value.As().Should().Contain("required: upper;"); - } + testOutput.Attributes["passwordrules"].Value.As().Should().Contain("required: upper;"); + } - [Fact] - public void ProcessIdentityPasswordRules_WhenRequireUppercaseIsFalse_ExpectNoRequiredUpperAttribute() - { - var sut = CreateSut(); - var options = new PasswordOptions {RequireUppercase = false}; + [Fact] + public void ProcessIdentityPasswordRules_WhenRequireUppercaseIsFalse_ExpectNoRequiredUpperAttribute() + { + var sut = CreateSut(); + var options = new PasswordOptions {RequireUppercase = false}; - sut.ProcessIdentityPasswordRules(options, testOutput); + sut.ProcessIdentityPasswordRules(options, testOutput); - testOutput.Attributes["passwordrules"].Value.As().Should().NotContain("required: upper;"); - } + testOutput.Attributes["passwordrules"].Value.As().Should().NotContain("required: upper;"); + } - [Fact] - public void ProcessIdentityPasswordRules_WhenRequireDigitIsTrue_ExpectRequiredDigitAttribute() - { - var sut = CreateSut(); - var options = new PasswordOptions {RequireDigit = true}; + [Fact] + public void ProcessIdentityPasswordRules_WhenRequireDigitIsTrue_ExpectRequiredDigitAttribute() + { + var sut = CreateSut(); + var options = new PasswordOptions {RequireDigit = true}; - sut.ProcessIdentityPasswordRules(options, testOutput); + sut.ProcessIdentityPasswordRules(options, testOutput); - testOutput.Attributes["passwordrules"].Value.As().Should().Contain("required: digit;"); - } + testOutput.Attributes["passwordrules"].Value.As().Should().Contain("required: digit;"); + } - [Fact] - public void ProcessIdentityPasswordRules_WhenRequireDigitIsFalse_ExpectNoRequiredDigitAttribute() - { - var sut = CreateSut(); - var options = new PasswordOptions {RequireDigit = false}; + [Fact] + public void ProcessIdentityPasswordRules_WhenRequireDigitIsFalse_ExpectNoRequiredDigitAttribute() + { + var sut = CreateSut(); + var options = new PasswordOptions {RequireDigit = false}; - sut.ProcessIdentityPasswordRules(options, testOutput); + sut.ProcessIdentityPasswordRules(options, testOutput); - testOutput.Attributes["passwordrules"].Value.As().Should().NotContain("required: digit;"); - } + testOutput.Attributes["passwordrules"].Value.As().Should().NotContain("required: digit;"); + } - [Fact] - public void ProcessIdentityPasswordRules_WhenRequireNonAlphanumericIsTrue_ExpectRequiredCharactersAttribute() - { - var sut = CreateSut(); - var options = new PasswordOptions {RequireNonAlphanumeric = true}; + [Fact] + public void ProcessIdentityPasswordRules_WhenRequireNonAlphanumericIsTrue_ExpectRequiredCharactersAttribute() + { + var sut = CreateSut(); + var options = new PasswordOptions {RequireNonAlphanumeric = true}; - sut.ProcessIdentityPasswordRules(options, testOutput); + sut.ProcessIdentityPasswordRules(options, testOutput); - testOutput.Attributes["passwordrules"].Value.As().Should().Contain("required: special;"); - } + testOutput.Attributes["passwordrules"].Value.As().Should().Contain("required: special;"); + } - [Fact] - public void ProcessIdentityPasswordRules_WhenRequireNonAlphanumericIsFalse_ExpectNoRequiredCharactersAttribute() - { - var sut = CreateSut(); - var options = new PasswordOptions {RequireNonAlphanumeric = false}; + [Fact] + public void ProcessIdentityPasswordRules_WhenRequireNonAlphanumericIsFalse_ExpectNoRequiredCharactersAttribute() + { + var sut = CreateSut(); + var options = new PasswordOptions {RequireNonAlphanumeric = false}; - sut.ProcessIdentityPasswordRules(options, testOutput); + sut.ProcessIdentityPasswordRules(options, testOutput); - testOutput.Attributes["passwordrules"].Value.As().Should().NotContain("required: ["); - } + testOutput.Attributes["passwordrules"].Value.As().Should().NotContain("required: ["); + } - [Fact] - public void ProcessIdentityPasswordRules_WhenExtendedOptionsWithMaxLength_ExpectMaxLengthAttribute() - { - const int expectedMaxLength = 42; + [Fact] + public void ProcessIdentityPasswordRules_WhenExtendedOptionsWithMaxLength_ExpectMaxLengthAttribute() + { + const int expectedMaxLength = 42; - var sut = CreateSut(); - var options = new ExtendedPasswordOptions {MaxLength = expectedMaxLength}; + var sut = CreateSut(); + var options = new ExtendedPasswordOptions {MaxLength = expectedMaxLength}; - sut.ProcessIdentityPasswordRules(options, testOutput); - - testOutput.Attributes["passwordrules"].Value.As().Should().Contain($"maxlength: {expectedMaxLength};"); - testOutput.Attributes["maxlength"].Value.Should().Be(expectedMaxLength); - } - - [Theory] - [InlineData(null)] - [InlineData(0)] - [InlineData(-1)] - public void ProcessIdentityPasswordRules_WhenExtendedOptionsWithInvalidMaxLength_ExpectNoMaxLengthAttribute(int? expectedMaxLength) - { - var sut = CreateSut(); - var options = new ExtendedPasswordOptions {MaxLength = expectedMaxLength}; + sut.ProcessIdentityPasswordRules(options, testOutput); + + testOutput.Attributes["passwordrules"].Value.As().Should().Contain($"maxlength: {expectedMaxLength};"); + testOutput.Attributes["maxlength"].Value.Should().Be(expectedMaxLength); + } + + [Theory] + [InlineData(null)] + [InlineData(0)] + [InlineData(-1)] + public void ProcessIdentityPasswordRules_WhenExtendedOptionsWithInvalidMaxLength_ExpectNoMaxLengthAttribute(int? expectedMaxLength) + { + var sut = CreateSut(); + var options = new ExtendedPasswordOptions {MaxLength = expectedMaxLength}; - sut.ProcessIdentityPasswordRules(options, testOutput); - - testOutput.Attributes["passwordrules"].Value.As().Should().NotContain("maxlength"); - testOutput.Attributes["maxlength"].Should().BeNull(); - } - - [Theory] - [InlineData(0)] - [InlineData(2)] - public void ProcessIdentityPasswordRules_WhenExtendedOptionsWithMaxConsecutiveChars_ExpectMaxConsecutiveCharsAttribute(int? expectedMaxConsecutiveChars) - { - var sut = CreateSut(); - var options = new ExtendedPasswordOptions {MaxConsecutiveChars = expectedMaxConsecutiveChars}; + sut.ProcessIdentityPasswordRules(options, testOutput); + + testOutput.Attributes["passwordrules"].Value.As().Should().NotContain("maxlength"); + testOutput.Attributes["maxlength"].Should().BeNull(); + } + + [Theory] + [InlineData(0)] + [InlineData(2)] + public void ProcessIdentityPasswordRules_WhenExtendedOptionsWithMaxConsecutiveChars_ExpectMaxConsecutiveCharsAttribute(int? expectedMaxConsecutiveChars) + { + var sut = CreateSut(); + var options = new ExtendedPasswordOptions {MaxConsecutiveChars = expectedMaxConsecutiveChars}; - sut.ProcessIdentityPasswordRules(options, testOutput); - - testOutput.Attributes["passwordrules"].Value.As().Should().Contain($"max-consecutive: {expectedMaxConsecutiveChars};"); - } - - [Theory] - [InlineData(null)] - [InlineData(-1)] - public void ProcessIdentityPasswordRules_WhenExtendedOptionsWithInvalidMaxConsecutiveChars_ExpectNoMaxConsecutiveCharsAttribute(int? expectedMaxConsecutiveChars) - { - var sut = CreateSut(); - var options = new ExtendedPasswordOptions {MaxConsecutiveChars = expectedMaxConsecutiveChars}; + sut.ProcessIdentityPasswordRules(options, testOutput); + + testOutput.Attributes["passwordrules"].Value.As().Should().Contain($"max-consecutive: {expectedMaxConsecutiveChars};"); + } + + [Theory] + [InlineData(null)] + [InlineData(-1)] + public void ProcessIdentityPasswordRules_WhenExtendedOptionsWithInvalidMaxConsecutiveChars_ExpectNoMaxConsecutiveCharsAttribute(int? expectedMaxConsecutiveChars) + { + var sut = CreateSut(); + var options = new ExtendedPasswordOptions {MaxConsecutiveChars = expectedMaxConsecutiveChars}; - sut.ProcessIdentityPasswordRules(options, testOutput); + sut.ProcessIdentityPasswordRules(options, testOutput); - testOutput.Attributes["passwordrules"].Value.As().Should().NotContain("max-consecutive"); - } + testOutput.Attributes["passwordrules"].Value.As().Should().NotContain("max-consecutive"); } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Encoding/Base16Tests.cs b/test/ScottBrady.IdentityModel.Tests/Encoding/Base16Tests.cs index 3e995a9..1cff7b7 100644 --- a/test/ScottBrady.IdentityModel.Tests/Encoding/Base16Tests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Encoding/Base16Tests.cs @@ -3,21 +3,21 @@ using FluentAssertions; using Xunit; -namespace ScottBrady.IdentityModel.Tests +namespace ScottBrady.IdentityModel.Tests; + +public class Base16Tests { - public class Base16Tests + [Theory] + [InlineData("t", "74")] + [InlineData("te", "7465")] + [InlineData("tes", "746573")] + [InlineData("test", "74657374")] + [InlineData("test_", "746573745f")] + [InlineData("test_v", "746573745f76")] + [InlineData("test_va", "746573745f7661")] + [InlineData("test_val", "746573745f76616c")] + public void WithKnownValues_ExpectCorrectValuesEncoded(string testValue, string expectedResult) { - [Theory] - [InlineData("t", "74")] - [InlineData("te", "7465")] - [InlineData("tes", "746573")] - [InlineData("test", "74657374")] - [InlineData("test_", "746573745f")] - [InlineData("test_v", "746573745f76")] - [InlineData("test_va", "746573745f7661")] - [InlineData("test_val", "746573745f76616c")] - public void WithKnownValues_ExpectCorrectValuesEncoded(string testValue, string expectedResult) - { var testBytes = Encoding.UTF8.GetBytes(testValue); var result = Base16.Encode(testBytes); @@ -28,11 +28,11 @@ public void WithKnownValues_ExpectCorrectValuesEncoded(string testValue, string } - [Theory] - [InlineData("ᚠᛇᚻ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢᚱ᛫ᚠᛁᚱᚪ᛫ᚷᛖᚻᚹᛦᛚᚳᚢᛗ", "e19aa0e19b87e19abbe19babe19b92e19ba6e19aa6e19babe19aa0e19ab1e19aa9e19aa0e19aa2e19ab1e19babe19aa0e19b81e19ab1e19aaae19babe19ab7e19b96e19abbe19ab9e19ba6e19b9ae19ab3e19aa2e19b97")] - [InlineData("¥·£·€·$·¢·₡·₢·₣·₤·₥·₦·₧·₨·₩·₪·₫·₭·₮·₯·₹", "c2a5c2b7c2a3c2b7e282acc2b724c2b7c2a2c2b7e282a1c2b7e282a2c2b7e282a3c2b7e282a4c2b7e282a5c2b7e282a6c2b7e282a7c2b7e282a8c2b7e282a9c2b7e282aac2b7e282abc2b7e282adc2b7e282aec2b7e282afc2b7e282b9")] - public void WithUtf8Characters_ExpectCorrectValuesEncoded(string testValue, string expectedResult) - { + [Theory] + [InlineData("ᚠᛇᚻ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢᚱ᛫ᚠᛁᚱᚪ᛫ᚷᛖᚻᚹᛦᛚᚳᚢᛗ", "e19aa0e19b87e19abbe19babe19b92e19ba6e19aa6e19babe19aa0e19ab1e19aa9e19aa0e19aa2e19ab1e19babe19aa0e19b81e19ab1e19aaae19babe19ab7e19b96e19abbe19ab9e19ba6e19b9ae19ab3e19aa2e19b97")] + [InlineData("¥·£·€·$·¢·₡·₢·₣·₤·₥·₦·₧·₨·₩·₪·₫·₭·₮·₯·₹", "c2a5c2b7c2a3c2b7e282acc2b724c2b7c2a2c2b7e282a1c2b7e282a2c2b7e282a3c2b7e282a4c2b7e282a5c2b7e282a6c2b7e282a7c2b7e282a8c2b7e282a9c2b7e282aac2b7e282abc2b7e282adc2b7e282aec2b7e282afc2b7e282b9")] + public void WithUtf8Characters_ExpectCorrectValuesEncoded(string testValue, string expectedResult) + { var testBytes = Encoding.UTF8.GetBytes(testValue); var result = Base16.Encode(testBytes); @@ -42,9 +42,9 @@ public void WithUtf8Characters_ExpectCorrectValuesEncoded(string testValue, stri decodedBytes.Should().BeEquivalentTo(testBytes); } - [Fact] - public void WithRandomBytes_ExpectCorrectValuesEncoded() - { + [Fact] + public void WithRandomBytes_ExpectCorrectValuesEncoded() + { var bytes = new byte[128]; var rng = RandomNumberGenerator.Create(); rng.GetBytes(bytes); @@ -52,5 +52,4 @@ public void WithRandomBytes_ExpectCorrectValuesEncoded() var encodedBytes = Base16.Encode(bytes); Base16.Decode(encodedBytes).Should().BeEquivalentTo(bytes); } - } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Encoding/Base62Tests.cs b/test/ScottBrady.IdentityModel.Tests/Encoding/Base62Tests.cs index 939807f..73f180b 100644 --- a/test/ScottBrady.IdentityModel.Tests/Encoding/Base62Tests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Encoding/Base62Tests.cs @@ -3,21 +3,21 @@ using FluentAssertions; using Xunit; -namespace ScottBrady.IdentityModel.Tests +namespace ScottBrady.IdentityModel.Tests; + +public class Base62Tests { - public class Base62Tests + [Theory] + [InlineData("t", "1s")] + [InlineData("te", "7kb")] + [InlineData("tes", "W0Qd")] + [InlineData("test", "289lyu")] + [InlineData("test_", "8ngM7Ul")] + [InlineData("test_v", "aL8tKx1y")] + [InlineData("test_va", "2Q3IiUVk9J")] + [InlineData("test_val", "9zZdHhz4YSC")] + public void WithKnownValues_ExpectCorrectValuesEncoded(string testValue, string expectedResult) { - [Theory] - [InlineData("t", "1s")] - [InlineData("te", "7kb")] - [InlineData("tes", "W0Qd")] - [InlineData("test", "289lyu")] - [InlineData("test_", "8ngM7Ul")] - [InlineData("test_v", "aL8tKx1y")] - [InlineData("test_va", "2Q3IiUVk9J")] - [InlineData("test_val", "9zZdHhz4YSC")] - public void WithKnownValues_ExpectCorrectValuesEncoded(string testValue, string expectedResult) - { var testBytes = Encoding.UTF8.GetBytes(testValue); var result = Base62.Encode(testBytes); @@ -27,11 +27,11 @@ public void WithKnownValues_ExpectCorrectValuesEncoded(string testValue, string decodedBytes.Should().BeEquivalentTo(testBytes); } - [Theory] - [InlineData("ᚠᛇᚻ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢᚱ᛫ᚠᛁᚱᚪ᛫ᚷᛖᚻᚹᛦᛚᚳᚢᛗ", "Z2OiyFrg9j5RTozitQyWlPIpRF9zciF7WUm0omCp8p8amBsp3T7z3XtHx9aAcgG5J5ggJT7mLQP1WonC0PAUF7hrM4KT6dwoUqhBsRWHBS3gZXeCkIbJP")] - [InlineData("¥·£·€·$·¢·₡·₢·₣·₤·₥·₦·₧·₨·₩·₪·₫·₭·₮·₯·₹", "cyHYeZmwVtcfi8uomwZ9VTrEews1tZEkwNsVEOzPtGnuTpxFrKkQykOshm9OCqSa0YkPX13Js2w8QAcKpHsMHzdKzNG9htLkL6Pu6xFSwoSZycE8aUfGRIZTKcX8L")] - public void WithUtf8Characters_ExpectCorrectValuesEncoded(string testValue, string expectedResult) - { + [Theory] + [InlineData("ᚠᛇᚻ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢᚱ᛫ᚠᛁᚱᚪ᛫ᚷᛖᚻᚹᛦᛚᚳᚢᛗ", "Z2OiyFrg9j5RTozitQyWlPIpRF9zciF7WUm0omCp8p8amBsp3T7z3XtHx9aAcgG5J5ggJT7mLQP1WonC0PAUF7hrM4KT6dwoUqhBsRWHBS3gZXeCkIbJP")] + [InlineData("¥·£·€·$·¢·₡·₢·₣·₤·₥·₦·₧·₨·₩·₪·₫·₭·₮·₯·₹", "cyHYeZmwVtcfi8uomwZ9VTrEews1tZEkwNsVEOzPtGnuTpxFrKkQykOshm9OCqSa0YkPX13Js2w8QAcKpHsMHzdKzNG9htLkL6Pu6xFSwoSZycE8aUfGRIZTKcX8L")] + public void WithUtf8Characters_ExpectCorrectValuesEncoded(string testValue, string expectedResult) + { var testBytes = Encoding.UTF8.GetBytes(testValue); var result = Base62.Encode(testBytes); @@ -41,9 +41,9 @@ public void WithUtf8Characters_ExpectCorrectValuesEncoded(string testValue, stri decodedBytes.Should().BeEquivalentTo(testBytes); } - [Fact] - public void WithRandomBytes_ExpectCorrectValuesEncoded() - { + [Fact] + public void WithRandomBytes_ExpectCorrectValuesEncoded() + { var bytes = new byte[128]; var rng = RandomNumberGenerator.Create(); rng.GetBytes(bytes); @@ -51,5 +51,4 @@ public void WithRandomBytes_ExpectCorrectValuesEncoded() var encodedBytes = Base62.Encode(bytes); Base62.Decode(encodedBytes).Should().BeEquivalentTo(bytes); } - } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaSecurityTokenTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaSecurityTokenTests.cs index 1ba4142..f6911bc 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaSecurityTokenTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaSecurityTokenTests.cs @@ -5,13 +5,13 @@ using ScottBrady.IdentityModel.Tokens.Branca; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens.Branca +namespace ScottBrady.IdentityModel.Tests.Tokens.Branca; + +public class BrancaSecurityTokenTests { - public class BrancaSecurityTokenTests + [Fact] + public void ctor_ExpectBrancaTokenTimestampUsedForIssuedAt() { - [Fact] - public void ctor_ExpectBrancaTokenTimestampUsedForIssuedAt() - { const uint expectedIssuedAt = 1588341499; var jwt = JsonConvert.SerializeObject( @@ -27,12 +27,11 @@ public void ctor_ExpectBrancaTokenTimestampUsedForIssuedAt() token.IssuedAt.Should().Be(DateTimeOffset.FromUnixTimeSeconds(expectedIssuedAt).UtcDateTime); } - [Fact] - public void ctor_WhenPayloadIsNotUtf8_ExpectException() - { + [Fact] + public void ctor_WhenPayloadIsNotUtf8_ExpectException() + { var payload = Encoding.Unicode.GetBytes("������"); var exception = Assert.Throws(() => new BrancaSecurityToken(new BrancaToken(payload, 0))); exception.Message.Should().Contain("Token does not contain valid JSON"); } - } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaTokenHandlerTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaTokenHandlerTests.cs index 82a1fda..c17e2ee 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaTokenHandlerTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaTokenHandlerTests.cs @@ -13,504 +13,503 @@ using ScottBrady.IdentityModel.Tokens.Branca; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens.Branca +namespace ScottBrady.IdentityModel.Tests.Tokens.Branca; + +public class BrancaTokenHandlerTests { - public class BrancaTokenHandlerTests + private const string ValidToken = "5K6fDIqRhrSuqGE3FbuxAPd19P2toAsbBxOn4bgSame9ti6QZUQJkrggCypBJIEXF6tvhgjeMZTV76UkiqXNSvqHebeplccFrhepHkxU1SlSSFoAMKs5TUomcg6ZgDhiaYDs3IlypSxafP4uvKmu0VD"; + private readonly byte[] validKey = Encoding.UTF8.GetBytes("supersecretkeyyoushouldnotcommit"); + private static readonly byte[] ExpectedPayload = Encoding.UTF8.GetBytes("{\"user\":\"scott@scottbrady91.com\",\"scope\":[\"read\",\"write\",\"delete\"]}"); + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void CanReadToken_WhenTokenIsNullOrWhitespace_ExpectFalse(string token) { - private const string ValidToken = "5K6fDIqRhrSuqGE3FbuxAPd19P2toAsbBxOn4bgSame9ti6QZUQJkrggCypBJIEXF6tvhgjeMZTV76UkiqXNSvqHebeplccFrhepHkxU1SlSSFoAMKs5TUomcg6ZgDhiaYDs3IlypSxafP4uvKmu0VD"; - private readonly byte[] validKey = Encoding.UTF8.GetBytes("supersecretkeyyoushouldnotcommit"); - private static readonly byte[] ExpectedPayload = Encoding.UTF8.GetBytes("{\"user\":\"scott@scottbrady91.com\",\"scope\":[\"read\",\"write\",\"delete\"]}"); - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void CanReadToken_WhenTokenIsNullOrWhitespace_ExpectFalse(string token) - { - var handler = new BrancaTokenHandler(); - var canReadToken = handler.CanReadToken(token); + var handler = new BrancaTokenHandler(); + var canReadToken = handler.CanReadToken(token); - canReadToken.Should().BeFalse(); - } + canReadToken.Should().BeFalse(); + } - [Fact] - public void CanReadToken_WhenTokenIsTooLong_ExpectFalse() - { - var tokenBytes = new byte[TokenValidationParameters.DefaultMaximumTokenSizeInBytes + 1]; - new Random().NextBytes(tokenBytes); + [Fact] + public void CanReadToken_WhenTokenIsTooLong_ExpectFalse() + { + var tokenBytes = new byte[TokenValidationParameters.DefaultMaximumTokenSizeInBytes + 1]; + new Random().NextBytes(tokenBytes); - var canReadToken = new BrancaTokenHandler().CanReadToken(Convert.ToBase64String(tokenBytes)); + var canReadToken = new BrancaTokenHandler().CanReadToken(Convert.ToBase64String(tokenBytes)); - canReadToken.Should().BeFalse(); - } + canReadToken.Should().BeFalse(); + } - [Fact] - public void CanReadToken_WhenJwtToken_ExpectFalse() - { - const string jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJuYW1lIjoiU2NvdHQgQnJhZHkiLCJpYXQiOjE1ODU3Njc0Mjl9.DcGCOpx19JQzVVeZPHgqB73rbLaCUsx-k6PuFdit6IM"; + [Fact] + public void CanReadToken_WhenJwtToken_ExpectFalse() + { + const string jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJuYW1lIjoiU2NvdHQgQnJhZHkiLCJpYXQiOjE1ODU3Njc0Mjl9.DcGCOpx19JQzVVeZPHgqB73rbLaCUsx-k6PuFdit6IM"; - var canReadToken = new BrancaTokenHandler().CanReadToken(jwt); + var canReadToken = new BrancaTokenHandler().CanReadToken(jwt); - canReadToken.Should().BeFalse(); - } + canReadToken.Should().BeFalse(); + } - [Fact] - public void CanReadToken_WhenTokenContainsNonBase64Characters_ExpectFalse() - { - const string token = "token=="; + [Fact] + public void CanReadToken_WhenTokenContainsNonBase64Characters_ExpectFalse() + { + const string token = "token=="; - var canReadToken = new BrancaTokenHandler().CanReadToken(token); + var canReadToken = new BrancaTokenHandler().CanReadToken(token); - canReadToken.Should().BeFalse(); - } + canReadToken.Should().BeFalse(); + } - [Fact] - public void CanReadToken_WhenBrancaToken_ExpectTrue() - { - var canReadToken = new BrancaTokenHandler().CanReadToken(ValidToken); + [Fact] + public void CanReadToken_WhenBrancaToken_ExpectTrue() + { + var canReadToken = new BrancaTokenHandler().CanReadToken(ValidToken); - canReadToken.Should().BeTrue(); - } + canReadToken.Should().BeTrue(); + } - [Fact] - public void CanValidateToken_ExpectTrue() - => new BrancaTokenHandler().CanValidateToken.Should().BeTrue(); + [Fact] + public void CanValidateToken_ExpectTrue() + => new BrancaTokenHandler().CanValidateToken.Should().BeTrue(); - [Fact] - public void CreateToken_WhenPayloadIsNull_ExpectArgumentNullException() - { - var handler = new BrancaTokenHandler(); - Assert.Throws(() => handler.CreateToken(null, validKey)); - } + [Fact] + public void CreateToken_WhenPayloadIsNull_ExpectArgumentNullException() + { + var handler = new BrancaTokenHandler(); + Assert.Throws(() => handler.CreateToken(null, validKey)); + } - [Fact] - public void CreateToken_WhenKeyIsNull_ExpectInvalidOperationException() - => Assert.Throws(() => new BrancaTokenHandler().CreateToken("test", null)); + [Fact] + public void CreateToken_WhenKeyIsNull_ExpectInvalidOperationException() + => Assert.Throws(() => new BrancaTokenHandler().CreateToken("test", null)); - [Fact] - public void CreateToken_WhenKeyIsNot32Bytes_ExpectInvalidOperationException() - => Assert.Throws(() => - new BrancaTokenHandler().CreateToken("test", Encoding.UTF8.GetBytes("iamonly14bytes"))); + [Fact] + public void CreateToken_WhenKeyIsNot32Bytes_ExpectInvalidOperationException() + => Assert.Throws(() => + new BrancaTokenHandler().CreateToken("test", Encoding.UTF8.GetBytes("iamonly14bytes"))); - [Fact] - public void CreateToken_WhenTokenGenerated_ExpectBas62EncodedTokenWithCorrectLength() - { - var payload = CreateTestPayload(); - var handler = new BrancaTokenHandler(); + [Fact] + public void CreateToken_WhenTokenGenerated_ExpectBas62EncodedTokenWithCorrectLength() + { + var payload = CreateTestPayload(); + var handler = new BrancaTokenHandler(); - var token = handler.CreateToken(payload, 0, validKey); + var token = handler.CreateToken(payload, 0, validKey); - token.Any(x => !Base62.CharacterSet.Contains(x)).Should().BeFalse(); - Base62.Decode(token).Length.Should().Be(payload.Length + 29 + 16); - } + token.Any(x => !Base62.CharacterSet.Contains(x)).Should().BeFalse(); + Base62.Decode(token).Length.Should().Be(payload.Length + 29 + 16); + } - [Fact] - public void CreateToken_WhenSecurityTokenDescriptorIsNull_ExpectArgumentNullException() - => Assert.Throws(() => new BrancaTokenHandler().CreateToken(null)); + [Fact] + public void CreateToken_WhenSecurityTokenDescriptorIsNull_ExpectArgumentNullException() + => Assert.Throws(() => new BrancaTokenHandler().CreateToken(null)); - [Fact] - public void CreateAndDecryptToken_WithSecurityTokenDescriptor_ExpectCorrectBrancaTimestampAndNoIatClaim() - { - var handler = new BrancaTokenHandler(); + [Fact] + public void CreateAndDecryptToken_WithSecurityTokenDescriptor_ExpectCorrectBrancaTimestampAndNoIatClaim() + { + var handler = new BrancaTokenHandler(); - var token = handler.CreateToken(new SecurityTokenDescriptor - { - EncryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(validKey), ExtendedSecurityAlgorithms.XChaCha20Poly1305) - }); + var token = handler.CreateToken(new SecurityTokenDescriptor + { + EncryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(validKey), ExtendedSecurityAlgorithms.XChaCha20Poly1305) + }); - var parsedToken = handler.DecryptToken(token, validKey); - var jObject = JObject.Parse(Encoding.UTF8.GetString(parsedToken.Payload)); - jObject["iat"].Should().BeNull(); + var parsedToken = handler.DecryptToken(token, validKey); + var jObject = JObject.Parse(Encoding.UTF8.GetString(parsedToken.Payload)); + jObject["iat"].Should().BeNull(); - parsedToken.Timestamp.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMilliseconds(1500)); - } + parsedToken.Timestamp.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMilliseconds(1500)); + } - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void DecryptToken_WhenTokenIsNullOrWhitespace_ExpectArgumentNullException(string token) - { - var handler = new BrancaTokenHandler(); - Assert.Throws(() => handler.DecryptToken(token, validKey)); - } - - [Fact] - public void DecryptToken_WhenKeyIsNull_ExpectInvalidOperationException() - => Assert.Throws(() => new BrancaTokenHandler().DecryptToken(ValidToken, null)); - - [Fact] - public void DecryptToken_WhenKeyIsNot32Bytes_ExpectInvalidOperationException() - => Assert.Throws(() => - new BrancaTokenHandler().DecryptToken(ValidToken, Encoding.UTF8.GetBytes("iamonly14bytes"))); + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void DecryptToken_WhenTokenIsNullOrWhitespace_ExpectArgumentNullException(string token) + { + var handler = new BrancaTokenHandler(); + Assert.Throws(() => handler.DecryptToken(token, validKey)); + } + + [Fact] + public void DecryptToken_WhenKeyIsNull_ExpectInvalidOperationException() + => Assert.Throws(() => new BrancaTokenHandler().DecryptToken(ValidToken, null)); + + [Fact] + public void DecryptToken_WhenKeyIsNot32Bytes_ExpectInvalidOperationException() + => Assert.Throws(() => + new BrancaTokenHandler().DecryptToken(ValidToken, Encoding.UTF8.GetBytes("iamonly14bytes"))); - [Fact] - public void DecryptToken_WhenTokenHasInvalidLength_ExpectSecurityTokenException() - { - var bytes = new byte[20]; - new Random().NextBytes(bytes); + [Fact] + public void DecryptToken_WhenTokenHasInvalidLength_ExpectSecurityTokenException() + { + var bytes = new byte[20]; + new Random().NextBytes(bytes); - Assert.Throws(() => - new BrancaTokenHandler().DecryptToken(Base62.Encode(bytes), validKey)); - } + Assert.Throws(() => + new BrancaTokenHandler().DecryptToken(Base62.Encode(bytes), validKey)); + } - [Fact] - public void DecryptToken_WhenTokenHasIncorrectVersion_ExpectSecurityTokenException() - { - var bytes = new byte[120]; - new Random().NextBytes(bytes); - bytes[0] = 0x00; + [Fact] + public void DecryptToken_WhenTokenHasIncorrectVersion_ExpectSecurityTokenException() + { + var bytes = new byte[120]; + new Random().NextBytes(bytes); + bytes[0] = 0x00; - Assert.Throws(() => - new BrancaTokenHandler().DecryptToken(Base62.Encode(bytes), validKey)); - } + Assert.Throws(() => + new BrancaTokenHandler().DecryptToken(Base62.Encode(bytes), validKey)); + } - [Fact] - public void DecryptToken_WhenValidToken_ExpectCorrectPayload() - { - var parsedToken = new BrancaTokenHandler().DecryptToken(ValidToken, validKey); - parsedToken.Payload.Should().BeEquivalentTo(ExpectedPayload); - } + [Fact] + public void DecryptToken_WhenValidToken_ExpectCorrectPayload() + { + var parsedToken = new BrancaTokenHandler().DecryptToken(ValidToken, validKey); + parsedToken.Payload.Should().BeEquivalentTo(ExpectedPayload); + } - [Fact] - public void EncryptAndDecryptToken_ExpectCorrectPayloadAndTimestamp() - { - var payload = Guid.NewGuid().ToString(); - var handler = new BrancaTokenHandler(); + [Fact] + public void EncryptAndDecryptToken_ExpectCorrectPayloadAndTimestamp() + { + var payload = Guid.NewGuid().ToString(); + var handler = new BrancaTokenHandler(); - var token = handler.CreateToken(payload, validKey); - var decryptedPayload = handler.DecryptToken(token, validKey); + var token = handler.CreateToken(payload, validKey); + var decryptedPayload = handler.DecryptToken(token, validKey); - decryptedPayload.Payload.Should().BeEquivalentTo(Encoding.UTF8.GetBytes(payload)); - decryptedPayload.Timestamp.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMilliseconds(1000)); - } + decryptedPayload.Payload.Should().BeEquivalentTo(Encoding.UTF8.GetBytes(payload)); + decryptedPayload.Timestamp.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMilliseconds(1000)); + } - [Fact] - public void EncryptAndDecryptToken_WithExplicitTimestamp_ExpectCorrectPayloadAndTimestamp() - { - var payload = Guid.NewGuid().ToString(); - var timestamp = new DateTime(2020, 08, 22).ToUniversalTime(); - var handler = new BrancaTokenHandler(); + [Fact] + public void EncryptAndDecryptToken_WithExplicitTimestamp_ExpectCorrectPayloadAndTimestamp() + { + var payload = Guid.NewGuid().ToString(); + var timestamp = new DateTime(2020, 08, 22).ToUniversalTime(); + var handler = new BrancaTokenHandler(); - var token = handler.CreateToken(payload, timestamp, validKey); - var decryptedPayload = handler.DecryptToken(token, validKey); + var token = handler.CreateToken(payload, timestamp, validKey); + var decryptedPayload = handler.DecryptToken(token, validKey); - decryptedPayload.Payload.Should().BeEquivalentTo(Encoding.UTF8.GetBytes(payload)); - decryptedPayload.Timestamp.Should().Be(timestamp); - } + decryptedPayload.Payload.Should().BeEquivalentTo(Encoding.UTF8.GetBytes(payload)); + decryptedPayload.Timestamp.Should().Be(timestamp); + } - [Fact] - public void EncryptAndDecryptToken_WithExplicitBrancaTimestamp_ExpectCorrectPayloadAndTimestamp() - { - var payload = CreateTestPayload(); - var timestamp = uint.MinValue; - var handler = new BrancaTokenHandler(); - - var token = handler.CreateToken(payload, timestamp, validKey); - var decryptedPayload = handler.DecryptToken(token, validKey); - - decryptedPayload.Payload.Should().BeEquivalentTo(payload); - decryptedPayload.Timestamp.Should().Be(new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc)); - decryptedPayload.BrancaFormatTimestamp.Should().Be(timestamp); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void ValidateToken_WhenTokenIsNullOrWhitespace_ExpectFailureWithArgumentNullException(string token) - { - var result = new BrancaTokenHandler().ValidateToken(token, new TokenValidationParameters()); + [Fact] + public void EncryptAndDecryptToken_WithExplicitBrancaTimestamp_ExpectCorrectPayloadAndTimestamp() + { + var payload = CreateTestPayload(); + var timestamp = uint.MinValue; + var handler = new BrancaTokenHandler(); - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } + var token = handler.CreateToken(payload, timestamp, validKey); + var decryptedPayload = handler.DecryptToken(token, validKey); - [Fact] - public void ValidateToken_WhenTokenValidationParametersAreNull_ExpectFailureWithArgumentNullException() - { - var result = new BrancaTokenHandler().ValidateToken(ValidToken, null); + decryptedPayload.Payload.Should().BeEquivalentTo(payload); + decryptedPayload.Timestamp.Should().Be(new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc)); + decryptedPayload.BrancaFormatTimestamp.Should().Be(timestamp); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void ValidateToken_WhenTokenIsNullOrWhitespace_ExpectFailureWithArgumentNullException(string token) + { + var result = new BrancaTokenHandler().ValidateToken(token, new TokenValidationParameters()); + + result.IsValid.Should().BeFalse(); + result.Exception.Should().BeOfType(); + } + + [Fact] + public void ValidateToken_WhenTokenValidationParametersAreNull_ExpectFailureWithArgumentNullException() + { + var result = new BrancaTokenHandler().ValidateToken(ValidToken, null); - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } + result.IsValid.Should().BeFalse(); + result.Exception.Should().BeOfType(); + } - [Fact] - public void ValidateToken_WhenTokenCannotBeRead_ExpectFailureWithSecurityTokenException() - { - var result = new BrancaTokenHandler().ValidateToken("=====", new TokenValidationParameters()); + [Fact] + public void ValidateToken_WhenTokenCannotBeRead_ExpectFailureWithSecurityTokenException() + { + var result = new BrancaTokenHandler().ValidateToken("=====", new TokenValidationParameters()); - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } + result.IsValid.Should().BeFalse(); + result.Exception.Should().BeOfType(); + } - [Fact] - public void ValidateToken_WhenIncorrectDecryptionKey_ExpectFailureWithSecurityTokenDecryptionFailedException() - { - var key = new byte[32]; - new Random().NextBytes(key); + [Fact] + public void ValidateToken_WhenIncorrectDecryptionKey_ExpectFailureWithSecurityTokenDecryptionFailedException() + { + var key = new byte[32]; + new Random().NextBytes(key); - var result = new BrancaTokenHandler().ValidateToken( - ValidToken, - new TokenValidationParameters {TokenDecryptionKey = new SymmetricSecurityKey(key)}); + var result = new BrancaTokenHandler().ValidateToken( + ValidToken, + new TokenValidationParameters {TokenDecryptionKey = new SymmetricSecurityKey(key)}); - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } + result.IsValid.Should().BeFalse(); + result.Exception.Should().BeOfType(); + } - [Fact] - public void ValidateToken_WhenTokenPayloadIsNotJson_ExpectFailureWithArgumentException() - { - const string tokenWithInvalidPayload = "Mvm6wbsyZMgClkmtiBf0lW3rEkvnCK5RgytoerJJex40b9yqh6GbSlfkFJHgFX9ocF"; + [Fact] + public void ValidateToken_WhenTokenPayloadIsNotJson_ExpectFailureWithArgumentException() + { + const string tokenWithInvalidPayload = "Mvm6wbsyZMgClkmtiBf0lW3rEkvnCK5RgytoerJJex40b9yqh6GbSlfkFJHgFX9ocF"; - var result = new BrancaTokenHandler().ValidateToken( - tokenWithInvalidPayload, - new TokenValidationParameters {TokenDecryptionKey = new SymmetricSecurityKey(validKey)}); + var result = new BrancaTokenHandler().ValidateToken( + tokenWithInvalidPayload, + new TokenValidationParameters {TokenDecryptionKey = new SymmetricSecurityKey(validKey)}); - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } + result.IsValid.Should().BeFalse(); + result.Exception.Should().BeOfType(); + } - [Fact] - public void ValidateToken_WhenValidToken_ExpectSuccessResultWithSecurityTokenAndClaimsIdentity() - { - var expectedIdentity = new ClaimsIdentity("test"); - - var mockHandler = new Mock {CallBase = true}; - mockHandler.Protected() - .Setup("ValidateTokenPayload", - ItExpr.IsAny(), - ItExpr.IsAny()) - .Returns(new TokenValidationResult - { - ClaimsIdentity = expectedIdentity, - IsValid = true - }); - - var result = mockHandler.Object.ValidateToken( - ValidToken, - new TokenValidationParameters {TokenDecryptionKey = new SymmetricSecurityKey(validKey)}); - - result.IsValid.Should().BeTrue(); - result.ClaimsIdentity.Should().Be(expectedIdentity); - result.SecurityToken.Should().NotBeNull(); - } - - [Fact] - public void ValidateToken_WhenSaveSignInTokenIsTrue_ExpectIdentityBootstrapContext() - { - const string expectedToken = ValidToken; - var expectedIdentity = new ClaimsIdentity("test"); - - var mockHandler = new Mock {CallBase = true}; - mockHandler.Protected() - .Setup("ValidateTokenPayload", - ItExpr.IsAny(), - ItExpr.IsAny()) - .Returns(new TokenValidationResult - { - ClaimsIdentity = expectedIdentity, - IsValid = true - }); - - var result = mockHandler.Object.ValidateToken( - expectedToken, - new TokenValidationParameters - { - TokenDecryptionKey = new SymmetricSecurityKey(validKey), - SaveSigninToken = true - }); - - result.IsValid.Should().BeTrue(); - result.ClaimsIdentity.BootstrapContext.Should().Be(expectedToken); - } - - [Fact] - public void CreateAndValidateToken_WithSecurityTokenDescriptor_ExpectCorrectBrancaTimestampAndNoIatClaim() - { - const string issuer = "me"; - const string audience = "you"; - const string subject = "123"; - var expires = DateTime.UtcNow.AddDays(1); - var notBefore = DateTime.UtcNow; + [Fact] + public void ValidateToken_WhenValidToken_ExpectSuccessResultWithSecurityTokenAndClaimsIdentity() + { + var expectedIdentity = new ClaimsIdentity("test"); - var handler = new BrancaTokenHandler(); - - var token = handler.CreateToken(new SecurityTokenDescriptor + var mockHandler = new Mock {CallBase = true}; + mockHandler.Protected() + .Setup("ValidateTokenPayload", + ItExpr.IsAny(), + ItExpr.IsAny()) + .Returns(new TokenValidationResult { - Issuer = issuer, - Audience = audience, - Expires = expires, - NotBefore = notBefore, - Claims = new Dictionary {{"sub", subject}}, - EncryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(validKey), ExtendedSecurityAlgorithms.XChaCha20Poly1305) + ClaimsIdentity = expectedIdentity, + IsValid = true }); - var validatedToken = handler.ValidateToken(token, new TokenValidationParameters + var result = mockHandler.Object.ValidateToken( + ValidToken, + new TokenValidationParameters {TokenDecryptionKey = new SymmetricSecurityKey(validKey)}); + + result.IsValid.Should().BeTrue(); + result.ClaimsIdentity.Should().Be(expectedIdentity); + result.SecurityToken.Should().NotBeNull(); + } + + [Fact] + public void ValidateToken_WhenSaveSignInTokenIsTrue_ExpectIdentityBootstrapContext() + { + const string expectedToken = ValidToken; + var expectedIdentity = new ClaimsIdentity("test"); + + var mockHandler = new Mock {CallBase = true}; + mockHandler.Protected() + .Setup("ValidateTokenPayload", + ItExpr.IsAny(), + ItExpr.IsAny()) + .Returns(new TokenValidationResult { - ValidIssuer = issuer, - ValidAudience = audience, - TokenDecryptionKey = new SymmetricSecurityKey(validKey) + ClaimsIdentity = expectedIdentity, + IsValid = true }); - validatedToken.IsValid.Should().BeTrue(); - validatedToken.ClaimsIdentity.Claims.Should().Contain( - x => x.Type == "sub" && x.Value == subject); - - var brancaToken = (BrancaSecurityToken) validatedToken.SecurityToken; - brancaToken.Issuer.Should().Be(issuer); - brancaToken.Audiences.Should().Contain(audience); - brancaToken.Subject.Should().Be(subject); - brancaToken.IssuedAt.Should().BeCloseTo(notBefore, TimeSpan.FromSeconds(1)); - brancaToken.ValidFrom.Should().BeCloseTo(notBefore, TimeSpan.FromSeconds(1)); - brancaToken.ValidTo.Should().BeCloseTo(expires, TimeSpan.FromSeconds(1)); - } - - [Fact] - public void GetBrancaDecryptionKeys_WheInvalidKeysInParameters_ExpectInvalidKeysRemoved() - { - var expectedKey = new byte[32]; - new Random().NextBytes(expectedKey); - - var handler = new TestBrancaTokenHandler(); - var keys = handler.GetBrancaDecryptionKeys("test", new TokenValidationParameters + var result = mockHandler.Object.ValidateToken( + expectedToken, + new TokenValidationParameters { - TokenDecryptionKeyResolver = (token, securityToken, kid, parameters) => new List(), - TokenDecryptionKey = new SymmetricSecurityKey(expectedKey), - TokenDecryptionKeys = new[] {new RsaSecurityKey(RSA.Create())} - }).ToList(); + TokenDecryptionKey = new SymmetricSecurityKey(validKey), + SaveSigninToken = true + }); - keys.Count.Should().Be(1); - keys.Should().Contain(x => x.Key.SequenceEqual(expectedKey)); - } + result.IsValid.Should().BeTrue(); + result.ClaimsIdentity.BootstrapContext.Should().Be(expectedToken); + } + + [Fact] + public void CreateAndValidateToken_WithSecurityTokenDescriptor_ExpectCorrectBrancaTimestampAndNoIatClaim() + { + const string issuer = "me"; + const string audience = "you"; + const string subject = "123"; + var expires = DateTime.UtcNow.AddDays(1); + var notBefore = DateTime.UtcNow; + + var handler = new BrancaTokenHandler(); + + var token = handler.CreateToken(new SecurityTokenDescriptor + { + Issuer = issuer, + Audience = audience, + Expires = expires, + NotBefore = notBefore, + Claims = new Dictionary {{"sub", subject}}, + EncryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(validKey), ExtendedSecurityAlgorithms.XChaCha20Poly1305) + }); + + var validatedToken = handler.ValidateToken(token, new TokenValidationParameters + { + ValidIssuer = issuer, + ValidAudience = audience, + TokenDecryptionKey = new SymmetricSecurityKey(validKey) + }); + + validatedToken.IsValid.Should().BeTrue(); + validatedToken.ClaimsIdentity.Claims.Should().Contain( + x => x.Type == "sub" && x.Value == subject); + + var brancaToken = (BrancaSecurityToken) validatedToken.SecurityToken; + brancaToken.Issuer.Should().Be(issuer); + brancaToken.Audiences.Should().Contain(audience); + brancaToken.Subject.Should().Be(subject); + brancaToken.IssuedAt.Should().BeCloseTo(notBefore, TimeSpan.FromSeconds(1)); + brancaToken.ValidFrom.Should().BeCloseTo(notBefore, TimeSpan.FromSeconds(1)); + brancaToken.ValidTo.Should().BeCloseTo(expires, TimeSpan.FromSeconds(1)); + } - [Fact] - public void IsValidKey_WhenKeyIsNot32Bytes_ExpectFalse() + [Fact] + public void GetBrancaDecryptionKeys_WheInvalidKeysInParameters_ExpectInvalidKeysRemoved() + { + var expectedKey = new byte[32]; + new Random().NextBytes(expectedKey); + + var handler = new TestBrancaTokenHandler(); + var keys = handler.GetBrancaDecryptionKeys("test", new TokenValidationParameters { - var key = new byte[16]; - new Random().NextBytes(key); + TokenDecryptionKeyResolver = (token, securityToken, kid, parameters) => new List(), + TokenDecryptionKey = new SymmetricSecurityKey(expectedKey), + TokenDecryptionKeys = new[] {new RsaSecurityKey(RSA.Create())} + }).ToList(); + + keys.Count.Should().Be(1); + keys.Should().Contain(x => x.Key.SequenceEqual(expectedKey)); + } - var isValidKey = new TestBrancaTokenHandler().IsValidKey(key); + [Fact] + public void IsValidKey_WhenKeyIsNot32Bytes_ExpectFalse() + { + var key = new byte[16]; + new Random().NextBytes(key); - isValidKey.Should().BeFalse(); - } + var isValidKey = new TestBrancaTokenHandler().IsValidKey(key); - [Fact] - public void IsValidKey_WhenKeyIsValid_ExpectTrue() - { - var key = new byte[32]; - new Random().NextBytes(key); + isValidKey.Should().BeFalse(); + } - var isValidKey = new TestBrancaTokenHandler().IsValidKey(key); + [Fact] + public void IsValidKey_WhenKeyIsValid_ExpectTrue() + { + var key = new byte[32]; + new Random().NextBytes(key); - isValidKey.Should().BeTrue(); - } + var isValidKey = new TestBrancaTokenHandler().IsValidKey(key); - [Fact] - public void IsValidKey_WhenSecurityKeyIsNot32Bytes_ExpectFalse() - { - var keyBytes = new byte[16]; - new Random().NextBytes(keyBytes); - var key = new SymmetricSecurityKey(keyBytes); + isValidKey.Should().BeTrue(); + } - var isValidKey = new TestBrancaTokenHandler().IsValidKey(key); + [Fact] + public void IsValidKey_WhenSecurityKeyIsNot32Bytes_ExpectFalse() + { + var keyBytes = new byte[16]; + new Random().NextBytes(keyBytes); + var key = new SymmetricSecurityKey(keyBytes); - isValidKey.Should().BeFalse(); - } + var isValidKey = new TestBrancaTokenHandler().IsValidKey(key); - [Fact] - public void IsValidKey_WhenSecurityKeyIsNotSymmetricSecurityKey_ExpectFalse() - { - var key = new RsaSecurityKey(RSA.Create()); + isValidKey.Should().BeFalse(); + } - var isValidKey = new TestBrancaTokenHandler().IsValidKey(key); + [Fact] + public void IsValidKey_WhenSecurityKeyIsNotSymmetricSecurityKey_ExpectFalse() + { + var key = new RsaSecurityKey(RSA.Create()); - isValidKey.Should().BeFalse(); - } + var isValidKey = new TestBrancaTokenHandler().IsValidKey(key); - [Fact] - public void IsValidKey_WhenSecurityKeyIsValid_ExpectTrue() - { - var keyBytes = new byte[32]; - new Random().NextBytes(keyBytes); - var key = new SymmetricSecurityKey(keyBytes); + isValidKey.Should().BeFalse(); + } - var isValidKey = new TestBrancaTokenHandler().IsValidKey(key); + [Fact] + public void IsValidKey_WhenSecurityKeyIsValid_ExpectTrue() + { + var keyBytes = new byte[32]; + new Random().NextBytes(keyBytes); + var key = new SymmetricSecurityKey(keyBytes); - isValidKey.Should().BeTrue(); - } + var isValidKey = new TestBrancaTokenHandler().IsValidKey(key); - [Fact] - public void IsValidKey_WhenEcryptingCredentialsKeyIsNot32Bytes_ExpectFalse() - { - var keyBytes = new byte[16]; - new Random().NextBytes(keyBytes); - var key = new SymmetricSecurityKey(keyBytes); - var credentials = new EncryptingCredentials(key, ExtendedSecurityAlgorithms.XChaCha20Poly1305); + isValidKey.Should().BeTrue(); + } - var isValidKey = new TestBrancaTokenHandler().IsValidKey(credentials); + [Fact] + public void IsValidKey_WhenEcryptingCredentialsKeyIsNot32Bytes_ExpectFalse() + { + var keyBytes = new byte[16]; + new Random().NextBytes(keyBytes); + var key = new SymmetricSecurityKey(keyBytes); + var credentials = new EncryptingCredentials(key, ExtendedSecurityAlgorithms.XChaCha20Poly1305); - isValidKey.Should().BeFalse(); - } + var isValidKey = new TestBrancaTokenHandler().IsValidKey(credentials); - [Fact] - public void IsValidKey_WhenEcryptingCredentialsHasKeyWrappingSet_ExpectFalse() - { - var keyBytes = new byte[32]; - new Random().NextBytes(keyBytes); - var key = new SymmetricSecurityKey(keyBytes); - var credentials = new EncryptingCredentials( - key, - SecurityAlgorithms.Aes256KeyWrap, - ExtendedSecurityAlgorithms.XChaCha20Poly1305); + isValidKey.Should().BeFalse(); + } - var isValidKey = new TestBrancaTokenHandler().IsValidKey(credentials); + [Fact] + public void IsValidKey_WhenEcryptingCredentialsHasKeyWrappingSet_ExpectFalse() + { + var keyBytes = new byte[32]; + new Random().NextBytes(keyBytes); + var key = new SymmetricSecurityKey(keyBytes); + var credentials = new EncryptingCredentials( + key, + SecurityAlgorithms.Aes256KeyWrap, + ExtendedSecurityAlgorithms.XChaCha20Poly1305); - isValidKey.Should().BeFalse(); - } + var isValidKey = new TestBrancaTokenHandler().IsValidKey(credentials); - [Fact] - public void IsValidKey_WhenEcryptingCredentialsHasIncorrectEncryptionAlgorithm_ExpectFalse() - { - var keyBytes = new byte[32]; - new Random().NextBytes(keyBytes); - var key = new SymmetricSecurityKey(keyBytes); - var credentials = new EncryptingCredentials(key, SecurityAlgorithms.Aes128Encryption); + isValidKey.Should().BeFalse(); + } - var isValidKey = new TestBrancaTokenHandler().IsValidKey(credentials); + [Fact] + public void IsValidKey_WhenEcryptingCredentialsHasIncorrectEncryptionAlgorithm_ExpectFalse() + { + var keyBytes = new byte[32]; + new Random().NextBytes(keyBytes); + var key = new SymmetricSecurityKey(keyBytes); + var credentials = new EncryptingCredentials(key, SecurityAlgorithms.Aes128Encryption); - isValidKey.Should().BeFalse(); - } + var isValidKey = new TestBrancaTokenHandler().IsValidKey(credentials); - [Fact] - public void IsValidKey_WhenEcryptingCredentialsIsValid_ExpectTrue() - { - var keyBytes = new byte[32]; - new Random().NextBytes(keyBytes); - var key = new SymmetricSecurityKey(keyBytes); - var credentials = new EncryptingCredentials(key, ExtendedSecurityAlgorithms.XChaCha20Poly1305); + isValidKey.Should().BeFalse(); + } - var isValidKey = new TestBrancaTokenHandler().IsValidKey(credentials); + [Fact] + public void IsValidKey_WhenEcryptingCredentialsIsValid_ExpectTrue() + { + var keyBytes = new byte[32]; + new Random().NextBytes(keyBytes); + var key = new SymmetricSecurityKey(keyBytes); + var credentials = new EncryptingCredentials(key, ExtendedSecurityAlgorithms.XChaCha20Poly1305); - isValidKey.Should().BeTrue(); - } + var isValidKey = new TestBrancaTokenHandler().IsValidKey(credentials); - private static byte[] CreateTestPayload() - { - var payload = new byte[32]; - RandomNumberGenerator.Fill(payload); - return payload; - } + isValidKey.Should().BeTrue(); } - public class TestBrancaTokenHandler : BrancaTokenHandler + private static byte[] CreateTestPayload() { - public new IEnumerable GetBrancaDecryptionKeys(string token, TokenValidationParameters validationParameters) - => base.GetBrancaDecryptionKeys(token, validationParameters); - - public new bool IsValidKey(byte[] key) => base.IsValidKey(key); - public new bool IsValidKey(SecurityKey key) => base.IsValidKey(key); - public new bool IsValidKey(EncryptingCredentials credentials) => base.IsValidKey(credentials); + var payload = new byte[32]; + RandomNumberGenerator.Fill(payload); + return payload; } +} + +public class TestBrancaTokenHandler : BrancaTokenHandler +{ + public new IEnumerable GetBrancaDecryptionKeys(string token, TokenValidationParameters validationParameters) + => base.GetBrancaDecryptionKeys(token, validationParameters); + + public new bool IsValidKey(byte[] key) => base.IsValidKey(key); + public new bool IsValidKey(SecurityKey key) => base.IsValidKey(key); + public new bool IsValidKey(EncryptingCredentials credentials) => base.IsValidKey(credentials); } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaTokenTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaTokenTests.cs index 7a4efa2..82c1d25 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaTokenTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaTokenTests.cs @@ -4,13 +4,13 @@ using ScottBrady.IdentityModel.Tokens.Branca; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens.Branca +namespace ScottBrady.IdentityModel.Tests.Tokens.Branca; + +public class BrancaTokenTests { - public class BrancaTokenTests + [Fact] + public void ctor_ExpectPropertiesSet() { - [Fact] - public void ctor_ExpectPropertiesSet() - { var payload = new byte[32]; RandomNumberGenerator.Fill(payload); const uint timestamp = uint.MinValue; @@ -22,9 +22,9 @@ public void ctor_ExpectPropertiesSet() token.Timestamp.Should().Be(new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc)); } - [Fact] - public void GetDateTime_WhenTimestampIsZero_ExpectUnixTimeStart() - { + [Fact] + public void GetDateTime_WhenTimestampIsZero_ExpectUnixTimeStart() + { const uint timestamp = 0; var dateTime = BrancaToken.GetDateTime(timestamp); @@ -32,9 +32,9 @@ public void GetDateTime_WhenTimestampIsZero_ExpectUnixTimeStart() dateTime.Should().Be(new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc)); } - [Fact] - public void GetDateTime_WhenTimestampIs27November_ExpectCorrectDateTime() - { + [Fact] + public void GetDateTime_WhenTimestampIs27November_ExpectCorrectDateTime() + { const uint timestamp = 123206400; var dateTime = BrancaToken.GetDateTime(timestamp); @@ -42,9 +42,9 @@ public void GetDateTime_WhenTimestampIs27November_ExpectCorrectDateTime() dateTime.Should().Be(new DateTime(1973, 11, 27, 0, 0, 0, DateTimeKind.Utc)); } - [Fact] - public void GetDateTime_WhenTimestampIsMaxValue_ExpectCorrectDateTime() - { + [Fact] + public void GetDateTime_WhenTimestampIsMaxValue_ExpectCorrectDateTime() + { const uint timestamp = uint.MaxValue; var dateTime = BrancaToken.GetDateTime(timestamp); @@ -52,42 +52,41 @@ public void GetDateTime_WhenTimestampIsMaxValue_ExpectCorrectDateTime() dateTime.Should().Be(new DateTime(2106, 02, 07, 06, 28, 15, DateTimeKind.Utc)); } - [Fact] - public void GetBrancaTimestamp_WhenDateBeforeUnixTimeStart_ExpectException() - { + [Fact] + public void GetBrancaTimestamp_WhenDateBeforeUnixTimeStart_ExpectException() + { Assert.Throws(() => BrancaToken.GetBrancaTimestamp(new DateTime(1969, 01, 01))); } - [Fact] - public void GetBrancaTimestamp_WhenUnixTimeStart_ExpectZero() - { + [Fact] + public void GetBrancaTimestamp_WhenUnixTimeStart_ExpectZero() + { var timestamp = BrancaToken.GetBrancaTimestamp(new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc)); timestamp.Should().Be(uint.MinValue); } - [Fact] - public void GetBrancaTimestamp_When27November_ExpectZero() - { + [Fact] + public void GetBrancaTimestamp_When27November_ExpectZero() + { var timestamp = BrancaToken.GetBrancaTimestamp(new DateTime(1973, 11, 27, 0, 0, 0, DateTimeKind.Utc)); timestamp.Should().Be(123206400); } - [Fact] - public void GetBrancaTimestamp_WhenMaxTimestamp_ExpectUintMax() - { + [Fact] + public void GetBrancaTimestamp_WhenMaxTimestamp_ExpectUintMax() + { var timestamp = BrancaToken.GetBrancaTimestamp(new DateTime(2106, 02, 07, 06, 28, 15, DateTimeKind.Utc)); timestamp.Should().Be(uint.MaxValue); } - [Fact] - public void GetBrancaTimestamp_WhenAfterMaxTimestamp_ExpectInvalidOperationException() - { + [Fact] + public void GetBrancaTimestamp_WhenAfterMaxTimestamp_ExpectInvalidOperationException() + { Assert.Throws(() => BrancaToken.GetBrancaTimestamp(new DateTime(2106, 02, 07, 06, 28, 16, DateTimeKind.Utc))); } - } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/TestVectors/BrancaTestVectors.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/TestVectors/BrancaTestVectors.cs index d85b658..8325caa 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/TestVectors/BrancaTestVectors.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/TestVectors/BrancaTestVectors.cs @@ -6,18 +6,18 @@ using ScottBrady.IdentityModel.Tokens.Branca; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens.Branca +namespace ScottBrady.IdentityModel.Tests.Tokens.Branca; + +/// +/// Test vectors from https://github.com/tuupola/branca-spec +/// +public class BrancaTestVectors { - /// - /// Test vectors from https://github.com/tuupola/branca-spec - /// - public class BrancaTestVectors - { - public static readonly TheoryData EncodingTestVectors = new TheoryData(); - public static readonly TheoryData DecodingTestVectors = new TheoryData(); + public static readonly TheoryData EncodingTestVectors = new TheoryData(); + public static readonly TheoryData DecodingTestVectors = new TheoryData(); - static BrancaTestVectors() - { + static BrancaTestVectors() + { var file = File.OpenRead("Tokens/Branca/TestVectors/testvectors.json"); var data = JsonNode.Parse(file); if (data == null) throw new Exception("Failed to load test vectors"); @@ -36,18 +36,18 @@ static BrancaTestVectors() } } - [Theory, MemberData(nameof(EncodingTestVectors))] - public void CreateToken_ExpectCorrectResult(BrancaTestVector testVector) - { + [Theory, MemberData(nameof(EncodingTestVectors))] + public void CreateToken_ExpectCorrectResult(BrancaTestVector testVector) + { var handler = new TestBrancaTokenHandler {Nonce = testVector.Nonce}; var token = handler.CreateToken(testVector.Message, testVector.TimeStamp, testVector.Key); token.Should().Be(testVector.Token); } - [Theory, MemberData(nameof(DecodingTestVectors))] - public void ValidateToken_ExpectCorrectResult(BrancaTestVector testVector) - { + [Theory, MemberData(nameof(DecodingTestVectors))] + public void ValidateToken_ExpectCorrectResult(BrancaTestVector testVector) + { var handler = new BrancaTokenHandler(); BrancaToken result = null; @@ -76,16 +76,16 @@ public void ValidateToken_ExpectCorrectResult(BrancaTestVector testVector) } } - public class TestBrancaTokenHandler : BrancaTokenHandler - { - public byte[] Nonce { get; set; } - protected override byte[] GenerateNonce() => Nonce ?? base.GenerateNonce(); - } + public class TestBrancaTokenHandler : BrancaTokenHandler + { + public byte[] Nonce { get; set; } + protected override byte[] GenerateNonce() => Nonce ?? base.GenerateNonce(); + } - public class BrancaTestVector + public class BrancaTestVector + { + public BrancaTestVector(JsonNode data) { - public BrancaTestVector(JsonNode data) - { Id = data["id"]!.GetValue(); Comment = data["comment"]?.GetValue(); Token = data["token"]?.GetValue(); @@ -103,14 +103,13 @@ public BrancaTestVector(JsonNode data) Key = Base16.Decode(keyHex); } - public int Id { get; } - public string Comment { get; } - public byte[] Key { get; } - public byte[] Nonce { get; } - public uint TimeStamp { get; } - public string Token { get; } - public byte[] Message { get; } - public bool IsValid { get; } - } + public int Id { get; } + public string Comment { get; } + public byte[] Key { get; } + public byte[] Nonce { get; } + public uint TimeStamp { get; } + public string Token { get; } + public byte[] Message { get; } + public bool IsValid { get; } } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/EdDSA/EdDsaSecurityKeyTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/EdDSA/EdDsaSecurityKeyTests.cs index 86292cf..49f18de 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/EdDSA/EdDsaSecurityKeyTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/EdDSA/EdDsaSecurityKeyTests.cs @@ -9,61 +9,60 @@ using ScottBrady.IdentityModel.Tokens; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens.EdDSA +namespace ScottBrady.IdentityModel.Tests.Tokens.EdDSA; + +public class EdDsaSecurityKeyTests { - public class EdDsaSecurityKeyTests + [Fact] + public void ctor_WhenKeyParametersAreNull_ExpectArgumentNullException() { - [Fact] - public void ctor_WhenKeyParametersAreNull_ExpectArgumentNullException() - { #pragma warning disable CS0618 - Assert.Throws(() => new EdDsaSecurityKey((Ed25519PublicKeyParameters) null)); + Assert.Throws(() => new EdDsaSecurityKey((Ed25519PublicKeyParameters) null)); #pragma warning restore CS0618 - } + } - [Fact] - public void ctor_WhenEd25519PrivateKey_ExpectKeySetAndCorrectCurve() - { - var keyPair = GenerateEd25519KeyPair(); + [Fact] + public void ctor_WhenEd25519PrivateKey_ExpectKeySetAndCorrectCurve() + { + var keyPair = GenerateEd25519KeyPair(); #pragma warning disable CS0618 - var securityKey = new EdDsaSecurityKey((Ed25519PrivateKeyParameters) keyPair.Private); + var securityKey = new EdDsaSecurityKey((Ed25519PrivateKeyParameters) keyPair.Private); #pragma warning restore CS0618 - securityKey.CryptoProviderFactory.CustomCryptoProvider.Should().BeOfType(); - securityKey.EdDsa.Parameters.D.Should().BeEquivalentTo(((Ed25519PrivateKeyParameters) keyPair.Private).GetEncoded()); - securityKey.EdDsa.Parameters.Curve.Should().Be(ExtendedSecurityAlgorithms.Curves.Ed25519); - securityKey.PrivateKeyStatus.Should().Be(PrivateKeyStatus.Exists); + securityKey.CryptoProviderFactory.CustomCryptoProvider.Should().BeOfType(); + securityKey.EdDsa.Parameters.D.Should().BeEquivalentTo(((Ed25519PrivateKeyParameters) keyPair.Private).GetEncoded()); + securityKey.EdDsa.Parameters.Curve.Should().Be(ExtendedSecurityAlgorithms.Curves.Ed25519); + securityKey.PrivateKeyStatus.Should().Be(PrivateKeyStatus.Exists); #pragma warning disable 618 - securityKey.HasPrivateKey.Should().BeTrue(); + securityKey.HasPrivateKey.Should().BeTrue(); #pragma warning restore 618 - } + } - [Fact] - public void ctor_WhenEd25519PublicKey_ExpectKeySetAndCorrectCurve() - { - var keyPair = GenerateEd25519KeyPair(); + [Fact] + public void ctor_WhenEd25519PublicKey_ExpectKeySetAndCorrectCurve() + { + var keyPair = GenerateEd25519KeyPair(); #pragma warning disable CS0618 - var securityKey = new EdDsaSecurityKey((Ed25519PublicKeyParameters) keyPair.Public); + var securityKey = new EdDsaSecurityKey((Ed25519PublicKeyParameters) keyPair.Public); #pragma warning restore CS0618 - securityKey.CryptoProviderFactory.CustomCryptoProvider.Should().BeOfType(); - securityKey.EdDsa.Parameters.X.Should().BeEquivalentTo(((Ed25519PublicKeyParameters) keyPair.Public).GetEncoded()); - securityKey.EdDsa.Parameters.Curve.Should().Be(ExtendedSecurityAlgorithms.Curves.Ed25519); - securityKey.PrivateKeyStatus.Should().Be(PrivateKeyStatus.DoesNotExist); + securityKey.CryptoProviderFactory.CustomCryptoProvider.Should().BeOfType(); + securityKey.EdDsa.Parameters.X.Should().BeEquivalentTo(((Ed25519PublicKeyParameters) keyPair.Public).GetEncoded()); + securityKey.EdDsa.Parameters.Curve.Should().Be(ExtendedSecurityAlgorithms.Curves.Ed25519); + securityKey.PrivateKeyStatus.Should().Be(PrivateKeyStatus.DoesNotExist); #pragma warning disable 618 - securityKey.HasPrivateKey.Should().BeFalse(); + securityKey.HasPrivateKey.Should().BeFalse(); #pragma warning restore 618 - } + } - private static AsymmetricCipherKeyPair GenerateEd25519KeyPair() - { - var keyPairGenerator = new Ed25519KeyPairGenerator(); - keyPairGenerator.Init(new Ed25519KeyGenerationParameters(new SecureRandom())); - return keyPairGenerator.GenerateKeyPair(); - } + private static AsymmetricCipherKeyPair GenerateEd25519KeyPair() + { + var keyPairGenerator = new Ed25519KeyPairGenerator(); + keyPairGenerator.Init(new Ed25519KeyGenerationParameters(new SecureRandom())); + return keyPairGenerator.GenerateKeyPair(); } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/EdDSA/EdDsaSignatureProviderTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/EdDSA/EdDsaSignatureProviderTests.cs index d32647c..7b400a2 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/EdDSA/EdDsaSignatureProviderTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/EdDSA/EdDsaSignatureProviderTests.cs @@ -6,15 +6,15 @@ using ScottBrady.IdentityModel.Tokens; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens.EdDSA +namespace ScottBrady.IdentityModel.Tests.Tokens.EdDSA; + +public class EdDsaSignatureProviderTests { - public class EdDsaSignatureProviderTests - { - // privateKey = "FU1F1QTjYwfB-xkO6aknnBifE_Ywa94U04xpd-XJfBs" + // privateKey = "FU1F1QTjYwfB-xkO6aknnBifE_Ywa94U04xpd-XJfBs" - [Fact] - public void ctor_ExpectPropertiesSet() - { + [Fact] + public void ctor_ExpectPropertiesSet() + { var expectedSecurityKey = new EdDsaSecurityKey(EdDsa.Create(ExtendedSecurityAlgorithms.Curves.Ed25519)); var expectedAlgorithm = ExtendedSecurityAlgorithms.EdDsa; @@ -24,9 +24,9 @@ public void ctor_ExpectPropertiesSet() provider.Algorithm.Should().Be(expectedAlgorithm); } - [Fact] - public void Sign_WhenSigningWithEd25519Curve_ExpectCorrectSignature() - { + [Fact] + public void Sign_WhenSigningWithEd25519Curve_ExpectCorrectSignature() + { const string plaintext = "eyJraWQiOiIxMjMiLCJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJhdWQiOiJ5b3UiLCJzdWIiOiJib2IiLCJpc3MiOiJtZSIsImV4cCI6MTU5MDg0MTg4N30"; const string expectedSignature = @@ -43,9 +43,9 @@ public void Sign_WhenSigningWithEd25519Curve_ExpectCorrectSignature() signature.Should().BeEquivalentTo(Base64UrlEncoder.DecodeBytes(expectedSignature)); } - [Fact] - public void Verify_WhenJwtSignedWithEd25519Curve_ExpectTrue() - { + [Fact] + public void Verify_WhenJwtSignedWithEd25519Curve_ExpectTrue() + { const string plaintext = "eyJraWQiOiIxMjMiLCJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJhdWQiOiJ5b3UiLCJzdWIiOiJib2IiLCJpc3MiOiJtZSIsImV4cCI6MTU5MDg0MTg4N30"; const string signature = @@ -64,9 +64,9 @@ public void Verify_WhenJwtSignedWithEd25519Curve_ExpectTrue() isValidSignature.Should().BeTrue(); } - [Fact] - public void VerifyWithOffsets_WhenJwtSignedWithEd25519Curve_ExpectTrue() - { + [Fact] + public void VerifyWithOffsets_WhenJwtSignedWithEd25519Curve_ExpectTrue() + { const string plaintext = "eyJraWQiOiIxMjMiLCJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJhdWQiOiJ5b3UiLCJzdWIiOiJib2IiLCJpc3MiOiJtZSIsImV4cCI6MTU5MDg0MTg4N30"; const string signature = @@ -89,5 +89,4 @@ public void VerifyWithOffsets_WhenJwtSignedWithEd25519Curve_ExpectTrue() isValidSignature.Should().BeTrue(); } - } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/EdDSA/TestVectors/Rfc8037TestVectors.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/EdDSA/TestVectors/Rfc8037TestVectors.cs index 4ffcab4..65426c9 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/EdDSA/TestVectors/Rfc8037TestVectors.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/EdDSA/TestVectors/Rfc8037TestVectors.cs @@ -5,16 +5,16 @@ using ScottBrady.IdentityModel.Tokens; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens.EdDSA +namespace ScottBrady.IdentityModel.Tests.Tokens.EdDSA; + +public class Rfc8037TestVectors { - public class Rfc8037TestVectors - { - private readonly byte[] privateKey = Base64UrlEncoder.DecodeBytes("nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"); - private readonly byte[] publicKey = Base64UrlEncoder.DecodeBytes("11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); + private readonly byte[] privateKey = Base64UrlEncoder.DecodeBytes("nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"); + private readonly byte[] publicKey = Base64UrlEncoder.DecodeBytes("11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); - [Fact] - public void A_4_Ed25519_Signing() - { + [Fact] + public void A_4_Ed25519_Signing() + { byte[] plaintext = Encoding.UTF8.GetBytes("eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc"); byte[] expectedSignature = Base64UrlEncoder.DecodeBytes("hgyY0il_MGCjP0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_MuM0KAg"); @@ -25,9 +25,9 @@ public void A_4_Ed25519_Signing() signatureProvider.Sign(plaintext).Should().BeEquivalentTo(expectedSignature); } - [Fact] - public void A_5_Ed25519_Validation() - { + [Fact] + public void A_5_Ed25519_Validation() + { byte[] plaintext = Encoding.UTF8.GetBytes("eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc"); byte[] signature = Base64UrlEncoder.DecodeBytes("hgyY0il_MGCjP0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_MuM0KAg"); @@ -37,5 +37,4 @@ public void A_5_Ed25519_Validation() signatureProvider.Verify(plaintext, signature).Should().BeTrue(); } - } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/ExtendedCryptoProviderTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/ExtendedCryptoProviderTests.cs index 2b9ebc7..dfb7bbb 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/ExtendedCryptoProviderTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/ExtendedCryptoProviderTests.cs @@ -7,58 +7,58 @@ using ScottBrady.IdentityModel.Tokens; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens +namespace ScottBrady.IdentityModel.Tests.Tokens; + +public class ExtendedCryptoProviderTests { - public class ExtendedCryptoProviderTests - { - private ExtendedCryptoProvider sut = new ExtendedCryptoProvider(); + private ExtendedCryptoProvider sut = new ExtendedCryptoProvider(); - [Theory] - [InlineData("eddsa")] - [InlineData("RS256")] - [InlineData("EDDSA")] - public void IsSupportedAlgorithm_WhenNotSupportedAlgorithm_ExpectFalse(string algorithm) - => sut.IsSupportedAlgorithm(algorithm); + [Theory] + [InlineData("eddsa")] + [InlineData("RS256")] + [InlineData("EDDSA")] + public void IsSupportedAlgorithm_WhenNotSupportedAlgorithm_ExpectFalse(string algorithm) + => sut.IsSupportedAlgorithm(algorithm); - [Theory] - [InlineData(ExtendedSecurityAlgorithms.EdDsa)] - public void IsSupportedAlgorithm_WhenSupportedAlgorithm_ExpectTrue(string algorithm) - => sut.IsSupportedAlgorithm(algorithm); + [Theory] + [InlineData(ExtendedSecurityAlgorithms.EdDsa)] + public void IsSupportedAlgorithm_WhenSupportedAlgorithm_ExpectTrue(string algorithm) + => sut.IsSupportedAlgorithm(algorithm); - [Fact] - public void Release_WhenObjectImplementsIDisposable_ExpectObjectDisposed() - { + [Fact] + public void Release_WhenObjectImplementsIDisposable_ExpectObjectDisposed() + { var memoryStream = new MemoryStream(); sut.Release(memoryStream); Assert.Throws(() => memoryStream.Read(Span.Empty)); } - [Fact] - public void Release_WhenObjectDoesNotImplementIDisposable_ExpectNoOp() - { + [Fact] + public void Release_WhenObjectDoesNotImplementIDisposable_ExpectNoOp() + { var uri = new Uri("urn:test"); sut.Release(uri); } - [Fact] - public void Create_WhenAlgorithmIsNotEdDsaButHasEdDsaSecurityKey_ExpectNotSupportedException() - { + [Fact] + public void Create_WhenAlgorithmIsNotEdDsaButHasEdDsaSecurityKey_ExpectNotSupportedException() + { var securityKey = new EdDsaSecurityKey(EdDsa.Create(ExtendedSecurityAlgorithms.Curves.Ed25519)); Assert.Throws(() => sut.Create(SecurityAlgorithms.RsaSha256, securityKey)); } - [Fact] - public void Create_WhenAlgorithmIsEdDsaButIsNotEdDsaSecurityKey_ExpectNotSupportedException() - { + [Fact] + public void Create_WhenAlgorithmIsEdDsaButIsNotEdDsaSecurityKey_ExpectNotSupportedException() + { var securityKey = new RsaSecurityKey(RSA.Create()); Assert.Throws(() => sut.Create(ExtendedSecurityAlgorithms.EdDsa, securityKey)); } - [Fact] - public void Create_WhenAlgorithmIsEdDsaWithEdDsaSecurityKey_ExpectEdDsaSignatureProvider() - { + [Fact] + public void Create_WhenAlgorithmIsEdDsaWithEdDsaSecurityKey_ExpectEdDsaSignatureProvider() + { var securityKey = new EdDsaSecurityKey(EdDsa.Create(ExtendedSecurityAlgorithms.Curves.Ed25519)); var signatureProvider = sut.Create(ExtendedSecurityAlgorithms.EdDsa, securityKey); @@ -67,5 +67,4 @@ public void Create_WhenAlgorithmIsEdDsaWithEdDsaSecurityKey_ExpectEdDsaSignature edDsaSignatureProvider.Algorithm.Should().Be(ExtendedSecurityAlgorithms.EdDsa); edDsaSignatureProvider.Key.Should().Be(securityKey); } - } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/JsonWebTokenHandlerTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/JsonWebTokenHandlerTests.cs index 4b0f6d4..351daa1 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/JsonWebTokenHandlerTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/JsonWebTokenHandlerTests.cs @@ -7,33 +7,33 @@ using ScottBrady.IdentityModel.Tokens; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens +namespace ScottBrady.IdentityModel.Tests.Tokens; + +public class JsonWebTokenHandlerTests { - public class JsonWebTokenHandlerTests + private const string Issuer = "me"; + private const string Audience = "you"; + private const string Subject = "123"; + + private readonly SecurityTokenDescriptor securityTokenDescriptor = new SecurityTokenDescriptor + { + Issuer = Issuer, + Audience = Audience, + Expires = DateTime.UtcNow.AddMinutes(30), + Claims = new Dictionary {{"sub", Subject}} + }; + + private readonly TokenValidationParameters tokenValidationParameters = new TokenValidationParameters { - private const string Issuer = "me"; - private const string Audience = "you"; - private const string Subject = "123"; - - private readonly SecurityTokenDescriptor securityTokenDescriptor = new SecurityTokenDescriptor - { - Issuer = Issuer, - Audience = Audience, - Expires = DateTime.UtcNow.AddMinutes(30), - Claims = new Dictionary {{"sub", Subject}} - }; - - private readonly TokenValidationParameters tokenValidationParameters = new TokenValidationParameters - { - ValidIssuer = Issuer, - ValidAudience = Audience, - ValidateLifetime = true, - RequireExpirationTime = true - }; + ValidIssuer = Issuer, + ValidAudience = Audience, + ValidateLifetime = true, + RequireExpirationTime = true + }; - [Fact] - public void WhenEd25519TokenGenerated_ExpectEdDsaTokenVerifiable() - { + [Fact] + public void WhenEd25519TokenGenerated_ExpectEdDsaTokenVerifiable() + { var key = EdDsa.Create(ExtendedSecurityAlgorithms.Curves.Ed25519); var handler = new JsonWebTokenHandler(); @@ -48,9 +48,9 @@ public void WhenEd25519TokenGenerated_ExpectEdDsaTokenVerifiable() validationResult.ClaimsIdentity.Claims.Should().Contain(x => x.Type == "sub" && x.Value == Subject); } - [Fact] - public void WhenEd448TokenGenerated_ExpectEdDsaTokenVerifiable() - { + [Fact] + public void WhenEd448TokenGenerated_ExpectEdDsaTokenVerifiable() + { var key = EdDsa.Create(ExtendedSecurityAlgorithms.Curves.Ed448); var handler = new JsonWebTokenHandler(); @@ -65,9 +65,9 @@ public void WhenEd448TokenGenerated_ExpectEdDsaTokenVerifiable() validationResult.ClaimsIdentity.Claims.Should().Contain(x => x.Type == "sub" && x.Value == Subject); } - [Fact] - public void WhenEd25519SignatureValidatedUsingEs448_ExpectInvalidToken() - { + [Fact] + public void WhenEd25519SignatureValidatedUsingEs448_ExpectInvalidToken() + { var signingKey = EdDsa.Create(ExtendedSecurityAlgorithms.Curves.Ed25519); var validationKey = EdDsa.Create(ExtendedSecurityAlgorithms.Curves.Ed448); @@ -81,5 +81,4 @@ public void WhenEd25519SignatureValidatedUsingEs448_ExpectInvalidToken() validationResult.IsValid.Should().BeFalse(); } - } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadExtensionsTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadExtensionsTests.cs index ed1425c..10d303b 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadExtensionsTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadExtensionsTests.cs @@ -7,314 +7,313 @@ using ScottBrady.IdentityModel.Tokens; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens +namespace ScottBrady.IdentityModel.Tests.Tokens; + +public class JwtPayloadExtensionsTests { - public class JwtPayloadExtensionsTests + [Fact] + public void ToJwtPayload_WhenTokenDescriptorIsNull_ExpectArgumentNullException() { - [Fact] - public void ToJwtPayload_WhenTokenDescriptorIsNull_ExpectArgumentNullException() - { - SecurityTokenDescriptor descriptor = null; - Assert.Throws(() => descriptor.ToJwtPayload()); - } + SecurityTokenDescriptor descriptor = null; + Assert.Throws(() => descriptor.ToJwtPayload()); + } - [Fact] - public void ToJwtPayload_WhenMultipleSubjectClaimsOfSameType_ExpectJsonArray() - { - const string claimType = "email"; - var claimValues = new[] {"bob@test", "alice@test"}; + [Fact] + public void ToJwtPayload_WhenMultipleSubjectClaimsOfSameType_ExpectJsonArray() + { + const string claimType = "email"; + var claimValues = new[] {"bob@test", "alice@test"}; - var descriptor = new SecurityTokenDescriptor(); - descriptor.Subject = new ClaimsIdentity(new List - { - new Claim(claimType, claimValues[0]), - new Claim(claimType, claimValues[1]) - }, "test"); - - var jwtPayload = descriptor.ToJwtPayload(); - - var claims = JObject.Parse(jwtPayload)[claimType]; - claims.Values().Should().Contain(claimValues); - } - - [Fact] - public void ToJwtPayload_WhenMultipleClaimsOfSameType_ExpectJsonArray() + var descriptor = new SecurityTokenDescriptor(); + descriptor.Subject = new ClaimsIdentity(new List { - const string claimType = "email"; - var claimValues = new[] {"bob@test", "alice@test"}; + new Claim(claimType, claimValues[0]), + new Claim(claimType, claimValues[1]) + }, "test"); + + var jwtPayload = descriptor.ToJwtPayload(); + + var claims = JObject.Parse(jwtPayload)[claimType]; + claims.Values().Should().Contain(claimValues); + } + + [Fact] + public void ToJwtPayload_WhenMultipleClaimsOfSameType_ExpectJsonArray() + { + const string claimType = "email"; + var claimValues = new[] {"bob@test", "alice@test"}; - var descriptor = new SecurityTokenDescriptor(); - descriptor.Claims = new Dictionary - { - {claimType, claimValues} - }; + var descriptor = new SecurityTokenDescriptor(); + descriptor.Claims = new Dictionary + { + {claimType, claimValues} + }; - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var claims = JObject.Parse(jwtPayload)[claimType]; - claims.Values().Should().Contain(claimValues); - } + var claims = JObject.Parse(jwtPayload)[claimType]; + claims.Values().Should().Contain(claimValues); + } - [Fact] - public void ToJwtPayload_WhenSubjectAndClaimsContainDuplicateTypes_ExpecSubjectClaimsReplaced() - { - var claimType = Guid.NewGuid().ToString(); - var expectedClaimValue = Guid.NewGuid().ToString(); + [Fact] + public void ToJwtPayload_WhenSubjectAndClaimsContainDuplicateTypes_ExpecSubjectClaimsReplaced() + { + var claimType = Guid.NewGuid().ToString(); + var expectedClaimValue = Guid.NewGuid().ToString(); - var descriptor = new SecurityTokenDescriptor(); - descriptor.Subject = new ClaimsIdentity(new List {new Claim(claimType, Guid.NewGuid().ToString())}); - descriptor.Claims = new Dictionary {{claimType, expectedClaimValue}}; + var descriptor = new SecurityTokenDescriptor(); + descriptor.Subject = new ClaimsIdentity(new List {new Claim(claimType, Guid.NewGuid().ToString())}); + descriptor.Claims = new Dictionary {{claimType, expectedClaimValue}}; - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var claims = JObject.Parse(jwtPayload)[claimType]; - claims.Value().Should().Contain(expectedClaimValue); - } + var claims = JObject.Parse(jwtPayload)[claimType]; + claims.Value().Should().Contain(expectedClaimValue); + } - [Fact] - public void ToJwtPayload_WhenIssuerSet_ExpectIssuerClaim() - { - var descriptor = new SecurityTokenDescriptor(); - descriptor.Issuer = "me"; + [Fact] + public void ToJwtPayload_WhenIssuerSet_ExpectIssuerClaim() + { + var descriptor = new SecurityTokenDescriptor(); + descriptor.Issuer = "me"; - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var issuer = JObject.Parse(jwtPayload)["iss"]; - issuer.Value().Should().Be(descriptor.Issuer); - } + var issuer = JObject.Parse(jwtPayload)["iss"]; + issuer.Value().Should().Be(descriptor.Issuer); + } - [Fact] - public void ToJwtPayload_WhenIssuerSetInSubject_ExpectSubjectIssuerClaim() - { - var expectedIssuer = Guid.NewGuid().ToString(); + [Fact] + public void ToJwtPayload_WhenIssuerSetInSubject_ExpectSubjectIssuerClaim() + { + var expectedIssuer = Guid.NewGuid().ToString(); - var descriptor = new SecurityTokenDescriptor(); - descriptor.Subject = new ClaimsIdentity(new List {new Claim("iss", expectedIssuer)}); - descriptor.Issuer = Guid.NewGuid().ToString(); + var descriptor = new SecurityTokenDescriptor(); + descriptor.Subject = new ClaimsIdentity(new List {new Claim("iss", expectedIssuer)}); + descriptor.Issuer = Guid.NewGuid().ToString(); - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var issuer = JObject.Parse(jwtPayload)["iss"]; - issuer.Value().Should().Be(expectedIssuer); - } + var issuer = JObject.Parse(jwtPayload)["iss"]; + issuer.Value().Should().Be(expectedIssuer); + } - [Fact] - public void ToJwtPayload_WhenIssuerSetInClaims_ExpectClaimsIssuerClaim() - { - var expectedIssuer = Guid.NewGuid().ToString(); + [Fact] + public void ToJwtPayload_WhenIssuerSetInClaims_ExpectClaimsIssuerClaim() + { + var expectedIssuer = Guid.NewGuid().ToString(); - var descriptor = new SecurityTokenDescriptor(); - descriptor.Claims = new Dictionary {{"iss", expectedIssuer}}; - descriptor.Issuer = Guid.NewGuid().ToString(); + var descriptor = new SecurityTokenDescriptor(); + descriptor.Claims = new Dictionary {{"iss", expectedIssuer}}; + descriptor.Issuer = Guid.NewGuid().ToString(); - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var issuer = JObject.Parse(jwtPayload)["iss"]; - issuer.Value().Should().Be(expectedIssuer); - } + var issuer = JObject.Parse(jwtPayload)["iss"]; + issuer.Value().Should().Be(expectedIssuer); + } - [Fact] - public void ToJwtPayload_WhenAudienceSet_ExpectAudienceClaim() - { - var descriptor = new SecurityTokenDescriptor(); - descriptor.Audience = "you"; + [Fact] + public void ToJwtPayload_WhenAudienceSet_ExpectAudienceClaim() + { + var descriptor = new SecurityTokenDescriptor(); + descriptor.Audience = "you"; - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var audience = JObject.Parse(jwtPayload)["aud"]; - audience.Value().Should().Be(descriptor.Audience); - } + var audience = JObject.Parse(jwtPayload)["aud"]; + audience.Value().Should().Be(descriptor.Audience); + } - [Fact] - public void ToJwtPayload_WhenAudienceSetInSubject_ExpectSubjectAudienceClaim() - { - var expectedAudience = Guid.NewGuid().ToString(); + [Fact] + public void ToJwtPayload_WhenAudienceSetInSubject_ExpectSubjectAudienceClaim() + { + var expectedAudience = Guid.NewGuid().ToString(); - var descriptor = new SecurityTokenDescriptor(); - descriptor.Subject = new ClaimsIdentity(new List {new Claim("aud", expectedAudience)}); - descriptor.Audience = Guid.NewGuid().ToString(); + var descriptor = new SecurityTokenDescriptor(); + descriptor.Subject = new ClaimsIdentity(new List {new Claim("aud", expectedAudience)}); + descriptor.Audience = Guid.NewGuid().ToString(); - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var audience = JObject.Parse(jwtPayload)["aud"]; - audience.Value().Should().Be(expectedAudience); - } + var audience = JObject.Parse(jwtPayload)["aud"]; + audience.Value().Should().Be(expectedAudience); + } - [Fact] - public void ToJwtPayload_WhenAudienceSetInClaims_ExpectClaimsAudienceClaim() - { - var expectedAudience = Guid.NewGuid().ToString(); + [Fact] + public void ToJwtPayload_WhenAudienceSetInClaims_ExpectClaimsAudienceClaim() + { + var expectedAudience = Guid.NewGuid().ToString(); - var descriptor = new SecurityTokenDescriptor(); - descriptor.Claims = new Dictionary {{"aud", expectedAudience}}; - descriptor.Audience = Guid.NewGuid().ToString(); + var descriptor = new SecurityTokenDescriptor(); + descriptor.Claims = new Dictionary {{"aud", expectedAudience}}; + descriptor.Audience = Guid.NewGuid().ToString(); - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var audience = JObject.Parse(jwtPayload)["aud"]; - audience.Value().Should().Be(expectedAudience); - } + var audience = JObject.Parse(jwtPayload)["aud"]; + audience.Value().Should().Be(expectedAudience); + } - [Fact] - public void ToJwtPayload_WhenExpiryNotSet_ExpectExpirySetToOneHour() - { - var descriptor = new SecurityTokenDescriptor(); + [Fact] + public void ToJwtPayload_WhenExpiryNotSet_ExpectExpirySetToOneHour() + { + var descriptor = new SecurityTokenDescriptor(); - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var expiry = JObject.Parse(jwtPayload)["exp"]; - expiry.Value().Should().BeCloseTo( - (long) (EpochTime.GetIntDate(DateTime.UtcNow) + TimeSpan.FromMinutes(60).TotalSeconds), - 10); - } + var expiry = JObject.Parse(jwtPayload)["exp"]; + expiry.Value().Should().BeCloseTo( + (long) (EpochTime.GetIntDate(DateTime.UtcNow) + TimeSpan.FromMinutes(60).TotalSeconds), + 10); + } - [Fact] - public void ToJwtPayload_WhenExpirySet_ExpectExpiryClaim() - { - var expectedExpiry = DateTime.UtcNow.AddMinutes(5); + [Fact] + public void ToJwtPayload_WhenExpirySet_ExpectExpiryClaim() + { + var expectedExpiry = DateTime.UtcNow.AddMinutes(5); - var descriptor = new SecurityTokenDescriptor(); - descriptor.Expires = expectedExpiry; + var descriptor = new SecurityTokenDescriptor(); + descriptor.Expires = expectedExpiry; - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var expiry = JObject.Parse(jwtPayload)["exp"]; - expiry.Value().Should().Be(EpochTime.GetIntDate(expectedExpiry)); - } + var expiry = JObject.Parse(jwtPayload)["exp"]; + expiry.Value().Should().Be(EpochTime.GetIntDate(expectedExpiry)); + } - [Fact] - public void ToJwtPayload_WhenExpirySetInClaims_ExpectClaimsExpiryClaim() - { - var expectedExpiry = EpochTime.GetIntDate(DateTime.UtcNow.AddMinutes(5)); + [Fact] + public void ToJwtPayload_WhenExpirySetInClaims_ExpectClaimsExpiryClaim() + { + var expectedExpiry = EpochTime.GetIntDate(DateTime.UtcNow.AddMinutes(5)); - var descriptor = new SecurityTokenDescriptor(); - descriptor.Claims = new Dictionary{{"exp", expectedExpiry}}; - descriptor.Expires = DateTime.UtcNow.AddHours(42); + var descriptor = new SecurityTokenDescriptor(); + descriptor.Claims = new Dictionary{{"exp", expectedExpiry}}; + descriptor.Expires = DateTime.UtcNow.AddHours(42); - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var expiry = JObject.Parse(jwtPayload)["exp"]; - expiry.Value().Should().Be(expectedExpiry); - } + var expiry = JObject.Parse(jwtPayload)["exp"]; + expiry.Value().Should().Be(expectedExpiry); + } - [Fact] - public void ToJwtPayload_WhenIssuedAtNotSet_ExpectIssuedAtSetToNow() - { - var descriptor = new SecurityTokenDescriptor(); + [Fact] + public void ToJwtPayload_WhenIssuedAtNotSet_ExpectIssuedAtSetToNow() + { + var descriptor = new SecurityTokenDescriptor(); - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var issuedAt = JObject.Parse(jwtPayload)["iat"]; - issuedAt.Value().Should().BeCloseTo(EpochTime.GetIntDate(DateTime.UtcNow), 10); - } + var issuedAt = JObject.Parse(jwtPayload)["iat"]; + issuedAt.Value().Should().BeCloseTo(EpochTime.GetIntDate(DateTime.UtcNow), 10); + } - [Fact] - public void ToJwtPayload_WhenIssuedAtSet_ExpectIssuedAtClaim() - { - var expectedIssuedAt = DateTime.UtcNow.AddMinutes(5); + [Fact] + public void ToJwtPayload_WhenIssuedAtSet_ExpectIssuedAtClaim() + { + var expectedIssuedAt = DateTime.UtcNow.AddMinutes(5); - var descriptor = new SecurityTokenDescriptor(); - descriptor.IssuedAt = expectedIssuedAt; + var descriptor = new SecurityTokenDescriptor(); + descriptor.IssuedAt = expectedIssuedAt; - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var issuedAt = JObject.Parse(jwtPayload)["iat"]; - issuedAt.Value().Should().Be(EpochTime.GetIntDate(expectedIssuedAt)); - } + var issuedAt = JObject.Parse(jwtPayload)["iat"]; + issuedAt.Value().Should().Be(EpochTime.GetIntDate(expectedIssuedAt)); + } - [Fact] - public void ToJwtPayload_WhenIssuedAtSetInClaims_ExpectClaimsIssuedAtClaim() - { - var expectedIssuedAt = EpochTime.GetIntDate(DateTime.UtcNow.AddMinutes(5)); + [Fact] + public void ToJwtPayload_WhenIssuedAtSetInClaims_ExpectClaimsIssuedAtClaim() + { + var expectedIssuedAt = EpochTime.GetIntDate(DateTime.UtcNow.AddMinutes(5)); - var descriptor = new SecurityTokenDescriptor(); - descriptor.Claims = new Dictionary{{"iat", expectedIssuedAt}}; - descriptor.IssuedAt = DateTime.UtcNow.AddHours(42); + var descriptor = new SecurityTokenDescriptor(); + descriptor.Claims = new Dictionary{{"iat", expectedIssuedAt}}; + descriptor.IssuedAt = DateTime.UtcNow.AddHours(42); - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var issuedAt = JObject.Parse(jwtPayload)["iat"]; - issuedAt.Value().Should().Be(expectedIssuedAt); - } + var issuedAt = JObject.Parse(jwtPayload)["iat"]; + issuedAt.Value().Should().Be(expectedIssuedAt); + } - [Fact] - public void ToJwtPayload_WhenNotBeforeNotSet_ExpectNotBeforeSetToNow() - { - var descriptor = new SecurityTokenDescriptor(); + [Fact] + public void ToJwtPayload_WhenNotBeforeNotSet_ExpectNotBeforeSetToNow() + { + var descriptor = new SecurityTokenDescriptor(); - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var notBefore = JObject.Parse(jwtPayload)["nbf"]; - notBefore.Value().Should().BeCloseTo(EpochTime.GetIntDate(DateTime.UtcNow), 10); - } + var notBefore = JObject.Parse(jwtPayload)["nbf"]; + notBefore.Value().Should().BeCloseTo(EpochTime.GetIntDate(DateTime.UtcNow), 10); + } - [Fact] - public void ToJwtPayload_WhenNotBeforeSet_ExpectNotBeforeClaim() - { - var expectedNotBefore = DateTime.UtcNow.AddMinutes(5); + [Fact] + public void ToJwtPayload_WhenNotBeforeSet_ExpectNotBeforeClaim() + { + var expectedNotBefore = DateTime.UtcNow.AddMinutes(5); - var descriptor = new SecurityTokenDescriptor(); - descriptor.NotBefore = expectedNotBefore; + var descriptor = new SecurityTokenDescriptor(); + descriptor.NotBefore = expectedNotBefore; - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var notBefore = JObject.Parse(jwtPayload)["nbf"]; - notBefore.Value().Should().Be(EpochTime.GetIntDate(expectedNotBefore)); - } + var notBefore = JObject.Parse(jwtPayload)["nbf"]; + notBefore.Value().Should().Be(EpochTime.GetIntDate(expectedNotBefore)); + } - [Fact] - public void ToJwtPayload_WhenNotBeforeSetInClaims_ExpectClaimsNotBeforeClaim() - { - var expectedNotBefore = EpochTime.GetIntDate(DateTime.UtcNow.AddMinutes(5)); + [Fact] + public void ToJwtPayload_WhenNotBeforeSetInClaims_ExpectClaimsNotBeforeClaim() + { + var expectedNotBefore = EpochTime.GetIntDate(DateTime.UtcNow.AddMinutes(5)); - var descriptor = new SecurityTokenDescriptor(); - descriptor.Claims = new Dictionary{{"nbf", expectedNotBefore}}; - descriptor.NotBefore = DateTime.UtcNow.AddHours(42); + var descriptor = new SecurityTokenDescriptor(); + descriptor.Claims = new Dictionary{{"nbf", expectedNotBefore}}; + descriptor.NotBefore = DateTime.UtcNow.AddHours(42); - var jwtPayload = descriptor.ToJwtPayload(); + var jwtPayload = descriptor.ToJwtPayload(); - var notBefore = JObject.Parse(jwtPayload)["nbf"]; - notBefore.Value().Should().Be(expectedNotBefore); - } + var notBefore = JObject.Parse(jwtPayload)["nbf"]; + notBefore.Value().Should().Be(expectedNotBefore); + } - [Fact] - public void ToJwtPayload_WhenIsoDateFormat_ExpectNoOverencoding() - { - var descriptor = new SecurityTokenDescriptor(); + [Fact] + public void ToJwtPayload_WhenIsoDateFormat_ExpectNoOverencoding() + { + var descriptor = new SecurityTokenDescriptor(); - var jwtPayload = descriptor.ToJwtPayload(JwtDateTimeFormat.Iso); + var jwtPayload = descriptor.ToJwtPayload(JwtDateTimeFormat.Iso); - var expiry = jwtPayload.Should().NotContain(@"\u002B"); - } + var expiry = jwtPayload.Should().NotContain(@"\u002B"); + } - [Fact] - public void ToJwtClaimDictionary_WhenClaimTypeHasSingleValue_ExpectSingleClaim() - { - var claim = new Claim("email", "bob@test"); + [Fact] + public void ToJwtClaimDictionary_WhenClaimTypeHasSingleValue_ExpectSingleClaim() + { + var claim = new Claim("email", "bob@test"); - var dictionary = JwtPayloadExtensions.ToJwtClaimDictionary(new List {claim}); + var dictionary = JwtPayloadExtensions.ToJwtClaimDictionary(new List {claim}); - var values = dictionary[claim.Type]; - values.ToString().Should().Be(claim.Value); - } + var values = dictionary[claim.Type]; + values.ToString().Should().Be(claim.Value); + } - [Fact] - public void ToJwtClaimDictionary_WhenClaimTypeHasMultipleValues_ExpectEntryWithArrayValue() - { - const string claimType = "email"; - const string value1 = "bob@test"; - const string value2 = "alice@test"; + [Fact] + public void ToJwtClaimDictionary_WhenClaimTypeHasMultipleValues_ExpectEntryWithArrayValue() + { + const string claimType = "email"; + const string value1 = "bob@test"; + const string value2 = "alice@test"; - var dictionary = JwtPayloadExtensions.ToJwtClaimDictionary(new List - { - new Claim(claimType, value1), new Claim(claimType, value2) - }); + var dictionary = JwtPayloadExtensions.ToJwtClaimDictionary(new List + { + new Claim(claimType, value1), new Claim(claimType, value2) + }); - var entry = dictionary[claimType]; - var values = entry as IList; + var entry = dictionary[claimType]; + var values = entry as IList; - values.Should().Contain(value1); - values.Should().Contain(value2); - } + values.Should().Contain(value1); + values.Should().Contain(value2); } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadSecurityTokenTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadSecurityTokenTests.cs index 37291b7..9e312aa 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadSecurityTokenTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadSecurityTokenTests.cs @@ -5,76 +5,75 @@ using ScottBrady.IdentityModel.Tokens; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens +namespace ScottBrady.IdentityModel.Tests.Tokens; + +public class JwtPayloadSecurityTokenTests { - public class JwtPayloadSecurityTokenTests - { - [Fact] - public void ctor_WhenTokenPayloadIsNotJson_ExpectArgumentException() - => Assert.Throws(() => new TestJwtPayloadSecurityToken("")); + [Fact] + public void ctor_WhenTokenPayloadIsNotJson_ExpectArgumentException() + => Assert.Throws(() => new TestJwtPayloadSecurityToken("")); - [Fact] - public void ctor_WhenTokenContainsJson_ExpectJwtClaimsParsed() + [Fact] + public void ctor_WhenTokenContainsJson_ExpectJwtClaimsParsed() + { + var jwt = new { - var jwt = new - { - jti = "xyz", - iss = "me", - aud = "you", - sub = "123", - actort = "them", // 🤷‍ - iat = 1588341409, - nbf = 1588341410, - exp = 1588341499 - }; + jti = "xyz", + iss = "me", + aud = "you", + sub = "123", + actort = "them", // 🤷‍ + iat = 1588341409, + nbf = 1588341410, + exp = 1588341499 + }; - var token = new TestJwtPayloadSecurityToken(JsonConvert.SerializeObject(jwt)); + var token = new TestJwtPayloadSecurityToken(JsonConvert.SerializeObject(jwt)); - token.Id.Should().Be(jwt.jti); - token.Issuer.Should().Be(jwt.iss); - token.Audiences.Should().Contain(jwt.aud); - token.Subject.Should().Be(jwt.sub); - token.Actor.Should().Be(jwt.actort); - token.IssuedAt.Should().Be(DateTimeOffset.FromUnixTimeSeconds(jwt.iat).UtcDateTime); - token.ValidFrom.Should().Be(DateTimeOffset.FromUnixTimeSeconds(jwt.nbf).UtcDateTime); - token.ValidTo.Should().Be(DateTimeOffset.FromUnixTimeSeconds(jwt.exp).UtcDateTime); - } + token.Id.Should().Be(jwt.jti); + token.Issuer.Should().Be(jwt.iss); + token.Audiences.Should().Contain(jwt.aud); + token.Subject.Should().Be(jwt.sub); + token.Actor.Should().Be(jwt.actort); + token.IssuedAt.Should().Be(DateTimeOffset.FromUnixTimeSeconds(jwt.iat).UtcDateTime); + token.ValidFrom.Should().Be(DateTimeOffset.FromUnixTimeSeconds(jwt.nbf).UtcDateTime); + token.ValidTo.Should().Be(DateTimeOffset.FromUnixTimeSeconds(jwt.exp).UtcDateTime); + } - [Fact] - public void ctor_WhenTokenContainsJson_ExpectClaimsParsed() + [Fact] + public void ctor_WhenTokenContainsJson_ExpectClaimsParsed() + { + var jwt = new { - var jwt = new - { - jti = "xyz", - iss = "me", - aud = "you", - sub = "123", - actort = "them", - iat = 1588341409, - nbf = 1588341410, - exp = 1588341499 - }; + jti = "xyz", + iss = "me", + aud = "you", + sub = "123", + actort = "them", + iat = 1588341409, + nbf = 1588341410, + exp = 1588341499 + }; - var token = new TestJwtPayloadSecurityToken(JsonConvert.SerializeObject(jwt)); + var token = new TestJwtPayloadSecurityToken(JsonConvert.SerializeObject(jwt)); - token.Claims.Should().Contain(x => x.Type == "jti" && x.Value == jwt.jti); - token.Claims.Should().Contain(x => x.Type == "iss" && x.Value == jwt.iss); - token.Claims.Should().Contain(x => x.Type == "aud" && x.Value == jwt.aud); - token.Claims.Should().Contain(x => x.Type == "sub" && x.Value == jwt.sub); - token.Claims.Should().Contain(x => x.Type == "actort" && x.Value == jwt.actort); - token.Claims.Should().Contain(x => x.Type == "iat" && x.Value == jwt.iat.ToString()); - token.Claims.Should().Contain(x => x.Type == "nbf" && x.Value == jwt.nbf.ToString()); - token.Claims.Should().Contain(x => x.Type == "exp" && x.Value == jwt.exp.ToString()); - } + token.Claims.Should().Contain(x => x.Type == "jti" && x.Value == jwt.jti); + token.Claims.Should().Contain(x => x.Type == "iss" && x.Value == jwt.iss); + token.Claims.Should().Contain(x => x.Type == "aud" && x.Value == jwt.aud); + token.Claims.Should().Contain(x => x.Type == "sub" && x.Value == jwt.sub); + token.Claims.Should().Contain(x => x.Type == "actort" && x.Value == jwt.actort); + token.Claims.Should().Contain(x => x.Type == "iat" && x.Value == jwt.iat.ToString()); + token.Claims.Should().Contain(x => x.Type == "nbf" && x.Value == jwt.nbf.ToString()); + token.Claims.Should().Contain(x => x.Type == "exp" && x.Value == jwt.exp.ToString()); } +} - internal class TestJwtPayloadSecurityToken : JwtPayloadSecurityToken +internal class TestJwtPayloadSecurityToken : JwtPayloadSecurityToken +{ + public TestJwtPayloadSecurityToken(string payload) : base(payload) { - public TestJwtPayloadSecurityToken(string payload) : base(payload) - { - } - - public override SecurityKey SecurityKey { get; } - public override SecurityKey SigningKey { get; set; } } + + public override SecurityKey SecurityKey { get; } + public override SecurityKey SigningKey { get; set; } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadTokenHandlerTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadTokenHandlerTests.cs index b1da03e..15675c9 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadTokenHandlerTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadTokenHandlerTests.cs @@ -10,27 +10,27 @@ using ScottBrady.IdentityModel.Tokens; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens +namespace ScottBrady.IdentityModel.Tests.Tokens; + +public class JwtPayloadTokenHandlerTests { - public class JwtPayloadTokenHandlerTests + [Fact] + public void CreateClaimsIdentity_WhenTokenIsNull_ExpectArgumentNullException() { - [Fact] - public void CreateClaimsIdentity_WhenTokenIsNull_ExpectArgumentNullException() - { var handler = new TestJwtPayloadTokenHandler(); Assert.Throws(() => handler.TestCreateClaimsIdentity(null, new TokenValidationParameters())); } - [Fact] - public void CreateClaimsIdentity_WhenTokenValidationParametersAreNull_ExpectArgumentNullException() - { + [Fact] + public void CreateClaimsIdentity_WhenTokenValidationParametersAreNull_ExpectArgumentNullException() + { var handler = new TestJwtPayloadTokenHandler(); Assert.Throws(() => handler.TestCreateClaimsIdentity(new MockableJwtPayloadSecurityToken(), null)); } - [Fact] - public void CreateClaimsIdentity_WhenTokenHasIssuer_ExpectClaimsToUseTokenIdentity() - { + [Fact] + public void CreateClaimsIdentity_WhenTokenHasIssuer_ExpectClaimsToUseTokenIdentity() + { const string expectedIssuer = "ids"; var mockToken = new Mock(); mockToken.Setup(x => x.Issuer).Returns(expectedIssuer); @@ -44,9 +44,9 @@ public void CreateClaimsIdentity_WhenTokenHasIssuer_ExpectClaimsToUseTokenIdenti identity.Claims.All(x => x.OriginalIssuer == expectedIssuer).Should().BeTrue(); } - [Fact] - public void CreateClaimsIdentity_WhenTokenHasNoIssuer_ExpectClaimsToUseDefaultIssuer() - { + [Fact] + public void CreateClaimsIdentity_WhenTokenHasNoIssuer_ExpectClaimsToUseDefaultIssuer() + { var mockToken = new Mock(); mockToken.Setup(x => x.Issuer).Returns((string) null); mockToken.Setup(x => x.Claims).Returns(new List {new Claim("sub", "123")}); @@ -59,9 +59,9 @@ public void CreateClaimsIdentity_WhenTokenHasNoIssuer_ExpectClaimsToUseDefaultIs identity.Claims.All(x => x.OriginalIssuer == ClaimsIdentity.DefaultIssuer).Should().BeTrue(); } - [Fact] - public void CreateClaimsIdentity_WhenTokenHasClaimsWithProperties_ExpectPropertiesPersisted() - { + [Fact] + public void CreateClaimsIdentity_WhenTokenHasClaimsWithProperties_ExpectPropertiesPersisted() + { var expectedProperty = new KeyValuePair("test", "test_val"); var claimWithProperty = new Claim("sub", "123") {Properties = {expectedProperty}}; @@ -77,18 +77,18 @@ public void CreateClaimsIdentity_WhenTokenHasClaimsWithProperties_ExpectProperti mappedClaim.Properties.Should().Contain(expectedProperty); } - [Fact] - public void ValidateTokenPayload_WhenTokenIsNull_ExpectResultWithArgumentNullException() - { + [Fact] + public void ValidateTokenPayload_WhenTokenIsNull_ExpectResultWithArgumentNullException() + { var result = new TestJwtPayloadTokenHandler().TestValidateTokenPayload(null, new TokenValidationParameters()); result.IsValid.Should().BeFalse(); result.Exception.Should().BeOfType(); } - [Fact] - public void ValidateTokenPayload_WhenTokenValidationParametersAreNull_ExpectResultWithArgumentNullException() - { + [Fact] + public void ValidateTokenPayload_WhenTokenValidationParametersAreNull_ExpectResultWithArgumentNullException() + { var token = CreateMockToken().Object; var result = new TestJwtPayloadTokenHandler().TestValidateTokenPayload(token, null); @@ -97,9 +97,9 @@ public void ValidateTokenPayload_WhenTokenValidationParametersAreNull_ExpectResu result.Exception.Should().BeOfType(); } - [Fact] - public void ValidateTokenPayload_WhenInvalidLifetime_ExpectFailureResultWithCorrectException() - { + [Fact] + public void ValidateTokenPayload_WhenInvalidLifetime_ExpectFailureResultWithCorrectException() + { var expectedException = new InvalidOperationException("correct error"); var token = CreateMockToken().Object; @@ -118,9 +118,9 @@ public void ValidateTokenPayload_WhenInvalidLifetime_ExpectFailureResultWithCorr result.Exception.Should().Be(expectedException); } - [Fact] - public void ValidateTokenPayload_WhenInvalidAudience_ExpectFailureResultWithCorrectException() - { + [Fact] + public void ValidateTokenPayload_WhenInvalidAudience_ExpectFailureResultWithCorrectException() + { var expectedException = new InvalidOperationException("correct error"); var token = CreateMockToken().Object; @@ -138,9 +138,9 @@ public void ValidateTokenPayload_WhenInvalidAudience_ExpectFailureResultWithCorr result.Exception.Should().Be(expectedException); } - [Fact] - public void ValidateTokenPayload_WhenInvalidIssuer_ExpectFailureResultWithCorrectException() - { + [Fact] + public void ValidateTokenPayload_WhenInvalidIssuer_ExpectFailureResultWithCorrectException() + { var expectedException = new InvalidOperationException("correct error"); var token = CreateMockToken().Object; @@ -158,9 +158,9 @@ public void ValidateTokenPayload_WhenInvalidIssuer_ExpectFailureResultWithCorrec result.Exception.Should().Be(expectedException); } - [Fact] - public void ValidateTokenPayload_WhenTokenReplay_ExpectFailureResultWithCorrectException() - { + [Fact] + public void ValidateTokenPayload_WhenTokenReplay_ExpectFailureResultWithCorrectException() + { var expectedException = new InvalidOperationException("correct error"); var token = CreateMockToken().Object; @@ -178,9 +178,9 @@ public void ValidateTokenPayload_WhenTokenReplay_ExpectFailureResultWithCorrectE result.Exception.Should().Be(expectedException); } - [Fact] - public void ValidateTokenPayload_WhenValidToken_ExpectSuccessResult() - { + [Fact] + public void ValidateTokenPayload_WhenValidToken_ExpectSuccessResult() + { var expectedIdentity = new ClaimsIdentity("test"); var token = CreateMockToken().Object; @@ -198,9 +198,9 @@ public void ValidateTokenPayload_WhenValidToken_ExpectSuccessResult() result.SecurityToken.Should().Be(token); } - [Fact] - public void GetDecryptionKeys_WhenKeyResolverReturnsKey_ExpectKeyFromResolver() - { + [Fact] + public void GetDecryptionKeys_WhenKeyResolverReturnsKey_ExpectKeyFromResolver() + { var expectedKey = new RsaSecurityKey(RSA.Create()); var handler = new TestJwtPayloadTokenHandler(); @@ -214,9 +214,9 @@ public void GetDecryptionKeys_WhenKeyResolverReturnsKey_ExpectKeyFromResolver() keys.Should().Contain(expectedKey); } - [Fact] - public void GetDecryptionKeys_WheKeysInParameters_ExpectAllKeys() - { + [Fact] + public void GetDecryptionKeys_WheKeysInParameters_ExpectAllKeys() + { var expectedKey1 = new RsaSecurityKey(RSA.Create()); var expectedKey2 = new RsaSecurityKey(RSA.Create()); @@ -233,9 +233,9 @@ public void GetDecryptionKeys_WheKeysInParameters_ExpectAllKeys() keys.Should().Contain(expectedKey2); } - [Fact] - public void GetSigningKeys_WhenKeyResolverReturnsKey_ExpectKeyFromResolver() - { + [Fact] + public void GetSigningKeys_WhenKeyResolverReturnsKey_ExpectKeyFromResolver() + { var expectedKey = new RsaSecurityKey(RSA.Create()); var handler = new TestJwtPayloadTokenHandler(); @@ -249,9 +249,9 @@ public void GetSigningKeys_WhenKeyResolverReturnsKey_ExpectKeyFromResolver() keys.Should().Contain(expectedKey); } - [Fact] - public void GetSigningKeys_WheKeysInParameters_ExpectAllKeys() - { + [Fact] + public void GetSigningKeys_WheKeysInParameters_ExpectAllKeys() + { var expectedKey1 = new RsaSecurityKey(RSA.Create()); var expectedKey2 = new RsaSecurityKey(RSA.Create()); @@ -268,9 +268,9 @@ public void GetSigningKeys_WheKeysInParameters_ExpectAllKeys() keys.Should().Contain(expectedKey2); } - [Fact] - public void ValidateToken_ISecurityTokenValidator_WhenSuccess_ExpectInnerTokenAndIdentity() - { + [Fact] + public void ValidateToken_ISecurityTokenValidator_WhenSuccess_ExpectInnerTokenAndIdentity() + { var token = Guid.NewGuid().ToString(); var validationParameters = new TokenValidationParameters {ValidIssuer = Guid.NewGuid().ToString()}; @@ -292,9 +292,9 @@ public void ValidateToken_ISecurityTokenValidator_WhenSuccess_ExpectInnerTokenAn parsedToken.Should().Be(expectedSecurityToken); } - [Fact] - public void ValidateToken_ISecurityTokenValidator_WhenFailure_ExpectInnerException() - { + [Fact] + public void ValidateToken_ISecurityTokenValidator_WhenFailure_ExpectInnerException() + { var token = Guid.NewGuid().ToString(); var validationParameters = new TokenValidationParameters(); @@ -317,8 +317,8 @@ public void ValidateToken_ISecurityTokenValidator_WhenFailure_ExpectInnerExcepti exception.Should().Be(expectedException); } - private static Mock CreateMockToken() - { + private static Mock CreateMockToken() + { var mockToken = new Mock(); mockToken.Setup(x => x.ValidTo).Returns(null); @@ -330,8 +330,8 @@ private static Mock CreateMockToken() return mockToken; } - private static Mock CreateMockHandler() - { + private static Mock CreateMockHandler() + { var mockHandler = new Mock {CallBase = false}; mockHandler.Setup(x => x.TestValidateTokenPayload(It.IsAny(), It.IsAny())) .CallBase(); @@ -340,35 +340,34 @@ private static Mock CreateMockHandler() return mockHandler; } - } +} - public class TestJwtPayloadTokenHandler : JwtPayloadTokenHandler - { - public virtual TokenValidationResult TestValidateTokenPayload(JwtPayloadSecurityToken token, TokenValidationParameters validationParameters) - => base.ValidateTokenPayload(token, validationParameters); - public virtual ClaimsIdentity TestCreateClaimsIdentity(JwtPayloadSecurityToken jwtToken, TokenValidationParameters validationParameters) - => base.CreateClaimsIdentity(jwtToken, validationParameters); +public class TestJwtPayloadTokenHandler : JwtPayloadTokenHandler +{ + public virtual TokenValidationResult TestValidateTokenPayload(JwtPayloadSecurityToken token, TokenValidationParameters validationParameters) + => base.ValidateTokenPayload(token, validationParameters); + public virtual ClaimsIdentity TestCreateClaimsIdentity(JwtPayloadSecurityToken jwtToken, TokenValidationParameters validationParameters) + => base.CreateClaimsIdentity(jwtToken, validationParameters); - public virtual IEnumerable TestGetDecryptionKeys(string token, TokenValidationParameters validationParameters) - => base.GetDecryptionKeys(token, validationParameters); + public virtual IEnumerable TestGetDecryptionKeys(string token, TokenValidationParameters validationParameters) + => base.GetDecryptionKeys(token, validationParameters); - public virtual IEnumerable TestGetSigningKeys(string token, TokenValidationParameters validationParameters) - => base.GetSigningKeys(token, validationParameters); + public virtual IEnumerable TestGetSigningKeys(string token, TokenValidationParameters validationParameters) + => base.GetSigningKeys(token, validationParameters); - public override bool CanReadToken(string securityToken) - { + public override bool CanReadToken(string securityToken) + { throw new NotImplementedException(); } - public override TokenValidationResult ValidateToken(string token, TokenValidationParameters validationParameters) - { + public override TokenValidationResult ValidateToken(string token, TokenValidationParameters validationParameters) + { throw new NotImplementedException(); } - } +} - public class MockableJwtPayloadSecurityToken : JwtPayloadSecurityToken - { - public override SecurityKey SecurityKey => throw new NotImplementedException(); - public override SecurityKey SigningKey { get; set; } - } +public class MockableJwtPayloadSecurityToken : JwtPayloadSecurityToken +{ + public override SecurityKey SecurityKey => throw new NotImplementedException(); + public override SecurityKey SigningKey { get; set; } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/JwtSecurityTokenHandlerTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/JwtSecurityTokenHandlerTests.cs index fbd68c3..6b5c3b5 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/JwtSecurityTokenHandlerTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/JwtSecurityTokenHandlerTests.cs @@ -7,13 +7,13 @@ using ScottBrady.IdentityModel.Tokens; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens +namespace ScottBrady.IdentityModel.Tests.Tokens; + +public class JwtSecurityTokenHandlerTests { - public class JwtSecurityTokenHandlerTests + [Fact] + public void WhenEdDsaTokenGenerated_ExpectEdDsaTokenVerifiable() { - [Fact] - public void WhenEdDsaTokenGenerated_ExpectEdDsaTokenVerifiable() - { const string issuer = "me"; const string audience = "you"; const string subject = "123"; @@ -43,5 +43,4 @@ public void WhenEdDsaTokenGenerated_ExpectEdDsaTokenVerifiable() validationResult.Claims.Should().Contain(x => x.Type == "sub" && x.Value == subject); } - } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoSecurityTokenTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoSecurityTokenTests.cs index 7cf7301..79c4097 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoSecurityTokenTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoSecurityTokenTests.cs @@ -4,70 +4,69 @@ using ScottBrady.IdentityModel.Tokens.Paseto; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto +namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto; + +public class PasetoSecurityTokenTests { - public class PasetoSecurityTokenTests + [Fact] + public void IssuedAt_WhenIatClaimHasIsoFormat_ExpectDateTime() { - [Fact] - public void IssuedAt_WhenIatClaimHasIsoFormat_ExpectDateTime() - { - var expectedDateTime = new DateTime(2038, 03, 17, 01, 02, 03, DateTimeKind.Utc); + var expectedDateTime = new DateTime(2038, 03, 17, 01, 02, 03, DateTimeKind.Utc); - var jwt = new - { - iss = "me", - aud = "you", - iat = "2038-03-17T01:02:03+00:00" - }; + var jwt = new + { + iss = "me", + aud = "you", + iat = "2038-03-17T01:02:03+00:00" + }; - var innerToken = new TestPasetoToken(); - innerToken.SetPayload(JsonConvert.SerializeObject(jwt)); - var token = new PasetoSecurityToken(innerToken); + var innerToken = new TestPasetoToken(); + innerToken.SetPayload(JsonConvert.SerializeObject(jwt)); + var token = new PasetoSecurityToken(innerToken); - token.IssuedAt.Should().BeCloseTo(expectedDateTime, TimeSpan.FromMilliseconds(10)); - } + token.IssuedAt.Should().BeCloseTo(expectedDateTime, TimeSpan.FromMilliseconds(10)); + } - [Fact] - public void ValidFrom_WhenIatClaimHasIsoFormat_ExpectDateTime() - { - var expectedDateTime = new DateTime(2028, 03, 17, 01, 02, 03, DateTimeKind.Utc); + [Fact] + public void ValidFrom_WhenIatClaimHasIsoFormat_ExpectDateTime() + { + var expectedDateTime = new DateTime(2028, 03, 17, 01, 02, 03, DateTimeKind.Utc); - var jwt = new - { - iss = "me", - aud = "you", - nbf = "2028-03-17T01:02:03+00:00" - }; + var jwt = new + { + iss = "me", + aud = "you", + nbf = "2028-03-17T01:02:03+00:00" + }; - var innerToken = new TestPasetoToken(); - innerToken.SetPayload(JsonConvert.SerializeObject(jwt)); - var token = new PasetoSecurityToken(innerToken); + var innerToken = new TestPasetoToken(); + innerToken.SetPayload(JsonConvert.SerializeObject(jwt)); + var token = new PasetoSecurityToken(innerToken); - token.ValidFrom.Should().BeCloseTo(expectedDateTime, TimeSpan.FromMilliseconds(10)); - } + token.ValidFrom.Should().BeCloseTo(expectedDateTime, TimeSpan.FromMilliseconds(10)); + } - [Fact] - public void ValidTo_WhenIatClaimHasIsoFormat_ExpectDateTime() - { - var expectedDateTime = new DateTime(2018, 03, 17, 01, 02, 03, DateTimeKind.Utc); + [Fact] + public void ValidTo_WhenIatClaimHasIsoFormat_ExpectDateTime() + { + var expectedDateTime = new DateTime(2018, 03, 17, 01, 02, 03, DateTimeKind.Utc); - var jwt = new - { - iss = "me", - aud = "you", - exp = "2018-03-17T01:02:03+00:00" - }; + var jwt = new + { + iss = "me", + aud = "you", + exp = "2018-03-17T01:02:03+00:00" + }; - var innerToken = new TestPasetoToken(); - innerToken.SetPayload(JsonConvert.SerializeObject(jwt)); - var token = new PasetoSecurityToken(innerToken); + var innerToken = new TestPasetoToken(); + innerToken.SetPayload(JsonConvert.SerializeObject(jwt)); + var token = new PasetoSecurityToken(innerToken); - token.ValidTo.Should().BeCloseTo(expectedDateTime, TimeSpan.FromMilliseconds(10)); - } + token.ValidTo.Should().BeCloseTo(expectedDateTime, TimeSpan.FromMilliseconds(10)); } +} - public class TestPasetoToken : PasetoToken - { - public new void SetPayload(string payload) => Payload = payload; - } +public class TestPasetoToken : PasetoToken +{ + public new void SetPayload(string payload) => Payload = payload; } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoTokenHandlerTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoTokenHandlerTests.cs index cc39b4c..14eff68 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoTokenHandlerTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoTokenHandlerTests.cs @@ -10,18 +10,18 @@ using ScottBrady.IdentityModel.Tokens.Paseto; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto +namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto; + +public class PasetoTokenHandlerTests { - public class PasetoTokenHandlerTests - { - private const string TestVersion = "test"; - private readonly Mock mockVersionStrategy = new Mock(); + private const string TestVersion = "test"; + private readonly Mock mockVersionStrategy = new Mock(); - private readonly Mock mockedSut; - private readonly PasetoTokenHandler sut; + private readonly Mock mockedSut; + private readonly PasetoTokenHandler sut; - public PasetoTokenHandlerTests() - { + public PasetoTokenHandlerTests() + { mockedSut = new Mock( new Dictionary{{TestVersion, mockVersionStrategy.Object}}) { @@ -31,21 +31,21 @@ public PasetoTokenHandlerTests() sut = mockedSut.Object; } - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void CanReadToken_WhenTokenIsNullOrWhitespace_ExpectFalse(string token) - { + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void CanReadToken_WhenTokenIsNullOrWhitespace_ExpectFalse(string token) + { var handler = new PasetoTokenHandler(); var canReadToken = handler.CanReadToken(token); canReadToken.Should().BeFalse(); } - [Fact] - public void CanReadToken_WhenTokenIsTooLong_ExpectFalse() - { + [Fact] + public void CanReadToken_WhenTokenIsTooLong_ExpectFalse() + { var tokenBytes = new byte[TokenValidationParameters.DefaultMaximumTokenSizeInBytes + 1]; new Random().NextBytes(tokenBytes); @@ -54,9 +54,9 @@ public void CanReadToken_WhenTokenIsTooLong_ExpectFalse() canReadToken.Should().BeFalse(); } - [Fact] - public void CanReadToken_WhenTokenHasTooManySegments_ExpectFalse() - { + [Fact] + public void CanReadToken_WhenTokenHasTooManySegments_ExpectFalse() + { const string invalidToken = "ey.ey.ey.ey.ey.ey"; var canReadToken = sut.CanReadToken(invalidToken); @@ -64,9 +64,9 @@ public void CanReadToken_WhenTokenHasTooManySegments_ExpectFalse() canReadToken.Should().BeFalse(); } - [Fact] - public void CanReadToken_WhenBrancaToken_ExpectFalse() - { + [Fact] + public void CanReadToken_WhenBrancaToken_ExpectFalse() + { const string brancaToken = "5K6Oid5pXkASEGvv63CHxpKhSX9passYQ4QhdSdCuOEnHlvBrvX414fWX6zUceAdg3DY9yTVQcmVZn0xr9lsBKBHDzOLNAGVlCs1SHlWIuFDfB8yGXO8EyNPnH9CBMueSEtNmISgcjM1ZmfmcD2EtE6"; var canReadToken = sut.CanReadToken(brancaToken); @@ -74,41 +74,41 @@ public void CanReadToken_WhenBrancaToken_ExpectFalse() canReadToken.Should().BeFalse(); } - [Fact] - public void CanReadToken_WhenPasetoToken_ExpectTrue() - { + [Fact] + public void CanReadToken_WhenPasetoToken_ExpectTrue() + { var canReadToken = sut.CanReadToken(CreateTestToken()); canReadToken.Should().BeTrue(); } - [Fact] - public void CanValidateToken_ExpectTrue() - => sut.CanValidateToken.Should().BeTrue(); + [Fact] + public void CanValidateToken_ExpectTrue() + => sut.CanValidateToken.Should().BeTrue(); - [Fact] - public void CreateToken_WhenSecurityTokenDescriptorIsNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.CreateToken(null)); + [Fact] + public void CreateToken_WhenSecurityTokenDescriptorIsNull_ExpectArgumentNullException() + => Assert.Throws(() => sut.CreateToken(null)); - [Fact] - public void CreateToken_WhenTokenVersionIsNotSupported_ExpectSecurityTokenException() - { + [Fact] + public void CreateToken_WhenTokenVersionIsNotSupported_ExpectSecurityTokenException() + { var tokenDescriptor = new PasetoSecurityTokenDescriptor("v42", PasetoConstants.Purposes.Public); Assert.Throws(() => sut.CreateToken(tokenDescriptor)); } - [Fact] - public void CreateToken_WhenTokenPurposeNotSupported_ExpectSecurityTokenException() - { + [Fact] + public void CreateToken_WhenTokenPurposeNotSupported_ExpectSecurityTokenException() + { var tokenDescriptor = new PasetoSecurityTokenDescriptor(TestVersion, "external"); Assert.Throws(() => sut.CreateToken(tokenDescriptor)); } - [Fact] - public void CreateToken_WhenLocalEncryptionThrowsException_ExpectSameException() - { + [Fact] + public void CreateToken_WhenLocalEncryptionThrowsException_ExpectSameException() + { var expectedException = new ApplicationException("local"); var tokenDescriptor = new PasetoSecurityTokenDescriptor(TestVersion, PasetoConstants.Purposes.Local); @@ -119,9 +119,9 @@ public void CreateToken_WhenLocalEncryptionThrowsException_ExpectSameException() exception.Should().Be(expectedException); } - [Fact] - public void CreateToken_WhenPublicSigningThrowsException_ExpectSameException() - { + [Fact] + public void CreateToken_WhenPublicSigningThrowsException_ExpectSameException() + { var expectedException = new ApplicationException("public"); var tokenDescriptor = new PasetoSecurityTokenDescriptor(TestVersion, PasetoConstants.Purposes.Public); @@ -132,9 +132,9 @@ public void CreateToken_WhenPublicSigningThrowsException_ExpectSameException() exception.Should().Be(expectedException); } - [Fact] - public void CreateToken_WhenLocalEncryptionSucceeds_ExpectLocalToken() - { + [Fact] + public void CreateToken_WhenLocalEncryptionSucceeds_ExpectLocalToken() + { const string expectedToken = "local"; var tokenDescriptor = new PasetoSecurityTokenDescriptor(TestVersion, PasetoConstants.Purposes.Local); @@ -146,9 +146,9 @@ public void CreateToken_WhenLocalEncryptionSucceeds_ExpectLocalToken() token.Should().Be(expectedToken); } - [Fact] - public void CreateToken_WhenPublicSigningSucceeds_ExpectPublicToken() - { + [Fact] + public void CreateToken_WhenPublicSigningSucceeds_ExpectPublicToken() + { const string expectedToken = "public"; var tokenDescriptor = new PasetoSecurityTokenDescriptor(TestVersion, PasetoConstants.Purposes.Public); @@ -160,57 +160,57 @@ public void CreateToken_WhenPublicSigningSucceeds_ExpectPublicToken() token.Should().Be(expectedToken); } - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void ValidateToken_WhenTokenIsNullOrWhitespace_ExpectFailureWithArgumentNullException(string token) - { + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void ValidateToken_WhenTokenIsNullOrWhitespace_ExpectFailureWithArgumentNullException(string token) + { var result = sut.ValidateToken(token, new TokenValidationParameters()); result.IsValid.Should().BeFalse(); result.Exception.Should().BeOfType(); } - [Fact] - public void ValidateToken_WhenTokenValidationParametersAreNull_ExpectFailureWithArgumentNullException() - { + [Fact] + public void ValidateToken_WhenTokenValidationParametersAreNull_ExpectFailureWithArgumentNullException() + { var result = sut.ValidateToken(CreateTestToken(), null); result.IsValid.Should().BeFalse(); result.Exception.Should().BeOfType(); } - [Fact] - public void ValidateToken_WhenTokenCannotBeRead_ExpectFailureWithSecurityTokenException() - { + [Fact] + public void ValidateToken_WhenTokenCannotBeRead_ExpectFailureWithSecurityTokenException() + { var result = sut.ValidateToken("ey.ey", new TokenValidationParameters()); result.IsValid.Should().BeFalse(); result.Exception.Should().BeOfType(); } - [Fact] - public void ValidateToken_WhenTokenVersionIsNotSupported_ExpectSecurityTokenException() - { + [Fact] + public void ValidateToken_WhenTokenVersionIsNotSupported_ExpectSecurityTokenException() + { var result = sut.ValidateToken(CreateTestToken(version: "v42"), new TokenValidationParameters()); result.IsValid.Should().BeFalse(); result.Exception.Should().BeOfType(); } - [Fact] - public void ValidateToken_WhenTokenPurposeNotSupported_ExpectSecurityTokenException() - { + [Fact] + public void ValidateToken_WhenTokenPurposeNotSupported_ExpectSecurityTokenException() + { var result = sut.ValidateToken(CreateTestToken(purpose: "notapurpose"), new TokenValidationParameters()); result.IsValid.Should().BeFalse(); result.Exception.Should().BeOfType(); } - [Fact] - public void ValidateToken_WhenLocalTokenValidationFails_ExpectFailureResultWithInnerException() - { + [Fact] + public void ValidateToken_WhenLocalTokenValidationFails_ExpectFailureResultWithInnerException() + { var expectedException = new ApplicationException("local"); mockVersionStrategy.Setup(x => x.Decrypt(It.IsAny(), It.IsAny>())) @@ -222,9 +222,9 @@ public void ValidateToken_WhenLocalTokenValidationFails_ExpectFailureResultWithI result.Exception.Should().Be(expectedException); } - [Fact] - public void ValidateToken_WhenPublicTokenValidationFails_ExpectFailureResultWithInnerException() - { + [Fact] + public void ValidateToken_WhenPublicTokenValidationFails_ExpectFailureResultWithInnerException() + { var expectedException = new ApplicationException("public"); mockVersionStrategy.Setup(x => x.Verify(It.IsAny(), It.IsAny>())) @@ -236,9 +236,9 @@ public void ValidateToken_WhenPublicTokenValidationFails_ExpectFailureResultWith result.Exception.Should().Be(expectedException); } - [Fact] - public void ValidateToken_WhenTokenPayloadValidationFails_ExpectPayloadValidationResult() - { + [Fact] + public void ValidateToken_WhenTokenPayloadValidationFails_ExpectPayloadValidationResult() + { var expectedResult = new TokenValidationResult {IsValid = false, Exception = new ApplicationException("validation")}; mockVersionStrategy.Setup(x => x.Verify(It.IsAny(), It.IsAny>())) @@ -254,9 +254,9 @@ public void ValidateToken_WhenTokenPayloadValidationFails_ExpectPayloadValidatio result.Should().Be(expectedResult); } - [Fact] - public void ValidateToken_WhenValidLocalToken_ExpectSuccessResultWithSecurityTokenAndClaimsIdentity() - { + [Fact] + public void ValidateToken_WhenValidLocalToken_ExpectSuccessResultWithSecurityTokenAndClaimsIdentity() + { var expectedIdentity = new ClaimsIdentity("test"); mockVersionStrategy.Setup(x => x.Decrypt(It.IsAny(), It.IsAny>())) @@ -278,9 +278,9 @@ public void ValidateToken_WhenValidLocalToken_ExpectSuccessResultWithSecurityTok result.SecurityToken.Should().NotBeNull(); } - [Fact] - public void ValidateToken_WhenValidPublicToken_ExpectSuccessResultWithSecurityTokenAndClaimsIdentity() - { + [Fact] + public void ValidateToken_WhenValidPublicToken_ExpectSuccessResultWithSecurityTokenAndClaimsIdentity() + { var expectedIdentity = new ClaimsIdentity("test"); mockVersionStrategy.Setup(x => x.Verify(It.IsAny(), It.IsAny>())) @@ -302,9 +302,9 @@ public void ValidateToken_WhenValidPublicToken_ExpectSuccessResultWithSecurityTo result.SecurityToken.Should().NotBeNull(); } - [Fact] - public void ValidateToken_WhenSaveSignInTokenIsTrue_ExpectIdentityBootstrapContext() - { + [Fact] + public void ValidateToken_WhenSaveSignInTokenIsTrue_ExpectIdentityBootstrapContext() + { var expectedToken = CreateTestToken(purpose: PasetoConstants.Purposes.Public); var expectedIdentity = new ClaimsIdentity("test"); @@ -326,9 +326,9 @@ public void ValidateToken_WhenSaveSignInTokenIsTrue_ExpectIdentityBootstrapConte result.ClaimsIdentity.BootstrapContext.Should().Be(expectedToken); } - [Fact] - public void CreateAndValidateToken_WhenV2PublicToken_ExpectCorrectClaims() - { + [Fact] + public void CreateAndValidateToken_WhenV2PublicToken_ExpectCorrectClaims() + { const string expectedClaimType = "name"; const string expectedClaimValue = "scott"; const string issuer = "me"; @@ -364,9 +364,8 @@ public void CreateAndValidateToken_WhenV2PublicToken_ExpectCorrectClaims() result.ClaimsIdentity.HasClaim(expectedClaimType, expectedClaimValue).Should().BeTrue(); } - private static string CreateTestToken(string version = TestVersion, string purpose = "public", string payload = "ey") - => $"{version}.{purpose}.{payload}"; - } + private static string CreateTestToken(string version = TestVersion, string purpose = "public", string payload = "ey") + => $"{version}.{purpose}.{payload}"; +} - internal class TestPasetoSecurityToken : PasetoSecurityToken { } -} \ No newline at end of file +internal class TestPasetoSecurityToken : PasetoSecurityToken { } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoTokenTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoTokenTests.cs index e68e1e9..47d59c1 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoTokenTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoTokenTests.cs @@ -4,26 +4,26 @@ using ScottBrady.IdentityModel.Tokens.Paseto; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto +namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto; + +public class PasetoTokenTests { - public class PasetoTokenTests - { - private const string ValidToken = "v2.local.xyz"; + private const string ValidToken = "v2.local.xyz"; - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void ctor_WhenTokenIsNullOrWhitespace_ExpectArgumentNullException(string token) - => Assert.Throws(() => new PasetoToken(token)); + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void ctor_WhenTokenIsNullOrWhitespace_ExpectArgumentNullException(string token) + => Assert.Throws(() => new PasetoToken(token)); - [Fact] - public void ctor_WhenTokenHasTooManyParts_ExpectArgumentException() - => Assert.Throws(() => new PasetoToken("ey.ey.ey.ey.ey")); + [Fact] + public void ctor_WhenTokenHasTooManyParts_ExpectArgumentException() + => Assert.Throws(() => new PasetoToken("ey.ey.ey.ey.ey")); - [Fact] - public void ctor_WhenValidPasetoToken_ExpectCorrectProperties() - { + [Fact] + public void ctor_WhenValidPasetoToken_ExpectCorrectProperties() + { const string expectedVersion = "v2"; const string expectedPurpose = "public"; const string expectedPayload = "fa919c9d3d1248f29213521a40fc2b57"; @@ -40,9 +40,9 @@ public void ctor_WhenValidPasetoToken_ExpectCorrectProperties() pasetoToken.EncodedFooter.Should().BeNull(); } - [Fact] - public void ctor_WhenValidPasetoTokenWithFooter_ExpectCorrectProperties() - { + [Fact] + public void ctor_WhenValidPasetoTokenWithFooter_ExpectCorrectProperties() + { const string expectedVersion = "v2"; const string expectedPurpose = "public"; const string expectedPayload = "fa919c9d3d1248f29213521a40fc2b57"; @@ -61,25 +61,25 @@ public void ctor_WhenValidPasetoTokenWithFooter_ExpectCorrectProperties() pasetoToken.Payload.Should().BeNull(); } - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void SetPayload_WhenPayloadIsNullOrWhitespace_ExpectArgumentNullException(string payload) - => Assert.Throws(() => new PasetoToken(ValidToken).SetPayload(payload)); + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void SetPayload_WhenPayloadIsNullOrWhitespace_ExpectArgumentNullException(string payload) + => Assert.Throws(() => new PasetoToken(ValidToken).SetPayload(payload)); - [Fact] - public void SetPayload_WhenPayloadIsNotJson_ExpectArgumentException() - { + [Fact] + public void SetPayload_WhenPayloadIsNotJson_ExpectArgumentException() + { const string invalidPayload = "oops"; var token = new PasetoToken(ValidToken); Assert.Throws(() => token.SetPayload(invalidPayload)); } - [Fact] - public void SetPayload_WhenValidPayload_ExpectParsedPayload() - { + [Fact] + public void SetPayload_WhenValidPayload_ExpectParsedPayload() + { const string expectedKey = "test"; const string expectedValue = "test_val"; @@ -90,5 +90,4 @@ public void SetPayload_WhenValidPayload_ExpectParsedPayload() token.Payload.Should().Be(payload); } - } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/TestVectors/PasetoTestVectors.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/TestVectors/PasetoTestVectors.cs index 89a4533..d46d908 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/TestVectors/PasetoTestVectors.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/TestVectors/PasetoTestVectors.cs @@ -10,17 +10,17 @@ using ScottBrady.IdentityModel.Tokens.Paseto; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto +namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto; + +/// +/// Test vectors from https://github.com/paseto-standard/test-vectors +/// +public class PasetoTestVectors { - /// - /// Test vectors from https://github.com/paseto-standard/test-vectors - /// - public class PasetoTestVectors - { - public static readonly TheoryData TestVectors = new TheoryData(); + public static readonly TheoryData TestVectors = new TheoryData(); - static PasetoTestVectors() - { + static PasetoTestVectors() + { var file = File.OpenRead("Tokens/Paseto/TestVectors/testvectors.json"); var data = JsonNode.Parse(file); if (data == null) throw new Exception("Failed to load test vectors"); @@ -35,9 +35,9 @@ static PasetoTestVectors() } } - [Theory, MemberData(nameof(TestVectors))] - public void ValidateToken_ExpectCorrectResult(PasetoTestVector testVector) - { + [Theory, MemberData(nameof(TestVectors))] + public void ValidateToken_ExpectCorrectResult(PasetoTestVector testVector) + { var handler = new PasetoTokenHandler(); var result = handler.ValidateToken(testVector.Token, new TokenValidationParameters { @@ -76,12 +76,12 @@ public void ValidateToken_ExpectCorrectResult(PasetoTestVector testVector) } } } - } +} - public class PasetoTestVector +public class PasetoTestVector +{ + public PasetoTestVector(string version, JsonNode data) { - public PasetoTestVector(string version, JsonNode data) - { Name = data["name"]?.GetValue(); ShouldFail = data["expect-fail"]?.GetValue() ?? throw new Exception("Unable to parse expect-fail"); Token = data["token"]?.GetValue(); @@ -111,10 +111,9 @@ public PasetoTestVector(string version, JsonNode data) } } - public string Name { get; } - public bool ShouldFail { get; } - public SecurityKey Key { get; } - public string Token { get; } - public JsonNode ExpectedPayload { get; } - } + public string Name { get; } + public bool ShouldFail { get; } + public SecurityKey Key { get; } + public string Token { get; } + public JsonNode ExpectedPayload { get; } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersion1Tests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersion1Tests.cs index befbad3..1d9c151 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersion1Tests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersion1Tests.cs @@ -8,207 +8,206 @@ using ScottBrady.IdentityModel.Tokens.Paseto; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto +namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto; + +public class PasetoVersion1Tests { - public class PasetoVersion1Tests - { - private const string ValidVersion = "v1"; - private const string ValidPublicPurpose = "public"; - private const string ValidPublicPayload = "eyJzdWIiOiIxMjMiLCJleHAiOiIyMDIwLTA1LTA3VDE3OjExOjM4LjM3MTE3MTJaIn1HginqDCa4m01vI75OaWrFyAYCA1k9_sx36XVDEcHosOkk6coBDwDfoOaSFA_wE3nkfyuy3fTr7g6BpdzPbIb5qhI4Wpdy_zhhyEz7kC8ZSaDNN0tnBT0sL1c6hSuWKGh3tT6qPmjUmJwIv2ZjosozSmRF7bhWKJDsvTzQN6EFBddcvQpPQok9Ekdgzd70_Yxjl9YlUizF7WOiDm-R6m3xy_Mk8IRGQwiArYGmJRmR82W97ajqdBUJD8kbaFQglDxwEMcX-T4AqXCttjhdQi-JcXX34SDTyxE-8m02X8eNrKg64L6ZAFDAzbaa2bz3EUo5ULW2XaG4DW2zZ4nFd9m2"; - private readonly string validToken = $"{ValidVersion}.{ValidPublicPurpose}.{ValidPublicPayload}"; - - private const string ValidSigningPrivateKey = - "PFJTQUtleVZhbHVlPjxNb2R1bHVzPm9VQ002dEdieFc5eGZqWm1mbmQzTThLQkFzd0ZmbHVLY2IwV1RNMXMzeVh2c3dZci9MbkFVVGVhNjF4QWRma3BSbVVuT3VrODRwZUN5OEVkMzdZcXVHL05WMldsT0dRckxMRkh3UklIMGdmU0IzMjFpdlVzN2xqbDc5S25TRmpEU0ZqcUJNTEJTSS93ZlhobCtYTGZrTjczaGJmeTNSRzVTUDU4Vm5UUEQveWFRczlmNVdVVHhCSFBKNWx4Ump3cVpTemJjZE05cHNtcVFHWGcveUVCejFsMlJQaCtTK0R1aEw5TU1iRWdTb0lXanFKaWEzUFllRDF5WEt3RjlPdjlaa3V4L21ZZjRkRW9pWUZXV05jS3ZSSGFTVFBjTFd0NnZpUXZsekREd09FUG9HdlI4SkNreUJ2a1J0Q1VBeVAyMEpkYzFGK0xqdEp3dkIyNTRBTjU1UT09PC9Nb2R1bHVzPjxFeHBvbmVudD5BUUFCPC9FeHBvbmVudD48UD54Mlp3Y1N1Ni9KeTB5UFRqcVJXTlA1OVhtMk5hNDFEajdQeThsNHBXbWZKWkExTWxBMzRMNUpLVEpJMHZEWStyMTBhN1JRcVJSeHpseEVnQStvMHQwbW5uckRZbGJYbDB4OGlON04vb2w2OHZ0NEJtWFZCWGdxYURpNUJoaWtvLzVzd3EvOVhBd1ZKYm1zVFBCSjVGdi9DQWxUSytNbStacUt6MzlHVTYvMHM9PC9QPjxRPnp3WU80MVdGRUFyTS80R3hDOW1HZEhJeEFKR2RBaWoyZWJ0NjMrQk9QRUxpelZySjdudVR0ZGtxZnlhRHR5eXpGTWxVRHV2VVpIMU1YNnlsUldsQWQrSlhZcUFRZVlML2poYXZYL0llL2NsRE5YRnlRbTNoeitrOFRZZDV2KzFId0RjVTNWVUJlQnVTa3d3bmgrSXBHMU5HeWk4b0RJZldYZThIUXRsdVBZOD08L1E+PERQPllPbGN4T1FvSVJaWWwwTE9VeU55WHZXbXNwTDdYWGUzRHp0V3ZhQXlydWVtYzRNNWZoVUkycktTYVRWbEpRWXEwcHBCOGpCTW8yOWNES1dpTkNQaG5WNXpock5hUlhhK1Ywc1dENFpUbVVVL3Y4UGIvSVpMd2VnRUR4VEJFMkU2NVlWZGNMSUcyTzZhTHdKd1N5SlJiQlFMcW5mYkVOQkVza0krMEwxU2l6az08L0RQPjxEUT5XRTJyT0FpWVV6bG9LMnYwU3F1a0VETk05NE1reDNFVmdPTVpERGt1NWNGWjRHSGpWQmZkNzJrTUdXUWlOcFdZWlR0aTRXSnlHOUxlS3NrSFRjNFJNNUdWMkhtUnpXSzFBclJtWmJSdXg2MTdQMlorYUJ0YWdFWnA5Ri9lN0tDWFJFTzZZSllMcEdHT2FhNTdoaGhQbEZvM0RiS0RrS1M0S1NUMW9ld0FlNzA9PC9EUT48SW52ZXJzZVE+SVpvNFVZRzVOVWt6TlA3LzVXTzhkVW9BUFJJU2htVithNTh5dWZWRjZtMUl3NTRJVDNTVWhXOVh5dFdMa0ZsWmJZakhxMmZlaUhDOUw0OGFUTG1SMmlIejVKUjJKN2pENnJGbis1SFNZR2l0OGpZbnBvNGpvVjZwaDAraWhOVnMzbkk3OEZVcFhmVDdWd0hhZDF4SXJ0QzA0VHBPODFjSjZpbi91TTRBVDI4PTwvSW52ZXJzZVE+PEQ+bEtQSEFmR2prRlJSSHRHUW13VU9pVlRDelV3NXlDY2pzQUpuMnZZRlpKRTRxaUtIUzVnQ0VodWFuMWZUUjZ3Y2d2cGROaTJuWlF2YWttMTZWeXc1cHZmUUpiN1psT2lvNzdLZS9QYmM1SnMyM0piaFVLejk5TnRYWVVFaDJFdVIvMCtPc0VMQ0hsd29oOUFDMS9VdTVnRFIwNTRqcmVwWGpGU2hVcVNyOWdRaG5sejhreHlhdUpaK3hKQWorSWVnd3lzRkp0ZWVLUldJZjdDZ3ZESFZrMk8rcjBJcTZldTBWTFMxbHladlNNOWJyWDlxOVRYUzMvODJFQ2M2UW9PbUk5NEFTOXhmcGZWeGIyZjJEQ0dSN0dZL1M2WWJTRGpJMXpMQWxQRzZiWVJRTFlsR1FVd3NvdHkrYzFZK1ZCc1R3VEk4WTFneTVOemdES2x6S3I3ZXhRPT08L0Q+PC9SU0FLZXlWYWx1ZT4="; - private const string ValidSigningPublicKey = - "PFJTQUtleVZhbHVlPjxNb2R1bHVzPm9VQ002dEdieFc5eGZqWm1mbmQzTThLQkFzd0ZmbHVLY2IwV1RNMXMzeVh2c3dZci9MbkFVVGVhNjF4QWRma3BSbVVuT3VrODRwZUN5OEVkMzdZcXVHL05WMldsT0dRckxMRkh3UklIMGdmU0IzMjFpdlVzN2xqbDc5S25TRmpEU0ZqcUJNTEJTSS93ZlhobCtYTGZrTjczaGJmeTNSRzVTUDU4Vm5UUEQveWFRczlmNVdVVHhCSFBKNWx4Ump3cVpTemJjZE05cHNtcVFHWGcveUVCejFsMlJQaCtTK0R1aEw5TU1iRWdTb0lXanFKaWEzUFllRDF5WEt3RjlPdjlaa3V4L21ZZjRkRW9pWUZXV05jS3ZSSGFTVFBjTFd0NnZpUXZsekREd09FUG9HdlI4SkNreUJ2a1J0Q1VBeVAyMEpkYzFGK0xqdEp3dkIyNTRBTjU1UT09PC9Nb2R1bHVzPjxFeHBvbmVudD5BUUFCPC9FeHBvbmVudD48L1JTQUtleVZhbHVlPg=="; - - private readonly SigningCredentials validSigningCredentials; - private readonly List validVerificationKeys; + private const string ValidVersion = "v1"; + private const string ValidPublicPurpose = "public"; + private const string ValidPublicPayload = "eyJzdWIiOiIxMjMiLCJleHAiOiIyMDIwLTA1LTA3VDE3OjExOjM4LjM3MTE3MTJaIn1HginqDCa4m01vI75OaWrFyAYCA1k9_sx36XVDEcHosOkk6coBDwDfoOaSFA_wE3nkfyuy3fTr7g6BpdzPbIb5qhI4Wpdy_zhhyEz7kC8ZSaDNN0tnBT0sL1c6hSuWKGh3tT6qPmjUmJwIv2ZjosozSmRF7bhWKJDsvTzQN6EFBddcvQpPQok9Ekdgzd70_Yxjl9YlUizF7WOiDm-R6m3xy_Mk8IRGQwiArYGmJRmR82W97ajqdBUJD8kbaFQglDxwEMcX-T4AqXCttjhdQi-JcXX34SDTyxE-8m02X8eNrKg64L6ZAFDAzbaa2bz3EUo5ULW2XaG4DW2zZ4nFd9m2"; + private readonly string validToken = $"{ValidVersion}.{ValidPublicPurpose}.{ValidPublicPayload}"; + + private const string ValidSigningPrivateKey = + "PFJTQUtleVZhbHVlPjxNb2R1bHVzPm9VQ002dEdieFc5eGZqWm1mbmQzTThLQkFzd0ZmbHVLY2IwV1RNMXMzeVh2c3dZci9MbkFVVGVhNjF4QWRma3BSbVVuT3VrODRwZUN5OEVkMzdZcXVHL05WMldsT0dRckxMRkh3UklIMGdmU0IzMjFpdlVzN2xqbDc5S25TRmpEU0ZqcUJNTEJTSS93ZlhobCtYTGZrTjczaGJmeTNSRzVTUDU4Vm5UUEQveWFRczlmNVdVVHhCSFBKNWx4Ump3cVpTemJjZE05cHNtcVFHWGcveUVCejFsMlJQaCtTK0R1aEw5TU1iRWdTb0lXanFKaWEzUFllRDF5WEt3RjlPdjlaa3V4L21ZZjRkRW9pWUZXV05jS3ZSSGFTVFBjTFd0NnZpUXZsekREd09FUG9HdlI4SkNreUJ2a1J0Q1VBeVAyMEpkYzFGK0xqdEp3dkIyNTRBTjU1UT09PC9Nb2R1bHVzPjxFeHBvbmVudD5BUUFCPC9FeHBvbmVudD48UD54Mlp3Y1N1Ni9KeTB5UFRqcVJXTlA1OVhtMk5hNDFEajdQeThsNHBXbWZKWkExTWxBMzRMNUpLVEpJMHZEWStyMTBhN1JRcVJSeHpseEVnQStvMHQwbW5uckRZbGJYbDB4OGlON04vb2w2OHZ0NEJtWFZCWGdxYURpNUJoaWtvLzVzd3EvOVhBd1ZKYm1zVFBCSjVGdi9DQWxUSytNbStacUt6MzlHVTYvMHM9PC9QPjxRPnp3WU80MVdGRUFyTS80R3hDOW1HZEhJeEFKR2RBaWoyZWJ0NjMrQk9QRUxpelZySjdudVR0ZGtxZnlhRHR5eXpGTWxVRHV2VVpIMU1YNnlsUldsQWQrSlhZcUFRZVlML2poYXZYL0llL2NsRE5YRnlRbTNoeitrOFRZZDV2KzFId0RjVTNWVUJlQnVTa3d3bmgrSXBHMU5HeWk4b0RJZldYZThIUXRsdVBZOD08L1E+PERQPllPbGN4T1FvSVJaWWwwTE9VeU55WHZXbXNwTDdYWGUzRHp0V3ZhQXlydWVtYzRNNWZoVUkycktTYVRWbEpRWXEwcHBCOGpCTW8yOWNES1dpTkNQaG5WNXpock5hUlhhK1Ywc1dENFpUbVVVL3Y4UGIvSVpMd2VnRUR4VEJFMkU2NVlWZGNMSUcyTzZhTHdKd1N5SlJiQlFMcW5mYkVOQkVza0krMEwxU2l6az08L0RQPjxEUT5XRTJyT0FpWVV6bG9LMnYwU3F1a0VETk05NE1reDNFVmdPTVpERGt1NWNGWjRHSGpWQmZkNzJrTUdXUWlOcFdZWlR0aTRXSnlHOUxlS3NrSFRjNFJNNUdWMkhtUnpXSzFBclJtWmJSdXg2MTdQMlorYUJ0YWdFWnA5Ri9lN0tDWFJFTzZZSllMcEdHT2FhNTdoaGhQbEZvM0RiS0RrS1M0S1NUMW9ld0FlNzA9PC9EUT48SW52ZXJzZVE+SVpvNFVZRzVOVWt6TlA3LzVXTzhkVW9BUFJJU2htVithNTh5dWZWRjZtMUl3NTRJVDNTVWhXOVh5dFdMa0ZsWmJZakhxMmZlaUhDOUw0OGFUTG1SMmlIejVKUjJKN2pENnJGbis1SFNZR2l0OGpZbnBvNGpvVjZwaDAraWhOVnMzbkk3OEZVcFhmVDdWd0hhZDF4SXJ0QzA0VHBPODFjSjZpbi91TTRBVDI4PTwvSW52ZXJzZVE+PEQ+bEtQSEFmR2prRlJSSHRHUW13VU9pVlRDelV3NXlDY2pzQUpuMnZZRlpKRTRxaUtIUzVnQ0VodWFuMWZUUjZ3Y2d2cGROaTJuWlF2YWttMTZWeXc1cHZmUUpiN1psT2lvNzdLZS9QYmM1SnMyM0piaFVLejk5TnRYWVVFaDJFdVIvMCtPc0VMQ0hsd29oOUFDMS9VdTVnRFIwNTRqcmVwWGpGU2hVcVNyOWdRaG5sejhreHlhdUpaK3hKQWorSWVnd3lzRkp0ZWVLUldJZjdDZ3ZESFZrMk8rcjBJcTZldTBWTFMxbHladlNNOWJyWDlxOVRYUzMvODJFQ2M2UW9PbUk5NEFTOXhmcGZWeGIyZjJEQ0dSN0dZL1M2WWJTRGpJMXpMQWxQRzZiWVJRTFlsR1FVd3NvdHkrYzFZK1ZCc1R3VEk4WTFneTVOemdES2x6S3I3ZXhRPT08L0Q+PC9SU0FLZXlWYWx1ZT4="; + private const string ValidSigningPublicKey = + "PFJTQUtleVZhbHVlPjxNb2R1bHVzPm9VQ002dEdieFc5eGZqWm1mbmQzTThLQkFzd0ZmbHVLY2IwV1RNMXMzeVh2c3dZci9MbkFVVGVhNjF4QWRma3BSbVVuT3VrODRwZUN5OEVkMzdZcXVHL05WMldsT0dRckxMRkh3UklIMGdmU0IzMjFpdlVzN2xqbDc5S25TRmpEU0ZqcUJNTEJTSS93ZlhobCtYTGZrTjczaGJmeTNSRzVTUDU4Vm5UUEQveWFRczlmNVdVVHhCSFBKNWx4Ump3cVpTemJjZE05cHNtcVFHWGcveUVCejFsMlJQaCtTK0R1aEw5TU1iRWdTb0lXanFKaWEzUFllRDF5WEt3RjlPdjlaa3V4L21ZZjRkRW9pWUZXV05jS3ZSSGFTVFBjTFd0NnZpUXZsekREd09FUG9HdlI4SkNreUJ2a1J0Q1VBeVAyMEpkYzFGK0xqdEp3dkIyNTRBTjU1UT09PC9Nb2R1bHVzPjxFeHBvbmVudD5BUUFCPC9FeHBvbmVudD48L1JTQUtleVZhbHVlPg=="; + + private readonly SigningCredentials validSigningCredentials; + private readonly List validVerificationKeys; - private readonly PasetoVersion1 sut = new PasetoVersion1(); + private readonly PasetoVersion1 sut = new PasetoVersion1(); - public PasetoVersion1Tests() - { - var privateKey = RSA.Create(); - privateKey.FromXmlString(Encoding.UTF8.GetString(Convert.FromBase64String(ValidSigningPrivateKey))); + public PasetoVersion1Tests() + { + var privateKey = RSA.Create(); + privateKey.FromXmlString(Encoding.UTF8.GetString(Convert.FromBase64String(ValidSigningPrivateKey))); - var publicKey = RSA.Create(); - publicKey.FromXmlString(Encoding.UTF8.GetString(Convert.FromBase64String(ValidSigningPublicKey))); + var publicKey = RSA.Create(); + publicKey.FromXmlString(Encoding.UTF8.GetString(Convert.FromBase64String(ValidSigningPublicKey))); - validSigningCredentials = new SigningCredentials(new RsaSecurityKey(privateKey), SecurityAlgorithms.RsaSsaPssSha384); - validVerificationKeys = new List {new RsaSecurityKey(publicKey)}; - } + validSigningCredentials = new SigningCredentials(new RsaSecurityKey(privateKey), SecurityAlgorithms.RsaSsaPssSha384); + validVerificationKeys = new List {new RsaSecurityKey(publicKey)}; + } - [Fact] - public void Sign_WhenPayloadIsNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.Sign(null, null, validSigningCredentials)); + [Fact] + public void Sign_WhenPayloadIsNull_ExpectArgumentNullException() + => Assert.Throws(() => sut.Sign(null, null, validSigningCredentials)); - [Fact] - public void Sign_WhenSigningCredentialsAreNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.Sign("test", null, null)); + [Fact] + public void Sign_WhenSigningCredentialsAreNull_ExpectArgumentNullException() + => Assert.Throws(() => sut.Sign("test", null, null)); - [Fact] - public void Sign_WhenSigningCredentialsDoNotContainRsaSecurityKey_ExpectSecurityTokenInvalidSigningKeyException() - { - var signingCredentials = new SigningCredentials(new ECDsaSecurityKey(ECDsa.Create()), SecurityAlgorithms.EcdsaSha256); + [Fact] + public void Sign_WhenSigningCredentialsDoNotContainRsaSecurityKey_ExpectSecurityTokenInvalidSigningKeyException() + { + var signingCredentials = new SigningCredentials(new ECDsaSecurityKey(ECDsa.Create()), SecurityAlgorithms.EcdsaSha256); - Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); - } + Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); + } - [Fact] - public void Sign_WhenSigningCredentialsNotConfiguredForPs384_ExpectSecurityTokenInvalidSigningKeyException() - { - var signingCredentials = new SigningCredentials(validSigningCredentials.Key, SecurityAlgorithms.RsaSha384); + [Fact] + public void Sign_WhenSigningCredentialsNotConfiguredForPs384_ExpectSecurityTokenInvalidSigningKeyException() + { + var signingCredentials = new SigningCredentials(validSigningCredentials.Key, SecurityAlgorithms.RsaSha384); - Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); - } + Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); + } - [Fact] - public void Sign_WhenSigningCredentialsDoNotContainPrivateKey_ExpectSecurityTokenInvalidSigningKeyException() - { - var signingCredentials = new SigningCredentials(validVerificationKeys.First(), SecurityAlgorithms.RsaSsaPssSha384); + [Fact] + public void Sign_WhenSigningCredentialsDoNotContainPrivateKey_ExpectSecurityTokenInvalidSigningKeyException() + { + var signingCredentials = new SigningCredentials(validVerificationKeys.First(), SecurityAlgorithms.RsaSsaPssSha384); - Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); - } + Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); + } - [Fact] - public void Sign_WhenTokenGenerated_ExpectThreeParts() - { - var token = sut.Sign("payload", null, validSigningCredentials); + [Fact] + public void Sign_WhenTokenGenerated_ExpectThreeParts() + { + var token = sut.Sign("payload", null, validSigningCredentials); - token.Split('.').Length.Should().Be(3); - } + token.Split('.').Length.Should().Be(3); + } - [Fact] - public void Sign_WhenTokenGeneratedWithFooter_ExpectFourParts() - { - var token = sut.Sign("payload", "footer", validSigningCredentials); + [Fact] + public void Sign_WhenTokenGeneratedWithFooter_ExpectFourParts() + { + var token = sut.Sign("payload", "footer", validSigningCredentials); - token.Split('.').Length.Should().Be(4); - } + token.Split('.').Length.Should().Be(4); + } - [Fact] - public void Verify_WhenTokenIsNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.Verify(null, validVerificationKeys)); + [Fact] + public void Verify_WhenTokenIsNull_ExpectArgumentNullException() + => Assert.Throws(() => sut.Verify(null, validVerificationKeys)); - [Fact] - public void Verify_WhenSecurityKeysAreNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.Verify(new PasetoToken(validToken), null)); + [Fact] + public void Verify_WhenSecurityKeysAreNull_ExpectArgumentNullException() + => Assert.Throws(() => sut.Verify(new PasetoToken(validToken), null)); - [Fact] - public void Verify_WhenSecurityKeysAreEmpty_ExpectArgumentNullException() - => Assert.Throws(() => sut.Verify(new PasetoToken(validToken), new List())); + [Fact] + public void Verify_WhenSecurityKeysAreEmpty_ExpectArgumentNullException() + => Assert.Throws(() => sut.Verify(new PasetoToken(validToken), new List())); - [Fact] - public void Verify_WhenNoRsaSecurityKeysPresent_ExpectSecurityTokenInvalidSigningKeyException() - { - var keys = new List {new ECDsaSecurityKey(ECDsa.Create())}; + [Fact] + public void Verify_WhenNoRsaSecurityKeysPresent_ExpectSecurityTokenInvalidSigningKeyException() + { + var keys = new List {new ECDsaSecurityKey(ECDsa.Create())}; - Assert.Throws(() => sut.Verify(new PasetoToken(validToken), keys)); - } + Assert.Throws(() => sut.Verify(new PasetoToken(validToken), keys)); + } - [Fact] - public void Verify_WhenIncorrectVersion_ExpectArgumentException() - { - var token = new PasetoToken($"v42.{ValidPublicPurpose}.{ValidPublicPayload}"); + [Fact] + public void Verify_WhenIncorrectVersion_ExpectArgumentException() + { + var token = new PasetoToken($"v42.{ValidPublicPurpose}.{ValidPublicPayload}"); - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } + Assert.Throws(() => sut.Verify(token, validVerificationKeys)); + } - [Fact] - public void Verify_WhenIncorrectPurpose_ExpectArgumentException() - { - var token = new PasetoToken($"{ValidVersion}.local.{ValidPublicPayload}"); - - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenPayloadDoesNotContainEnoughBytes_ExpectSecurityTokenInvalidSignatureException() - { - var payloadBytes = new byte[32]; - new Random().NextBytes(payloadBytes); + [Fact] + public void Verify_WhenIncorrectPurpose_ExpectArgumentException() + { + var token = new PasetoToken($"{ValidVersion}.local.{ValidPublicPayload}"); + + Assert.Throws(() => sut.Verify(token, validVerificationKeys)); + } + + [Fact] + public void Verify_WhenPayloadDoesNotContainEnoughBytes_ExpectSecurityTokenInvalidSignatureException() + { + var payloadBytes = new byte[32]; + new Random().NextBytes(payloadBytes); - var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payloadBytes)}"); + var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payloadBytes)}"); - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenPayloadDoesNotContainJson_ExpectSecurityTokenException() - { - var payloadValue = "test"; - var payloadValueBytes = Encoding.UTF8.GetBytes(payloadValue); + Assert.Throws(() => sut.Verify(token, validVerificationKeys)); + } + + [Fact] + public void Verify_WhenPayloadDoesNotContainJson_ExpectSecurityTokenException() + { + var payloadValue = "test"; + var payloadValueBytes = Encoding.UTF8.GetBytes(payloadValue); - var signature = new byte[256]; - new Random().NextBytes(signature); + var signature = new byte[256]; + new Random().NextBytes(signature); - var payload = new byte[payloadValueBytes.Length + signature.Length]; - Buffer.BlockCopy(payloadValueBytes, 0, payload, 0, payloadValueBytes.Length); - Buffer.BlockCopy(signature, 0, payload, payloadValueBytes.Length, signature.Length); + var payload = new byte[payloadValueBytes.Length + signature.Length]; + Buffer.BlockCopy(payloadValueBytes, 0, payload, 0, payloadValueBytes.Length); + Buffer.BlockCopy(signature, 0, payload, payloadValueBytes.Length, signature.Length); - var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payload)}"); + var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payload)}"); - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } + Assert.Throws(() => sut.Verify(token, validVerificationKeys)); + } - [Fact] - public void Verify_WhenSignatureInvalid_ExpectSecurityTokenInvalidSignatureException() - { - var payloadValue = "{ \"test\": \"test\" }"; - var payloadValueBytes = Encoding.UTF8.GetBytes(payloadValue); + [Fact] + public void Verify_WhenSignatureInvalid_ExpectSecurityTokenInvalidSignatureException() + { + var payloadValue = "{ \"test\": \"test\" }"; + var payloadValueBytes = Encoding.UTF8.GetBytes(payloadValue); - var signature = new byte[256]; - new Random().NextBytes(signature); - - var payload = new byte[payloadValueBytes.Length + signature.Length]; - Buffer.BlockCopy(payloadValueBytes, 0, payload, 0, payloadValueBytes.Length); - Buffer.BlockCopy(signature, 0, payload, payloadValueBytes.Length, signature.Length); - - var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payload)}"); - - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenSignatureIsValid_ExpectCorrectSecurityToken() - { - var token = new PasetoToken(validToken); - - var securityToken = sut.Verify(token, validVerificationKeys); - - securityToken.Should().NotBeNull(); - securityToken.RawToken.Should().Be(token.RawToken); - } - - [Fact] - public void SignAndVerify_WhenKeysAreValid_ExpectCorrectClaimsFromPayload() - { - const string expectedClaimType = "test"; - const string expectedClaimValue = "test_val"; - var expectedPayload = $"{{ \"{expectedClaimType}\": \"{expectedClaimValue}\" }}"; - - var token = sut.Sign(expectedPayload, null, validSigningCredentials); - var parsedToken = sut.Verify(new PasetoToken(token), validVerificationKeys); - - parsedToken.Claims.Should().Contain(x => x.Type == expectedClaimType && x.Value == expectedClaimValue); - parsedToken.RawToken.Should().Be(token); - } - - [Fact] - public void SignAndVerify_WhenKeysAreValidAndWithFooter_ExpectCorrectClaimsFromPayloadAndCorrectFooter() - { - const string expectedClaimType = "test"; - const string expectedClaimValue = "test_val"; - const string expectedFooter = "{\"kid\": \"123\"}"; - var expectedPayload = $"{{ \"{expectedClaimType}\": \"{expectedClaimValue}\" }}"; - - var token = sut.Sign(expectedPayload, expectedFooter, validSigningCredentials); - var parsedToken = sut.Verify(new PasetoToken(token), validVerificationKeys); - - parsedToken.Claims.Should().Contain(x => x.Type == expectedClaimType && x.Value == expectedClaimValue); - parsedToken.RawToken.Should().Be(token); - parsedToken.Footer.Should().Be(expectedFooter); - } + var signature = new byte[256]; + new Random().NextBytes(signature); + + var payload = new byte[payloadValueBytes.Length + signature.Length]; + Buffer.BlockCopy(payloadValueBytes, 0, payload, 0, payloadValueBytes.Length); + Buffer.BlockCopy(signature, 0, payload, payloadValueBytes.Length, signature.Length); + + var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payload)}"); + + Assert.Throws(() => sut.Verify(token, validVerificationKeys)); + } + + [Fact] + public void Verify_WhenSignatureIsValid_ExpectCorrectSecurityToken() + { + var token = new PasetoToken(validToken); + + var securityToken = sut.Verify(token, validVerificationKeys); + + securityToken.Should().NotBeNull(); + securityToken.RawToken.Should().Be(token.RawToken); + } + + [Fact] + public void SignAndVerify_WhenKeysAreValid_ExpectCorrectClaimsFromPayload() + { + const string expectedClaimType = "test"; + const string expectedClaimValue = "test_val"; + var expectedPayload = $"{{ \"{expectedClaimType}\": \"{expectedClaimValue}\" }}"; + + var token = sut.Sign(expectedPayload, null, validSigningCredentials); + var parsedToken = sut.Verify(new PasetoToken(token), validVerificationKeys); + + parsedToken.Claims.Should().Contain(x => x.Type == expectedClaimType && x.Value == expectedClaimValue); + parsedToken.RawToken.Should().Be(token); + } + + [Fact] + public void SignAndVerify_WhenKeysAreValidAndWithFooter_ExpectCorrectClaimsFromPayloadAndCorrectFooter() + { + const string expectedClaimType = "test"; + const string expectedClaimValue = "test_val"; + const string expectedFooter = "{\"kid\": \"123\"}"; + var expectedPayload = $"{{ \"{expectedClaimType}\": \"{expectedClaimValue}\" }}"; + + var token = sut.Sign(expectedPayload, expectedFooter, validSigningCredentials); + var parsedToken = sut.Verify(new PasetoToken(token), validVerificationKeys); + + parsedToken.Claims.Should().Contain(x => x.Type == expectedClaimType && x.Value == expectedClaimValue); + parsedToken.RawToken.Should().Be(token); + parsedToken.Footer.Should().Be(expectedFooter); } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersion2Tests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersion2Tests.cs index 46ee685..7a71944 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersion2Tests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersion2Tests.cs @@ -10,225 +10,224 @@ using ScottBrady.IdentityModel.Tokens.Paseto; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto +namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto; + +public class PasetoVersion2Tests { - public class PasetoVersion2Tests - { - private const string ValidVersion = "v2"; - private const string ValidPublicPurpose = "public"; - private const string ValidPublicPayload = "eyJzdWIiOiIxMjMiLCJleHAiOiIyMDIwLTA1LTAyVDE2OjIzOjQwLjI1Njg1MTVaIn08nP0mX2YJvYOcMLBpiFbFs1C2gyNAJg_kpuniow671AfrEZWRDZWmLAQbuKRQNiJ2gIrXVeC-tO20zrVQ58wK"; - private readonly string validToken = $"{ValidVersion}.{ValidPublicPurpose}.{ValidPublicPayload}"; + private const string ValidVersion = "v2"; + private const string ValidPublicPurpose = "public"; + private const string ValidPublicPayload = "eyJzdWIiOiIxMjMiLCJleHAiOiIyMDIwLTA1LTAyVDE2OjIzOjQwLjI1Njg1MTVaIn08nP0mX2YJvYOcMLBpiFbFs1C2gyNAJg_kpuniow671AfrEZWRDZWmLAQbuKRQNiJ2gIrXVeC-tO20zrVQ58wK"; + private readonly string validToken = $"{ValidVersion}.{ValidPublicPurpose}.{ValidPublicPayload}"; - private const string ValidSigningPrivateKey = "TYXei5+8Qd2ZqKIlEuJJ3S50WYuocFTrqK+3/gHVH9B2hpLtAgscF2c9QuWCzV9fQxal3XBqTXivXJPpp79vgw=="; - private const string ValidSigningPublicKey = "doaS7QILHBdnPULlgs1fX0MWpd1wak14r1yT6ae/b4M="; - - private readonly SigningCredentials validSigningCredentials = new SigningCredentials( - new EdDsaSecurityKey(EdDsa.Create( - new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {D = Convert.FromBase64String(ValidSigningPrivateKey)})), - ExtendedSecurityAlgorithms.EdDsa); - - private readonly List validVerificationKeys = new List - { - new EdDsaSecurityKey(EdDsa.Create( - new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {X = Convert.FromBase64String(ValidSigningPublicKey)})) - }; + private const string ValidSigningPrivateKey = "TYXei5+8Qd2ZqKIlEuJJ3S50WYuocFTrqK+3/gHVH9B2hpLtAgscF2c9QuWCzV9fQxal3XBqTXivXJPpp79vgw=="; + private const string ValidSigningPublicKey = "doaS7QILHBdnPULlgs1fX0MWpd1wak14r1yT6ae/b4M="; + + private readonly SigningCredentials validSigningCredentials = new SigningCredentials( + new EdDsaSecurityKey(EdDsa.Create( + new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {D = Convert.FromBase64String(ValidSigningPrivateKey)})), + ExtendedSecurityAlgorithms.EdDsa); + + private readonly List validVerificationKeys = new List + { + new EdDsaSecurityKey(EdDsa.Create( + new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {X = Convert.FromBase64String(ValidSigningPublicKey)})) + }; - private readonly PasetoVersion2 sut = new PasetoVersion2(); + private readonly PasetoVersion2 sut = new PasetoVersion2(); - [Fact] - public void Sign_WhenPayloadIsNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.Sign(null, null, validSigningCredentials)); + [Fact] + public void Sign_WhenPayloadIsNull_ExpectArgumentNullException() + => Assert.Throws(() => sut.Sign(null, null, validSigningCredentials)); - [Fact] - public void Sign_WhenSigningCredentialsAreNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.Sign("test", null, null)); + [Fact] + public void Sign_WhenSigningCredentialsAreNull_ExpectArgumentNullException() + => Assert.Throws(() => sut.Sign("test", null, null)); - [Fact] - public void Sign_WhenSigningCredentialsDoNotContainEdDsaSecurityKey_ExpectSecurityTokenInvalidSigningKeyException() - { - var signingCredentials = new SigningCredentials(new RsaSecurityKey(RSA.Create()), ExtendedSecurityAlgorithms.EdDsa); + [Fact] + public void Sign_WhenSigningCredentialsDoNotContainEdDsaSecurityKey_ExpectSecurityTokenInvalidSigningKeyException() + { + var signingCredentials = new SigningCredentials(new RsaSecurityKey(RSA.Create()), ExtendedSecurityAlgorithms.EdDsa); - Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); - } + Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); + } - [Fact] - public void Sign_WhenSigningCredentialsNotConfigureForEdDSA_ExpectSecurityTokenInvalidSigningKeyException() - { - var signingCredentials = new SigningCredentials(validSigningCredentials.Key, ExtendedSecurityAlgorithms.XChaCha20Poly1305); + [Fact] + public void Sign_WhenSigningCredentialsNotConfigureForEdDSA_ExpectSecurityTokenInvalidSigningKeyException() + { + var signingCredentials = new SigningCredentials(validSigningCredentials.Key, ExtendedSecurityAlgorithms.XChaCha20Poly1305); - Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); - } + Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); + } - [Fact] - public void Sign_WhenSigningCredentialsDoNotContainPrivateKey_ExpectSecurityTokenInvalidSigningKeyException() - { - var signingCredentials = new SigningCredentials(validVerificationKeys.First(), ExtendedSecurityAlgorithms.EdDsa); + [Fact] + public void Sign_WhenSigningCredentialsDoNotContainPrivateKey_ExpectSecurityTokenInvalidSigningKeyException() + { + var signingCredentials = new SigningCredentials(validVerificationKeys.First(), ExtendedSecurityAlgorithms.EdDsa); - Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); - } + Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); + } - [Fact] - public void Sign_WhenTokenGenerated_ExpectThreeParts() - { - var token = sut.Sign("payload", null, validSigningCredentials); + [Fact] + public void Sign_WhenTokenGenerated_ExpectThreeParts() + { + var token = sut.Sign("payload", null, validSigningCredentials); - token.Split('.').Length.Should().Be(3); - } + token.Split('.').Length.Should().Be(3); + } - [Fact] - public void Sign_WhenTokenGeneratedWithFooter_ExpectFourParts() - { - var token = sut.Sign("payload", "footer", validSigningCredentials); + [Fact] + public void Sign_WhenTokenGeneratedWithFooter_ExpectFourParts() + { + var token = sut.Sign("payload", "footer", validSigningCredentials); - token.Split('.').Length.Should().Be(4); - } + token.Split('.').Length.Should().Be(4); + } - [Fact] - public void Verify_WhenTokenIsNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.Verify(null, validVerificationKeys)); + [Fact] + public void Verify_WhenTokenIsNull_ExpectArgumentNullException() + => Assert.Throws(() => sut.Verify(null, validVerificationKeys)); - [Fact] - public void Verify_WhenSecurityKeysAreNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.Verify(new PasetoToken(validToken), null)); + [Fact] + public void Verify_WhenSecurityKeysAreNull_ExpectArgumentNullException() + => Assert.Throws(() => sut.Verify(new PasetoToken(validToken), null)); - [Fact] - public void Verify_WhenSecurityKeysAreEmpty_ExpectArgumentNullException() - => Assert.Throws(() => sut.Verify(new PasetoToken(validToken), new List())); + [Fact] + public void Verify_WhenSecurityKeysAreEmpty_ExpectArgumentNullException() + => Assert.Throws(() => sut.Verify(new PasetoToken(validToken), new List())); - [Fact] - public void Verify_WhenNoEdDsaSecurityKeysPresent_ExpectSecurityTokenInvalidSigningKeyException() - { - var keys = new List {new RsaSecurityKey(RSA.Create())}; + [Fact] + public void Verify_WhenNoEdDsaSecurityKeysPresent_ExpectSecurityTokenInvalidSigningKeyException() + { + var keys = new List {new RsaSecurityKey(RSA.Create())}; - Assert.Throws(() => sut.Verify(new PasetoToken(validToken), keys)); - } + Assert.Throws(() => sut.Verify(new PasetoToken(validToken), keys)); + } - [Fact] - public void Verify_WhenIncorrectVersion_ExpectArgumentException() - { - var token = new PasetoToken($"v42.{ValidPublicPurpose}.{ValidPublicPayload}"); + [Fact] + public void Verify_WhenIncorrectVersion_ExpectArgumentException() + { + var token = new PasetoToken($"v42.{ValidPublicPurpose}.{ValidPublicPayload}"); - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } + Assert.Throws(() => sut.Verify(token, validVerificationKeys)); + } - [Fact] - public void Verify_WhenIncorrectPurpose_ExpectArgumentException() - { - var token = new PasetoToken($"{ValidVersion}.local.{ValidPublicPayload}"); - - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenPayloadIsNotBase64UrlEncodedValue_ExpectFormatException() - { - var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.ey!!"); - - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenPayloadDoesNotContainEnoughBytes_ExpectSecurityTokenInvalidSignatureException() - { - var payloadBytes = new byte[32]; - new Random().NextBytes(payloadBytes); + [Fact] + public void Verify_WhenIncorrectPurpose_ExpectArgumentException() + { + var token = new PasetoToken($"{ValidVersion}.local.{ValidPublicPayload}"); + + Assert.Throws(() => sut.Verify(token, validVerificationKeys)); + } + + [Fact] + public void Verify_WhenPayloadIsNotBase64UrlEncodedValue_ExpectFormatException() + { + var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.ey!!"); + + Assert.Throws(() => sut.Verify(token, validVerificationKeys)); + } + + [Fact] + public void Verify_WhenPayloadDoesNotContainEnoughBytes_ExpectSecurityTokenInvalidSignatureException() + { + var payloadBytes = new byte[32]; + new Random().NextBytes(payloadBytes); - var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payloadBytes)}"); + var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payloadBytes)}"); - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenPayloadDoesNotContainJson_ExpectSecurityTokenException() - { - var payloadValue = "test"; - var payloadValueBytes = Encoding.UTF8.GetBytes(payloadValue); + Assert.Throws(() => sut.Verify(token, validVerificationKeys)); + } + + [Fact] + public void Verify_WhenPayloadDoesNotContainJson_ExpectSecurityTokenException() + { + var payloadValue = "test"; + var payloadValueBytes = Encoding.UTF8.GetBytes(payloadValue); - var signature = new byte[64]; - new Random().NextBytes(signature); + var signature = new byte[64]; + new Random().NextBytes(signature); - var payload = new byte[payloadValueBytes.Length + signature.Length]; - Buffer.BlockCopy(payloadValueBytes, 0, payload, 0, payloadValueBytes.Length); - Buffer.BlockCopy(signature, 0, payload, payloadValueBytes.Length, signature.Length); + var payload = new byte[payloadValueBytes.Length + signature.Length]; + Buffer.BlockCopy(payloadValueBytes, 0, payload, 0, payloadValueBytes.Length); + Buffer.BlockCopy(signature, 0, payload, payloadValueBytes.Length, signature.Length); - var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payload)}"); + var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payload)}"); - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } + Assert.Throws(() => sut.Verify(token, validVerificationKeys)); + } - [Fact] - public void Verify_WhenSignatureInvalid_ExpectSecurityTokenInvalidSignatureException() - { - var payloadValue = "{ \"test\": \"test\" }"; - var payloadValueBytes = Encoding.UTF8.GetBytes(payloadValue); + [Fact] + public void Verify_WhenSignatureInvalid_ExpectSecurityTokenInvalidSignatureException() + { + var payloadValue = "{ \"test\": \"test\" }"; + var payloadValueBytes = Encoding.UTF8.GetBytes(payloadValue); - var signature = new byte[64]; - new Random().NextBytes(signature); - - var payload = new byte[payloadValueBytes.Length + signature.Length]; - Buffer.BlockCopy(payloadValueBytes, 0, payload, 0, payloadValueBytes.Length); - Buffer.BlockCopy(signature, 0, payload, payloadValueBytes.Length, signature.Length); - - var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payload)}"); - - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenSignatureIsValid_ExpectCorrectSecurityToken() - { - // "wxFZtnkkIXbcNh4WTYbTS8WgEyWaYRhfT1603kN6SdQ=" - // "v2.public.eyJzdWIiOiIxMjMiLCJleHAiOiIyMDIwLTA1LTAzVDEzOjE0OjE0LjE5MDA1OFoiff5U7ni0Bd5yame3wT41v26UMyH56JA4Un077FPn_UkGpx78fVgbegW0FEMLw0J61ms0OJHarRzyRrX4dWn6LgA" - var token = new PasetoToken(validToken); - - var securityToken = sut.Verify(token, validVerificationKeys); - - securityToken.Should().NotBeNull(); - securityToken.RawToken.Should().Be(token.RawToken); - } - - [Fact] - public void Verify_WhenFooterPresentAndSignatureIsValid_ExpectCorrectSecurityToken() - { - var tokenWithFooter = new PasetoToken( - "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9flsZsx_gYCR0N_Ec2QxJFFpvQAs7h9HtKwbVK2n1MJ3Rz-hwe8KUqjnd8FAnIJZ601tp7lGkguU63oGbomhoBw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9"); - var publicKey = Convert.FromBase64String("Hrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI="); - - var securityToken = sut.Verify(tokenWithFooter, - new[] {new EdDsaSecurityKey(EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {X = publicKey}))}); - - securityToken.Should().NotBeNull(); - securityToken.RawToken.Should().Be(tokenWithFooter.RawToken); - } - - [Fact] - public void SignAndVerify_WhenKeysAreValid_ExpectCorrectClaimsFromPayload() - { - const string expectedClaimType = "test"; - const string expectedClaimValue = "test_val"; - var expectedPayload = $"{{ \"{expectedClaimType}\": \"{expectedClaimValue}\" }}"; - - var token = sut.Sign(expectedPayload, null, validSigningCredentials); - var parsedToken = sut.Verify(new PasetoToken(token), validVerificationKeys); - - parsedToken.Claims.Should().Contain(x => x.Type == expectedClaimType && x.Value == expectedClaimValue); - parsedToken.RawToken.Should().Be(token); - } - - [Fact] - public void SignAndVerify_WhenKeysAreValidAndWithFooter_ExpectCorrectClaimsFromPayloadAndCorrectFooter() - { - const string expectedClaimType = "test"; - const string expectedClaimValue = "test_val"; - const string expectedFooter = "{'kid': '123'}"; - var expectedPayload = $"{{ \"{expectedClaimType}\": \"{expectedClaimValue}\" }}"; - - var token = sut.Sign(expectedPayload, expectedFooter, validSigningCredentials); - var parsedToken = sut.Verify(new PasetoToken(token), validVerificationKeys); - - parsedToken.Claims.Should().Contain(x => x.Type == expectedClaimType && x.Value == expectedClaimValue); - parsedToken.RawToken.Should().Be(token); - parsedToken.Footer.Should().Be(expectedFooter); - } + var signature = new byte[64]; + new Random().NextBytes(signature); + + var payload = new byte[payloadValueBytes.Length + signature.Length]; + Buffer.BlockCopy(payloadValueBytes, 0, payload, 0, payloadValueBytes.Length); + Buffer.BlockCopy(signature, 0, payload, payloadValueBytes.Length, signature.Length); + + var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payload)}"); + + Assert.Throws(() => sut.Verify(token, validVerificationKeys)); + } + + [Fact] + public void Verify_WhenSignatureIsValid_ExpectCorrectSecurityToken() + { + // "wxFZtnkkIXbcNh4WTYbTS8WgEyWaYRhfT1603kN6SdQ=" + // "v2.public.eyJzdWIiOiIxMjMiLCJleHAiOiIyMDIwLTA1LTAzVDEzOjE0OjE0LjE5MDA1OFoiff5U7ni0Bd5yame3wT41v26UMyH56JA4Un077FPn_UkGpx78fVgbegW0FEMLw0J61ms0OJHarRzyRrX4dWn6LgA" + var token = new PasetoToken(validToken); + + var securityToken = sut.Verify(token, validVerificationKeys); + + securityToken.Should().NotBeNull(); + securityToken.RawToken.Should().Be(token.RawToken); + } + + [Fact] + public void Verify_WhenFooterPresentAndSignatureIsValid_ExpectCorrectSecurityToken() + { + var tokenWithFooter = new PasetoToken( + "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9flsZsx_gYCR0N_Ec2QxJFFpvQAs7h9HtKwbVK2n1MJ3Rz-hwe8KUqjnd8FAnIJZ601tp7lGkguU63oGbomhoBw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9"); + var publicKey = Convert.FromBase64String("Hrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI="); + + var securityToken = sut.Verify(tokenWithFooter, + new[] {new EdDsaSecurityKey(EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {X = publicKey}))}); + + securityToken.Should().NotBeNull(); + securityToken.RawToken.Should().Be(tokenWithFooter.RawToken); + } + + [Fact] + public void SignAndVerify_WhenKeysAreValid_ExpectCorrectClaimsFromPayload() + { + const string expectedClaimType = "test"; + const string expectedClaimValue = "test_val"; + var expectedPayload = $"{{ \"{expectedClaimType}\": \"{expectedClaimValue}\" }}"; + + var token = sut.Sign(expectedPayload, null, validSigningCredentials); + var parsedToken = sut.Verify(new PasetoToken(token), validVerificationKeys); + + parsedToken.Claims.Should().Contain(x => x.Type == expectedClaimType && x.Value == expectedClaimValue); + parsedToken.RawToken.Should().Be(token); + } + + [Fact] + public void SignAndVerify_WhenKeysAreValidAndWithFooter_ExpectCorrectClaimsFromPayloadAndCorrectFooter() + { + const string expectedClaimType = "test"; + const string expectedClaimValue = "test_val"; + const string expectedFooter = "{'kid': '123'}"; + var expectedPayload = $"{{ \"{expectedClaimType}\": \"{expectedClaimValue}\" }}"; + + var token = sut.Sign(expectedPayload, expectedFooter, validSigningCredentials); + var parsedToken = sut.Verify(new PasetoToken(token), validVerificationKeys); + + parsedToken.Claims.Should().Contain(x => x.Type == expectedClaimType && x.Value == expectedClaimValue); + parsedToken.RawToken.Should().Be(token); + parsedToken.Footer.Should().Be(expectedFooter); } } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersionStrategyTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersionStrategyTests.cs index fdbc200..fc6d9cf 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersionStrategyTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersionStrategyTests.cs @@ -7,48 +7,47 @@ using ScottBrady.IdentityModel.Tokens.Paseto; using Xunit; -namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto +namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto; + +/// +/// https://github.com/paragonie/paseto/blob/master/docs/01-Protocol-Versions/Common.md +/// +public class PasetoVersionStrategyTests { - /// - /// https://github.com/paragonie/paseto/blob/master/docs/01-Protocol-Versions/Common.md - /// - public class PasetoVersionStrategyTests - { - [Fact] - public void PreAuthEncode_WhenPiecesIsNull_ExpectArgumentNullException() - => Assert.Throws(() => TestPasetoVersionStrategy.PreAuthEncodeSpy(null)); + [Fact] + public void PreAuthEncode_WhenPiecesIsNull_ExpectArgumentNullException() + => Assert.Throws(() => TestPasetoVersionStrategy.PreAuthEncodeSpy(null)); - [Fact] - public void PreAuthEncodeSpy_WhenEmptyCollection_ExpectKnownResponse() - { + [Fact] + public void PreAuthEncodeSpy_WhenEmptyCollection_ExpectKnownResponse() + { var encodedValue = TestPasetoVersionStrategy.PreAuthEncodeSpy(new List()); encodedValue.Should().BeEquivalentTo(new byte[] {0, 0, 0, 0, 0, 0, 0, 0}); } - [Fact] - public void PreAuthEncodeSpy_WhenEmptyString_ExpectKnownResponse() - { + [Fact] + public void PreAuthEncodeSpy_WhenEmptyString_ExpectKnownResponse() + { var encodedValue = TestPasetoVersionStrategy.PreAuthEncodeSpy(new[] {Encoding.UTF8.GetBytes(string.Empty)}); encodedValue.Should().BeEquivalentTo(new byte[] {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); } - [Fact] - public void PreAuthEncodeSpy_WhenTestString_ExpectKnownResponse() - { + [Fact] + public void PreAuthEncodeSpy_WhenTestString_ExpectKnownResponse() + { var testBytes = Encoding.UTF8.GetBytes("test"); var encodedValue = TestPasetoVersionStrategy.PreAuthEncodeSpy(new[] {testBytes}); encodedValue.Should().BeEquivalentTo(new byte[] {1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0}.Concat(testBytes)); } - } +} - public class TestPasetoVersionStrategy : PasetoVersionStrategy - { - public override string Encrypt(string payload, string footer, EncryptingCredentials encryptingCredentials) => throw new NotImplementedException(); - public override string Sign(string payload, string footer, SigningCredentials signingCredentials) => throw new NotImplementedException(); - public override PasetoSecurityToken Decrypt(PasetoToken token, IEnumerable decryptionKeys) => throw new System.NotImplementedException(); - public override PasetoSecurityToken Verify(PasetoToken token, IEnumerable signingKeys) => throw new System.NotImplementedException(); +public class TestPasetoVersionStrategy : PasetoVersionStrategy +{ + public override string Encrypt(string payload, string footer, EncryptingCredentials encryptingCredentials) => throw new NotImplementedException(); + public override string Sign(string payload, string footer, SigningCredentials signingCredentials) => throw new NotImplementedException(); + public override PasetoSecurityToken Decrypt(PasetoToken token, IEnumerable decryptionKeys) => throw new System.NotImplementedException(); + public override PasetoSecurityToken Verify(PasetoToken token, IEnumerable signingKeys) => throw new System.NotImplementedException(); - public static byte[] PreAuthEncodeSpy(IReadOnlyList pieces) => PreAuthEncode(pieces); - } + public static byte[] PreAuthEncodeSpy(IReadOnlyList pieces) => PreAuthEncode(pieces); } \ No newline at end of file