diff options
author | crupest <crupest@outlook.com> | 2019-05-16 21:57:56 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2019-05-16 21:57:56 +0800 |
commit | 3de4179449a209646e0e5a967d270f7fa0878c03 (patch) | |
tree | a71563526342e8cc4a2c99227fcd178ea6d1fbde | |
parent | 461a37e3f2058d98c69cf598c1cd34a1d405a11e (diff) | |
download | timeline-3de4179449a209646e0e5a967d270f7fa0878c03.tar.gz timeline-3de4179449a209646e0e5a967d270f7fa0878c03.tar.bz2 timeline-3de4179449a209646e0e5a967d270f7fa0878c03.zip |
Change roles in UserInfo into isadmin.
-rw-r--r-- | Timeline.Tests/Helpers/TestUsers.cs | 4 | ||||
-rw-r--r-- | Timeline.Tests/Helpers/UserInfoComparers.cs | 49 | ||||
-rw-r--r-- | Timeline.Tests/JwtTokenUnitTest.cs | 2 | ||||
-rw-r--r-- | Timeline.Tests/UserUnitTest.cs | 4 | ||||
-rw-r--r-- | Timeline/Controllers/UserController.cs | 8 | ||||
-rw-r--r-- | Timeline/Entities/Http/User.cs | 16 | ||||
-rw-r--r-- | Timeline/Entities/UserInfo.cs | 74 | ||||
-rw-r--r-- | Timeline/Entities/UserUtility.cs | 49 | ||||
-rw-r--r-- | Timeline/Services/JwtService.cs | 39 | ||||
-rw-r--r-- | Timeline/Services/UserService.cs | 39 |
10 files changed, 170 insertions, 114 deletions
diff --git a/Timeline.Tests/Helpers/TestUsers.cs b/Timeline.Tests/Helpers/TestUsers.cs index 89ddf218..dd00e38d 100644 --- a/Timeline.Tests/Helpers/TestUsers.cs +++ b/Timeline.Tests/Helpers/TestUsers.cs @@ -28,8 +28,8 @@ namespace Timeline.Tests.Helpers MockUsers = mockUsers; - var mockUserInfos = mockUsers.Select(u => UserInfo.Create(u)).ToList(); - mockUserInfos.Sort(UserInfo.Comparer); + var mockUserInfos = mockUsers.Select(u => UserUtility.CreateUserInfo(u)).ToList(); + mockUserInfos.Sort(UserInfoComparers.Comparer); MockUserInfos = mockUserInfos; } diff --git a/Timeline.Tests/Helpers/UserInfoComparers.cs b/Timeline.Tests/Helpers/UserInfoComparers.cs new file mode 100644 index 00000000..d88b6622 --- /dev/null +++ b/Timeline.Tests/Helpers/UserInfoComparers.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Timeline.Entities; + +namespace Timeline.Tests.Helpers +{ + public static class UserInfoComparers + { + public static IEqualityComparer<UserInfo> EqualityComparer { get; } = new EqualityComparerImpl(); + public static IComparer<UserInfo> Comparer { get; } = Comparer<UserInfo>.Create(Compare); + + + private class EqualityComparerImpl : IEqualityComparer<UserInfo> + { + bool IEqualityComparer<UserInfo>.Equals(UserInfo x, UserInfo y) + { + return Compare(x, y) == 0; + } + + int IEqualityComparer<UserInfo>.GetHashCode(UserInfo obj) + { + return obj.Username.GetHashCode() ^ obj.IsAdmin.GetHashCode(); + } + } + + public static int Compare(UserInfo left, UserInfo right) + { + if (left == null) + { + if (right == null) + return 0; + return -1; + } + + if (right == null) + return 1; + + var uc = string.Compare(left.Username, right.Username); + if (uc != 0) + return uc; + + if (left.IsAdmin == right.IsAdmin) + return 0; + + return left.IsAdmin ? -1 : 1; + } + } +} diff --git a/Timeline.Tests/JwtTokenUnitTest.cs b/Timeline.Tests/JwtTokenUnitTest.cs index 8a503bd7..a4e5432f 100644 --- a/Timeline.Tests/JwtTokenUnitTest.cs +++ b/Timeline.Tests/JwtTokenUnitTest.cs @@ -78,7 +78,7 @@ namespace Timeline.Tests Assert.True(result.IsValid); Assert.NotNull(result.UserInfo); Assert.Equal(createTokenResult.UserInfo.Username, result.UserInfo.Username); - Assert.Equal(createTokenResult.UserInfo.Roles, result.UserInfo.Roles); + Assert.Equal(createTokenResult.UserInfo.IsAdmin, result.UserInfo.IsAdmin); } } } diff --git a/Timeline.Tests/UserUnitTest.cs b/Timeline.Tests/UserUnitTest.cs index 7d8cc824..a4b4dace 100644 --- a/Timeline.Tests/UserUnitTest.cs +++ b/Timeline.Tests/UserUnitTest.cs @@ -28,8 +28,8 @@ namespace Timeline.Tests var res1 = await client.GetAsync("users"); Assert.Equal(HttpStatusCode.OK, res1.StatusCode); var users = JsonConvert.DeserializeObject<UserInfo[]>(await res1.Content.ReadAsStringAsync()).ToList(); - users.Sort(UserInfo.Comparer); - Assert.Equal(TestMockUsers.MockUserInfos, users, UserInfo.EqualityComparer); + users.Sort(UserInfoComparers.Comparer); + Assert.Equal(TestMockUsers.MockUserInfos, users, UserInfoComparers.EqualityComparer); } } } diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs index a18e36e9..6f708e8a 100644 --- a/Timeline/Controllers/UserController.cs +++ b/Timeline/Controllers/UserController.cs @@ -37,9 +37,9 @@ namespace Timeline.Controllers } [HttpPut("user/{username}"), Authorize(Roles = "admin")] - public async Task<IActionResult> Put([FromBody] UserModifyRequest request, [FromRoute] string username) + public async Task<IActionResult> Put([FromBody] UserPutRequest request, [FromRoute] string username) { - var result = await _userService.PutUser(username, request.Password, request.Roles); + var result = await _userService.PutUser(username, request.Password, request.IsAdmin); switch (result) { case PutUserResult.Created: @@ -52,9 +52,9 @@ namespace Timeline.Controllers } [HttpPatch("user/{username}"), Authorize(Roles = "admin")] - public async Task<IActionResult> Patch([FromBody] UserModifyRequest request, [FromRoute] string username) + public async Task<IActionResult> Patch([FromBody] UserPatchRequest request, [FromRoute] string username) { - var result = await _userService.PatchUser(username, request.Password, request.Roles); + var result = await _userService.PatchUser(username, request.Password, request.IsAdmin); switch (result) { case PatchUserResult.Success: diff --git a/Timeline/Entities/Http/User.cs b/Timeline/Entities/Http/User.cs index 31cafaa3..db3d5071 100644 --- a/Timeline/Entities/Http/User.cs +++ b/Timeline/Entities/Http/User.cs @@ -1,9 +1,15 @@ namespace Timeline.Entities.Http { - public class UserModifyRequest + public class UserPutRequest { public string Password { get; set; } - public string[] Roles { get; set; } + public bool IsAdmin { get; set; } + } + + public class UserPatchRequest + { + public string Password { get; set; } + public bool? IsAdmin { get; set; } } public static class UserPutResponse @@ -47,8 +53,8 @@ public const int ForbiddenCode = 1; public const int NotExistsCode = 2; - public static ReturnCodeMessageResponse Success {get;} = new ReturnCodeMessageResponse(SuccessCode, "Success to upload avatar."); - public static ReturnCodeMessageResponse Forbidden {get;} = new ReturnCodeMessageResponse(ForbiddenCode, "You are not allowed to upload the user's avatar."); - public static ReturnCodeMessageResponse NotExists {get;} = new ReturnCodeMessageResponse(NotExistsCode, "The username does not exists. If you are a user, try update your token."); + public static ReturnCodeMessageResponse Success { get; } = new ReturnCodeMessageResponse(SuccessCode, "Success to upload avatar."); + public static ReturnCodeMessageResponse Forbidden { get; } = new ReturnCodeMessageResponse(ForbiddenCode, "You are not allowed to upload the user's avatar."); + public static ReturnCodeMessageResponse NotExists { get; } = new ReturnCodeMessageResponse(NotExistsCode, "The username does not exists. If you are a user, try update your token."); } } diff --git a/Timeline/Entities/UserInfo.cs b/Timeline/Entities/UserInfo.cs index c9bcde5b..bb56df9d 100644 --- a/Timeline/Entities/UserInfo.cs +++ b/Timeline/Entities/UserInfo.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Timeline.Models; namespace Timeline.Entities { @@ -11,83 +10,18 @@ namespace Timeline.Entities { } - public UserInfo(string username, params string[] roles) + public UserInfo(string username, bool isAdmin) { Username = username; - Roles = roles; + IsAdmin = isAdmin; } - public static UserInfo Create(User user) - { - if (user == null) - throw new ArgumentNullException(nameof(user)); - return Create(user.Name, user.RoleString); - } - - public static UserInfo Create(string username, string roleString) => new UserInfo - { - Username = username, - Roles = RolesFromString(roleString) - }; - public string Username { get; set; } - public string[] Roles { get; set; } - - public static IEqualityComparer<UserInfo> EqualityComparer { get; } = new EqualityComparerImpl(); - public static IComparer<UserInfo> Comparer { get; } = Comparer<UserInfo>.Create(Compare); - - private static string[] RolesFromString(string roleString) - { - if (roleString == null) - return null; - return roleString.Split(',').Select(r => r.Trim()).ToArray(); - } - - private class EqualityComparerImpl : IEqualityComparer<UserInfo> - { - bool IEqualityComparer<UserInfo>.Equals(UserInfo x, UserInfo y) - { - return Compare(x, y) == 0; - } - - int IEqualityComparer<UserInfo>.GetHashCode(UserInfo obj) - { - return obj.Username.GetHashCode() ^ NormalizeRoles(obj.Roles).GetHashCode(); - } - } - - private static string NormalizeRoles(string[] rawRoles) - { - var roles = rawRoles.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim()).ToList(); - roles.Sort(); - return string.Join(',', roles); - } - - public static int Compare(UserInfo left, UserInfo right) - { - if (left == null) - { - if (right == null) - return 0; - return -1; - } - - if (right == null) - return 1; - - var uc = string.Compare(left.Username, right.Username); - if (uc != 0) - return uc; - - var leftRoles = NormalizeRoles(left.Roles); - var rightRoles = NormalizeRoles(right.Roles); - - return string.Compare(leftRoles, rightRoles); - } + public bool IsAdmin { get; set; } public override string ToString() { - return $"Username: {Username} ; Roles: {Roles}"; + return $"Username: {Username} ; IsAdmin: {IsAdmin}"; } } } diff --git a/Timeline/Entities/UserUtility.cs b/Timeline/Entities/UserUtility.cs new file mode 100644 index 00000000..9a272948 --- /dev/null +++ b/Timeline/Entities/UserUtility.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using Timeline.Entities; +using Timeline.Models; + +namespace Timeline.Entities +{ + public static class UserUtility + { + public const string UserRole = "user"; + public const string AdminRole = "admin"; + + public static string[] UserRoleArray { get; } = new string[] { UserRole }; + public static string[] AdminRoleArray { get; } = new string[] { UserRole, AdminRole }; + + public static string[] IsAdminToRoleArray(bool isAdmin) + { + return isAdmin ? AdminRoleArray : UserRoleArray; + } + + public static bool RoleArrayToIsAdmin(string[] roles) + { + return roles.Contains(AdminRole); + } + + public static string[] RoleStringToRoleArray(string roleString) + { + return roleString.Split(',').ToArray(); + } + + public static string RoleArrayToRoleString(string[] roles) + { + return string.Join(',', roles); + } + + public static string IsAdminToRoleString(bool isAdmin) + { + return RoleArrayToRoleString(IsAdminToRoleArray(isAdmin)); + } + + public static UserInfo CreateUserInfo(User user) + { + if (user == null) + throw new ArgumentNullException(nameof(user)); + return new UserInfo(user.Name, RoleArrayToIsAdmin(RoleStringToRoleArray(user.RoleString))); + } + + } +} diff --git a/Timeline/Services/JwtService.cs b/Timeline/Services/JwtService.cs index bf470354..f5df59a5 100644 --- a/Timeline/Services/JwtService.cs +++ b/Timeline/Services/JwtService.cs @@ -11,24 +11,28 @@ using Timeline.Entities; namespace Timeline.Services { + public class TokenInfo + { + public string Name { get; set; } + public string[] Roles { get; set; } + } + public interface IJwtService { /// <summary> /// Create a JWT token for a given user info. /// </summary> - /// <param name="userId">The user id contained in generate token.</param> - /// <param name="username">The username contained in token.</param> - /// <param name="roles">The roles contained in token.</param> + /// <param name="tokenInfo">The info to generate token.</param> /// <returns>Return the generated token.</returns> - string GenerateJwtToken(long userId, string username, string[] roles); + string GenerateJwtToken(TokenInfo tokenInfo); /// <summary> /// Verify a JWT token. /// Return null is <paramref name="token"/> is null. /// </summary> /// <param name="token">The token string to verify.</param> - /// <returns>Return null if <paramref name="token"/> is null or token is invalid. Return the saved user info otherwise.</returns> - UserInfo VerifyJwtToken(string token); + /// <returns>Return null if <paramref name="token"/> is null or token is invalid. Return the saved info otherwise.</returns> + TokenInfo VerifyJwtToken(string token); } @@ -44,14 +48,20 @@ namespace Timeline.Services _logger = logger; } - public string GenerateJwtToken(long id, string username, string[] roles) + public string GenerateJwtToken(TokenInfo tokenInfo) { + if (tokenInfo == null) + throw new ArgumentNullException(nameof(tokenInfo)); + if (tokenInfo.Name == null) + throw new ArgumentException("Name is null.", nameof(tokenInfo)); + if (tokenInfo.Roles == null) + throw new ArgumentException("Roles is null.", nameof(tokenInfo)); + var jwtConfig = _jwtConfig.CurrentValue; var identity = new ClaimsIdentity(); - identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, id.ToString())); - identity.AddClaim(new Claim(identity.NameClaimType, username)); - identity.AddClaims(roles.Select(role => new Claim(identity.RoleClaimType, role))); + identity.AddClaim(new Claim(identity.NameClaimType, tokenInfo.Name)); + identity.AddClaims(tokenInfo.Roles.Select(role => new Claim(identity.RoleClaimType, role))); var tokenDescriptor = new SecurityTokenDescriptor() { @@ -71,7 +81,7 @@ namespace Timeline.Services } - public UserInfo VerifyJwtToken(string token) + public TokenInfo VerifyJwtToken(string token) { if (token == null) return null; @@ -90,8 +100,11 @@ namespace Timeline.Services IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.SigningKey)) }, out SecurityToken validatedToken); - return new UserInfo(principal.Identity.Name, - principal.FindAll(ClaimTypes.Role).Select(c => c.Value).ToArray()); + return new TokenInfo + { + Name = principal.Identity.Name, + Roles = principal.FindAll(ClaimTypes.Role).Select(c => c.Value).ToArray() + }; } catch (Exception e) { diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index 8ab3bc54..9fe9e08f 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using Timeline.Entities; using Timeline.Models; +using static Timeline.Entities.UserUtility; namespace Timeline.Services { @@ -120,7 +121,7 @@ namespace Timeline.Services /// <param name="roles">Array of roles of user.</param> /// <returns>Return <see cref="PutUserResult.Created"/> if a new user is created. /// Return <see cref="PutUserResult.Modified"/> if a existing user is modified.</returns> - Task<PutUserResult> PutUser(string username, string password, string[] roles); + Task<PutUserResult> PutUser(string username, string password, bool isAdmin); /// <summary> /// Partially modify a use of given username. @@ -130,7 +131,7 @@ namespace Timeline.Services /// <param name="roles">New roles. If not modify, then null.</param> /// <returns>Return <see cref="PatchUserResult.Success"/> if modification succeeds. /// Return <see cref="PatchUserResult.NotExists"/> if the user of given username doesn't exist.</returns> - Task<PatchUserResult> PatchUser(string username, string password, string[] roles); + Task<PatchUserResult> PatchUser(string username, string password, bool? isAdmin); /// <summary> /// Delete a user of given username. @@ -203,12 +204,16 @@ namespace Timeline.Services if (verifyResult) { - var userInfo = UserInfo.Create(user); - + var roles = RoleStringToRoleArray(user.RoleString); + var token = _jwtService.GenerateJwtToken(new TokenInfo + { + Name = username, + Roles = roles + }); return new CreateTokenResult { - Token = _jwtService.GenerateJwtToken(user.Id, userInfo.Username, userInfo.Roles), - UserInfo = userInfo + Token = token, + UserInfo = new UserInfo(username, RoleArrayToIsAdmin(roles)) }; } else @@ -220,33 +225,33 @@ namespace Timeline.Services public async Task<UserInfo> VerifyToken(string token) { - var userInfo = _jwtService.VerifyJwtToken(token); + var tokenInfo = _jwtService.VerifyJwtToken(token); - if (userInfo == null) + if (tokenInfo == null) { _logger.LogInformation($"Verify token falied. Reason: invalid token. Token: {token} ."); return null; } - return await Task.FromResult(userInfo); + return await Task.FromResult(new UserInfo(tokenInfo.Name, RoleArrayToIsAdmin(tokenInfo.Roles))); } public async Task<UserInfo> GetUser(string username) { return await _databaseContext.Users .Where(user => user.Name == username) - .Select(user => UserInfo.Create(user.Name, user.RoleString)) + .Select(user => CreateUserInfo(user)) .SingleOrDefaultAsync(); } public async Task<UserInfo[]> ListUsers() { return await _databaseContext.Users - .Select(user => UserInfo.Create(user.Name, user.RoleString)) + .Select(user => CreateUserInfo(user)) .ToArrayAsync(); } - public async Task<PutUserResult> PutUser(string username, string password, string[] roles) + public async Task<PutUserResult> PutUser(string username, string password, bool isAdmin) { var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); @@ -256,20 +261,20 @@ namespace Timeline.Services { Name = username, EncryptedPassword = _passwordService.HashPassword(password), - RoleString = string.Join(',', roles) + RoleString = IsAdminToRoleString(isAdmin) }); await _databaseContext.SaveChangesAsync(); return PutUserResult.Created; } user.EncryptedPassword = _passwordService.HashPassword(password); - user.RoleString = string.Join(',', roles); + user.RoleString = IsAdminToRoleString(isAdmin); await _databaseContext.SaveChangesAsync(); return PutUserResult.Modified; } - public async Task<PatchUserResult> PatchUser(string username, string password, string[] roles) + public async Task<PatchUserResult> PatchUser(string username, string password, bool? isAdmin) { var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); @@ -284,10 +289,10 @@ namespace Timeline.Services user.EncryptedPassword = _passwordService.HashPassword(password); } - if (roles != null) + if (isAdmin != null) { modified = true; - user.RoleString = string.Join(',', roles); + user.RoleString = IsAdminToRoleString(isAdmin.Value); } if (modified) |