From 2ef31740d62a415e7df59f22c450ae954ee97193 Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Thu, 1 Aug 2019 21:22:55 +0800 Subject: Expired token now has a unique code. --- Timeline.Tests/Helpers/TestUsers.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'Timeline.Tests/Helpers/TestUsers.cs') diff --git a/Timeline.Tests/Helpers/TestUsers.cs b/Timeline.Tests/Helpers/TestUsers.cs index dd00e38d..ceecc7c0 100644 --- a/Timeline.Tests/Helpers/TestUsers.cs +++ b/Timeline.Tests/Helpers/TestUsers.cs @@ -17,13 +17,15 @@ namespace Timeline.Tests.Helpers { Name = "user", EncryptedPassword = passwordService.HashPassword("user"), - RoleString = "user" + RoleString = "user", + Version = 0, }); mockUsers.Add(new User { Name = "admin", EncryptedPassword = passwordService.HashPassword("admin"), - RoleString = "user,admin" + RoleString = "user,admin", + Version = 0, }); MockUsers = mockUsers; -- cgit v1.2.3 From ee506e832e19e84cba2f9cf1c2b0172ca3e092b6 Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Thu, 1 Aug 2019 21:58:39 +0800 Subject: Password service use exception. --- Timeline.Tests/Helpers/TestUsers.cs | 2 +- Timeline/Services/PasswordService.cs | 83 ++++++++++++++++++++---------------- 2 files changed, 47 insertions(+), 38 deletions(-) (limited to 'Timeline.Tests/Helpers/TestUsers.cs') diff --git a/Timeline.Tests/Helpers/TestUsers.cs b/Timeline.Tests/Helpers/TestUsers.cs index ceecc7c0..60ea5e27 100644 --- a/Timeline.Tests/Helpers/TestUsers.cs +++ b/Timeline.Tests/Helpers/TestUsers.cs @@ -11,7 +11,7 @@ namespace Timeline.Tests.Helpers static TestMockUsers() { var mockUsers = new List(); - var passwordService = new PasswordService(null); + var passwordService = new PasswordService(); mockUsers.Add(new User { diff --git a/Timeline/Services/PasswordService.cs b/Timeline/Services/PasswordService.cs index 106080f1..d114bb26 100644 --- a/Timeline/Services/PasswordService.cs +++ b/Timeline/Services/PasswordService.cs @@ -1,37 +1,59 @@ using Microsoft.AspNetCore.Cryptography.KeyDerivation; -using Microsoft.Extensions.Logging; using System; using System.Runtime.CompilerServices; using System.Security.Cryptography; namespace Timeline.Services { + /// + /// Hashed password is of bad format. + /// + /// + [Serializable] + public class HashedPasswordBadFromatException : Exception + { + public HashedPasswordBadFromatException(string hashedPassword, string message) : base(message) { HashedPassword = hashedPassword; } + public HashedPasswordBadFromatException(string hashedPassword, string message, Exception inner) : base(message, inner) { HashedPassword = hashedPassword; } + protected HashedPasswordBadFromatException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + + public string HashedPassword { get; private set; } + } + public interface IPasswordService { /// - /// Returns a hashed representation of the supplied . + /// Hash a password. /// /// The password to hash. /// A hashed representation of the supplied . + /// Thrown when is null. string HashPassword(string password); /// - /// Returns a boolean indicating the result of a password hash comparison. + /// Verify whether the password fits into the hashed one. + /// + /// Usually you only need to check the returned bool value. + /// Catching usually is not necessary. + /// Because if your program logic is right and always call + /// and in pair, this exception will never be thrown. + /// A thrown one usually means the data you saved is corupted, which is a critical problem. /// - /// The hash value for a user's stored password. + /// The hashed password. /// The password supplied for comparison. - /// True indicating success. Otherwise false. + /// True indicating password is right. Otherwise false. + /// Thrown when or is null. + /// Thrown when the hashed password is of bad format. bool VerifyPassword(string hashedPassword, string providedPassword); } - //TODO! Use exceptions!!! - /// /// Copied from https://github.com/aspnet/AspNetCore/blob/master/src/Identity/Extensions.Core/src/PasswordHasher.cs /// Remove V2 format and unnecessary format version check. /// Remove configuration options. /// Remove user related parts. - /// Add log for wrong format. + /// Change the exceptions. /// public class PasswordService : IPasswordService { @@ -45,17 +67,12 @@ namespace Timeline.Services * (All UInt32s are stored big-endian.) */ - private static EventId BadFormatEventId { get; } = new EventId(4000, "BadFormatPassword"); - private readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create(); - private readonly ILogger _logger; - public PasswordService(ILogger logger) + public PasswordService() { - _logger = logger; } - // Compares two byte arrays for equality. The method is specifically written so that the loop is not optimized. [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] private static bool ByteArraysEqual(byte[] a, byte[] b) @@ -109,31 +126,27 @@ namespace Timeline.Services return outputBytes; } - private void LogBadFormatError(string hashedPassword, string message, Exception exception = null) - { - if (_logger == null) - return; - - if (exception != null) - _logger.LogError(BadFormatEventId, exception, $"{message} Hashed password is {hashedPassword} ."); - else - _logger.LogError(BadFormatEventId, $"{message} Hashed password is {hashedPassword} ."); - } - - public virtual bool VerifyPassword(string hashedPassword, string providedPassword) + public bool VerifyPassword(string hashedPassword, string providedPassword) { if (hashedPassword == null) throw new ArgumentNullException(nameof(hashedPassword)); if (providedPassword == null) throw new ArgumentNullException(nameof(providedPassword)); - byte[] decodedHashedPassword = Convert.FromBase64String(hashedPassword); + byte[] decodedHashedPassword; + try + { + decodedHashedPassword = Convert.FromBase64String(hashedPassword); + } + catch (FormatException e) + { + throw new HashedPasswordBadFromatException(hashedPassword, "Not of valid base64 format. See inner exception.", e); + } // read the format marker from the hashed password if (decodedHashedPassword.Length == 0) { - LogBadFormatError(hashedPassword, "Decoded hashed password is of length 0."); - return false; + throw new HashedPasswordBadFromatException(hashedPassword, "Decoded hashed password is of length 0."); } switch (decodedHashedPassword[0]) { @@ -141,8 +154,7 @@ namespace Timeline.Services return VerifyHashedPasswordV3(decodedHashedPassword, providedPassword, hashedPassword); default: - LogBadFormatError(hashedPassword, "Unknown format marker."); - return false; // unknown format marker + throw new HashedPasswordBadFromatException(hashedPassword, "Unknown format marker."); } } @@ -158,8 +170,7 @@ namespace Timeline.Services // Read the salt: must be >= 128 bits if (saltLength < 128 / 8) { - LogBadFormatError(hashedPasswordString, "Salt length < 128 bits."); - return false; + throw new HashedPasswordBadFromatException(hashedPasswordString, "Salt length < 128 bits."); } byte[] salt = new byte[saltLength]; Buffer.BlockCopy(hashedPassword, 13, salt, 0, salt.Length); @@ -168,8 +179,7 @@ namespace Timeline.Services int subkeyLength = hashedPassword.Length - 13 - salt.Length; if (subkeyLength < 128 / 8) { - LogBadFormatError(hashedPasswordString, "Subkey length < 128 bits."); - return false; + throw new HashedPasswordBadFromatException(hashedPasswordString, "Subkey length < 128 bits."); } byte[] expectedSubkey = new byte[subkeyLength]; Buffer.BlockCopy(hashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length); @@ -183,8 +193,7 @@ namespace Timeline.Services // This should never occur except in the case of a malformed payload, where // we might go off the end of the array. Regardless, a malformed payload // implies verification failed. - LogBadFormatError(hashedPasswordString, "See exception.", e); - return false; + throw new HashedPasswordBadFromatException(hashedPasswordString, "See inner exception.", e); } } -- cgit v1.2.3 From 13f0a95397751783ac0b4dcfa4d8f1b7112de04c Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Sun, 4 Aug 2019 14:47:17 +0800 Subject: Clean codes. --- Timeline.Tests/AuthorizationUnitTest.cs | 7 +++---- .../Helpers/Authentication/AuthenticationExtensions.cs | 12 ------------ Timeline.Tests/Helpers/TestUsers.cs | 4 ++-- Timeline.Tests/Helpers/UserInfoComparers.cs | 2 -- 4 files changed, 5 insertions(+), 20 deletions(-) (limited to 'Timeline.Tests/Helpers/TestUsers.cs') diff --git a/Timeline.Tests/AuthorizationUnitTest.cs b/Timeline.Tests/AuthorizationUnitTest.cs index ee3deac8..1f707f15 100644 --- a/Timeline.Tests/AuthorizationUnitTest.cs +++ b/Timeline.Tests/AuthorizationUnitTest.cs @@ -44,12 +44,11 @@ namespace Timeline.Tests [Fact] public async Task UserAuthorizationTest() { - using (var client = _factory.CreateDefaultClient()) + using (var client = await _factory.CreateClientWithUser("user", "user")) { - var token = (await client.CreateUserTokenAsync("user", "user")).Token; - var response1 = await client.SendWithAuthenticationAsync(token, UserUrl); + var response1 = await client.GetAsync(UserUrl); Assert.Equal(HttpStatusCode.OK, response1.StatusCode); - var response2 = await client.SendWithAuthenticationAsync(token, AdminUrl); + var response2 = await client.GetAsync(AdminUrl); Assert.Equal(HttpStatusCode.Forbidden, response2.StatusCode); } } diff --git a/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs b/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs index f4e2e45a..03fb9714 100644 --- a/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs +++ b/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc.Testing; using Newtonsoft.Json; -using System; using System.Net.Http; using System.Threading.Tasks; using Timeline.Entities.Http; @@ -25,16 +24,5 @@ namespace Timeline.Tests.Helpers.Authentication client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); return client; } - - public static async Task SendWithAuthenticationAsync(this HttpClient client, string token, string path, Action requestBuilder = null) - { - var request = new HttpRequestMessage - { - RequestUri = new Uri(client.BaseAddress, path), - }; - request.Headers.Add("Authorization", "Bearer " + token); - requestBuilder?.Invoke(request); - return await client.SendAsync(request); - } } } diff --git a/Timeline.Tests/Helpers/TestUsers.cs b/Timeline.Tests/Helpers/TestUsers.cs index 60ea5e27..41dd83a9 100644 --- a/Timeline.Tests/Helpers/TestUsers.cs +++ b/Timeline.Tests/Helpers/TestUsers.cs @@ -17,14 +17,14 @@ namespace Timeline.Tests.Helpers { Name = "user", EncryptedPassword = passwordService.HashPassword("user"), - RoleString = "user", + RoleString = UserUtility.IsAdminToRoleString(false), Version = 0, }); mockUsers.Add(new User { Name = "admin", EncryptedPassword = passwordService.HashPassword("admin"), - RoleString = "user,admin", + RoleString = UserUtility.IsAdminToRoleString(true), Version = 0, }); diff --git a/Timeline.Tests/Helpers/UserInfoComparers.cs b/Timeline.Tests/Helpers/UserInfoComparers.cs index 0d91efe3..fcf37e5c 100644 --- a/Timeline.Tests/Helpers/UserInfoComparers.cs +++ b/Timeline.Tests/Helpers/UserInfoComparers.cs @@ -1,6 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; using Timeline.Entities; namespace Timeline.Tests.Helpers -- cgit v1.2.3