diff options
55 files changed, 2056 insertions, 617 deletions
diff --git a/BackEnd/Timeline.Tests/Helpers/TestApplication.cs b/BackEnd/Timeline.Tests/Helpers/TestApplication.cs index 684ffe2c..33d8b318 100644 --- a/BackEnd/Timeline.Tests/Helpers/TestApplication.cs +++ b/BackEnd/Timeline.Tests/Helpers/TestApplication.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
-using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
diff --git a/BackEnd/Timeline.Tests/Helpers/TestClock.cs b/BackEnd/Timeline.Tests/Helpers/TestClock.cs index 34adb245..a04a3eb6 100644 --- a/BackEnd/Timeline.Tests/Helpers/TestClock.cs +++ b/BackEnd/Timeline.Tests/Helpers/TestClock.cs @@ -1,7 +1,4 @@ using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
using Timeline.Services;
namespace Timeline.Tests.Helpers
diff --git a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs index f0c26180..74db74aa 100644 --- a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs +++ b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Logging.Abstractions; using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Migrations;
-using Timeline.Models;
using Timeline.Services;
using Xunit;
@@ -36,23 +35,13 @@ namespace Timeline.Tests.Helpers if (_createUser)
{
var passwordService = new PasswordService();
- var userService = new UserService(NullLogger<UserService>.Instance, context, passwordService, new Clock());
+ var userService = new UserService(NullLogger<UserService>.Instance, context, passwordService, new Clock(), new UserPermissionService(context));
- await userService.CreateUser(new User
- {
- Username = "admin",
- Password = "adminpw",
- Administrator = true,
- Nickname = "administrator"
- });
+ var admin = await userService.CreateUser("admin", "adminpw");
+ await userService.ModifyUser(admin.Id, new ModifyUserParams() { Nickname = "administrator" });
- await userService.CreateUser(new User
- {
- Username = "user",
- Password = "userpw",
- Administrator = false,
- Nickname = "imuser"
- });
+ var user = await userService.CreateUser("user", "userpw");
+ await userService.ModifyUser(user.Id, new ModifyUserParams() { Nickname = "imuser" });
}
}
}
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.Tests/IntegratedTests/IntegratedTestBase.cs b/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs index 7cf27297..f75ce69c 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs @@ -7,7 +7,6 @@ using System.Net.Http; using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
-using Timeline.Models;
using Timeline.Models.Converters;
using Timeline.Models.Http;
using Timeline.Services;
@@ -60,26 +59,14 @@ namespace Timeline.Tests.IntegratedTests using (var scope = TestApp.Host.Services.CreateScope())
{
- var users = new List<User>()
+ var users = new List<(string username, string password, string nickname)>()
{
- new User
- {
- Username = "admin",
- Password = "adminpw",
- Administrator = true,
- Nickname = "administrator"
- }
+ ("admin", "adminpw", "administrator")
};
for (int i = 1; i <= _userCount; i++)
{
- users.Add(new User
- {
- Username = $"user{i}",
- Password = $"user{i}pw",
- Administrator = false,
- Nickname = $"imuser{i}"
- });
+ users.Add(($"user{i}", $"user{i}pw", $"imuser{i}"));
}
var userInfoList = new List<UserInfo>();
@@ -87,7 +74,9 @@ namespace Timeline.Tests.IntegratedTests var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
foreach (var user in users)
{
- await userService.CreateUser(user);
+ var (username, password, nickname) = user;
+ var u = await userService.CreateUser(username, password);
+ await userService.ModifyUser(u.Id, new ModifyUserParams() { Nickname = nickname });
}
using var client = await CreateDefaultClient();
@@ -99,7 +88,7 @@ namespace Timeline.Tests.IntegratedTests options.Converters.Add(new JsonDateTimeConverter());
foreach (var user in users)
{
- var s = await client.GetStringAsync($"users/{user.Username}");
+ var s = await client.GetStringAsync($"users/{user.username}");
userInfoList.Add(JsonSerializer.Deserialize<UserInfo>(s, options));
}
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs index 480d66cd..9aac8188 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.DependencyInjection; using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
-using Timeline.Models;
using Timeline.Models.Http;
using Timeline.Services;
using Timeline.Tests.Helpers;
@@ -103,7 +102,8 @@ namespace Timeline.Tests.IntegratedTests {
// create a user for test
var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
- await userService.ModifyUser("user1", new User { Password = "user1pw" });
+ var id = await userService.GetUserIdByUsername("user1");
+ await userService.ModifyUser(id, new ModifyUserParams { Password = "user1pw" });
}
(await client.PostAsJsonAsync(VerifyTokenUrl,
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/UserAvatarTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/UserAvatarTest.cs index 66a12573..854a4ee6 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/UserAvatarTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/UserAvatarTest.cs @@ -10,7 +10,6 @@ using System.IO; using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
-using System.Net.Mime;
using System.Threading.Tasks;
using Timeline.Models.Http;
using Timeline.Services;
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/UserPermissionTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/UserPermissionTest.cs new file mode 100644 index 00000000..cf27a6c6 --- /dev/null +++ b/BackEnd/Timeline.Tests/IntegratedTests/UserPermissionTest.cs @@ -0,0 +1,308 @@ +using FluentAssertions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http.Json;
+using System.Threading.Tasks;
+using Timeline.Models.Http;
+using Timeline.Services;
+using Xunit;
+
+namespace Timeline.Tests.IntegratedTests
+{
+ public class UserPermissionTest : IntegratedTestBase
+ {
+ public UserPermissionTest() : base(3) { }
+
+ [Fact]
+ public async Task RootUserShouldReturnAllPermissions()
+ {
+ using var client = await CreateDefaultClient();
+ var res = await client.GetAsync("users/admin");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEquivalentTo(Enum.GetNames<UserPermission>());
+ }
+
+ [Fact]
+ public async Task NonRootUserShouldReturnNonPermissions()
+ {
+ using var client = await CreateDefaultClient();
+ var res = await client.GetAsync("users/user1");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEmpty();
+ }
+
+ public static IEnumerable<object[]> EveryPermissionTestData()
+ {
+ return Enum.GetValues<UserPermission>().Select(p => new object[] { p });
+ }
+
+ [Theory]
+ [MemberData(nameof(EveryPermissionTestData))]
+ public async Task ModifyRootUserPermissionShouldHaveNoEffect(UserPermission permission)
+ {
+ using var client = await CreateClientAsAdministrator();
+
+ {
+ var res = await client.DeleteAsync($"users/admin/permissions/{permission}");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ {
+ var res = await client.GetAsync("users/admin");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEquivalentTo(Enum.GetNames<UserPermission>());
+ }
+
+ {
+ var res = await client.PutAsync($"users/admin/permissions/{permission}", null);
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ {
+ var res = await client.GetAsync("users/admin");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEquivalentTo(Enum.GetNames<UserPermission>());
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EveryPermissionTestData))]
+ public async Task ModifyUserPermissionShouldWork(UserPermission permission)
+ {
+ using var client = await CreateClientAsAdministrator();
+
+ {
+ var res = await client.PutAsync($"users/user1/permissions/{permission}", null);
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ {
+ var res = await client.GetAsync("users/user1");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEquivalentTo(permission.ToString());
+ }
+
+ {
+ var res = await client.DeleteAsync($"users/user1/permissions/{permission}");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ {
+ var res = await client.GetAsync("users/user1");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEmpty();
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EveryPermissionTestData))]
+ public async Task PutExistPermissionShouldHaveNoEffect(UserPermission permission)
+ {
+ using var client = await CreateClientAsAdministrator();
+
+ {
+ var res = await client.PutAsync($"users/user1/permissions/{permission}", null);
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ {
+ var res = await client.GetAsync("users/user1");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEquivalentTo(permission.ToString());
+ }
+
+ {
+ var res = await client.PutAsync($"users/user1/permissions/{permission}", null);
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ {
+ var res = await client.GetAsync("users/user1");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEquivalentTo(permission.ToString());
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EveryPermissionTestData))]
+ public async Task DeleteNonExistPermissionShouldHaveNoEffect(UserPermission permission)
+ {
+ using var client = await CreateClientAsAdministrator();
+
+ {
+ var res = await client.DeleteAsync($"users/user1/permissions/{permission}");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ {
+ var res = await client.GetAsync("users/user1");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEmpty();
+ }
+ }
+
+ [Fact]
+ public async Task AGeneralTest()
+ {
+ using var client = await CreateClientAsAdministrator();
+
+ {
+ var res = await client.PutAsync($"users/user1/permissions/{UserPermission.AllTimelineManagement}", null);
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ {
+ var res = await client.GetAsync("users/user1");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEquivalentTo(UserPermission.AllTimelineManagement.ToString());
+ }
+
+ {
+ var res = await client.PutAsync($"users/user1/permissions/{UserPermission.HighlightTimelineManangement}", null);
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ {
+ var res = await client.GetAsync("users/user1");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEquivalentTo(UserPermission.AllTimelineManagement.ToString(),
+ UserPermission.HighlightTimelineManangement.ToString());
+ }
+
+ {
+ var res = await client.PutAsync($"users/user1/permissions/{UserPermission.UserManagement}", null);
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ {
+ var res = await client.GetAsync("users/user1");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEquivalentTo(
+ UserPermission.AllTimelineManagement.ToString(),
+ UserPermission.HighlightTimelineManangement.ToString(),
+ UserPermission.UserManagement.ToString());
+ }
+
+ {
+ var res = await client.DeleteAsync($"users/user1/permissions/{UserPermission.HighlightTimelineManangement}");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ {
+ var res = await client.GetAsync("users/user1");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEquivalentTo(
+ UserPermission.AllTimelineManagement.ToString(),
+ UserPermission.UserManagement.ToString());
+ }
+
+ {
+ var res = await client.DeleteAsync($"users/user1/permissions/{UserPermission.AllTimelineManagement}");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ {
+ var res = await client.GetAsync("users/user1");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEquivalentTo(UserPermission.UserManagement.ToString());
+ }
+
+ {
+ var res = await client.PutAsync($"users/user1/permissions/{UserPermission.HighlightTimelineManangement}", null);
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ {
+ var res = await client.GetAsync("users/user1");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEquivalentTo(
+ UserPermission.HighlightTimelineManangement.ToString(), UserPermission.UserManagement.ToString());
+ }
+
+ {
+ var res = await client.DeleteAsync($"users/user1/permissions/{UserPermission.HighlightTimelineManangement}");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ {
+ var res = await client.GetAsync("users/user1");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEquivalentTo(UserPermission.UserManagement.ToString());
+ }
+
+ {
+ var res = await client.DeleteAsync($"users/user1/permissions/{UserPermission.UserManagement}");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ {
+ var res = await client.GetAsync("users/user1");
+ res.StatusCode.Should().Be(HttpStatusCode.OK);
+ var body = await res.Content.ReadFromJsonAsync<UserInfo>();
+ body.Permissions.Should().BeEmpty();
+ }
+ }
+
+ [Theory]
+ [InlineData("users/user1/permissions/aaa")]
+ [InlineData("users/!!!/permissions/UserManagement")]
+ public async Task InvalidModel(string url)
+ {
+ using var client = await CreateClientAsAdministrator();
+
+ {
+ var res = await client.PutAsync(url, null);
+ res.StatusCode.Should().Be(HttpStatusCode.BadRequest);
+ var body = await res.Content.ReadFromJsonAsync<CommonResponse>();
+ body.Code.Should().Be(ErrorCodes.Common.InvalidModel);
+ }
+
+ {
+ var res = await client.DeleteAsync(url);
+ res.StatusCode.Should().Be(HttpStatusCode.BadRequest);
+ var body = await res.Content.ReadFromJsonAsync<CommonResponse>();
+ body.Code.Should().Be(ErrorCodes.Common.InvalidModel);
+ }
+ }
+
+ [Fact]
+ public async Task UserNotExist()
+ {
+ using var client = await CreateClientAsAdministrator();
+
+ const string url = "users/user123/permissions/UserManagement";
+
+ {
+ var res = await client.PutAsync(url, null);
+ res.StatusCode.Should().Be(HttpStatusCode.NotFound);
+ var body = await res.Content.ReadFromJsonAsync<CommonResponse>();
+ body.Code.Should().Be(ErrorCodes.UserCommon.NotExist);
+ }
+
+ {
+ var res = await client.DeleteAsync(url);
+ res.StatusCode.Should().Be(HttpStatusCode.NotFound);
+ var body = await res.Content.ReadFromJsonAsync<CommonResponse>();
+ body.Code.Should().Be(ErrorCodes.UserCommon.NotExist);
+ }
+ }
+ }
+}
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/UserTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/UserTest.cs index 9dfcc6a5..329e53f5 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/UserTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/UserTest.cs @@ -2,6 +2,7 @@ using FluentAssertions; using System.Collections.Generic;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Json;
using System.Threading.Tasks;
using Timeline.Models.Http;
using Timeline.Tests.Helpers;
@@ -129,13 +130,11 @@ namespace Timeline.Tests.IntegratedTests {
Username = "newuser",
Password = "newpw",
- Administrator = true,
Nickname = "aaa"
});
var body = res.Should().HaveStatusCode(200)
.And.HaveJsonBody<UserInfo>()
.Which;
- body.Administrator.Should().Be(true);
body.Nickname.Should().Be("aaa");
}
@@ -144,14 +143,14 @@ namespace Timeline.Tests.IntegratedTests var body = res.Should().HaveStatusCode(200)
.And.HaveJsonBody<UserInfo>()
.Which;
- body.Administrator.Should().Be(true);
body.Nickname.Should().Be("aaa");
}
{
+ var token = userClient.DefaultRequestHeaders.Authorization.Parameter;
// Token should expire.
- var res = await userClient.GetAsync("testing/auth/Authorize");
- res.Should().HaveStatusCode(HttpStatusCode.Unauthorized);
+ var res = await userClient.PostAsJsonAsync<VerifyTokenRequest>("token/verify", new() { Token = token });
+ res.Should().HaveStatusCode(HttpStatusCode.BadRequest);
}
{
@@ -236,14 +235,6 @@ namespace Timeline.Tests.IntegratedTests }
[Fact]
- public async Task Patch_Administrator_Forbid()
- {
- using var client = await CreateClientAsUser();
- var res = await client.PatchAsJsonAsync("users/user1", new UserPatchRequest { Administrator = true });
- res.Should().HaveStatusCode(HttpStatusCode.Forbidden);
- }
-
- [Fact]
public async Task Delete_Deleted()
{
using var client = await CreateClientAsAdministrator();
@@ -301,22 +292,16 @@ namespace Timeline.Tests.IntegratedTests {
Username = "aaa",
Password = "bbb",
- Administrator = true,
- Nickname = "ccc"
});
var body = res.Should().HaveStatusCode(200)
.And.HaveJsonBody<UserInfo>().Which;
body.Username.Should().Be("aaa");
- body.Nickname.Should().Be("ccc");
- body.Administrator.Should().BeTrue();
}
{
var res = await client.GetAsync("users/aaa");
var body = res.Should().HaveStatusCode(200)
.And.HaveJsonBody<UserInfo>().Which;
body.Username.Should().Be("aaa");
- body.Nickname.Should().Be("ccc");
- body.Administrator.Should().BeTrue();
}
{
// Test password.
@@ -326,12 +311,10 @@ namespace Timeline.Tests.IntegratedTests public static IEnumerable<object[]> Op_CreateUser_InvalidModel_Data()
{
- yield return new[] { new CreateUserRequest { Username = "aaa", Password = "bbb" } };
- yield return new[] { new CreateUserRequest { Username = "aaa", Administrator = true } };
- yield return new[] { new CreateUserRequest { Password = "bbb", Administrator = true } };
- yield return new[] { new CreateUserRequest { Username = "a!a", Password = "bbb", Administrator = true } };
- yield return new[] { new CreateUserRequest { Username = "aaa", Password = "", Administrator = true } };
- yield return new[] { new CreateUserRequest { Username = "aaa", Password = "bbb", Administrator = true, Nickname = new string('a', 40) } };
+ yield return new[] { new CreateUserRequest { Username = "aaa" } };
+ yield return new[] { new CreateUserRequest { Password = "bbb" } };
+ yield return new[] { new CreateUserRequest { Username = "a!a", Password = "bbb" } };
+ yield return new[] { new CreateUserRequest { Username = "aaa", Password = "" } };
}
[Theory]
@@ -354,7 +337,6 @@ namespace Timeline.Tests.IntegratedTests {
Username = "user1",
Password = "bbb",
- Administrator = false
});
res.Should().HaveStatusCode(400)
.And.HaveCommonBody(ErrorCodes.UserController.UsernameConflict);
@@ -370,7 +352,6 @@ namespace Timeline.Tests.IntegratedTests {
Username = "aaa",
Password = "bbb",
- Administrator = false
});
res.Should().HaveStatusCode(HttpStatusCode.Unauthorized);
}
@@ -385,7 +366,6 @@ namespace Timeline.Tests.IntegratedTests {
Username = "aaa",
Password = "bbb",
- Administrator = false
});
res.Should().HaveStatusCode(HttpStatusCode.Forbidden);
}
diff --git a/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs b/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs new file mode 100644 index 00000000..7c97158c --- /dev/null +++ b/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs @@ -0,0 +1,49 @@ +using System.Threading.Tasks;
+using Timeline.Entities;
+using Timeline.Tests.Helpers;
+using Xunit;
+
+namespace Timeline.Tests.Services
+{
+ public abstract class DatabaseBasedTest : IAsyncLifetime
+ {
+ protected TestDatabase TestDatabase { get; }
+ protected DatabaseContext Database { get; private set; }
+
+ protected DatabaseBasedTest(bool databaseCreateUsers = true)
+ {
+ TestDatabase = new TestDatabase(databaseCreateUsers);
+ }
+
+ public async Task InitializeAsync()
+ {
+ await TestDatabase.InitializeAsync();
+ Database = TestDatabase.CreateContext();
+ await OnDatabaseCreatedAsync();
+ OnDatabaseCreated();
+ }
+
+ public async Task DisposeAsync()
+ {
+ BeforeDatabaseDestroy();
+ await BeforeDatabaseDestroyAsync();
+ await Database.DisposeAsync();
+ await TestDatabase.DisposeAsync();
+ }
+
+
+ protected virtual void OnDatabaseCreated() { }
+ protected virtual void BeforeDatabaseDestroy() { }
+
+
+ protected virtual Task OnDatabaseCreatedAsync()
+ {
+ return Task.CompletedTask;
+ }
+
+ protected virtual Task BeforeDatabaseDestroyAsync()
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs b/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs index 5a774b78..73fdd32f 100644 --- a/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs +++ b/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-using Timeline.Entities;
using Timeline.Models;
using Timeline.Services;
using Timeline.Services.Exceptions;
@@ -13,12 +12,8 @@ using Xunit; namespace Timeline.Tests.Services
{
- public class TimelineServiceTest : IAsyncLifetime, IDisposable
+ public class TimelineServiceTest : DatabaseBasedTest, IDisposable
{
- private readonly TestDatabase _testDatabase = new TestDatabase();
-
- private DatabaseContext _databaseContext;
-
private readonly PasswordService _passwordService = new PasswordService();
private readonly ETagGenerator _eTagGenerator = new ETagGenerator();
@@ -29,6 +24,8 @@ namespace Timeline.Tests.Services private DataManager _dataManager;
+ private UserPermissionService _userPermissionService;
+
private UserService _userService;
private TimelineService _timelineService;
@@ -39,20 +36,13 @@ namespace Timeline.Tests.Services {
}
- public async Task InitializeAsync()
- {
- await _testDatabase.InitializeAsync();
- _databaseContext = _testDatabase.CreateContext();
- _dataManager = new DataManager(_databaseContext, _eTagGenerator);
- _userService = new UserService(NullLogger<UserService>.Instance, _databaseContext, _passwordService, _clock);
- _timelineService = new TimelineService(NullLogger<TimelineService>.Instance, _databaseContext, _dataManager, _userService, _imageValidator, _clock);
- _userDeleteService = new UserDeleteService(NullLogger<UserDeleteService>.Instance, _databaseContext, _timelineService);
- }
-
- public async Task DisposeAsync()
+ protected override void OnDatabaseCreated()
{
- await _testDatabase.DisposeAsync();
- await _databaseContext.DisposeAsync();
+ _dataManager = new DataManager(Database, _eTagGenerator);
+ _userPermissionService = new UserPermissionService(Database);
+ _userService = new UserService(NullLogger<UserService>.Instance, Database, _passwordService, _clock, _userPermissionService);
+ _timelineService = new TimelineService(NullLogger<TimelineService>.Instance, Database, _dataManager, _userService, _imageValidator, _clock);
+ _userDeleteService = new UserDeleteService(NullLogger<UserDeleteService>.Instance, Database, _timelineService);
}
public void Dispose()
@@ -220,13 +210,13 @@ namespace Timeline.Tests.Services }
{
- await _userService.ModifyUser(userId, new User { Nickname = "haha" });
+ await _userService.ModifyUser(userId, new ModifyUserParams { Nickname = "haha" });
var posts = await _timelineService.GetPosts(timelineName, time2);
posts.Should().HaveCount(0);
}
{
- await _userService.ModifyUser(userId, new User { Username = "haha" });
+ await _userService.ModifyUser(userId, new ModifyUserParams { Username = "haha" });
var posts = await _timelineService.GetPosts(timelineName, time2);
posts.Should().HaveCount(4);
}
diff --git a/BackEnd/Timeline.Tests/Services/UserPermissionServiceTest.cs b/BackEnd/Timeline.Tests/Services/UserPermissionServiceTest.cs new file mode 100644 index 00000000..cea11b34 --- /dev/null +++ b/BackEnd/Timeline.Tests/Services/UserPermissionServiceTest.cs @@ -0,0 +1,118 @@ +using FluentAssertions;
+using System;
+using System.Threading.Tasks;
+using Timeline.Services;
+using Timeline.Services.Exceptions;
+using Xunit;
+
+namespace Timeline.Tests.Services
+{
+ public class UserPermissionServiceTest : DatabaseBasedTest
+ {
+ private UserPermissionService _service;
+
+ public UserPermissionServiceTest()
+ {
+
+ }
+
+ protected override void OnDatabaseCreated()
+ {
+ _service = new UserPermissionService(Database);
+ }
+
+ [Fact]
+ public async Task GetPermissionsOfRootUserShouldReturnAll()
+ {
+ var permission = await _service.GetPermissionsOfUserAsync(1);
+ permission.Should().BeEquivalentTo(Enum.GetValues<UserPermission>());
+ }
+
+ [Fact]
+ public async Task GetPermissionsOfNonRootUserShouldReturnNone()
+ {
+ var permission = await _service.GetPermissionsOfUserAsync(2);
+ permission.Should().BeEmpty();
+ }
+
+ [Fact]
+ public async Task GetPermissionsOfInexistentUserShouldThrow()
+ {
+ await _service.Awaiting(s => s.GetPermissionsOfUserAsync(10)).Should().ThrowAsync<UserNotExistException>();
+ }
+
+ [Fact]
+ public async Task GetPermissionsOfInexistentUserShouldNotThrowIfNotCheck()
+ {
+ await _service.Awaiting(s => s.GetPermissionsOfUserAsync(10, false)).Should().NotThrowAsync();
+ }
+
+ [Fact]
+ public async Task ModifyPermissionOnRootUserShouldHaveNoEffect()
+ {
+ await _service.AddPermissionToUserAsync(1, UserPermission.AllTimelineManagement);
+ {
+ var permission = await _service.GetPermissionsOfUserAsync(1);
+ permission.Should().BeEquivalentTo(Enum.GetValues<UserPermission>());
+ }
+ await _service.RemovePermissionFromUserAsync(1, UserPermission.AllTimelineManagement);
+ {
+ var permission = await _service.GetPermissionsOfUserAsync(1);
+ permission.Should().BeEquivalentTo(Enum.GetValues<UserPermission>());
+ }
+ }
+
+ [Fact]
+ public async Task ModifyPermissionOnNonRootUserShouldWork()
+ {
+ await _service.AddPermissionToUserAsync(2, UserPermission.AllTimelineManagement);
+ {
+ var permission = await _service.GetPermissionsOfUserAsync(2);
+ permission.Should().BeEquivalentTo(UserPermission.AllTimelineManagement);
+ }
+ await _service.AddPermissionToUserAsync(2, UserPermission.HighlightTimelineManangement);
+ {
+ var permission = await _service.GetPermissionsOfUserAsync(2);
+ permission.Should().BeEquivalentTo(UserPermission.AllTimelineManagement, UserPermission.HighlightTimelineManangement);
+ }
+
+ // Add duplicate permission should work.
+ await _service.AddPermissionToUserAsync(2, UserPermission.HighlightTimelineManangement);
+ {
+ var permission = await _service.GetPermissionsOfUserAsync(2);
+ permission.Should().BeEquivalentTo(UserPermission.AllTimelineManagement, UserPermission.HighlightTimelineManangement);
+ }
+
+ await _service.RemovePermissionFromUserAsync(2, UserPermission.HighlightTimelineManangement);
+ {
+ var permission = await _service.GetPermissionsOfUserAsync(2);
+ permission.Should().BeEquivalentTo(UserPermission.AllTimelineManagement);
+ }
+
+ // Remove non-owned permission should work.
+ await _service.RemovePermissionFromUserAsync(2, UserPermission.HighlightTimelineManangement);
+ {
+ var permission = await _service.GetPermissionsOfUserAsync(2);
+ permission.Should().BeEquivalentTo(UserPermission.AllTimelineManagement);
+ }
+ }
+
+ [Fact]
+ public async Task AddPermissionToInexistentUserShouldThrown()
+ {
+ await _service.Awaiting(s => s.AddPermissionToUserAsync(10, UserPermission.HighlightTimelineManangement)).Should().ThrowAsync<UserNotExistException>();
+ }
+
+ [Fact]
+ public async Task RemovePermissionFromInexistentUserShouldThrown()
+ {
+ await _service.Awaiting(s => s.RemovePermissionFromUserAsync(10, UserPermission.HighlightTimelineManangement)).Should().ThrowAsync<UserNotExistException>();
+ }
+
+ [Fact]
+ public async Task RemovePermissionFromInexistentUserShouldNotThrownIfNotCheck()
+ {
+ await _service.Awaiting(s => s.RemovePermissionFromUserAsync(10, UserPermission.HighlightTimelineManangement, false)).Should().NotThrowAsync();
+ }
+ }
+}
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/ControllerAuthExtensions.cs b/BackEnd/Timeline/Controllers/ControllerAuthExtensions.cs index 00a65454..9096978d 100644 --- a/BackEnd/Timeline/Controllers/ControllerAuthExtensions.cs +++ b/BackEnd/Timeline/Controllers/ControllerAuthExtensions.cs @@ -2,15 +2,16 @@ using System;
using System.Security.Claims;
using Timeline.Auth;
+using Timeline.Services;
using static Timeline.Resources.Controllers.ControllerAuthExtensions;
namespace Timeline.Controllers
{
public static class ControllerAuthExtensions
{
- public static bool IsAdministrator(this ControllerBase controller)
+ public static bool UserHasPermission(this ControllerBase controller, UserPermission permission)
{
- return controller.User != null && controller.User.IsAdministrator();
+ return controller.User != null && controller.User.HasPermission(permission);
}
public static long GetUserId(this ControllerBase controller)
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/Controllers/TimelineController.cs b/BackEnd/Timeline/Controllers/TimelineController.cs index 9a3147ea..45060b5d 100644 --- a/BackEnd/Timeline/Controllers/TimelineController.cs +++ b/BackEnd/Timeline/Controllers/TimelineController.cs @@ -43,6 +43,8 @@ namespace Timeline.Controllers _mapper = mapper;
}
+ private bool UserHasAllTimelineManagementPermission => this.UserHasPermission(UserPermission.AllTimelineManagement);
+
/// <summary>
/// List all timelines.
/// </summary>
@@ -180,7 +182,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<List<TimelinePostInfo>>> PostListGet([FromRoute][GeneralTimelineName] string name, [FromQuery] DateTime? modifiedSince, [FromQuery] bool? includeDeleted)
{
- if (!this.IsAdministrator() && !await _service.HasReadPermission(name, this.GetOptionalUserId()))
+ if (!UserHasAllTimelineManagementPermission && !await _service.HasReadPermission(name, this.GetOptionalUserId()))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
@@ -208,7 +210,7 @@ namespace Timeline.Controllers public async Task<IActionResult> PostDataGet([FromRoute][GeneralTimelineName] string name, [FromRoute] long id, [FromHeader(Name = "If-None-Match")] string? ifNoneMatch)
{
_ = ifNoneMatch;
- if (!this.IsAdministrator() && !await _service.HasReadPermission(name, this.GetOptionalUserId()))
+ if (!UserHasAllTimelineManagementPermission && !await _service.HasReadPermission(name, this.GetOptionalUserId()))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
@@ -246,7 +248,7 @@ namespace Timeline.Controllers public async Task<ActionResult<TimelinePostInfo>> PostPost([FromRoute][GeneralTimelineName] string name, [FromBody] TimelinePostCreateRequest body)
{
var id = this.GetUserId();
- if (!this.IsAdministrator() && !await _service.IsMemberOf(name, id))
+ if (!UserHasAllTimelineManagementPermission && !await _service.IsMemberOf(name, id))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
@@ -313,7 +315,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<CommonDeleteResponse>> PostDelete([FromRoute][GeneralTimelineName] string name, [FromRoute] long id)
{
- if (!this.IsAdministrator() && !await _service.HasPostModifyPermission(name, id, this.GetUserId()))
+ if (!UserHasAllTimelineManagementPermission && !await _service.HasPostModifyPermission(name, id, this.GetUserId()))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
@@ -342,7 +344,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<TimelineInfo>> TimelinePatch([FromRoute][GeneralTimelineName] string name, [FromBody] TimelinePatchRequest body)
{
- if (!this.IsAdministrator() && !(await _service.HasManagePermission(name, this.GetUserId())))
+ if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(name, this.GetUserId())))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
@@ -365,7 +367,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult> TimelineMemberPut([FromRoute][GeneralTimelineName] string name, [FromRoute][Username] string member)
{
- if (!this.IsAdministrator() && !(await _service.HasManagePermission(name, this.GetUserId())))
+ if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(name, this.GetUserId())))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
@@ -393,7 +395,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult> TimelineMemberDelete([FromRoute][GeneralTimelineName] string name, [FromRoute][Username] string member)
{
- if (!this.IsAdministrator() && !(await _service.HasManagePermission(name, this.GetUserId())))
+ if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(name, this.GetUserId())))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
@@ -448,7 +450,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<CommonDeleteResponse>> TimelineDelete([FromRoute][TimelineName] string name)
{
- if (!this.IsAdministrator() && !(await _service.HasManagePermission(name, this.GetUserId())))
+ if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(name, this.GetUserId())))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
@@ -472,7 +474,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<TimelineInfo>> TimelineOpChangeName([FromBody] TimelineChangeNameRequest body)
{
- if (!this.IsAdministrator() && !(await _service.HasManagePermission(body.OldName, this.GetUserId())))
+ if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(body.OldName, this.GetUserId())))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
diff --git a/BackEnd/Timeline/Controllers/UserAvatarController.cs b/BackEnd/Timeline/Controllers/UserAvatarController.cs index bc4afa30..f3b7fff8 100644 --- a/BackEnd/Timeline/Controllers/UserAvatarController.cs +++ b/BackEnd/Timeline/Controllers/UserAvatarController.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers;
using System;
using System.Threading.Tasks;
-using Timeline.Auth;
using Timeline.Filters;
using Timeline.Helpers;
using Timeline.Models;
@@ -86,7 +85,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> Put([FromRoute][Username] string username, [FromBody] ByteData body)
{
- if (!User.IsAdministrator() && User.Identity.Name != username)
+ if (!this.UserHasPermission(UserPermission.UserManagement) && User.Identity!.Name != username)
{
_logger.LogInformation(Log.Format(LogPutForbid,
("Operator Username", User.Identity.Name), ("Username To Put Avatar", username)));
@@ -149,10 +148,10 @@ namespace Timeline.Controllers [Authorize]
public async Task<IActionResult> Delete([FromRoute][Username] string username)
{
- if (!User.IsAdministrator() && User.Identity.Name != username)
+ if (!this.UserHasPermission(UserPermission.UserManagement) && User.Identity!.Name != username)
{
_logger.LogInformation(Log.Format(LogDeleteForbid,
- ("Operator Username", User.Identity.Name), ("Username To Delete Avatar", username)));
+ ("Operator Username", User.Identity!.Name), ("Username To Delete Avatar", username)));
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
diff --git a/BackEnd/Timeline/Controllers/UserController.cs b/BackEnd/Timeline/Controllers/UserController.cs index 02c09aab..bbdb5d57 100644 --- a/BackEnd/Timeline/Controllers/UserController.cs +++ b/BackEnd/Timeline/Controllers/UserController.cs @@ -26,20 +26,24 @@ namespace Timeline.Controllers {
private readonly ILogger<UserController> _logger;
private readonly IUserService _userService;
+ private readonly IUserPermissionService _userPermissionService;
private readonly IUserDeleteService _userDeleteService;
private readonly IMapper _mapper;
/// <summary></summary>
- public UserController(ILogger<UserController> logger, IUserService userService, IUserDeleteService userDeleteService, IMapper mapper)
+ public UserController(ILogger<UserController> logger, IUserService userService, IUserPermissionService userPermissionService, IUserDeleteService userDeleteService, IMapper mapper)
{
_logger = logger;
_userService = userService;
+ _userPermissionService = userPermissionService;
_userDeleteService = userDeleteService;
_mapper = mapper;
}
private UserInfo ConvertToUserInfo(User user) => _mapper.Map<UserInfo>(user);
+ private bool UserHasUserManagementPermission => this.UserHasPermission(UserPermission.UserManagement);
+
/// <summary>
/// Get all users.
/// </summary>
@@ -65,7 +69,8 @@ namespace Timeline.Controllers {
try
{
- var user = await _userService.GetUserByUsername(username);
+ var id = await _userService.GetUserIdByUsername(username);
+ var user = await _userService.GetUser(id);
return Ok(ConvertToUserInfo(user));
}
catch (UserNotExistException e)
@@ -89,11 +94,12 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<UserInfo>> Patch([FromBody] UserPatchRequest body, [FromRoute][Username] string username)
{
- if (this.IsAdministrator())
+ if (UserHasUserManagementPermission)
{
try
{
- var user = await _userService.ModifyUser(username, _mapper.Map<User>(body));
+ var id = await _userService.GetUserIdByUsername(username);
+ var user = await _userService.ModifyUser(id, _mapper.Map<ModifyUserParams>(body));
return Ok(ConvertToUserInfo(user));
}
catch (UserNotExistException e)
@@ -108,7 +114,7 @@ namespace Timeline.Controllers }
else
{
- if (User.Identity.Name != username)
+ if (User.Identity!.Name != username)
return StatusCode(StatusCodes.Status403Forbidden,
ErrorResponse.Common.CustomMessage_Forbid(Common_Forbid_NotSelf));
@@ -120,11 +126,7 @@ namespace Timeline.Controllers return StatusCode(StatusCodes.Status403Forbidden,
ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Password));
- if (body.Administrator != null)
- return StatusCode(StatusCodes.Status403Forbidden,
- ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Administrator));
-
- var user = await _userService.ModifyUser(this.GetUserId(), _mapper.Map<User>(body));
+ var user = await _userService.ModifyUser(this.GetUserId(), _mapper.Map<ModifyUserParams>(body));
return Ok(ConvertToUserInfo(user));
}
}
@@ -134,7 +136,7 @@ namespace Timeline.Controllers /// </summary>
/// <param name="username">Username of the user to delete.</param>
/// <returns>Info of deletion.</returns>
- [HttpDelete("users/{username}"), AdminAuthorize]
+ [HttpDelete("users/{username}"), PermissionAuthorize(UserPermission.UserManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
@@ -151,7 +153,7 @@ namespace Timeline.Controllers /// Create a new user. You have to be administrator.
/// </summary>
/// <returns>The new user's info.</returns>
- [HttpPost("userop/createuser"), AdminAuthorize]
+ [HttpPost("userop/createuser"), PermissionAuthorize(UserPermission.UserManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
@@ -160,7 +162,7 @@ namespace Timeline.Controllers {
try
{
- var user = await _userService.CreateUser(_mapper.Map<User>(body));
+ var user = await _userService.CreateUser(body.Username, body.Password);
return Ok(ConvertToUserInfo(user));
}
catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.User)
@@ -186,10 +188,50 @@ namespace Timeline.Controllers catch (BadPasswordException e)
{
_logger.LogInformation(e, Log.Format(LogChangePasswordBadPassword,
- ("Username", User.Identity.Name), ("Old Password", request.OldPassword)));
+ ("Username", User.Identity!.Name), ("Old Password", request.OldPassword)));
return BadRequest(ErrorResponse.UserController.ChangePassword_BadOldPassword());
}
// User can't be non-existent or the token is bad.
}
+
+ [HttpPut("users/{username}/permissions/{permission}"), PermissionAuthorize(UserPermission.UserManagement)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task<ActionResult> PutUserPermission([FromRoute][Username] string username, [FromRoute] UserPermission permission)
+ {
+ try
+ {
+ var id = await _userService.GetUserIdByUsername(username);
+ await _userPermissionService.AddPermissionToUserAsync(id, permission);
+ return Ok();
+ }
+ catch (UserNotExistException)
+ {
+ return NotFound(ErrorResponse.UserCommon.NotExist());
+ }
+ }
+
+ [HttpDelete("users/{username}/permissions/{permission}"), PermissionAuthorize(UserPermission.UserManagement)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task<ActionResult> DeleteUserPermission([FromRoute][Username] string username, [FromRoute] UserPermission permission)
+ {
+ try
+ {
+ var id = await _userService.GetUserIdByUsername(username);
+ await _userPermissionService.RemovePermissionFromUserAsync(id, permission);
+ return Ok();
+ }
+ catch (UserNotExistException)
+ {
+ return NotFound(ErrorResponse.UserCommon.NotExist());
+ }
+ }
}
}
diff --git a/BackEnd/Timeline/Entities/DatabaseContext.cs b/BackEnd/Timeline/Entities/DatabaseContext.cs index ecadd703..e4203392 100644 --- a/BackEnd/Timeline/Entities/DatabaseContext.cs +++ b/BackEnd/Timeline/Entities/DatabaseContext.cs @@ -25,6 +25,7 @@ namespace Timeline.Entities public DbSet<UserEntity> Users { get; set; } = default!;
public DbSet<UserAvatarEntity> UserAvatars { get; set; } = default!;
+ public DbSet<UserPermissionEntity> UserPermission { get; set; } = default!;
public DbSet<TimelineEntity> Timelines { get; set; } = default!;
public DbSet<TimelinePostEntity> TimelinePosts { get; set; } = default!;
public DbSet<TimelineMemberEntity> TimelineMembers { get; set; } = default!;
diff --git a/BackEnd/Timeline/Entities/UserAvatarEntity.cs b/BackEnd/Timeline/Entities/UserAvatarEntity.cs index 3c2720f7..96a8e3ff 100644 --- a/BackEnd/Timeline/Entities/UserAvatarEntity.cs +++ b/BackEnd/Timeline/Entities/UserAvatarEntity.cs @@ -4,7 +4,6 @@ using System.ComponentModel.DataAnnotations.Schema; namespace Timeline.Entities
{
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "This is data base entity.")]
[Table("user_avatars")]
public class UserAvatarEntity
{
diff --git a/BackEnd/Timeline/Entities/UserEntity.cs b/BackEnd/Timeline/Entities/UserEntity.cs index 0cfaa335..6a256a31 100644 --- a/BackEnd/Timeline/Entities/UserEntity.cs +++ b/BackEnd/Timeline/Entities/UserEntity.cs @@ -5,13 +5,6 @@ using System.ComponentModel.DataAnnotations.Schema; namespace Timeline.Entities
{
- public static class UserRoles
- {
- public const string Admin = "admin";
- public const string User = "user";
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "This is an entity class.")]
[Table("users")]
public class UserEntity
{
@@ -30,9 +23,6 @@ namespace Timeline.Entities [Column("password"), Required]
public string Password { get; set; } = default!;
- [Column("roles"), Required]
- public string Roles { get; set; } = default!;
-
[Column("version"), Required]
public long Version { get; set; }
@@ -47,10 +37,14 @@ namespace Timeline.Entities public UserAvatarEntity? Avatar { get; set; }
+#pragma warning disable CA2227 // Collection properties should be read only
+ public List<UserPermissionEntity> Permissions { get; set; } = default!;
+
public List<TimelineEntity> Timelines { get; set; } = default!;
public List<TimelinePostEntity> TimelinePosts { get; set; } = default!;
public List<TimelineMemberEntity> TimelinesJoined { get; set; } = default!;
+#pragma warning restore CA2227 // Collection properties should be read only
}
}
diff --git a/BackEnd/Timeline/Entities/UserPermissionEntity.cs b/BackEnd/Timeline/Entities/UserPermissionEntity.cs new file mode 100644 index 00000000..395ad0bd --- /dev/null +++ b/BackEnd/Timeline/Entities/UserPermissionEntity.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Timeline.Entities
+{
+ [Table("user_permission")]
+ public class UserPermissionEntity
+ {
+ [Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public long Id { get; set; }
+
+ [Column("user_id")]
+ public long UserId { get; set; }
+
+ [ForeignKey(nameof(UserId))]
+ public UserEntity User { get; set; } = default!;
+
+ [Column("permission")]
+ public string Permission { get; set; } = default!;
+ }
+}
diff --git a/BackEnd/Timeline/GlobalSuppressions.cs b/BackEnd/Timeline/GlobalSuppressions.cs index 2b0da576..155ed9ff 100644 --- a/BackEnd/Timeline/GlobalSuppressions.cs +++ b/BackEnd/Timeline/GlobalSuppressions.cs @@ -5,10 +5,8 @@ [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "This is not a UI application.")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "This is not bad.")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "No need to check the null because it's ASP.Net's duty.", Scope = "namespaceanddescendants", Target = "Timeline.Controllers")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Migrations code are auto generated.", Scope = "namespaceanddescendants", Target = "Timeline.Migrations")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Generated error response identifiers.", Scope = "type", Target = "Timeline.Models.Http.ErrorResponse")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Generated error response identifiers.", Scope = "type", Target = "Timeline.Models.Http.ErrorResponse")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "Generated error response.", Scope = "type", Target = "Timeline.Models.Http.ErrorResponse")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1056:Uri properties should not be strings", Justification = "That's unnecessary.")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Adundant")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Redundant")]
diff --git a/BackEnd/Timeline/Migrations/20200105150407_Initialize.cs b/BackEnd/Timeline/Migrations/20200105150407_Initialize.cs index 4e12ef83..420cc13d 100644 --- a/BackEnd/Timeline/Migrations/20200105150407_Initialize.cs +++ b/BackEnd/Timeline/Migrations/20200105150407_Initialize.cs @@ -1,5 +1,5 @@ -using System;
-using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Migrations;
+using System;
namespace Timeline.Migrations
{
diff --git a/BackEnd/Timeline/Migrations/20200221064341_AddJwtToken.cs b/BackEnd/Timeline/Migrations/20200221064341_AddJwtToken.cs index 628970c6..a16fcf4d 100644 --- a/BackEnd/Timeline/Migrations/20200221064341_AddJwtToken.cs +++ b/BackEnd/Timeline/Migrations/20200221064341_AddJwtToken.cs @@ -1,6 +1,5 @@ -using System;
+using Microsoft.EntityFrameworkCore.Migrations;
using System.Security.Cryptography;
-using Microsoft.EntityFrameworkCore.Migrations;
namespace Timeline.Migrations
{
diff --git a/BackEnd/Timeline/Migrations/20200229103848_AddPostLocalId.cs b/BackEnd/Timeline/Migrations/20200229103848_AddPostLocalId.cs index 497b38a1..e4e41ade 100644 --- a/BackEnd/Timeline/Migrations/20200229103848_AddPostLocalId.cs +++ b/BackEnd/Timeline/Migrations/20200229103848_AddPostLocalId.cs @@ -1,5 +1,4 @@ -using System;
-using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Migrations;
namespace Timeline.Migrations
{
diff --git a/BackEnd/Timeline/Migrations/20200306110049_AddDataTable.cs b/BackEnd/Timeline/Migrations/20200306110049_AddDataTable.cs index e33bf4c9..ddd3a908 100644 --- a/BackEnd/Timeline/Migrations/20200306110049_AddDataTable.cs +++ b/BackEnd/Timeline/Migrations/20200306110049_AddDataTable.cs @@ -1,5 +1,4 @@ -using System;
-using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Migrations;
namespace Timeline.Migrations
{
diff --git a/BackEnd/Timeline/Migrations/20200618064936_TimelineAddModifiedTime.cs b/BackEnd/Timeline/Migrations/20200618064936_TimelineAddModifiedTime.cs index c277fe39..84879ef9 100644 --- a/BackEnd/Timeline/Migrations/20200618064936_TimelineAddModifiedTime.cs +++ b/BackEnd/Timeline/Migrations/20200618064936_TimelineAddModifiedTime.cs @@ -1,6 +1,6 @@ -using System;
-using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using System;
namespace Timeline.Migrations
{
diff --git a/BackEnd/Timeline/Migrations/20200810155908_AddTimesToUser.cs b/BackEnd/Timeline/Migrations/20200810155908_AddTimesToUser.cs index 369f85e6..55721a59 100644 --- a/BackEnd/Timeline/Migrations/20200810155908_AddTimesToUser.cs +++ b/BackEnd/Timeline/Migrations/20200810155908_AddTimesToUser.cs @@ -1,5 +1,4 @@ -using System;
-using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Migrations;
namespace Timeline.Migrations
{
diff --git a/BackEnd/Timeline/Migrations/20201112084015_AddUserPermission.Designer.cs b/BackEnd/Timeline/Migrations/20201112084015_AddUserPermission.Designer.cs new file mode 100644 index 00000000..54a480c4 --- /dev/null +++ b/BackEnd/Timeline/Migrations/20201112084015_AddUserPermission.Designer.cs @@ -0,0 +1,407 @@ +// <auto-generated />
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Timeline.Entities;
+
+namespace Timeline.Migrations
+{
+ [DbContext(typeof(DatabaseContext))]
+ [Migration("20201112084015_AddUserPermission")]
+ partial class AddUserPermission
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "5.0.0");
+
+ modelBuilder.Entity("Timeline.Entities.DataEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<byte[]>("Data")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("data");
+
+ b.Property<int>("Ref")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ref");
+
+ b.Property<string>("Tag")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("tag");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tag")
+ .IsUnique();
+
+ b.ToTable("data");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<byte[]>("Key")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("key");
+
+ b.HasKey("Id");
+
+ b.ToTable("jwt_token");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<DateTime>("CreateTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("create_time");
+
+ b.Property<long>("CurrentPostLocalId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("current_post_local_id");
+
+ b.Property<string>("Description")
+ .HasColumnType("TEXT")
+ .HasColumnName("description");
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified");
+
+ b.Property<string>("Name")
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.Property<DateTime>("NameLastModified")
+ .HasColumnType("TEXT")
+ .HasColumnName("name_last_modified");
+
+ b.Property<long>("OwnerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("owner");
+
+ b.Property<string>("Title")
+ .HasColumnType("TEXT")
+ .HasColumnName("title");
+
+ b.Property<string>("UniqueId")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("unique_id")
+ .HasDefaultValueSql("lower(hex(randomblob(16)))");
+
+ b.Property<int>("Visibility")
+ .HasColumnType("INTEGER")
+ .HasColumnName("visibility");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OwnerId");
+
+ b.ToTable("timelines");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<long>("TimelineId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TimelineId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("timeline_members");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<long?>("AuthorId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("author");
+
+ b.Property<string>("Content")
+ .HasColumnType("TEXT")
+ .HasColumnName("content");
+
+ b.Property<string>("ContentType")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("content_type");
+
+ b.Property<string>("ExtraContent")
+ .HasColumnType("TEXT")
+ .HasColumnName("extra_content");
+
+ b.Property<DateTime>("LastUpdated")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_updated");
+
+ b.Property<long>("LocalId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("local_id");
+
+ b.Property<DateTime>("Time")
+ .HasColumnType("TEXT")
+ .HasColumnName("time");
+
+ b.Property<long>("TimelineId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AuthorId");
+
+ b.HasIndex("TimelineId");
+
+ b.ToTable("timeline_posts");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<string>("DataTag")
+ .HasColumnType("TEXT")
+ .HasColumnName("data_tag");
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified");
+
+ b.Property<string>("Type")
+ .HasColumnType("TEXT")
+ .HasColumnName("type");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("user_avatars");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<DateTime>("CreateTime")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("create_time")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<DateTime>("LastModified")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<string>("Nickname")
+ .HasColumnType("TEXT")
+ .HasColumnName("nickname");
+
+ b.Property<string>("Password")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("password");
+
+ b.Property<string>("Roles")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("roles");
+
+ b.Property<string>("UniqueId")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("unique_id")
+ .HasDefaultValueSql("lower(hex(randomblob(16)))");
+
+ b.Property<string>("Username")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("username");
+
+ b.Property<DateTime>("UsernameChangeTime")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("username_change_time")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<long>("Version")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0L)
+ .HasColumnName("version");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Username")
+ .IsUnique();
+
+ b.ToTable("users");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserPermissionEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<string>("Permission")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("permission");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("user_permission");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Owner")
+ .WithMany("Timelines")
+ .HasForeignKey("OwnerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Owner");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany("Members")
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany("TimelinesJoined")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Timeline");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Author")
+ .WithMany("TimelinePosts")
+ .HasForeignKey("AuthorId");
+
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany("Posts")
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Author");
+
+ b.Navigation("Timeline");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithOne("Avatar")
+ .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserPermissionEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany("Permissions")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.Navigation("Members");
+
+ b.Navigation("Posts");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserEntity", b =>
+ {
+ b.Navigation("Avatar");
+
+ b.Navigation("Permissions");
+
+ b.Navigation("TimelinePosts");
+
+ b.Navigation("Timelines");
+
+ b.Navigation("TimelinesJoined");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Migrations/20201112084015_AddUserPermission.cs b/BackEnd/Timeline/Migrations/20201112084015_AddUserPermission.cs new file mode 100644 index 00000000..b2288374 --- /dev/null +++ b/BackEnd/Timeline/Migrations/20201112084015_AddUserPermission.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace Timeline.Migrations
+{
+ public partial class AddUserPermission : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "user_permission",
+ columns: table => new
+ {
+ id = table.Column<long>(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ user_id = table.Column<long>(type: "INTEGER", nullable: false),
+ permission = table.Column<string>(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_user_permission", x => x.id);
+ table.ForeignKey(
+ name: "FK_user_permission_users_user_id",
+ column: x => x.user_id,
+ principalTable: "users",
+ principalColumn: "id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_user_permission_user_id",
+ table: "user_permission",
+ column: "user_id");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "user_permission");
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Migrations/20201113081411_RemoveRolesFromUser.Designer.cs b/BackEnd/Timeline/Migrations/20201113081411_RemoveRolesFromUser.Designer.cs new file mode 100644 index 00000000..324738a5 --- /dev/null +++ b/BackEnd/Timeline/Migrations/20201113081411_RemoveRolesFromUser.Designer.cs @@ -0,0 +1,402 @@ +// <auto-generated />
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Timeline.Entities;
+
+namespace Timeline.Migrations
+{
+ [DbContext(typeof(DatabaseContext))]
+ [Migration("20201113081411_RemoveRolesFromUser")]
+ partial class RemoveRolesFromUser
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "5.0.0");
+
+ modelBuilder.Entity("Timeline.Entities.DataEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<byte[]>("Data")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("data");
+
+ b.Property<int>("Ref")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ref");
+
+ b.Property<string>("Tag")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("tag");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tag")
+ .IsUnique();
+
+ b.ToTable("data");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<byte[]>("Key")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("key");
+
+ b.HasKey("Id");
+
+ b.ToTable("jwt_token");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<DateTime>("CreateTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("create_time");
+
+ b.Property<long>("CurrentPostLocalId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("current_post_local_id");
+
+ b.Property<string>("Description")
+ .HasColumnType("TEXT")
+ .HasColumnName("description");
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified");
+
+ b.Property<string>("Name")
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.Property<DateTime>("NameLastModified")
+ .HasColumnType("TEXT")
+ .HasColumnName("name_last_modified");
+
+ b.Property<long>("OwnerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("owner");
+
+ b.Property<string>("Title")
+ .HasColumnType("TEXT")
+ .HasColumnName("title");
+
+ b.Property<string>("UniqueId")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("unique_id")
+ .HasDefaultValueSql("lower(hex(randomblob(16)))");
+
+ b.Property<int>("Visibility")
+ .HasColumnType("INTEGER")
+ .HasColumnName("visibility");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OwnerId");
+
+ b.ToTable("timelines");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<long>("TimelineId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TimelineId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("timeline_members");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<long?>("AuthorId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("author");
+
+ b.Property<string>("Content")
+ .HasColumnType("TEXT")
+ .HasColumnName("content");
+
+ b.Property<string>("ContentType")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("content_type");
+
+ b.Property<string>("ExtraContent")
+ .HasColumnType("TEXT")
+ .HasColumnName("extra_content");
+
+ b.Property<DateTime>("LastUpdated")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_updated");
+
+ b.Property<long>("LocalId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("local_id");
+
+ b.Property<DateTime>("Time")
+ .HasColumnType("TEXT")
+ .HasColumnName("time");
+
+ b.Property<long>("TimelineId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AuthorId");
+
+ b.HasIndex("TimelineId");
+
+ b.ToTable("timeline_posts");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<string>("DataTag")
+ .HasColumnType("TEXT")
+ .HasColumnName("data_tag");
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified");
+
+ b.Property<string>("Type")
+ .HasColumnType("TEXT")
+ .HasColumnName("type");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("user_avatars");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<DateTime>("CreateTime")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("create_time")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<DateTime>("LastModified")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<string>("Nickname")
+ .HasColumnType("TEXT")
+ .HasColumnName("nickname");
+
+ b.Property<string>("Password")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("password");
+
+ b.Property<string>("UniqueId")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("unique_id")
+ .HasDefaultValueSql("lower(hex(randomblob(16)))");
+
+ b.Property<string>("Username")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("username");
+
+ b.Property<DateTime>("UsernameChangeTime")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("username_change_time")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<long>("Version")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0L)
+ .HasColumnName("version");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Username")
+ .IsUnique();
+
+ b.ToTable("users");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserPermissionEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<string>("Permission")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("permission");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("user_permission");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Owner")
+ .WithMany("Timelines")
+ .HasForeignKey("OwnerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Owner");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany("Members")
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany("TimelinesJoined")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Timeline");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Author")
+ .WithMany("TimelinePosts")
+ .HasForeignKey("AuthorId");
+
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany("Posts")
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Author");
+
+ b.Navigation("Timeline");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithOne("Avatar")
+ .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserPermissionEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany("Permissions")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.Navigation("Members");
+
+ b.Navigation("Posts");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserEntity", b =>
+ {
+ b.Navigation("Avatar");
+
+ b.Navigation("Permissions");
+
+ b.Navigation("TimelinePosts");
+
+ b.Navigation("Timelines");
+
+ b.Navigation("TimelinesJoined");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Migrations/20201113081411_RemoveRolesFromUser.cs b/BackEnd/Timeline/Migrations/20201113081411_RemoveRolesFromUser.cs new file mode 100644 index 00000000..33d79b33 --- /dev/null +++ b/BackEnd/Timeline/Migrations/20201113081411_RemoveRolesFromUser.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace Timeline.Migrations
+{
+ public partial class RemoveRolesFromUser : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "roles",
+ table: "users");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn<string>(
+ name: "roles",
+ table: "users",
+ type: "TEXT",
+ nullable: false,
+ defaultValue: "");
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs b/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs index 65ae6c9a..2f0f75a2 100644 --- a/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs +++ b/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs @@ -14,28 +14,28 @@ namespace Timeline.Migrations {
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "3.1.7");
+ .HasAnnotation("ProductVersion", "5.0.0");
modelBuilder.Entity("Timeline.Entities.DataEntity", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
- .HasColumnName("id")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
b.Property<byte[]>("Data")
.IsRequired()
- .HasColumnName("data")
- .HasColumnType("BLOB");
+ .HasColumnType("BLOB")
+ .HasColumnName("data");
b.Property<int>("Ref")
- .HasColumnName("ref")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("ref");
b.Property<string>("Tag")
.IsRequired()
- .HasColumnName("tag")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("tag");
b.HasKey("Id");
@@ -49,13 +49,13 @@ namespace Timeline.Migrations {
b.Property<long>("Id")
.ValueGeneratedOnAdd()
- .HasColumnName("id")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
b.Property<byte[]>("Key")
.IsRequired()
- .HasColumnName("key")
- .HasColumnType("BLOB");
+ .HasColumnType("BLOB")
+ .HasColumnName("key");
b.HasKey("Id");
@@ -66,51 +66,51 @@ namespace Timeline.Migrations {
b.Property<long>("Id")
.ValueGeneratedOnAdd()
- .HasColumnName("id")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
b.Property<DateTime>("CreateTime")
- .HasColumnName("create_time")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("create_time");
b.Property<long>("CurrentPostLocalId")
- .HasColumnName("current_post_local_id")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("current_post_local_id");
b.Property<string>("Description")
- .HasColumnName("description")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("description");
b.Property<DateTime>("LastModified")
- .HasColumnName("last_modified")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified");
b.Property<string>("Name")
- .HasColumnName("name")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
b.Property<DateTime>("NameLastModified")
- .HasColumnName("name_last_modified")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("name_last_modified");
b.Property<long>("OwnerId")
- .HasColumnName("owner")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("owner");
b.Property<string>("Title")
- .HasColumnName("title")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("title");
b.Property<string>("UniqueId")
.IsRequired()
.ValueGeneratedOnAdd()
- .HasColumnName("unique_id")
.HasColumnType("TEXT")
+ .HasColumnName("unique_id")
.HasDefaultValueSql("lower(hex(randomblob(16)))");
b.Property<int>("Visibility")
- .HasColumnName("visibility")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("visibility");
b.HasKey("Id");
@@ -123,16 +123,16 @@ namespace Timeline.Migrations {
b.Property<long>("Id")
.ValueGeneratedOnAdd()
- .HasColumnName("id")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
b.Property<long>("TimelineId")
- .HasColumnName("timeline")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline");
b.Property<long>("UserId")
- .HasColumnName("user")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("user");
b.HasKey("Id");
@@ -147,41 +147,41 @@ namespace Timeline.Migrations {
b.Property<long>("Id")
.ValueGeneratedOnAdd()
- .HasColumnName("id")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
b.Property<long?>("AuthorId")
- .HasColumnName("author")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("author");
b.Property<string>("Content")
- .HasColumnName("content")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("content");
b.Property<string>("ContentType")
.IsRequired()
- .HasColumnName("content_type")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("content_type");
b.Property<string>("ExtraContent")
- .HasColumnName("extra_content")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("extra_content");
b.Property<DateTime>("LastUpdated")
- .HasColumnName("last_updated")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("last_updated");
b.Property<long>("LocalId")
- .HasColumnName("local_id")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("local_id");
b.Property<DateTime>("Time")
- .HasColumnName("time")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("time");
b.Property<long>("TimelineId")
- .HasColumnName("timeline")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline");
b.HasKey("Id");
@@ -196,24 +196,24 @@ namespace Timeline.Migrations {
b.Property<long>("Id")
.ValueGeneratedOnAdd()
- .HasColumnName("id")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
b.Property<string>("DataTag")
- .HasColumnName("data_tag")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("data_tag");
b.Property<DateTime>("LastModified")
- .HasColumnName("last_modified")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified");
b.Property<string>("Type")
- .HasColumnName("type")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("type");
b.Property<long>("UserId")
- .HasColumnName("user")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("user");
b.HasKey("Id");
@@ -227,58 +227,53 @@ namespace Timeline.Migrations {
b.Property<long>("Id")
.ValueGeneratedOnAdd()
- .HasColumnName("id")
- .HasColumnType("INTEGER");
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
b.Property<DateTime>("CreateTime")
.ValueGeneratedOnAdd()
- .HasColumnName("create_time")
.HasColumnType("TEXT")
+ .HasColumnName("create_time")
.HasDefaultValueSql("datetime('now', 'utc')");
b.Property<DateTime>("LastModified")
.ValueGeneratedOnAdd()
- .HasColumnName("last_modified")
.HasColumnType("TEXT")
+ .HasColumnName("last_modified")
.HasDefaultValueSql("datetime('now', 'utc')");
b.Property<string>("Nickname")
- .HasColumnName("nickname")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("nickname");
b.Property<string>("Password")
.IsRequired()
- .HasColumnName("password")
- .HasColumnType("TEXT");
-
- b.Property<string>("Roles")
- .IsRequired()
- .HasColumnName("roles")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("password");
b.Property<string>("UniqueId")
.IsRequired()
.ValueGeneratedOnAdd()
- .HasColumnName("unique_id")
.HasColumnType("TEXT")
+ .HasColumnName("unique_id")
.HasDefaultValueSql("lower(hex(randomblob(16)))");
b.Property<string>("Username")
.IsRequired()
- .HasColumnName("username")
- .HasColumnType("TEXT");
+ .HasColumnType("TEXT")
+ .HasColumnName("username");
b.Property<DateTime>("UsernameChangeTime")
.ValueGeneratedOnAdd()
- .HasColumnName("username_change_time")
.HasColumnType("TEXT")
+ .HasColumnName("username_change_time")
.HasDefaultValueSql("datetime('now', 'utc')");
b.Property<long>("Version")
.ValueGeneratedOnAdd()
- .HasColumnName("version")
.HasColumnType("INTEGER")
- .HasDefaultValue(0L);
+ .HasDefaultValue(0L)
+ .HasColumnName("version");
b.HasKey("Id");
@@ -288,6 +283,29 @@ namespace Timeline.Migrations b.ToTable("users");
});
+ modelBuilder.Entity("Timeline.Entities.UserPermissionEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<string>("Permission")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("permission");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("user_permission");
+ });
+
modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
{
b.HasOne("Timeline.Entities.UserEntity", "Owner")
@@ -295,6 +313,8 @@ namespace Timeline.Migrations .HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
+
+ b.Navigation("Owner");
});
modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
@@ -310,6 +330,10 @@ namespace Timeline.Migrations .HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
+
+ b.Navigation("Timeline");
+
+ b.Navigation("User");
});
modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
@@ -323,6 +347,10 @@ namespace Timeline.Migrations .HasForeignKey("TimelineId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
+
+ b.Navigation("Author");
+
+ b.Navigation("Timeline");
});
modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
@@ -332,6 +360,39 @@ namespace Timeline.Migrations .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserPermissionEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany("Permissions")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.Navigation("Members");
+
+ b.Navigation("Posts");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserEntity", b =>
+ {
+ b.Navigation("Avatar");
+
+ b.Navigation("Permissions");
+
+ b.Navigation("TimelinePosts");
+
+ b.Navigation("Timelines");
+
+ b.Navigation("TimelinesJoined");
});
#pragma warning restore 612, 618
}
diff --git a/BackEnd/Timeline/Models/Converters/JsonDateTimeConverter.cs b/BackEnd/Timeline/Models/Converters/JsonDateTimeConverter.cs index 94b5cab0..72a2908c 100644 --- a/BackEnd/Timeline/Models/Converters/JsonDateTimeConverter.cs +++ b/BackEnd/Timeline/Models/Converters/JsonDateTimeConverter.cs @@ -12,7 +12,7 @@ namespace Timeline.Models.Converters public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Debug.Assert(typeToConvert == typeof(DateTime));
- return DateTime.Parse(reader.GetString(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
+ return DateTime.Parse(reader.GetString()!, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
diff --git a/BackEnd/Timeline/Models/Http/Common.cs b/BackEnd/Timeline/Models/Http/Common.cs index 5fa22c9e..2101a1bb 100644 --- a/BackEnd/Timeline/Models/Http/Common.cs +++ b/BackEnd/Timeline/Models/Http/Common.cs @@ -94,13 +94,13 @@ namespace Timeline.Models.Http public bool Delete { get; set; }
}
- /// <summary></summary>
+ /// <summary></summary>
public CommonDeleteResponse()
{
}
- /// <summary></summary>
+ /// <summary></summary>
public CommonDeleteResponse(int code, string message, bool delete)
: base(code, message, new ResponseData(delete))
{
diff --git a/BackEnd/Timeline/Models/Http/UserController.cs b/BackEnd/Timeline/Models/Http/UserController.cs index 6bc5a66e..92a63874 100644 --- a/BackEnd/Timeline/Models/Http/UserController.cs +++ b/BackEnd/Timeline/Models/Http/UserController.cs @@ -2,6 +2,7 @@ using AutoMapper; using System.ComponentModel.DataAnnotations;
using Timeline.Controllers;
using Timeline.Models.Validation;
+using Timeline.Services;
namespace Timeline.Models.Http
{
@@ -27,11 +28,6 @@ namespace Timeline.Models.Http /// </summary>
[Nickname]
public string? Nickname { get; set; }
-
- /// <summary>
- /// Whether to be administrator. Null if not change. Need to be administrator.
- /// </summary>
- public bool? Administrator { get; set; }
}
/// <summary>
@@ -50,18 +46,6 @@ namespace Timeline.Models.Http /// </summary>
[Required, MinLength(1)]
public string Password { get; set; } = default!;
-
- /// <summary>
- /// Whether the new user is administrator.
- /// </summary>
- [Required]
- public bool? Administrator { get; set; }
-
- /// <summary>
- /// Nickname of the new user.
- /// </summary>
- [Nickname]
- public string? Nickname { get; set; }
}
/// <summary>
@@ -86,8 +70,7 @@ namespace Timeline.Models.Http {
public UserControllerAutoMapperProfile()
{
- CreateMap<UserPatchRequest, User>(MemberList.Source);
- CreateMap<CreateUserRequest, User>(MemberList.Source);
+ CreateMap<UserPatchRequest, ModifyUserParams>();
}
}
}
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/Models/Validation/Validator.cs b/BackEnd/Timeline/Models/Validation/Validator.cs index aef7891c..b7e754d3 100644 --- a/BackEnd/Timeline/Models/Validation/Validator.cs +++ b/BackEnd/Timeline/Models/Validation/Validator.cs @@ -111,12 +111,12 @@ namespace Timeline.Models.Validation }
}
- protected override ValidationResult IsValid(object value, ValidationContext validationContext)
+ protected override ValidationResult IsValid(object? value, ValidationContext validationContext)
{
var (result, message) = _validator.Validate(value);
if (result)
{
- return ValidationResult.Success;
+ return ValidationResult.Success!;
}
else
{
diff --git a/BackEnd/Timeline/Resources/Services/UserService.Designer.cs b/BackEnd/Timeline/Resources/Services/UserService.Designer.cs index cdf7f390..564dd26c 100644 --- a/BackEnd/Timeline/Resources/Services/UserService.Designer.cs +++ b/BackEnd/Timeline/Resources/Services/UserService.Designer.cs @@ -70,7 +70,7 @@ namespace Timeline.Resources.Services { }
/// <summary>
- /// Looks up a localized string similar to Nickname is of bad format, because {}..
+ /// Looks up a localized string similar to Nickname is of bad format, because {0}..
/// </summary>
internal static string ExceptionNicknameBadFormat {
get {
@@ -106,7 +106,7 @@ namespace Timeline.Resources.Services { }
/// <summary>
- /// Looks up a localized string similar to Username is of bad format, because {}..
+ /// Looks up a localized string similar to Username is of bad format, because {0}..
/// </summary>
internal static string ExceptionUsernameBadFormat {
get {
diff --git a/BackEnd/Timeline/Resources/Services/UserService.resx b/BackEnd/Timeline/Resources/Services/UserService.resx index 09bd4abb..1f3c0011 100644 --- a/BackEnd/Timeline/Resources/Services/UserService.resx +++ b/BackEnd/Timeline/Resources/Services/UserService.resx @@ -121,7 +121,7 @@ <value>New username is of bad format.</value>
</data>
<data name="ExceptionNicknameBadFormat" xml:space="preserve">
- <value>Nickname is of bad format, because {}.</value>
+ <value>Nickname is of bad format, because {0}.</value>
</data>
<data name="ExceptionOldUsernameBadFormat" xml:space="preserve">
<value>Old username is of bad format.</value>
@@ -133,7 +133,7 @@ <value>Password can't be null.</value>
</data>
<data name="ExceptionUsernameBadFormat" xml:space="preserve">
- <value>Username is of bad format, because {}.</value>
+ <value>Username is of bad format, because {0}.</value>
</data>
<data name="ExceptionUsernameConflict" xml:space="preserve">
<value>A user with given username already exists.</value>
diff --git a/BackEnd/Timeline/Services/EntityNames.cs b/BackEnd/Timeline/Services/EntityNames.cs index 0ce1de3b..7dae6a4a 100644 --- a/BackEnd/Timeline/Services/EntityNames.cs +++ b/BackEnd/Timeline/Services/EntityNames.cs @@ -1,9 +1,4 @@ -using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace Timeline.Services
+namespace Timeline.Services
{
public static class EntityNames
{
diff --git a/BackEnd/Timeline/Services/TimelineService.cs b/BackEnd/Timeline/Services/TimelineService.cs index 4bcae596..769e8bed 100644 --- a/BackEnd/Timeline/Services/TimelineService.cs +++ b/BackEnd/Timeline/Services/TimelineService.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using System.Threading;
using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Helpers;
@@ -414,12 +413,12 @@ namespace Timeline.Services /// Remember to include Members when query.
private async Task<Models.Timeline> MapTimelineFromEntity(TimelineEntity entity)
{
- var owner = await _userService.GetUserById(entity.OwnerId);
+ var owner = await _userService.GetUser(entity.OwnerId);
var members = new List<User>();
foreach (var memberEntity in entity.Members)
{
- members.Add(await _userService.GetUserById(memberEntity.UserId));
+ members.Add(await _userService.GetUser(memberEntity.UserId));
}
var name = entity.Name ?? ("@" + owner.Username);
@@ -441,7 +440,7 @@ namespace Timeline.Services private async Task<TimelinePost> MapTimelinePostFromEntity(TimelinePostEntity entity, string timelineName)
{
- User? author = entity.AuthorId.HasValue ? await _userService.GetUserById(entity.AuthorId.Value) : null;
+ User? author = entity.AuthorId.HasValue ? await _userService.GetUser(entity.AuthorId.Value) : null;
ITimelinePostContent? content = null;
@@ -699,7 +698,7 @@ namespace Timeline.Services var timelineId = await FindTimelineId(timelineName);
var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync();
- var author = await _userService.GetUserById(authorId);
+ var author = await _userService.GetUser(authorId);
var currentTime = _clock.GetCurrentTime();
var finalTime = time ?? currentTime;
@@ -742,7 +741,7 @@ namespace Timeline.Services var timelineId = await FindTimelineId(timelineName);
var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync();
- var author = await _userService.GetUserById(authorId);
+ var author = await _userService.GetUser(authorId);
var imageFormat = await _imageValidator.Validate(data);
@@ -1098,14 +1097,14 @@ namespace Timeline.Services ValidateTimelineName(name, nameof(name));
- var user = await _userService.GetUserById(owner);
+ var user = await _userService.GetUser(owner);
var conflict = await _database.Timelines.AnyAsync(t => t.Name == name);
if (conflict)
throw new EntityAlreadyExistException(EntityNames.Timeline, null, ExceptionTimelineNameConflict);
- var newEntity = CreateNewTimelineEntity(name, user.Id!.Value);
+ var newEntity = CreateNewTimelineEntity(name, user.Id);
_database.Timelines.Add(newEntity);
await _database.SaveChangesAsync();
diff --git a/BackEnd/Timeline/Services/UserDeleteService.cs b/BackEnd/Timeline/Services/UserDeleteService.cs index 845de573..b6306682 100644 --- a/BackEnd/Timeline/Services/UserDeleteService.cs +++ b/BackEnd/Timeline/Services/UserDeleteService.cs @@ -1,7 +1,6 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
-using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
diff --git a/BackEnd/Timeline/Services/UserPermissionService.cs b/BackEnd/Timeline/Services/UserPermissionService.cs new file mode 100644 index 00000000..ff09b4ee --- /dev/null +++ b/BackEnd/Timeline/Services/UserPermissionService.cs @@ -0,0 +1,212 @@ +using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Timeline.Entities;
+using Timeline.Services.Exceptions;
+
+namespace Timeline.Services
+{
+ public enum UserPermission
+ {
+ /// <summary>
+ /// This permission allows to manage user (creating, deleting or modifying).
+ /// </summary>
+ UserManagement,
+ /// <summary>
+ /// This permission allows to view and modify all timelines.
+ /// </summary>
+ AllTimelineManagement,
+ /// <summary>
+ /// This permission allow to add or remove highlight timelines.
+ /// </summary>
+ HighlightTimelineManangement
+ }
+
+ /// <summary>
+ /// Represents a user's permissions.
+ /// </summary>
+ public class UserPermissions : IEnumerable<UserPermission>
+ {
+ public static UserPermissions AllPermissions { get; } = new UserPermissions(Enum.GetValues<UserPermission>());
+
+ /// <summary>
+ /// Create an instance containing given permissions.
+ /// </summary>
+ /// <param name="permissions">Permission list.</param>
+ public UserPermissions(params UserPermission[] permissions) : this(permissions as IEnumerable<UserPermission>)
+ {
+
+ }
+
+ /// <summary>
+ /// Create an instance containing given permissions.
+ /// </summary>
+ /// <param name="permissions">Permission list.</param>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="permissions"/> is null.</exception>
+ public UserPermissions(IEnumerable<UserPermission> permissions)
+ {
+ if (permissions == null) throw new ArgumentNullException(nameof(permissions));
+ _permissions = new HashSet<UserPermission>(permissions);
+ }
+
+ private readonly HashSet<UserPermission> _permissions = new();
+
+ /// <summary>
+ /// Check if a permission is contained in the list.
+ /// </summary>
+ /// <param name="permission">The permission to check.</param>
+ /// <returns>True if contains. Otherwise false.</returns>
+ public bool Contains(UserPermission permission)
+ {
+ return _permissions.Contains(permission);
+ }
+
+ /// <summary>
+ /// To a serializable string list.
+ /// </summary>
+ /// <returns>A string list.</returns>
+ public List<string> ToStringList()
+ {
+ return _permissions.Select(p => p.ToString()).ToList();
+ }
+
+ /// <summary>
+ /// Convert a string list to user permissions.
+ /// </summary>
+ /// <param name="list">The string list.</param>
+ /// <returns>An instance.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="list"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when there is unknown permission name.</exception>
+ public static UserPermissions FromStringList(IEnumerable<string> list)
+ {
+ List<UserPermission> permissions = new();
+
+ foreach (var value in list)
+ {
+ if (Enum.TryParse<UserPermission>(value, false, out var result))
+ {
+ permissions.Add(result);
+ }
+ else
+ {
+ throw new ArgumentException("Unknown permission name.", nameof(list));
+ }
+ }
+
+ return new UserPermissions(permissions);
+ }
+
+ public IEnumerator<UserPermission> GetEnumerator()
+ {
+ return _permissions.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)_permissions).GetEnumerator();
+ }
+ }
+
+ public interface IUserPermissionService
+ {
+ /// <summary>
+ /// Get permissions of a user.
+ /// </summary>
+ /// <param name="userId">The id of the user.</param>
+ /// <param name="checkUserExistence">Whether check the user's existence.</param>
+ /// <returns>The permission list.</returns>
+ /// <exception cref="UserNotExistException">Thrown when <paramref name="checkUserExistence"/> is true and user does not exist.</exception>
+ Task<UserPermissions> GetPermissionsOfUserAsync(long userId, bool checkUserExistence = true);
+
+ /// <summary>
+ /// Add a permission to user.
+ /// </summary>
+ /// <param name="userId">The id of the user.</param>
+ /// <param name="permission">The new permission.</param>
+ /// <exception cref="UserNotExistException">Thrown when user does not exist.</exception>
+ Task AddPermissionToUserAsync(long userId, UserPermission permission);
+
+ /// <summary>
+ /// Remove a permission from user.
+ /// </summary>
+ /// <param name="userId">The id of the user.</param>
+ /// <param name="permission">The permission.</param>
+ /// <param name="checkUserExistence">Whether check the user's existence.</param>
+ /// <exception cref="UserNotExistException">Thrown when <paramref name="checkUserExistence"/> is true and user does not exist.</exception>
+ Task RemovePermissionFromUserAsync(long userId, UserPermission permission, bool checkUserExistence = true);
+ }
+
+ public class UserPermissionService : IUserPermissionService
+ {
+ private readonly DatabaseContext _database;
+
+ public UserPermissionService(DatabaseContext database)
+ {
+ _database = database;
+ }
+
+ private async Task CheckUserExistence(long userId, bool checkUserExistence)
+ {
+ if (checkUserExistence)
+ {
+ var existence = await _database.Users.AnyAsync(u => u.Id == userId);
+ if (!existence)
+ {
+ throw new UserNotExistException(userId);
+ }
+ }
+ }
+
+ public async Task<UserPermissions> GetPermissionsOfUserAsync(long userId, bool checkUserExistence = true)
+ {
+ if (userId == 1) // The init administrator account.
+ {
+ return UserPermissions.AllPermissions;
+ }
+
+ await CheckUserExistence(userId, checkUserExistence);
+
+ var permissionNameList = await _database.UserPermission.Where(e => e.UserId == userId).Select(e => e.Permission).ToListAsync();
+
+ return UserPermissions.FromStringList(permissionNameList);
+ }
+
+ public async Task AddPermissionToUserAsync(long userId, UserPermission permission)
+ {
+ if (userId == 1) // The init administrator account.
+ return;
+
+ await CheckUserExistence(userId, true);
+
+ var alreadyHas = await _database.UserPermission
+ .AnyAsync(e => e.UserId == userId && e.Permission == permission.ToString());
+
+ if (alreadyHas) return;
+
+ _database.UserPermission.Add(new UserPermissionEntity { UserId = userId, Permission = permission.ToString() });
+
+ await _database.SaveChangesAsync();
+ }
+
+ public async Task RemovePermissionFromUserAsync(long userId, UserPermission permission, bool checkUserExistence = true)
+ {
+ if (userId == 1) // The init administrator account.
+ return;
+
+ await CheckUserExistence(userId, checkUserExistence);
+
+ var entity = await _database.UserPermission
+ .Where(e => e.UserId == userId && e.Permission == permission.ToString())
+ .SingleOrDefaultAsync();
+
+ if (entity == null) return;
+
+ _database.UserPermission.Remove(entity);
+
+ await _database.SaveChangesAsync();
+ }
+ }
+}
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..f83d2928 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,16 @@ using static Timeline.Resources.Services.UserService; namespace Timeline.Services
{
+ /// <summary>
+ /// Null means not change.
+ /// </summary>
+ public record ModifyUserParams
+ {
+ public string? Username { get; set; }
+ public string? Password { get; set; }
+ public string? Nickname { get; set; }
+ }
+
public interface IUserService
{
/// <summary>
@@ -33,17 +44,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 +60,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="ArgumentException">Thrown when some fields in <paramref name="param"/> 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 +107,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 +150,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 +186,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 +214,68 @@ 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,
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 +287,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 +305,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>();
diff --git a/BackEnd/Timeline/Swagger/DocumentDescriptionDocumentProcessor.cs b/BackEnd/Timeline/Swagger/DocumentDescriptionDocumentProcessor.cs index dc5ddd96..a3452cea 100644 --- a/BackEnd/Timeline/Swagger/DocumentDescriptionDocumentProcessor.cs +++ b/BackEnd/Timeline/Swagger/DocumentDescriptionDocumentProcessor.cs @@ -1,12 +1,10 @@ using NSwag.Generation.Processors;
using NSwag.Generation.Processors.Contexts;
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
-using System.Threading.Tasks;
using Timeline.Models.Http;
namespace Timeline.Swagger
|