From fded706989548a6f80aa7605ce70b7d20e49edb7 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/Controllers/TokenController.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'Timeline/Controllers/TokenController.cs') diff --git a/Timeline/Controllers/TokenController.cs b/Timeline/Controllers/TokenController.cs index 023bd53f..66c97b59 100644 --- a/Timeline/Controllers/TokenController.cs +++ b/Timeline/Controllers/TokenController.cs @@ -27,6 +27,7 @@ namespace Timeline.Controllers public const int Verify_BadToken = -2001; public const int Verify_UserNotExist = -2002; public const int Verify_BadVersion = -2003; + public const int Verify_Expired = -2004; } private readonly IUserService _userService; @@ -81,9 +82,18 @@ namespace Timeline.Controllers } catch (JwtTokenVerifyException e) { - var code = ErrorCodes.Verify_BadToken; - _logger.LogInformation(LoggingEventIds.VerifyFailed, e, "Attemp to verify a bad token because of bad format. Code: {} Token: {}.", code, request.Token); - return BadRequest(new CommonResponse(code, "A token of bad format.")); + if (e.ErrorCode == JwtTokenVerifyException.ErrorCodes.Expired) + { + var code = ErrorCodes.Verify_Expired; + _logger.LogInformation(LoggingEventIds.VerifyFailed, e, "Attemp to verify a expired token. Code: {} Token: {}.", code, request.Token); + return BadRequest(new CommonResponse(code, "A expired token.")); + } + else + { + var code = ErrorCodes.Verify_BadToken; + _logger.LogInformation(LoggingEventIds.VerifyFailed, e, "Attemp to verify a bad token because of bad format. Code: {} Token: {}.", code, request.Token); + return BadRequest(new CommonResponse(code, "A token of bad format.")); + } } catch (UserNotExistException e) { -- cgit v1.2.3 From 308c1faa5130913428b7348f78fe0fe2c406a63d Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Thu, 1 Aug 2019 22:32:40 +0800 Subject: Add token expire time. --- Timeline/Controllers/TokenController.cs | 24 +++++++++++++--- Timeline/Entities/Http/Token.cs | 2 ++ Timeline/Migrations/20190412144150_AddAdminUser.cs | 2 +- Timeline/Services/Clock.cs | 32 ++++++++++++++++++++++ Timeline/Services/JwtService.cs | 8 ++++-- Timeline/Services/UserService.cs | 7 +++-- Timeline/Startup.cs | 1 + 7 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 Timeline/Services/Clock.cs (limited to 'Timeline/Controllers/TokenController.cs') diff --git a/Timeline/Controllers/TokenController.cs b/Timeline/Controllers/TokenController.cs index 66c97b59..f9dcfd76 100644 --- a/Timeline/Controllers/TokenController.cs +++ b/Timeline/Controllers/TokenController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; +using System; using System.Threading.Tasks; using Timeline.Entities.Http; using Timeline.Services; @@ -23,6 +24,7 @@ namespace Timeline.Controllers { public const int Create_UserNotExist = -1001; public const int Create_BadPassword = -1002; + public const int Create_BadExpireOffset = -1003; public const int Verify_BadToken = -2001; public const int Verify_UserNotExist = -2002; @@ -32,28 +34,42 @@ namespace Timeline.Controllers private readonly IUserService _userService; private readonly ILogger _logger; + private readonly IClock _clock; - public TokenController(IUserService userService, ILogger logger) + public TokenController(IUserService userService, ILogger logger, IClock clock) { _userService = userService; _logger = logger; + _clock = clock; } [HttpPost("create")] [AllowAnonymous] public async Task Create([FromBody] CreateTokenRequest request) { + TimeSpan? expireOffset = null; + if (request.ExpireOffset != null) + { + if (request.ExpireOffset.Value <= 0.0) + { + var code = ErrorCodes.Create_BadExpireOffset; + _logger.LogInformation(LoggingEventIds.LogInFailed, "Attemp to login failed because expire time offset is bad. Code: {} Username: {} Password: {} Bad Expire Offset: {}.", code, request.Username, request.Password, request.ExpireOffset); + return BadRequest(new CommonResponse(code, "Expire time is not bigger than 0.")); + } + expireOffset = TimeSpan.FromDays(request.ExpireOffset.Value); + } + try { - var result = await _userService.CreateToken(request.Username, request.Password); - _logger.LogInformation(LoggingEventIds.LogInSucceeded, "Login succeeded. Username: {} .", request.Username); + var result = await _userService.CreateToken(request.Username, request.Password, expireOffset == null ? null : (DateTime?)(_clock.GetCurrentTime() + expireOffset.Value)); + _logger.LogInformation(LoggingEventIds.LogInSucceeded, "Login succeeded. Username: {} Expire Time Offset: {} days.", request.Username, request.ExpireOffset); return Ok(new CreateTokenResponse { Token = result.Token, User = result.User }); } - catch(UserNotExistException e) + catch (UserNotExistException e) { var code = ErrorCodes.Create_UserNotExist; _logger.LogInformation(LoggingEventIds.LogInFailed, e, "Attemp to login failed because user does not exist. Code: {} Username: {} Password: {} .", code, request.Username, request.Password); diff --git a/Timeline/Entities/Http/Token.cs b/Timeline/Entities/Http/Token.cs index aeb9fbf2..8a02ed2e 100644 --- a/Timeline/Entities/Http/Token.cs +++ b/Timeline/Entities/Http/Token.cs @@ -4,6 +4,8 @@ { public string Username { get; set; } public string Password { get; set; } + // in day + public double? ExpireOffset { get; set; } } public class CreateTokenResponse diff --git a/Timeline/Migrations/20190412144150_AddAdminUser.cs b/Timeline/Migrations/20190412144150_AddAdminUser.cs index 9fac05ff..1b3f14b7 100644 --- a/Timeline/Migrations/20190412144150_AddAdminUser.cs +++ b/Timeline/Migrations/20190412144150_AddAdminUser.cs @@ -8,7 +8,7 @@ namespace Timeline.Migrations protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.InsertData("user", new string[] { "name", "password", "roles" }, - new string[] { "crupest", new PasswordService(null).HashPassword("yang0101"), "user,admin" }); + new string[] { "crupest", new PasswordService().HashPassword("yang0101"), "user,admin" }); } protected override void Down(MigrationBuilder migrationBuilder) diff --git a/Timeline/Services/Clock.cs b/Timeline/Services/Clock.cs new file mode 100644 index 00000000..98451ad9 --- /dev/null +++ b/Timeline/Services/Clock.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Timeline.Services +{ + /// + /// Convenient for unit test. + /// + public interface IClock + { + /// + /// Get current time. + /// + /// Current time. + DateTime GetCurrentTime(); + } + + public class Clock : IClock + { + public Clock() + { + + } + + public DateTime GetCurrentTime() + { + return DateTime.Now; + } + } +} diff --git a/Timeline/Services/JwtService.cs b/Timeline/Services/JwtService.cs index f3416cce..52e892f6 100644 --- a/Timeline/Services/JwtService.cs +++ b/Timeline/Services/JwtService.cs @@ -94,10 +94,12 @@ namespace Timeline.Services private readonly IOptionsMonitor _jwtConfig; private readonly JwtSecurityTokenHandler _tokenHandler = new JwtSecurityTokenHandler(); + private readonly IClock _clock; - public JwtService(IOptionsMonitor jwtConfig) + public JwtService(IOptionsMonitor jwtConfig, IClock clock) { _jwtConfig = jwtConfig; + _clock = clock; } public string GenerateJwtToken(TokenInfo tokenInfo, DateTime? expires = null) @@ -118,8 +120,8 @@ namespace Timeline.Services Audience = config.Audience, SigningCredentials = new SigningCredentials( new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.SigningKey)), SecurityAlgorithms.HmacSha384), - IssuedAt = DateTime.Now, - Expires = expires.GetValueOrDefault(DateTime.Now.AddSeconds(config.DefaultExpireOffset)) + IssuedAt = _clock.GetCurrentTime(), + Expires = expires.GetValueOrDefault(_clock.GetCurrentTime().AddSeconds(config.DefaultExpireOffset)) }; var token = _tokenHandler.CreateToken(tokenDescriptor); diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index 3164a645..328dbff0 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -58,11 +58,12 @@ namespace Timeline.Services /// /// The username of the user to anthenticate. /// The password of the user to anthenticate. + /// The expired time point. Null then use default. See for what is default. /// An containing the created token and user info. /// Thrown when or is null. /// Thrown when the user with given username does not exist. /// Thrown when password is wrong. - Task CreateToken(string username, string password); + Task CreateToken(string username, string password, DateTime? expires = null); /// /// Verify the given token. @@ -170,7 +171,7 @@ namespace Timeline.Services _memoryCache.Remove(GenerateCacheKeyByUserId(id)); } - public async Task CreateToken(string username, string password) + public async Task CreateToken(string username, string password, DateTime? expires) { if (username == null) throw new ArgumentNullException(nameof(username)); @@ -198,7 +199,7 @@ namespace Timeline.Services { Id = user.Id, Version = user.Version - }); + }, expires); return new CreateTokenResult { Token = token, diff --git a/Timeline/Startup.cs b/Timeline/Startup.cs index a6965190..8f702da5 100644 --- a/Timeline/Startup.cs +++ b/Timeline/Startup.cs @@ -51,6 +51,7 @@ namespace Timeline services.AddScoped(); services.AddScoped(); services.AddTransient(); + services.AddTransient(); var databaseConfig = Configuration.GetSection(nameof(DatabaseConfig)).Get(); -- cgit v1.2.3 From a17aa8770b0b4861849c6e01812b2ff686497f02 Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Sun, 4 Aug 2019 15:02:58 +0800 Subject: Add unit tests for token. --- Timeline.Tests/Helpers/ResponseExtensions.cs | 14 ++++ Timeline.Tests/JwtTokenUnitTest.cs | 75 ------------------- Timeline.Tests/TokenUnitTest.cs | 103 +++++++++++++++++++++++++++ Timeline/Controllers/TokenController.cs | 2 +- 4 files changed, 118 insertions(+), 76 deletions(-) create mode 100644 Timeline.Tests/Helpers/ResponseExtensions.cs delete mode 100644 Timeline.Tests/JwtTokenUnitTest.cs create mode 100644 Timeline.Tests/TokenUnitTest.cs (limited to 'Timeline/Controllers/TokenController.cs') diff --git a/Timeline.Tests/Helpers/ResponseExtensions.cs b/Timeline.Tests/Helpers/ResponseExtensions.cs new file mode 100644 index 00000000..86ac1c88 --- /dev/null +++ b/Timeline.Tests/Helpers/ResponseExtensions.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Timeline.Tests.Helpers +{ + public static class ResponseExtensions + { + public static async Task ReadBodyAsJson(this HttpResponseMessage response) + { + return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + } + } +} diff --git a/Timeline.Tests/JwtTokenUnitTest.cs b/Timeline.Tests/JwtTokenUnitTest.cs deleted file mode 100644 index 6c0d4213..00000000 --- a/Timeline.Tests/JwtTokenUnitTest.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Microsoft.AspNetCore.Mvc.Testing; -using Newtonsoft.Json; -using System.Net; -using System.Net.Http; -using Timeline.Entities.Http; -using Timeline.Tests.Helpers; -using Timeline.Tests.Helpers.Authentication; -using Xunit; -using Xunit.Abstractions; - -namespace Timeline.Tests -{ - public class JwtTokenUnitTest : IClassFixture> - { - private const string CreateTokenUrl = "token/create"; - private const string VerifyTokenUrl = "token/verify"; - - private readonly WebApplicationFactory _factory; - - public JwtTokenUnitTest(WebApplicationFactory factory, ITestOutputHelper outputHelper) - { - _factory = factory.WithTestConfig(outputHelper); - } - - [Fact] - public async void CreateTokenTest_BadCredential() - { - using (var client = _factory.CreateDefaultClient()) - { - var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = "???", Password = "???" }); - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - } - } - - [Fact] - public async void CreateTokenTest_GoodCredential() - { - using (var client = _factory.CreateDefaultClient()) - { - var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = "user", Password = "user" }); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var result = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - Assert.NotNull(result.Token); - Assert.NotNull(result.User); - } - } - - [Fact] - public async void VerifyTokenTest_BadToken() - { - using (var client = _factory.CreateDefaultClient()) - { - var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = "bad token hahaha" }); - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - } - } - - [Fact] - public async void VerifyTokenTest_GoodToken() - { - using (var client = _factory.CreateDefaultClient()) - { - var createTokenResult = await client.CreateUserTokenAsync("admin", "admin"); - - var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = createTokenResult.Token }); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var result = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - Assert.NotNull(result.User); - Assert.Equal(createTokenResult.User.Username, result.User.Username); - Assert.Equal(createTokenResult.User.Administrator, result.User.Administrator); - } - } - } -} diff --git a/Timeline.Tests/TokenUnitTest.cs b/Timeline.Tests/TokenUnitTest.cs new file mode 100644 index 00000000..27c2ed32 --- /dev/null +++ b/Timeline.Tests/TokenUnitTest.cs @@ -0,0 +1,103 @@ +using Microsoft.AspNetCore.Mvc.Testing; +using Newtonsoft.Json; +using System.Linq; +using System.Net; +using System.Net.Http; +using Timeline.Controllers; +using Timeline.Entities.Http; +using Timeline.Tests.Helpers; +using Timeline.Tests.Helpers.Authentication; +using Xunit; +using Xunit.Abstractions; + +namespace Timeline.Tests +{ + public class TokenUnitTest : IClassFixture> + { + private const string CreateTokenUrl = "token/create"; + private const string VerifyTokenUrl = "token/verify"; + + private readonly WebApplicationFactory _factory; + + public TokenUnitTest(WebApplicationFactory factory, ITestOutputHelper outputHelper) + { + _factory = factory.WithTestConfig(outputHelper); + } + + [Fact] + public async void CreateTokenTest_UserNotExist() + { + using (var client = _factory.CreateDefaultClient()) + { + var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = "usernotexist", Password = "???" }); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var body = await response.ReadBodyAsJson(); + Assert.Equal(TokenController.ErrorCodes.Create_UserNotExist, body.Code); + } + } + + [Fact] + public async void CreateTokenTest_BadPassword() + { + using (var client = _factory.CreateDefaultClient()) + { + var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = "user", Password = "???" }); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var body = await response.ReadBodyAsJson(); + Assert.Equal(TokenController.ErrorCodes.Create_BadPassword, body.Code); + } + } + + [Fact] + public async void CreateTokenTest_BadExpireOffset() + { + using (var client = _factory.CreateDefaultClient()) + { + var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = "???", Password = "???", ExpireOffset = -1000 }); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var body = await response.ReadBodyAsJson(); + Assert.Equal(TokenController.ErrorCodes.Create_BadExpireOffset, body.Code); + } + } + + [Fact] + public async void CreateTokenTest_Success() + { + using (var client = _factory.CreateDefaultClient()) + { + var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = "user", Password = "user" }); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.ReadBodyAsJson(); + Assert.NotEmpty(body.Token); + Assert.Equal(TestMockUsers.MockUserInfos.Where(u => u.Username == "user").Single(), body.User, UserInfoComparers.EqualityComparer); + } + } + + [Fact] + public async void VerifyTokenTest_BadToken() + { + using (var client = _factory.CreateDefaultClient()) + { + var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = "bad token hahaha" }); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + } + + [Fact] + public async void VerifyTokenTest_GoodToken() + { + using (var client = _factory.CreateDefaultClient()) + { + var createTokenResult = await client.CreateUserTokenAsync("admin", "admin"); + + var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = createTokenResult.Token }); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var result = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + Assert.NotNull(result.User); + Assert.Equal(createTokenResult.User.Username, result.User.Username); + Assert.Equal(createTokenResult.User.Administrator, result.User.Administrator); + } + } + } +} diff --git a/Timeline/Controllers/TokenController.cs b/Timeline/Controllers/TokenController.cs index f9dcfd76..66cf3dad 100644 --- a/Timeline/Controllers/TokenController.cs +++ b/Timeline/Controllers/TokenController.cs @@ -20,7 +20,7 @@ namespace Timeline.Controllers public const int VerifyFailed = 2001; } - private static class ErrorCodes + public static class ErrorCodes { public const int Create_UserNotExist = -1001; public const int Create_BadPassword = -1002; -- cgit v1.2.3 From 36da221444807742a9e26f2ba636d9c6aef6155f Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Sun, 4 Aug 2019 15:46:31 +0800 Subject: Continue to add unit tests for token. Fix a bug thanks to unit test. --- Timeline.Tests/Helpers/TestClock.cs | 25 ++++++++++ .../Helpers/WebApplicationFactoryExtensions.cs | 6 +++ Timeline.Tests/TokenUnitTest.cs | 58 +++++++++++++++++++--- Timeline/Controllers/TokenController.cs | 2 +- Timeline/Services/UserService.cs | 23 ++++----- 5 files changed, 92 insertions(+), 22 deletions(-) create mode 100644 Timeline.Tests/Helpers/TestClock.cs (limited to 'Timeline/Controllers/TokenController.cs') diff --git a/Timeline.Tests/Helpers/TestClock.cs b/Timeline.Tests/Helpers/TestClock.cs new file mode 100644 index 00000000..fc200be9 --- /dev/null +++ b/Timeline.Tests/Helpers/TestClock.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; +using System; +using Timeline.Services; + +namespace Timeline.Tests.Helpers +{ + public class TestClock : IClock + { + DateTime? MockCurrentTime { get; set; } = null; + + public DateTime GetCurrentTime() + { + return MockCurrentTime.GetValueOrDefault(DateTime.Now); + } + } + + public static class TestClockWebApplicationFactoryExtensions + { + public static TestClock GetTestClock(this WebApplicationFactory factory) where T : class + { + return factory.Server.Host.Services.GetRequiredService() as TestClock; + } + } +} diff --git a/Timeline.Tests/Helpers/WebApplicationFactoryExtensions.cs b/Timeline.Tests/Helpers/WebApplicationFactoryExtensions.cs index a7616b41..aa005ba3 100644 --- a/Timeline.Tests/Helpers/WebApplicationFactoryExtensions.cs +++ b/Timeline.Tests/Helpers/WebApplicationFactoryExtensions.cs @@ -1,9 +1,11 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Timeline.Models; +using Timeline.Services; using Xunit.Abstractions; namespace Timeline.Tests.Helpers @@ -46,6 +48,10 @@ namespace Timeline.Tests.Helpers db.Users.AddRange(TestMockUsers.MockUsers); db.SaveChanges(); } + }) + .ConfigureTestServices(services => + { + services.AddSingleton(); }); }); } diff --git a/Timeline.Tests/TokenUnitTest.cs b/Timeline.Tests/TokenUnitTest.cs index 27c2ed32..d7df8797 100644 --- a/Timeline.Tests/TokenUnitTest.cs +++ b/Timeline.Tests/TokenUnitTest.cs @@ -1,10 +1,14 @@ using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using System.Linq; using System.Net; using System.Net.Http; using Timeline.Controllers; +using Timeline.Entities; using Timeline.Entities.Http; +using Timeline.Models; +using Timeline.Services; using Timeline.Tests.Helpers; using Timeline.Tests.Helpers.Authentication; using Xunit; @@ -80,23 +84,63 @@ namespace Timeline.Tests { var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = "bad token hahaha" }); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var body = await response.ReadBodyAsJson(); + Assert.Equal(TokenController.ErrorCodes.Verify_BadToken, body.Code); } } [Fact] - public async void VerifyTokenTest_GoodToken() + public async void VerifyTokenTest_BadVersion_AND_UserNotExist() { using (var client = _factory.CreateDefaultClient()) { - var createTokenResult = await client.CreateUserTokenAsync("admin", "admin"); + using (var scope = _factory.Server.Host.Services.CreateScope()) // UserService is scoped. + { + // create a user for test + var userService = scope.ServiceProvider.GetRequiredService(); + + const string username = "verifytokentest0"; + const string password = "12345678"; + + await userService.PutUser(username, password, false); + + // create a token + var token = (await client.CreateUserTokenAsync(username, password)).Token; + // increase version + await userService.PatchUser(username, null, null); + + // test against bad version + var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = token }); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var body = await response.ReadBodyAsJson(); + Assert.Equal(TokenController.ErrorCodes.Verify_BadVersion, body.Code); + + // create another token + var token2 = (await client.CreateUserTokenAsync(username, password)).Token; + + // delete user + await userService.DeleteUser(username); + + // test against user not exist + var response2 = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = token }); + Assert.Equal(HttpStatusCode.BadRequest, response2.StatusCode); + var body2 = await response2.ReadBodyAsJson(); + Assert.Equal(TokenController.ErrorCodes.Verify_UserNotExist, body2.Code); + } + } + } + + [Fact] + public async void VerifyTokenTest_Success() + { + using (var client = _factory.CreateDefaultClient()) + { + var createTokenResult = await client.CreateUserTokenAsync("admin", "admin"); var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = createTokenResult.Token }); Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var result = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - Assert.NotNull(result.User); - Assert.Equal(createTokenResult.User.Username, result.User.Username); - Assert.Equal(createTokenResult.User.Administrator, result.User.Administrator); + var body = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + Assert.Equal(TestMockUsers.MockUserInfos.Where(u => u.Username == "user").Single(), body.User, UserInfoComparers.EqualityComparer); } } } diff --git a/Timeline/Controllers/TokenController.cs b/Timeline/Controllers/TokenController.cs index 66cf3dad..21f87ded 100644 --- a/Timeline/Controllers/TokenController.cs +++ b/Timeline/Controllers/TokenController.cs @@ -119,7 +119,7 @@ namespace Timeline.Controllers } catch (BadTokenVersionException e) { - var code = ErrorCodes.Verify_BadToken; + var code = ErrorCodes.Verify_BadVersion; _logger.LogInformation(LoggingEventIds.VerifyFailed, e, "Attemp to verify a bad token because version is old. Code: {} Token: {}.", code, request.Token); return BadRequest(new CommonResponse(code, "The token is expired. Try recreate a token.")); } diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index 328dbff0..65ac98d3 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -19,7 +19,7 @@ namespace Timeline.Services [Serializable] public class UserNotExistException : Exception { - public UserNotExistException(): base("The user does not exist.") { } + public UserNotExistException() : base("The user does not exist.") { } public UserNotExistException(string message) : base(message) { } public UserNotExistException(string message, Exception inner) : base(message, inner) { } protected UserNotExistException( @@ -30,7 +30,7 @@ namespace Timeline.Services [Serializable] public class BadPasswordException : Exception { - public BadPasswordException(): base("Password is wrong.") { } + public BadPasswordException() : base("Password is wrong.") { } public BadPasswordException(string message) : base(message) { } public BadPasswordException(string message, Exception inner) : base(message, inner) { } protected BadPasswordException( @@ -42,7 +42,7 @@ namespace Timeline.Services [Serializable] public class BadTokenVersionException : Exception { - public BadTokenVersionException(): base("Token version is expired.") { } + public BadTokenVersionException() : base("Token version is expired.") { } public BadTokenVersionException(string message) : base(message) { } public BadTokenVersionException(string message, Exception inner) : base(message, inner) { } protected BadTokenVersionException( @@ -105,6 +105,8 @@ namespace Timeline.Services /// /// Partially modify a user of given username. + /// + /// Note that whether actually modified or not, Version of the user will always increase. /// /// Username of the user to modify. Can't be null. /// New password. Null if not modify. @@ -309,27 +311,20 @@ namespace Timeline.Services if (user == null) throw new UserNotExistException(); - bool modified = false; - if (password != null) { - modified = true; user.EncryptedPassword = _passwordService.HashPassword(password); } if (administrator != null) { - modified = true; user.RoleString = IsAdminToRoleString(administrator.Value); } - if (modified) - { - user.Version += 1; - await _databaseContext.SaveChangesAsync(); - //clear cache - RemoveCache(user.Id); - } + user.Version += 1; + await _databaseContext.SaveChangesAsync(); + //clear cache + RemoveCache(user.Id); } public async Task DeleteUser(string username) -- cgit v1.2.3