diff options
author | crupest <crupest@outlook.com> | 2020-11-12 21:38:43 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2020-11-12 21:38:43 +0800 |
commit | ee1b2b5b100268aa510257a1a2cd4cd03f9fc72b (patch) | |
tree | 221a2af5008e180e180f5400f127a0d404ed5158 | |
parent | 2c6b812382e04956793d90ba4148dd4aa7da3b70 (diff) | |
download | timeline-ee1b2b5b100268aa510257a1a2cd4cd03f9fc72b.tar.gz timeline-ee1b2b5b100268aa510257a1a2cd4cd03f9fc72b.tar.bz2 timeline-ee1b2b5b100268aa510257a1a2cd4cd03f9fc72b.zip |
...
-rw-r--r-- | BackEnd/Timeline.Tests/IntegratedTests/AuthorizationTest.cs | 52 | ||||
-rw-r--r-- | BackEnd/Timeline/Auth/Attribute.cs | 21 | ||||
-rw-r--r-- | BackEnd/Timeline/Auth/MyAuthenticationHandler.cs | 9 | ||||
-rw-r--r-- | BackEnd/Timeline/Auth/PermissionAuthorizeAttribute.cs | 30 | ||||
-rw-r--r-- | BackEnd/Timeline/Auth/PermissionPolicyProvider.cs | 35 | ||||
-rw-r--r-- | BackEnd/Timeline/Auth/PrincipalExtensions.cs | 10 | ||||
-rw-r--r-- | BackEnd/Timeline/Controllers/Testing/TestingAuthController.cs | 32 | ||||
-rw-r--r-- | BackEnd/Timeline/Entities/UserEntity.cs | 6 | ||||
-rw-r--r-- | BackEnd/Timeline/Models/Http/UserInfo.cs | 21 | ||||
-rw-r--r-- | BackEnd/Timeline/Models/User.cs | 28 | ||||
-rw-r--r-- | BackEnd/Timeline/Services/UserRoleConvert.cs | 43 | ||||
-rw-r--r-- | BackEnd/Timeline/Services/UserService.cs | 230 | ||||
-rw-r--r-- | BackEnd/Timeline/Services/UserTokenManager.cs | 6 | ||||
-rw-r--r-- | BackEnd/Timeline/Startup.cs | 4 |
14 files changed, 182 insertions, 345 deletions
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/AuthorizationTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/AuthorizationTest.cs deleted file mode 100644 index 38071394..00000000 --- a/BackEnd/Timeline.Tests/IntegratedTests/AuthorizationTest.cs +++ /dev/null @@ -1,52 +0,0 @@ -using FluentAssertions;
-using System.Net;
-using System.Threading.Tasks;
-using Timeline.Tests.Helpers;
-using Xunit;
-
-namespace Timeline.Tests.IntegratedTests
-{
- public class AuthorizationTest : IntegratedTestBase
- {
- private const string BaseUrl = "testing/auth/";
- private const string AuthorizeUrl = BaseUrl + "Authorize";
- private const string UserUrl = BaseUrl + "User";
- private const string AdminUrl = BaseUrl + "Admin";
-
- [Fact]
- public async Task UnauthenticationTest()
- {
- using var client = await CreateDefaultClient();
- var response = await client.GetAsync(AuthorizeUrl);
- response.Should().HaveStatusCode(HttpStatusCode.Unauthorized);
- }
-
- [Fact]
- public async Task AuthenticationTest()
- {
- using var client = await CreateClientAsUser();
- var response = await client.GetAsync(AuthorizeUrl);
- response.Should().HaveStatusCode(HttpStatusCode.OK);
- }
-
- [Fact]
- public async Task UserAuthorizationTest()
- {
- using var client = await CreateClientAsUser();
- var response1 = await client.GetAsync(UserUrl);
- response1.Should().HaveStatusCode(HttpStatusCode.OK);
- var response2 = await client.GetAsync(AdminUrl);
- response2.Should().HaveStatusCode(HttpStatusCode.Forbidden);
- }
-
- [Fact]
- public async Task AdminAuthorizationTest()
- {
- using var client = await CreateClientAsAdministrator();
- var response1 = await client.GetAsync(UserUrl);
- response1.Should().HaveStatusCode(HttpStatusCode.OK);
- var response2 = await client.GetAsync(AdminUrl);
- response2.Should().HaveStatusCode(HttpStatusCode.OK);
- }
- }
-}
diff --git a/BackEnd/Timeline/Auth/Attribute.cs b/BackEnd/Timeline/Auth/Attribute.cs deleted file mode 100644 index 86d0109b..00000000 --- a/BackEnd/Timeline/Auth/Attribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.AspNetCore.Authorization;
-using Timeline.Entities;
-
-namespace Timeline.Auth
-{
- public class AdminAuthorizeAttribute : AuthorizeAttribute
- {
- public AdminAuthorizeAttribute()
- {
- Roles = UserRoles.Admin;
- }
- }
-
- public class UserAuthorizeAttribute : AuthorizeAttribute
- {
- public UserAuthorizeAttribute()
- {
- Roles = UserRoles.User;
- }
- }
-}
diff --git a/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs b/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs index 3c97c329..b5e22a14 100644 --- a/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs +++ b/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs @@ -17,6 +17,7 @@ namespace Timeline.Auth {
public const string Scheme = "Bearer";
public const string DisplayName = "My Jwt Auth Scheme";
+ public const string PermissionClaimName = "Permission";
}
public class MyAuthenticationOptions : AuthenticationSchemeOptions
@@ -78,12 +79,12 @@ namespace Timeline.Auth try
{
- var userInfo = await _userTokenManager.VerifyToken(token);
+ var user = await _userTokenManager.VerifyToken(token);
var identity = new ClaimsIdentity(AuthenticationConstants.Scheme);
- identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userInfo.Id!.Value.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64));
- identity.AddClaim(new Claim(identity.NameClaimType, userInfo.Username, ClaimValueTypes.String));
- identity.AddClaims(UserRoleConvert.ToArray(userInfo.Administrator!.Value).Select(role => new Claim(identity.RoleClaimType, role, ClaimValueTypes.String)));
+ identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64));
+ identity.AddClaim(new Claim(identity.NameClaimType, user.Username, ClaimValueTypes.String));
+ identity.AddClaims(user.Permissions.Select(permission => new Claim(AuthenticationConstants.PermissionClaimName, permission.ToString(), ClaimValueTypes.String)));
var principal = new ClaimsPrincipal();
principal.AddIdentity(identity);
diff --git a/BackEnd/Timeline/Auth/PermissionAuthorizeAttribute.cs b/BackEnd/Timeline/Auth/PermissionAuthorizeAttribute.cs new file mode 100644 index 00000000..3df8dee5 --- /dev/null +++ b/BackEnd/Timeline/Auth/PermissionAuthorizeAttribute.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Authorization;
+using System;
+using System.Linq;
+using Timeline.Services;
+
+namespace Timeline.Auth
+{
+ public class PermissionAuthorizeAttribute : AuthorizeAttribute
+ {
+ public PermissionAuthorizeAttribute()
+ {
+
+ }
+
+ public PermissionAuthorizeAttribute(params UserPermission[] permissions)
+ {
+ Permissions = permissions;
+ }
+
+ public UserPermission[] Permissions
+ {
+ get => Policy == null ? Array.Empty<UserPermission>() : Policy[PermissionPolicyProvider.PolicyPrefix.Length..].Split(',')
+ .Select(s => Enum.Parse<UserPermission>(s)).ToArray();
+ set
+ {
+ Policy = $"{PermissionPolicyProvider.PolicyPrefix}{string.Join(',', value)}";
+ }
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Auth/PermissionPolicyProvider.cs b/BackEnd/Timeline/Auth/PermissionPolicyProvider.cs new file mode 100644 index 00000000..12a4fcd5 --- /dev/null +++ b/BackEnd/Timeline/Auth/PermissionPolicyProvider.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Authorization.Infrastructure;
+using System;
+using System.Threading.Tasks;
+
+namespace Timeline.Auth
+{
+ public class PermissionPolicyProvider : IAuthorizationPolicyProvider
+ {
+ public const string PolicyPrefix = "Permission-";
+
+ public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
+ {
+ return Task.FromResult(new AuthorizationPolicyBuilder(AuthenticationConstants.Scheme).RequireAuthenticatedUser().Build());
+ }
+
+ public Task<AuthorizationPolicy?> GetFallbackPolicyAsync()
+ {
+ return Task.FromResult<AuthorizationPolicy?>(null);
+ }
+
+ public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
+ {
+ if (policyName.StartsWith(PolicyPrefix, StringComparison.OrdinalIgnoreCase))
+ {
+ var permissions = policyName[PolicyPrefix.Length..].Split(',');
+
+ var policy = new AuthorizationPolicyBuilder(AuthenticationConstants.Scheme);
+ policy.AddRequirements(new ClaimsAuthorizationRequirement(AuthenticationConstants.PermissionClaimName, permissions));
+ return Task.FromResult<AuthorizationPolicy?>(policy.Build());
+ }
+ return Task.FromResult<AuthorizationPolicy?>(null);
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Auth/PrincipalExtensions.cs b/BackEnd/Timeline/Auth/PrincipalExtensions.cs index ad7a887f..9f86e8ac 100644 --- a/BackEnd/Timeline/Auth/PrincipalExtensions.cs +++ b/BackEnd/Timeline/Auth/PrincipalExtensions.cs @@ -1,13 +1,15 @@ -using System.Security.Principal;
-using Timeline.Entities;
+using System;
+using System.Security.Claims;
+using Timeline.Services;
namespace Timeline.Auth
{
internal static class PrincipalExtensions
{
- internal static bool IsAdministrator(this IPrincipal principal)
+ internal static bool HasPermission(this ClaimsPrincipal principal, UserPermission permission)
{
- return principal.IsInRole(UserRoles.Admin);
+ return principal.HasClaim(
+ claim => claim.Type == AuthenticationConstants.PermissionClaimName && string.Equals(claim.Value, permission.ToString(), StringComparison.InvariantCultureIgnoreCase));
}
}
}
diff --git a/BackEnd/Timeline/Controllers/Testing/TestingAuthController.cs b/BackEnd/Timeline/Controllers/Testing/TestingAuthController.cs deleted file mode 100644 index 4d3b3ec7..00000000 --- a/BackEnd/Timeline/Controllers/Testing/TestingAuthController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
-using Timeline.Auth;
-
-namespace Timeline.Controllers.Testing
-{
- [Route("testing/auth")]
- [ApiController]
- public class TestingAuthController : Controller
- {
- [HttpGet("[action]")]
- [Authorize]
- public ActionResult Authorize()
- {
- return Ok();
- }
-
- [HttpGet("[action]")]
- [UserAuthorize]
- public new ActionResult User()
- {
- return Ok();
- }
-
- [HttpGet("[action]")]
- [AdminAuthorize]
- public ActionResult Admin()
- {
- return Ok();
- }
- }
-}
diff --git a/BackEnd/Timeline/Entities/UserEntity.cs b/BackEnd/Timeline/Entities/UserEntity.cs index 83fe9aea..83fa8f89 100644 --- a/BackEnd/Timeline/Entities/UserEntity.cs +++ b/BackEnd/Timeline/Entities/UserEntity.cs @@ -5,12 +5,6 @@ using System.ComponentModel.DataAnnotations.Schema; namespace Timeline.Entities
{
- public static class UserRoles
- {
- public const string Admin = "admin";
- public const string User = "user";
- }
-
[Table("users")]
public class UserEntity
{
diff --git a/BackEnd/Timeline/Models/Http/UserInfo.cs b/BackEnd/Timeline/Models/Http/UserInfo.cs index d92a12c4..26b04e90 100644 --- a/BackEnd/Timeline/Models/Http/UserInfo.cs +++ b/BackEnd/Timeline/Models/Http/UserInfo.cs @@ -2,7 +2,9 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
+using System.Collections.Generic;
using Timeline.Controllers;
+using Timeline.Services;
namespace Timeline.Models.Http
{
@@ -27,6 +29,12 @@ namespace Timeline.Models.Http /// True if the user is a administrator.
/// </summary>
public bool? Administrator { get; set; } = default!;
+#pragma warning disable CA2227 // Collection properties should be read only
+ /// <summary>
+ /// The permissions of the user.
+ /// </summary>
+ public List<string> Permissions { get; set; } = default!;
+#pragma warning restore CA2227 // Collection properties should be read only
#pragma warning disable CA1707 // Identifiers should not contain underscores
/// <summary>
/// Related links.
@@ -54,6 +62,14 @@ namespace Timeline.Models.Http public string Timeline { get; set; } = default!;
}
+ public class UserPermissionsValueConverter : ITypeConverter<UserPermissions, List<string>>
+ {
+ public List<string> Convert(UserPermissions source, List<string> destination, ResolutionContext context)
+ {
+ return source.ToStringList();
+ }
+ }
+
public class UserInfoLinksValueResolver : IValueResolver<User, UserInfo, UserInfoLinks>
{
private readonly IActionContextAccessor _actionContextAccessor;
@@ -84,7 +100,10 @@ namespace Timeline.Models.Http {
public UserInfoAutoMapperProfile()
{
- CreateMap<User, UserInfo>().ForMember(u => u._links, opt => opt.MapFrom<UserInfoLinksValueResolver>());
+ CreateMap<UserPermissions, List<string>>()
+ .ConvertUsing<UserPermissionsValueConverter>();
+ CreateMap<User, UserInfo>()
+ .ForMember(u => u._links, opt => opt.MapFrom<UserInfoLinksValueResolver>());
}
}
}
diff --git a/BackEnd/Timeline/Models/User.cs b/BackEnd/Timeline/Models/User.cs index f08a62db..1e90cd1d 100644 --- a/BackEnd/Timeline/Models/User.cs +++ b/BackEnd/Timeline/Models/User.cs @@ -1,21 +1,23 @@ using System;
+using Timeline.Services;
namespace Timeline.Models
{
- public class User
+ public record User
{
- public string? UniqueId { get; set; }
- public string? Username { get; set; }
- public string? Nickname { get; set; }
- public bool? Administrator { get; set; }
+ public long Id { get; set; }
+ public string UniqueId { get; set; } = default!;
- #region secret
- public long? Id { get; set; }
- public string? Password { get; set; }
- public long? Version { get; set; }
- public DateTime? UsernameChangeTime { get; set; }
- public DateTime? CreateTime { get; set; }
- public DateTime? LastModified { get; set; }
- #endregion secret
+ public string Username { get; set; } = default!;
+ public string Nickname { get; set; } = default!;
+
+ [Obsolete("Use permissions instead.")]
+ public bool Administrator { get; set; }
+ public UserPermissions Permissions { get; set; } = default!;
+
+ public DateTime UsernameChangeTime { get; set; }
+ public DateTime CreateTime { get; set; }
+ public DateTime LastModified { get; set; }
+ public long Version { get; set; }
}
}
diff --git a/BackEnd/Timeline/Services/UserRoleConvert.cs b/BackEnd/Timeline/Services/UserRoleConvert.cs deleted file mode 100644 index f27ee1bb..00000000 --- a/BackEnd/Timeline/Services/UserRoleConvert.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System;
-using System.Collections.Generic;
-using System.Linq;
-using Timeline.Entities;
-
-namespace Timeline.Services
-{
- public static class UserRoleConvert
- {
- public const string UserRole = UserRoles.User;
- public const string AdminRole = UserRoles.Admin;
-
- public static string[] ToArray(bool administrator)
- {
- return administrator ? new string[] { UserRole, AdminRole } : new string[] { UserRole };
- }
-
- public static string[] ToArray(string s)
- {
- return s.Split(',').ToArray();
- }
-
- public static bool ToBool(IReadOnlyCollection<string> roles)
- {
- return roles.Contains(AdminRole);
- }
-
- public static string ToString(IReadOnlyCollection<string> roles)
- {
- return string.Join(',', roles);
- }
-
- public static string ToString(bool administrator)
- {
- return administrator ? UserRole + "," + AdminRole : UserRole;
- }
-
- public static bool ToBool(string s)
- {
- return s.Contains("admin", StringComparison.InvariantCulture);
- }
- }
-}
diff --git a/BackEnd/Timeline/Services/UserService.cs b/BackEnd/Timeline/Services/UserService.cs index 821bc33d..ed637ba3 100644 --- a/BackEnd/Timeline/Services/UserService.cs +++ b/BackEnd/Timeline/Services/UserService.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
+using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
@@ -13,6 +14,11 @@ using static Timeline.Resources.Services.UserService; namespace Timeline.Services
{
+ /// <summary>
+ /// Null means not change.
+ /// </summary>
+ public record ModifyUserParams(string? Username, string? Password, string? Nickname);
+
public interface IUserService
{
/// <summary>
@@ -33,17 +39,7 @@ namespace Timeline.Services /// <param name="id">The id of the user.</param>
/// <returns>The user info.</returns>
/// <exception cref="UserNotExistException">Thrown when the user with given id does not exist.</exception>
- Task<User> GetUserById(long id);
-
- /// <summary>
- /// Get the user info of given username.
- /// </summary>
- /// <param name="username">Username of the user.</param>
- /// <returns>The info of the user.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format.</exception>
- /// <exception cref="UserNotExistException">Thrown when the user with given username does not exist.</exception>
- Task<User> GetUserByUsername(string username);
+ Task<User> GetUser(long id);
/// <summary>
/// Get the user id of given username.
@@ -59,71 +55,31 @@ namespace Timeline.Services /// List all users.
/// </summary>
/// <returns>The user info of users.</returns>
- Task<User[]> GetUsers();
+ Task<List<User>> GetUsers();
/// <summary>
/// Create a user with given info.
/// </summary>
- /// <param name="info">The info of new user.</param>
+ /// <param name="username">The username of new user.</param>
+ /// <param name="password">The password of new user.</param>
/// <returns>The the new user.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="info"/>is null.</exception>
- /// <exception cref="ArgumentException">Thrown when some fields in <paramref name="info"/> is bad.</exception>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> or <paramref name="password"/> is of bad format.</exception>
/// <exception cref="EntityAlreadyExistException">Thrown when a user with given username already exists.</exception>
- /// <remarks>
- /// <see cref="User.Username"/> must not be null and must be a valid username.
- /// <see cref="User.Password"/> must not be null or empty.
- /// <see cref="User.Administrator"/> is false by default (null).
- /// <see cref="User.Nickname"/> must be a valid nickname if set. It is empty by default.
- /// Other fields are ignored.
- /// </remarks>
- Task<User> CreateUser(User info);
+ Task<User> CreateUser(string username, string password);
/// <summary>
- /// Modify a user's info.
+ /// Modify a user.
/// </summary>
/// <param name="id">The id of the user.</param>
- /// <param name="info">The new info. May be null.</param>
+ /// <param name="param">The new information.</param>
/// <returns>The new user info.</returns>
/// <exception cref="ArgumentException">Thrown when some fields in <paramref name="info"/> is bad.</exception>
/// <exception cref="UserNotExistException">Thrown when user with given id does not exist.</exception>
/// <remarks>
- /// Only <see cref="User.Username"/>, <see cref="User.Administrator"/>, <see cref="User.Password"/> and <see cref="User.Nickname"/> will be used.
- /// If null, then not change.
- /// Other fields are ignored.
/// Version will increase if password is changed.
- ///
- /// <see cref="User.Username"/> must be a valid username if set.
- /// <see cref="User.Password"/> can't be empty if set.
- /// <see cref="User.Nickname"/> must be a valid nickname if set.
- ///
- /// </remarks>
- /// <seealso cref="ModifyUser(string, User)"/>
- Task<User> ModifyUser(long id, User? info);
-
- /// <summary>
- /// Modify a user's info.
- /// </summary>
- /// <param name="username">The username of the user.</param>
- /// <param name="info">The new info. May be null.</param>
- /// <returns>The new user info.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format or some fields in <paramref name="info"/> is bad.</exception>
- /// <exception cref="UserNotExistException">Thrown when user with given id does not exist.</exception>
- /// <exception cref="EntityAlreadyExistException">Thrown when user with the newusername already exist.</exception>
- /// <remarks>
- /// Only <see cref="User.Administrator"/>, <see cref="User.Password"/> and <see cref="User.Nickname"/> will be used.
- /// If null, then not change.
- /// Other fields are ignored.
- /// After modified, even if nothing is changed, version will increase.
- ///
- /// <see cref="User.Username"/> must be a valid username if set.
- /// <see cref="User.Password"/> can't be empty if set.
- /// <see cref="User.Nickname"/> must be a valid nickname if set.
- ///
- /// Note: Whether <see cref="User.Version"/> is set or not, version will increase and not set to the specified value if there is one.
/// </remarks>
- /// <seealso cref="ModifyUser(long, User)"/>
- Task<User> ModifyUser(string username, User? info);
+ Task<User> ModifyUser(long id, ModifyUserParams? param);
/// <summary>
/// Try to change a user's password with old password.
@@ -146,15 +102,18 @@ namespace Timeline.Services private readonly DatabaseContext _databaseContext;
private readonly IPasswordService _passwordService;
+ private readonly IUserPermissionService _userPermissionService;
private readonly UsernameValidator _usernameValidator = new UsernameValidator();
private readonly NicknameValidator _nicknameValidator = new NicknameValidator();
- public UserService(ILogger<UserService> logger, DatabaseContext databaseContext, IPasswordService passwordService, IClock clock)
+
+ public UserService(ILogger<UserService> logger, DatabaseContext databaseContext, IPasswordService passwordService, IClock clock, IUserPermissionService userPermissionService)
{
_logger = logger;
_clock = clock;
_databaseContext = databaseContext;
_passwordService = passwordService;
+ _userPermissionService = userPermissionService;
}
private void CheckUsernameFormat(string username, string? paramName)
@@ -186,13 +145,15 @@ namespace Timeline.Services throw new EntityAlreadyExistException(EntityNames.User, ExceptionUsernameConflict);
}
- private static User CreateUserFromEntity(UserEntity entity)
+ private async Task<User> CreateUserFromEntity(UserEntity entity)
{
+ var permission = await _userPermissionService.GetPermissionsOfUserAsync(entity.Id);
return new User
{
UniqueId = entity.UniqueId,
Username = entity.Username,
- Administrator = UserRoleConvert.ToBool(entity.Roles),
+ Administrator = permission.Contains(UserPermission.UserManagement),
+ Permissions = permission,
Nickname = string.IsNullOrEmpty(entity.Nickname) ? entity.Username : entity.Nickname,
Id = entity.Id,
Version = entity.Version,
@@ -220,32 +181,17 @@ namespace Timeline.Services if (!_passwordService.VerifyPassword(entity.Password, password))
throw new BadPasswordException(password);
- return CreateUserFromEntity(entity);
+ return await CreateUserFromEntity(entity);
}
- public async Task<User> GetUserById(long id)
+ public async Task<User> GetUser(long id)
{
var user = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
if (user == null)
throw new UserNotExistException(id);
- return CreateUserFromEntity(user);
- }
-
- public async Task<User> GetUserByUsername(string username)
- {
- if (username == null)
- throw new ArgumentNullException(nameof(username));
-
- CheckUsernameFormat(username, nameof(username));
-
- var entity = await _databaseContext.Users.Where(user => user.Username == username).SingleOrDefaultAsync();
-
- if (entity == null)
- throw new UserNotExistException(username);
-
- return CreateUserFromEntity(entity);
+ return await CreateUserFromEntity(user);
}
public async Task<long> GetUserIdByUsername(string username)
@@ -263,78 +209,69 @@ namespace Timeline.Services return entity.Id;
}
- public async Task<User[]> GetUsers()
+ public async Task<List<User>> GetUsers()
{
- var entities = await _databaseContext.Users.ToArrayAsync();
- return entities.Select(user => CreateUserFromEntity(user)).ToArray();
+ List<User> result = new();
+ foreach (var entity in await _databaseContext.Users.ToArrayAsync())
+ {
+ result.Add(await CreateUserFromEntity(entity));
+ }
+ return result;
}
- public async Task<User> CreateUser(User info)
+ public async Task<User> CreateUser(string username, string password)
{
- if (info == null)
- throw new ArgumentNullException(nameof(info));
-
- if (info.Username == null)
- throw new ArgumentException(ExceptionUsernameNull, nameof(info));
- CheckUsernameFormat(info.Username, nameof(info));
-
- if (info.Password == null)
- throw new ArgumentException(ExceptionPasswordNull, nameof(info));
- CheckPasswordFormat(info.Password, nameof(info));
-
- if (info.Nickname != null)
- CheckNicknameFormat(info.Nickname, nameof(info));
+ if (username == null)
+ throw new ArgumentNullException(nameof(username));
+ if (password == null)
+ throw new ArgumentNullException(nameof(password));
- var username = info.Username;
+ CheckUsernameFormat(username, nameof(username));
+ CheckPasswordFormat(password, nameof(password));
var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username);
if (conflict)
ThrowUsernameConflict();
- var administrator = info.Administrator ?? false;
- var password = info.Password;
- var nickname = info.Nickname;
-
var newEntity = new UserEntity
{
Username = username,
Password = _passwordService.HashPassword(password),
- Roles = UserRoleConvert.ToString(administrator),
- Nickname = nickname,
+ Roles = "",
Version = 1
};
_databaseContext.Users.Add(newEntity);
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(Log.Format(LogDatabaseCreate,
- ("Id", newEntity.Id), ("Username", username), ("Administrator", administrator)));
+ _logger.LogInformation(Log.Format(LogDatabaseCreate, ("Id", newEntity.Id), ("Username", username)));
- return CreateUserFromEntity(newEntity);
+ return await CreateUserFromEntity(newEntity);
}
- private void ValidateModifyUserInfo(User? info)
+ public async Task<User> ModifyUser(long id, ModifyUserParams? param)
{
- if (info != null)
+ if (param != null)
{
- if (info.Username != null)
- CheckUsernameFormat(info.Username, nameof(info));
+ if (param.Username != null)
+ CheckUsernameFormat(param.Username, nameof(param));
- if (info.Password != null)
- CheckPasswordFormat(info.Password, nameof(info));
+ if (param.Password != null)
+ CheckPasswordFormat(param.Password, nameof(param));
- if (info.Nickname != null)
- CheckNicknameFormat(info.Nickname, nameof(info));
+ if (param.Nickname != null)
+ CheckNicknameFormat(param.Nickname, nameof(param));
}
- }
- private async Task UpdateUserEntity(UserEntity entity, User? info)
- {
- if (info != null)
+ var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
+ if (entity == null)
+ throw new UserNotExistException(id);
+
+ if (param != null)
{
var now = _clock.GetCurrentTime();
bool updateLastModified = false;
- var username = info.Username;
+ var username = param.Username;
if (username != null && username != entity.Username)
{
var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username);
@@ -346,21 +283,14 @@ namespace Timeline.Services updateLastModified = true;
}
- var password = info.Password;
+ var password = param.Password;
if (password != null)
{
entity.Password = _passwordService.HashPassword(password);
entity.Version += 1;
}
- var administrator = info.Administrator;
- if (administrator.HasValue && UserRoleConvert.ToBool(entity.Roles) != administrator)
- {
- entity.Roles = UserRoleConvert.ToString(administrator.Value);
- updateLastModified = true;
- }
-
- var nickname = info.Nickname;
+ var nickname = param.Nickname;
if (nickname != null && nickname != entity.Nickname)
{
entity.Nickname = nickname;
@@ -371,44 +301,12 @@ namespace Timeline.Services {
entity.LastModified = now;
}
- }
- }
+ await _databaseContext.SaveChangesAsync();
+ _logger.LogInformation(LogDatabaseUpdate, ("Id", id));
+ }
- public async Task<User> ModifyUser(long id, User? info)
- {
- ValidateModifyUserInfo(info);
-
- var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
- if (entity == null)
- throw new UserNotExistException(id);
-
- await UpdateUserEntity(entity, info);
-
- await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(LogDatabaseUpdate, ("Id", id));
-
- return CreateUserFromEntity(entity);
- }
-
- public async Task<User> ModifyUser(string username, User? info)
- {
- if (username == null)
- throw new ArgumentNullException(nameof(username));
- CheckUsernameFormat(username, nameof(username));
-
- ValidateModifyUserInfo(info);
-
- var entity = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync();
- if (entity == null)
- throw new UserNotExistException(username);
-
- await UpdateUserEntity(entity, info);
-
- await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(LogDatabaseUpdate, ("Username", username));
-
- return CreateUserFromEntity(entity);
+ return await CreateUserFromEntity(entity);
}
public async Task ChangePassword(long id, string oldPassword, string newPassword)
diff --git a/BackEnd/Timeline/Services/UserTokenManager.cs b/BackEnd/Timeline/Services/UserTokenManager.cs index 813dae67..09ecd19c 100644 --- a/BackEnd/Timeline/Services/UserTokenManager.cs +++ b/BackEnd/Timeline/Services/UserTokenManager.cs @@ -66,7 +66,7 @@ namespace Timeline.Services throw new ArgumentNullException(nameof(password));
var user = await _userService.VerifyCredential(username, password);
- var token = _userTokenService.GenerateToken(new UserTokenInfo { Id = user.Id!.Value, Version = user.Version!.Value, ExpireAt = expireAt });
+ var token = _userTokenService.GenerateToken(new UserTokenInfo { Id = user.Id, Version = user.Version, ExpireAt = expireAt });
return new UserTokenCreateResult { Token = token, User = user };
}
@@ -86,10 +86,10 @@ namespace Timeline.Services throw new UserTokenTimeExpireException(token, tokenInfo.ExpireAt.Value, currentTime);
}
- var user = await _userService.GetUserById(tokenInfo.Id);
+ var user = await _userService.GetUser(tokenInfo.Id);
if (tokenInfo.Version < user.Version)
- throw new UserTokenBadVersionException(token, tokenInfo.Version, user.Version.Value);
+ throw new UserTokenBadVersionException(token, tokenInfo.Version, user.Version);
return user;
}
diff --git a/BackEnd/Timeline/Startup.cs b/BackEnd/Timeline/Startup.cs index 576836eb..532c63d0 100644 --- a/BackEnd/Timeline/Startup.cs +++ b/BackEnd/Timeline/Startup.cs @@ -1,4 +1,5 @@ using AutoMapper;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
@@ -72,6 +73,8 @@ namespace Timeline .AddScheme<MyAuthenticationOptions, MyAuthenticationHandler>(AuthenticationConstants.Scheme, AuthenticationConstants.DisplayName, o => { });
services.AddAuthorization();
+ services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
+
services.AddSingleton<IPathProvider, PathProvider>();
services.AddSingleton<IDatabaseBackupService, DatabaseBackupService>();
@@ -85,6 +88,7 @@ namespace Timeline services.AddScoped<IUserDeleteService, UserDeleteService>();
services.AddScoped<IUserTokenService, JwtUserTokenService>();
services.AddScoped<IUserTokenManager, UserTokenManager>();
+ services.AddScoped<IUserPermissionService, UserPermissionService>();
services.AddScoped<IETagGenerator, ETagGenerator>();
services.AddScoped<IDataManager, DataManager>();
|