From 33f1358f4e5359329d28177384420e29abea3445 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 12 Nov 2020 16:42:46 +0800 Subject: refactor(database): Add user permission table. --- BackEnd/Timeline/Entities/UserAvatarEntity.cs | 1 - BackEnd/Timeline/Entities/UserEntity.cs | 5 +- BackEnd/Timeline/Entities/UserPermissionEntity.cs | 21 ++ .../20201112084015_AddUserPermission.Designer.cs | 407 +++++++++++++++++++++ .../Migrations/20201112084015_AddUserPermission.cs | 41 +++ .../Migrations/DatabaseContextModelSnapshot.cs | 234 +++++++----- 6 files changed, 623 insertions(+), 86 deletions(-) create mode 100644 BackEnd/Timeline/Entities/UserPermissionEntity.cs create mode 100644 BackEnd/Timeline/Migrations/20201112084015_AddUserPermission.Designer.cs create mode 100644 BackEnd/Timeline/Migrations/20201112084015_AddUserPermission.cs 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..83fe9aea 100644 --- a/BackEnd/Timeline/Entities/UserEntity.cs +++ b/BackEnd/Timeline/Entities/UserEntity.cs @@ -11,7 +11,6 @@ namespace Timeline.Entities 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 { @@ -47,10 +46,14 @@ namespace Timeline.Entities public UserAvatarEntity? Avatar { get; set; } +#pragma warning disable CA2227 // Collection properties should be read only + public List Permissions { get; set; } = default!; + public List Timelines { get; set; } = default!; public List TimelinePosts { get; set; } = default!; public List 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/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 @@ +// +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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("data"); + + b.Property("Ref") + .HasColumnType("INTEGER") + .HasColumnName("ref"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tag"); + + b.HasKey("Id"); + + b.HasIndex("Tag") + .IsUnique(); + + b.ToTable("data"); + }); + + modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Key") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("key"); + + b.HasKey("Id"); + + b.ToTable("jwt_token"); + }); + + modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("CreateTime") + .HasColumnType("TEXT") + .HasColumnName("create_time"); + + b.Property("CurrentPostLocalId") + .HasColumnType("INTEGER") + .HasColumnName("current_post_local_id"); + + b.Property("Description") + .HasColumnType("TEXT") + .HasColumnName("description"); + + b.Property("LastModified") + .HasColumnType("TEXT") + .HasColumnName("last_modified"); + + b.Property("Name") + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.Property("NameLastModified") + .HasColumnType("TEXT") + .HasColumnName("name_last_modified"); + + b.Property("OwnerId") + .HasColumnType("INTEGER") + .HasColumnName("owner"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.Property("UniqueId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("unique_id") + .HasDefaultValueSql("lower(hex(randomblob(16)))"); + + b.Property("Visibility") + .HasColumnType("INTEGER") + .HasColumnName("visibility"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.ToTable("timelines"); + }); + + modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("TimelineId") + .HasColumnType("INTEGER") + .HasColumnName("timeline"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("AuthorId") + .HasColumnType("INTEGER") + .HasColumnName("author"); + + b.Property("Content") + .HasColumnType("TEXT") + .HasColumnName("content"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("content_type"); + + b.Property("ExtraContent") + .HasColumnType("TEXT") + .HasColumnName("extra_content"); + + b.Property("LastUpdated") + .HasColumnType("TEXT") + .HasColumnName("last_updated"); + + b.Property("LocalId") + .HasColumnType("INTEGER") + .HasColumnName("local_id"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("DataTag") + .HasColumnType("TEXT") + .HasColumnName("data_tag"); + + b.Property("LastModified") + .HasColumnType("TEXT") + .HasColumnName("last_modified"); + + b.Property("Type") + .HasColumnType("TEXT") + .HasColumnName("type"); + + b.Property("UserId") + .HasColumnType("INTEGER") + .HasColumnName("user"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("user_avatars"); + }); + + modelBuilder.Entity("Timeline.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("CreateTime") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("create_time") + .HasDefaultValueSql("datetime('now', 'utc')"); + + b.Property("LastModified") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("last_modified") + .HasDefaultValueSql("datetime('now', 'utc')"); + + b.Property("Nickname") + .HasColumnType("TEXT") + .HasColumnName("nickname"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("password"); + + b.Property("Roles") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("roles"); + + b.Property("UniqueId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("unique_id") + .HasDefaultValueSql("lower(hex(randomblob(16)))"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("username"); + + b.Property("UsernameChangeTime") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("username_change_time") + .HasDefaultValueSql("datetime('now', 'utc')"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Permission") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("permission"); + + b.Property("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(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + user_id = table.Column(type: "INTEGER", nullable: false), + permission = table.Column(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/DatabaseContextModelSnapshot.cs b/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs index 65ae6c9a..95bb0ff5 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("Id") .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("id"); b.Property("Data") .IsRequired() - .HasColumnName("data") - .HasColumnType("BLOB"); + .HasColumnType("BLOB") + .HasColumnName("data"); b.Property("Ref") - .HasColumnName("ref") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("ref"); b.Property("Tag") .IsRequired() - .HasColumnName("tag") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("tag"); b.HasKey("Id"); @@ -49,13 +49,13 @@ namespace Timeline.Migrations { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("id"); b.Property("Key") .IsRequired() - .HasColumnName("key") - .HasColumnType("BLOB"); + .HasColumnType("BLOB") + .HasColumnName("key"); b.HasKey("Id"); @@ -66,51 +66,51 @@ namespace Timeline.Migrations { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("id"); b.Property("CreateTime") - .HasColumnName("create_time") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("create_time"); b.Property("CurrentPostLocalId") - .HasColumnName("current_post_local_id") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("current_post_local_id"); b.Property("Description") - .HasColumnName("description") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("description"); b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("last_modified"); b.Property("Name") - .HasColumnName("name") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("name"); b.Property("NameLastModified") - .HasColumnName("name_last_modified") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("name_last_modified"); b.Property("OwnerId") - .HasColumnName("owner") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("owner"); b.Property("Title") - .HasColumnName("title") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("title"); b.Property("UniqueId") .IsRequired() .ValueGeneratedOnAdd() - .HasColumnName("unique_id") .HasColumnType("TEXT") + .HasColumnName("unique_id") .HasDefaultValueSql("lower(hex(randomblob(16)))"); b.Property("Visibility") - .HasColumnName("visibility") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("visibility"); b.HasKey("Id"); @@ -123,16 +123,16 @@ namespace Timeline.Migrations { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("id"); b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("timeline"); b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("user"); b.HasKey("Id"); @@ -147,41 +147,41 @@ namespace Timeline.Migrations { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("id"); b.Property("AuthorId") - .HasColumnName("author") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("author"); b.Property("Content") - .HasColumnName("content") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("content"); b.Property("ContentType") .IsRequired() - .HasColumnName("content_type") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("content_type"); b.Property("ExtraContent") - .HasColumnName("extra_content") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("extra_content"); b.Property("LastUpdated") - .HasColumnName("last_updated") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("last_updated"); b.Property("LocalId") - .HasColumnName("local_id") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("local_id"); b.Property("Time") - .HasColumnName("time") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("time"); b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("timeline"); b.HasKey("Id"); @@ -196,24 +196,24 @@ namespace Timeline.Migrations { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("id"); b.Property("DataTag") - .HasColumnName("data_tag") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("data_tag"); b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("last_modified"); b.Property("Type") - .HasColumnName("type") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("type"); b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("user"); b.HasKey("Id"); @@ -227,58 +227,58 @@ namespace Timeline.Migrations { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); + .HasColumnType("INTEGER") + .HasColumnName("id"); b.Property("CreateTime") .ValueGeneratedOnAdd() - .HasColumnName("create_time") .HasColumnType("TEXT") + .HasColumnName("create_time") .HasDefaultValueSql("datetime('now', 'utc')"); b.Property("LastModified") .ValueGeneratedOnAdd() - .HasColumnName("last_modified") .HasColumnType("TEXT") + .HasColumnName("last_modified") .HasDefaultValueSql("datetime('now', 'utc')"); b.Property("Nickname") - .HasColumnName("nickname") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("nickname"); b.Property("Password") .IsRequired() - .HasColumnName("password") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("password"); b.Property("Roles") .IsRequired() - .HasColumnName("roles") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("roles"); b.Property("UniqueId") .IsRequired() .ValueGeneratedOnAdd() - .HasColumnName("unique_id") .HasColumnType("TEXT") + .HasColumnName("unique_id") .HasDefaultValueSql("lower(hex(randomblob(16)))"); b.Property("Username") .IsRequired() - .HasColumnName("username") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasColumnName("username"); b.Property("UsernameChangeTime") .ValueGeneratedOnAdd() - .HasColumnName("username_change_time") .HasColumnType("TEXT") + .HasColumnName("username_change_time") .HasDefaultValueSql("datetime('now', 'utc')"); b.Property("Version") .ValueGeneratedOnAdd() - .HasColumnName("version") .HasColumnType("INTEGER") - .HasDefaultValue(0L); + .HasDefaultValue(0L) + .HasColumnName("version"); b.HasKey("Id"); @@ -288,6 +288,29 @@ namespace Timeline.Migrations b.ToTable("users"); }); + modelBuilder.Entity("Timeline.Entities.UserPermissionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Permission") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("permission"); + + b.Property("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 +318,8 @@ namespace Timeline.Migrations .HasForeignKey("OwnerId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Owner"); }); modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => @@ -310,6 +335,10 @@ namespace Timeline.Migrations .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Timeline"); + + b.Navigation("User"); }); modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => @@ -323,6 +352,10 @@ namespace Timeline.Migrations .HasForeignKey("TimelineId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Author"); + + b.Navigation("Timeline"); }); modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => @@ -332,6 +365,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 } -- cgit v1.2.3 From 1dfafd9400c158576f9ede8f3012356746cb5ae0 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 12 Nov 2020 18:31:41 +0800 Subject: feat: Add user permission service. TODO: Add unit tests. --- .../Timeline.Tests/Services/DatabaseBasedTest.cs | 47 +++++ .../Timeline.Tests/Services/TimelineServiceTest.cs | 25 +-- .../Timeline.Tests/Services/UserPermissionTest.cs | 28 +++ BackEnd/Timeline/Entities/DatabaseContext.cs | 1 + BackEnd/Timeline/Services/UserPermissionService.cs | 213 +++++++++++++++++++++ 5 files changed, 295 insertions(+), 19 deletions(-) create mode 100644 BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs create mode 100644 BackEnd/Timeline.Tests/Services/UserPermissionTest.cs create mode 100644 BackEnd/Timeline/Services/UserPermissionService.cs diff --git a/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs b/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs new file mode 100644 index 00000000..838787e9 --- /dev/null +++ b/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +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; } = new TestDatabase(); + protected DatabaseContext Database { get; private set; } + + 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..19d2781a 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(); @@ -39,20 +34,12 @@ namespace Timeline.Tests.Services { } - public async Task InitializeAsync() - { - await _testDatabase.InitializeAsync(); - _databaseContext = _testDatabase.CreateContext(); - _dataManager = new DataManager(_databaseContext, _eTagGenerator); - _userService = new UserService(NullLogger.Instance, _databaseContext, _passwordService, _clock); - _timelineService = new TimelineService(NullLogger.Instance, _databaseContext, _dataManager, _userService, _imageValidator, _clock); - _userDeleteService = new UserDeleteService(NullLogger.Instance, _databaseContext, _timelineService); - } - - public async Task DisposeAsync() + protected override void OnDatabaseCreated() { - await _testDatabase.DisposeAsync(); - await _databaseContext.DisposeAsync(); + _dataManager = new DataManager(Database, _eTagGenerator); + _userService = new UserService(NullLogger.Instance, Database, _passwordService, _clock); + _timelineService = new TimelineService(NullLogger.Instance, Database, _dataManager, _userService, _imageValidator, _clock); + _userDeleteService = new UserDeleteService(NullLogger.Instance, Database, _timelineService); } public void Dispose() diff --git a/BackEnd/Timeline.Tests/Services/UserPermissionTest.cs b/BackEnd/Timeline.Tests/Services/UserPermissionTest.cs new file mode 100644 index 00000000..4bcbc633 --- /dev/null +++ b/BackEnd/Timeline.Tests/Services/UserPermissionTest.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Timeline.Entities; +using Timeline.Services; +using Timeline.Tests.Helpers; +using Xunit; + +namespace Timeline.Tests.Services +{ + public class UserPermissionTest : DatabaseBasedTest + { + private UserPermissionService _service; + + public UserPermissionTest() + { + + } + + protected override void OnDatabaseCreated() + { + _service = new UserPermissionService(Database); + } + + + } +} 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 Users { get; set; } = default!; public DbSet UserAvatars { get; set; } = default!; + public DbSet UserPermission { get; set; } = default!; public DbSet Timelines { get; set; } = default!; public DbSet TimelinePosts { get; set; } = default!; public DbSet TimelineMembers { get; set; } = default!; diff --git a/BackEnd/Timeline/Services/UserPermissionService.cs b/BackEnd/Timeline/Services/UserPermissionService.cs new file mode 100644 index 00000000..466ee252 --- /dev/null +++ b/BackEnd/Timeline/Services/UserPermissionService.cs @@ -0,0 +1,213 @@ +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 + { + /// + /// This permission allows to manage user (creating, deleting or modifying). + /// + UserManagement, + /// + /// This permission allows to view and modify all timelines. + /// + AllTimelineManagement, + /// + /// This permission allow to add or remove highlight timelines. + /// + HighlightTimelineManangement + } + + /// + /// Represents a user's permissions. + /// + public class UserPermissions : IEnumerable + { + public static UserPermissions AllPermissions { get; } = new UserPermissions(Enum.GetValues()); + + /// + /// Create an instance containing given permissions. + /// + /// Permission list. + public UserPermissions(params UserPermission[] permissions) : this(permissions as IEnumerable) + { + + } + + /// + /// Create an instance containing given permissions. + /// + /// Permission list. + /// Thrown when is null. + public UserPermissions(IEnumerable permissions) + { + if (permissions == null) throw new ArgumentNullException(nameof(permissions)); + _permissions = new HashSet(permissions); + } + + private readonly HashSet _permissions = new(); + + /// + /// Check if a permission is contained in the list. + /// + /// The permission to check. + /// True if contains. Otherwise false. + public bool Contains(UserPermission permission) + { + return _permissions.Contains(permission); + } + + /// + /// To a serializable string list. + /// + /// A string list. + public List ToStringList() + { + return _permissions.Select(p => p.ToString().ToUpperInvariant()).ToList(); + } + + /// + /// Convert a string list to user permissions. + /// + /// The string list. + /// An instance. + /// Thrown when is null. + /// Thrown when there is unknown permission name. + public static UserPermissions FromStringList(IEnumerable list) + { + List permissions = new(); + + foreach (var value in list) + { + if (Enum.TryParse(value, false, out var result)) + { + permissions.Add(result); + } + else + { + throw new ArgumentException("Unknown permission name.", nameof(list)); + } + } + + return new UserPermissions(permissions); + } + + public IEnumerator GetEnumerator() + { + return _permissions.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_permissions).GetEnumerator(); + } + } + + public interface IUserPermissionService + { + /// + /// Get permissions of a user. + /// + /// The id of the user. + /// Whether check the user's existence. + /// The permission list. + /// Thrown when is true and user does not exist. + Task GetPermissionsOfUserAsync(long userId, bool checkUserExistence = true); + + /// + /// Add a permission to user. + /// + /// The id of the user. + /// The new permission. + /// Whether check the user's existence. + /// Thrown when is true and user does not exist. + Task AddPermissionToUserAsync(long userId, UserPermission permission, bool checkUserExistence = true); + + /// + /// Remove a permission from user. + /// + /// The id of the user. + /// The permission. + /// Whether check the user's existence. + /// Thrown when is true and user does not exist. + 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 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, bool checkUserExistence) + { + if (userId == 1) // The init administrator account. + return; + + await CheckUserExistence(userId, checkUserExistence); + + var alreadyHas = await _database.UserPermission + .AnyAsync(e => e.UserId == userId && e.Permission.Equals(permission.ToString(), StringComparison.InvariantCultureIgnoreCase)); + + 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) + { + if (userId == 1) // The init administrator account. + return; + + await CheckUserExistence(userId, checkUserExistence); + + var entity = await _database.UserPermission + .Where(e => e.UserId == userId && e.Permission.Equals(permission.ToString(), StringComparison.InvariantCultureIgnoreCase)) + .SingleOrDefaultAsync(); + + if (entity == null) return; + + _database.UserPermission.Remove(entity); + + await _database.SaveChangesAsync(); + } + } +} -- cgit v1.2.3 From 2c6b812382e04956793d90ba4148dd4aa7da3b70 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 12 Nov 2020 20:03:31 +0800 Subject: test: Write tests for user permission service. --- .../Timeline.Tests/Services/DatabaseBasedTest.cs | 12 ++- .../Services/UserPermissionServiceTest.cs | 118 +++++++++++++++++++++ .../Timeline.Tests/Services/UserPermissionTest.cs | 28 ----- BackEnd/Timeline/Services/UserPermissionService.cs | 15 ++- 4 files changed, 132 insertions(+), 41 deletions(-) create mode 100644 BackEnd/Timeline.Tests/Services/UserPermissionServiceTest.cs delete mode 100644 BackEnd/Timeline.Tests/Services/UserPermissionTest.cs diff --git a/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs b/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs index 838787e9..7c97158c 100644 --- a/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs +++ b/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Threading.Tasks; using Timeline.Entities; using Timeline.Tests.Helpers; using Xunit; @@ -10,9 +7,14 @@ namespace Timeline.Tests.Services { public abstract class DatabaseBasedTest : IAsyncLifetime { - protected TestDatabase TestDatabase { get; } = new TestDatabase(); + 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(); 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()); + } + + [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(); + } + + [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()); + } + await _service.RemovePermissionFromUserAsync(1, UserPermission.AllTimelineManagement); + { + var permission = await _service.GetPermissionsOfUserAsync(1); + permission.Should().BeEquivalentTo(Enum.GetValues()); + } + } + + [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(); + } + + [Fact] + public async Task RemovePermissionFromInexistentUserShouldThrown() + { + await _service.Awaiting(s => s.RemovePermissionFromUserAsync(10, UserPermission.HighlightTimelineManangement)).Should().ThrowAsync(); + } + + [Fact] + public async Task RemovePermissionFromInexistentUserShouldNotThrownIfNotCheck() + { + await _service.Awaiting(s => s.RemovePermissionFromUserAsync(10, UserPermission.HighlightTimelineManangement, false)).Should().NotThrowAsync(); + } + } +} diff --git a/BackEnd/Timeline.Tests/Services/UserPermissionTest.cs b/BackEnd/Timeline.Tests/Services/UserPermissionTest.cs deleted file mode 100644 index 4bcbc633..00000000 --- a/BackEnd/Timeline.Tests/Services/UserPermissionTest.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Timeline.Entities; -using Timeline.Services; -using Timeline.Tests.Helpers; -using Xunit; - -namespace Timeline.Tests.Services -{ - public class UserPermissionTest : DatabaseBasedTest - { - private UserPermissionService _service; - - public UserPermissionTest() - { - - } - - protected override void OnDatabaseCreated() - { - _service = new UserPermissionService(Database); - } - - - } -} diff --git a/BackEnd/Timeline/Services/UserPermissionService.cs b/BackEnd/Timeline/Services/UserPermissionService.cs index 466ee252..deedf0a6 100644 --- a/BackEnd/Timeline/Services/UserPermissionService.cs +++ b/BackEnd/Timeline/Services/UserPermissionService.cs @@ -126,9 +126,8 @@ namespace Timeline.Services /// /// The id of the user. /// The new permission. - /// Whether check the user's existence. - /// Thrown when is true and user does not exist. - Task AddPermissionToUserAsync(long userId, UserPermission permission, bool checkUserExistence = true); + /// Thrown when user does not exist. + Task AddPermissionToUserAsync(long userId, UserPermission permission); /// /// Remove a permission from user. @@ -175,15 +174,15 @@ namespace Timeline.Services return UserPermissions.FromStringList(permissionNameList); } - public async Task AddPermissionToUserAsync(long userId, UserPermission permission, bool checkUserExistence) + public async Task AddPermissionToUserAsync(long userId, UserPermission permission) { if (userId == 1) // The init administrator account. return; - await CheckUserExistence(userId, checkUserExistence); + await CheckUserExistence(userId, true); var alreadyHas = await _database.UserPermission - .AnyAsync(e => e.UserId == userId && e.Permission.Equals(permission.ToString(), StringComparison.InvariantCultureIgnoreCase)); + .AnyAsync(e => e.UserId == userId && e.Permission == permission.ToString()); if (alreadyHas) return; @@ -192,7 +191,7 @@ namespace Timeline.Services await _database.SaveChangesAsync(); } - public async Task RemovePermissionFromUserAsync(long userId, UserPermission permission, bool checkUserExistence) + public async Task RemovePermissionFromUserAsync(long userId, UserPermission permission, bool checkUserExistence = true) { if (userId == 1) // The init administrator account. return; @@ -200,7 +199,7 @@ namespace Timeline.Services await CheckUserExistence(userId, checkUserExistence); var entity = await _database.UserPermission - .Where(e => e.UserId == userId && e.Permission.Equals(permission.ToString(), StringComparison.InvariantCultureIgnoreCase)) + .Where(e => e.UserId == userId && e.Permission == permission.ToString()) .SingleOrDefaultAsync(); if (entity == null) return; -- cgit v1.2.3 From ee1b2b5b100268aa510257a1a2cd4cd03f9fc72b Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 12 Nov 2020 21:38:43 +0800 Subject: ... --- .../IntegratedTests/AuthorizationTest.cs | 52 ----- BackEnd/Timeline/Auth/Attribute.cs | 21 -- BackEnd/Timeline/Auth/MyAuthenticationHandler.cs | 9 +- .../Timeline/Auth/PermissionAuthorizeAttribute.cs | 30 +++ BackEnd/Timeline/Auth/PermissionPolicyProvider.cs | 35 ++++ BackEnd/Timeline/Auth/PrincipalExtensions.cs | 10 +- .../Controllers/Testing/TestingAuthController.cs | 32 --- BackEnd/Timeline/Entities/UserEntity.cs | 6 - BackEnd/Timeline/Models/Http/UserInfo.cs | 21 +- BackEnd/Timeline/Models/User.cs | 28 +-- BackEnd/Timeline/Services/UserRoleConvert.cs | 43 ---- BackEnd/Timeline/Services/UserService.cs | 230 ++++++--------------- BackEnd/Timeline/Services/UserTokenManager.cs | 6 +- BackEnd/Timeline/Startup.cs | 4 + 14 files changed, 182 insertions(+), 345 deletions(-) delete mode 100644 BackEnd/Timeline.Tests/IntegratedTests/AuthorizationTest.cs delete mode 100644 BackEnd/Timeline/Auth/Attribute.cs create mode 100644 BackEnd/Timeline/Auth/PermissionAuthorizeAttribute.cs create mode 100644 BackEnd/Timeline/Auth/PermissionPolicyProvider.cs delete mode 100644 BackEnd/Timeline/Controllers/Testing/TestingAuthController.cs delete mode 100644 BackEnd/Timeline/Services/UserRoleConvert.cs diff --git a/BackEnd/Timeline.Tests/IntegratedTests/AuthorizationTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/AuthorizationTest.cs deleted file mode 100644 index 38071394..00000000 --- a/BackEnd/Timeline.Tests/IntegratedTests/AuthorizationTest.cs +++ /dev/null @@ -1,52 +0,0 @@ -using FluentAssertions; -using System.Net; -using System.Threading.Tasks; -using Timeline.Tests.Helpers; -using Xunit; - -namespace Timeline.Tests.IntegratedTests -{ - public class AuthorizationTest : IntegratedTestBase - { - private const string BaseUrl = "testing/auth/"; - private const string AuthorizeUrl = BaseUrl + "Authorize"; - private const string UserUrl = BaseUrl + "User"; - private const string AdminUrl = BaseUrl + "Admin"; - - [Fact] - public async Task UnauthenticationTest() - { - using var client = await CreateDefaultClient(); - var response = await client.GetAsync(AuthorizeUrl); - response.Should().HaveStatusCode(HttpStatusCode.Unauthorized); - } - - [Fact] - public async Task AuthenticationTest() - { - using var client = await CreateClientAsUser(); - var response = await client.GetAsync(AuthorizeUrl); - response.Should().HaveStatusCode(HttpStatusCode.OK); - } - - [Fact] - public async Task UserAuthorizationTest() - { - using var client = await CreateClientAsUser(); - var response1 = await client.GetAsync(UserUrl); - response1.Should().HaveStatusCode(HttpStatusCode.OK); - var response2 = await client.GetAsync(AdminUrl); - response2.Should().HaveStatusCode(HttpStatusCode.Forbidden); - } - - [Fact] - public async Task AdminAuthorizationTest() - { - using var client = await CreateClientAsAdministrator(); - var response1 = await client.GetAsync(UserUrl); - response1.Should().HaveStatusCode(HttpStatusCode.OK); - var response2 = await client.GetAsync(AdminUrl); - response2.Should().HaveStatusCode(HttpStatusCode.OK); - } - } -} diff --git a/BackEnd/Timeline/Auth/Attribute.cs b/BackEnd/Timeline/Auth/Attribute.cs deleted file mode 100644 index 86d0109b..00000000 --- a/BackEnd/Timeline/Auth/Attribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Timeline.Entities; - -namespace Timeline.Auth -{ - public class AdminAuthorizeAttribute : AuthorizeAttribute - { - public AdminAuthorizeAttribute() - { - Roles = UserRoles.Admin; - } - } - - public class UserAuthorizeAttribute : AuthorizeAttribute - { - public UserAuthorizeAttribute() - { - Roles = UserRoles.User; - } - } -} diff --git a/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs b/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs index 3c97c329..b5e22a14 100644 --- a/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs +++ b/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs @@ -17,6 +17,7 @@ namespace Timeline.Auth { public const string Scheme = "Bearer"; public const string DisplayName = "My Jwt Auth Scheme"; + public const string PermissionClaimName = "Permission"; } public class MyAuthenticationOptions : AuthenticationSchemeOptions @@ -78,12 +79,12 @@ namespace Timeline.Auth try { - var userInfo = await _userTokenManager.VerifyToken(token); + var user = await _userTokenManager.VerifyToken(token); var identity = new ClaimsIdentity(AuthenticationConstants.Scheme); - identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userInfo.Id!.Value.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64)); - identity.AddClaim(new Claim(identity.NameClaimType, userInfo.Username, ClaimValueTypes.String)); - identity.AddClaims(UserRoleConvert.ToArray(userInfo.Administrator!.Value).Select(role => new Claim(identity.RoleClaimType, role, ClaimValueTypes.String))); + identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64)); + identity.AddClaim(new Claim(identity.NameClaimType, user.Username, ClaimValueTypes.String)); + identity.AddClaims(user.Permissions.Select(permission => new Claim(AuthenticationConstants.PermissionClaimName, permission.ToString(), ClaimValueTypes.String))); var principal = new ClaimsPrincipal(); principal.AddIdentity(identity); diff --git a/BackEnd/Timeline/Auth/PermissionAuthorizeAttribute.cs b/BackEnd/Timeline/Auth/PermissionAuthorizeAttribute.cs new file mode 100644 index 00000000..3df8dee5 --- /dev/null +++ b/BackEnd/Timeline/Auth/PermissionAuthorizeAttribute.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Authorization; +using System; +using System.Linq; +using Timeline.Services; + +namespace Timeline.Auth +{ + public class PermissionAuthorizeAttribute : AuthorizeAttribute + { + public PermissionAuthorizeAttribute() + { + + } + + public PermissionAuthorizeAttribute(params UserPermission[] permissions) + { + Permissions = permissions; + } + + public UserPermission[] Permissions + { + get => Policy == null ? Array.Empty() : Policy[PermissionPolicyProvider.PolicyPrefix.Length..].Split(',') + .Select(s => Enum.Parse(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 GetDefaultPolicyAsync() + { + return Task.FromResult(new AuthorizationPolicyBuilder(AuthenticationConstants.Scheme).RequireAuthenticatedUser().Build()); + } + + public Task GetFallbackPolicyAsync() + { + return Task.FromResult(null); + } + + public Task 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(policy.Build()); + } + return Task.FromResult(null); + } + } +} diff --git a/BackEnd/Timeline/Auth/PrincipalExtensions.cs b/BackEnd/Timeline/Auth/PrincipalExtensions.cs index ad7a887f..9f86e8ac 100644 --- a/BackEnd/Timeline/Auth/PrincipalExtensions.cs +++ b/BackEnd/Timeline/Auth/PrincipalExtensions.cs @@ -1,13 +1,15 @@ -using System.Security.Principal; -using Timeline.Entities; +using System; +using System.Security.Claims; +using Timeline.Services; namespace Timeline.Auth { internal static class PrincipalExtensions { - internal static bool IsAdministrator(this IPrincipal principal) + internal static bool HasPermission(this ClaimsPrincipal principal, UserPermission permission) { - return principal.IsInRole(UserRoles.Admin); + return principal.HasClaim( + claim => claim.Type == AuthenticationConstants.PermissionClaimName && string.Equals(claim.Value, permission.ToString(), StringComparison.InvariantCultureIgnoreCase)); } } } diff --git a/BackEnd/Timeline/Controllers/Testing/TestingAuthController.cs b/BackEnd/Timeline/Controllers/Testing/TestingAuthController.cs deleted file mode 100644 index 4d3b3ec7..00000000 --- a/BackEnd/Timeline/Controllers/Testing/TestingAuthController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Timeline.Auth; - -namespace Timeline.Controllers.Testing -{ - [Route("testing/auth")] - [ApiController] - public class TestingAuthController : Controller - { - [HttpGet("[action]")] - [Authorize] - public ActionResult Authorize() - { - return Ok(); - } - - [HttpGet("[action]")] - [UserAuthorize] - public new ActionResult User() - { - return Ok(); - } - - [HttpGet("[action]")] - [AdminAuthorize] - public ActionResult Admin() - { - return Ok(); - } - } -} diff --git a/BackEnd/Timeline/Entities/UserEntity.cs b/BackEnd/Timeline/Entities/UserEntity.cs index 83fe9aea..83fa8f89 100644 --- a/BackEnd/Timeline/Entities/UserEntity.cs +++ b/BackEnd/Timeline/Entities/UserEntity.cs @@ -5,12 +5,6 @@ using System.ComponentModel.DataAnnotations.Schema; namespace Timeline.Entities { - public static class UserRoles - { - public const string Admin = "admin"; - public const string User = "user"; - } - [Table("users")] public class UserEntity { diff --git a/BackEnd/Timeline/Models/Http/UserInfo.cs b/BackEnd/Timeline/Models/Http/UserInfo.cs index d92a12c4..26b04e90 100644 --- a/BackEnd/Timeline/Models/Http/UserInfo.cs +++ b/BackEnd/Timeline/Models/Http/UserInfo.cs @@ -2,7 +2,9 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; +using System.Collections.Generic; using Timeline.Controllers; +using Timeline.Services; namespace Timeline.Models.Http { @@ -27,6 +29,12 @@ namespace Timeline.Models.Http /// True if the user is a administrator. /// public bool? Administrator { get; set; } = default!; +#pragma warning disable CA2227 // Collection properties should be read only + /// + /// The permissions of the user. + /// + public List Permissions { get; set; } = default!; +#pragma warning restore CA2227 // Collection properties should be read only #pragma warning disable CA1707 // Identifiers should not contain underscores /// /// Related links. @@ -54,6 +62,14 @@ namespace Timeline.Models.Http public string Timeline { get; set; } = default!; } + public class UserPermissionsValueConverter : ITypeConverter> + { + public List Convert(UserPermissions source, List destination, ResolutionContext context) + { + return source.ToStringList(); + } + } + public class UserInfoLinksValueResolver : IValueResolver { private readonly IActionContextAccessor _actionContextAccessor; @@ -84,7 +100,10 @@ namespace Timeline.Models.Http { public UserInfoAutoMapperProfile() { - CreateMap().ForMember(u => u._links, opt => opt.MapFrom()); + CreateMap>() + .ConvertUsing(); + CreateMap() + .ForMember(u => u._links, opt => opt.MapFrom()); } } } diff --git a/BackEnd/Timeline/Models/User.cs b/BackEnd/Timeline/Models/User.cs index f08a62db..1e90cd1d 100644 --- a/BackEnd/Timeline/Models/User.cs +++ b/BackEnd/Timeline/Models/User.cs @@ -1,21 +1,23 @@ using System; +using Timeline.Services; namespace Timeline.Models { - public class User + public record User { - public string? UniqueId { get; set; } - public string? Username { get; set; } - public string? Nickname { get; set; } - public bool? Administrator { get; set; } + public long Id { get; set; } + public string UniqueId { get; set; } = default!; - #region secret - public long? Id { get; set; } - public string? Password { get; set; } - public long? Version { get; set; } - public DateTime? UsernameChangeTime { get; set; } - public DateTime? CreateTime { get; set; } - public DateTime? LastModified { get; set; } - #endregion secret + public string Username { get; set; } = default!; + public string Nickname { get; set; } = default!; + + [Obsolete("Use permissions instead.")] + public bool Administrator { get; set; } + public UserPermissions Permissions { get; set; } = default!; + + public DateTime UsernameChangeTime { get; set; } + public DateTime CreateTime { get; set; } + public DateTime LastModified { get; set; } + public long Version { get; set; } } } diff --git a/BackEnd/Timeline/Services/UserRoleConvert.cs b/BackEnd/Timeline/Services/UserRoleConvert.cs deleted file mode 100644 index f27ee1bb..00000000 --- a/BackEnd/Timeline/Services/UserRoleConvert.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Timeline.Entities; - -namespace Timeline.Services -{ - public static class UserRoleConvert - { - public const string UserRole = UserRoles.User; - public const string AdminRole = UserRoles.Admin; - - public static string[] ToArray(bool administrator) - { - return administrator ? new string[] { UserRole, AdminRole } : new string[] { UserRole }; - } - - public static string[] ToArray(string s) - { - return s.Split(',').ToArray(); - } - - public static bool ToBool(IReadOnlyCollection roles) - { - return roles.Contains(AdminRole); - } - - public static string ToString(IReadOnlyCollection roles) - { - return string.Join(',', roles); - } - - public static string ToString(bool administrator) - { - return administrator ? UserRole + "," + AdminRole : UserRole; - } - - public static bool ToBool(string s) - { - return s.Contains("admin", StringComparison.InvariantCulture); - } - } -} diff --git a/BackEnd/Timeline/Services/UserService.cs b/BackEnd/Timeline/Services/UserService.cs index 821bc33d..ed637ba3 100644 --- a/BackEnd/Timeline/Services/UserService.cs +++ b/BackEnd/Timeline/Services/UserService.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading.Tasks; @@ -13,6 +14,11 @@ using static Timeline.Resources.Services.UserService; namespace Timeline.Services { + /// + /// Null means not change. + /// + public record ModifyUserParams(string? Username, string? Password, string? Nickname); + public interface IUserService { /// @@ -33,17 +39,7 @@ namespace Timeline.Services /// The id of the user. /// The user info. /// Thrown when the user with given id does not exist. - Task GetUserById(long id); - - /// - /// Get the user info of given username. - /// - /// Username of the user. - /// The info of the user. - /// Thrown when is null. - /// Thrown when is of bad format. - /// Thrown when the user with given username does not exist. - Task GetUserByUsername(string username); + Task GetUser(long id); /// /// Get the user id of given username. @@ -59,71 +55,31 @@ namespace Timeline.Services /// List all users. /// /// The user info of users. - Task GetUsers(); + Task> GetUsers(); /// /// Create a user with given info. /// - /// The info of new user. + /// The username of new user. + /// The password of new user. /// The the new user. - /// Thrown when is null. - /// Thrown when some fields in is bad. + /// Thrown when or is null. + /// Thrown when or is of bad format. /// Thrown when a user with given username already exists. - /// - /// must not be null and must be a valid username. - /// must not be null or empty. - /// is false by default (null). - /// must be a valid nickname if set. It is empty by default. - /// Other fields are ignored. - /// - Task CreateUser(User info); + Task CreateUser(string username, string password); /// - /// Modify a user's info. + /// Modify a user. /// /// The id of the user. - /// The new info. May be null. + /// The new information. /// The new user info. /// Thrown when some fields in is bad. /// Thrown when user with given id does not exist. /// - /// Only , , and will be used. - /// If null, then not change. - /// Other fields are ignored. /// Version will increase if password is changed. - /// - /// must be a valid username if set. - /// can't be empty if set. - /// must be a valid nickname if set. - /// - /// - /// - Task ModifyUser(long id, User? info); - - /// - /// Modify a user's info. - /// - /// The username of the user. - /// The new info. May be null. - /// The new user info. - /// Thrown when is null. - /// Thrown when is of bad format or some fields in is bad. - /// Thrown when user with given id does not exist. - /// Thrown when user with the newusername already exist. - /// - /// Only , and will be used. - /// If null, then not change. - /// Other fields are ignored. - /// After modified, even if nothing is changed, version will increase. - /// - /// must be a valid username if set. - /// can't be empty if set. - /// must be a valid nickname if set. - /// - /// Note: Whether is set or not, version will increase and not set to the specified value if there is one. /// - /// - Task ModifyUser(string username, User? info); + Task ModifyUser(long id, ModifyUserParams? param); /// /// Try to change a user's password with old password. @@ -146,15 +102,18 @@ namespace Timeline.Services private readonly DatabaseContext _databaseContext; private readonly IPasswordService _passwordService; + private readonly IUserPermissionService _userPermissionService; private readonly UsernameValidator _usernameValidator = new UsernameValidator(); private readonly NicknameValidator _nicknameValidator = new NicknameValidator(); - public UserService(ILogger logger, DatabaseContext databaseContext, IPasswordService passwordService, IClock clock) + + public UserService(ILogger logger, DatabaseContext databaseContext, IPasswordService passwordService, IClock clock, IUserPermissionService userPermissionService) { _logger = logger; _clock = clock; _databaseContext = databaseContext; _passwordService = passwordService; + _userPermissionService = userPermissionService; } private void CheckUsernameFormat(string username, string? paramName) @@ -186,13 +145,15 @@ namespace Timeline.Services throw new EntityAlreadyExistException(EntityNames.User, ExceptionUsernameConflict); } - private static User CreateUserFromEntity(UserEntity entity) + private async Task CreateUserFromEntity(UserEntity entity) { + var permission = await _userPermissionService.GetPermissionsOfUserAsync(entity.Id); return new User { UniqueId = entity.UniqueId, Username = entity.Username, - Administrator = UserRoleConvert.ToBool(entity.Roles), + Administrator = permission.Contains(UserPermission.UserManagement), + Permissions = permission, Nickname = string.IsNullOrEmpty(entity.Nickname) ? entity.Username : entity.Nickname, Id = entity.Id, Version = entity.Version, @@ -220,32 +181,17 @@ namespace Timeline.Services if (!_passwordService.VerifyPassword(entity.Password, password)) throw new BadPasswordException(password); - return CreateUserFromEntity(entity); + return await CreateUserFromEntity(entity); } - public async Task GetUserById(long id) + public async Task 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 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 GetUserIdByUsername(string username) @@ -263,78 +209,69 @@ namespace Timeline.Services return entity.Id; } - public async Task GetUsers() + public async Task> GetUsers() { - var entities = await _databaseContext.Users.ToArrayAsync(); - return entities.Select(user => CreateUserFromEntity(user)).ToArray(); + List result = new(); + foreach (var entity in await _databaseContext.Users.ToArrayAsync()) + { + result.Add(await CreateUserFromEntity(entity)); + } + return result; } - public async Task CreateUser(User info) + public async Task CreateUser(string username, string password) { - if (info == null) - throw new ArgumentNullException(nameof(info)); - - if (info.Username == null) - throw new ArgumentException(ExceptionUsernameNull, nameof(info)); - CheckUsernameFormat(info.Username, nameof(info)); - - if (info.Password == null) - throw new ArgumentException(ExceptionPasswordNull, nameof(info)); - CheckPasswordFormat(info.Password, nameof(info)); - - if (info.Nickname != null) - CheckNicknameFormat(info.Nickname, nameof(info)); + if (username == null) + throw new ArgumentNullException(nameof(username)); + if (password == null) + throw new ArgumentNullException(nameof(password)); - var username = info.Username; + CheckUsernameFormat(username, nameof(username)); + CheckPasswordFormat(password, nameof(password)); var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username); if (conflict) ThrowUsernameConflict(); - var administrator = info.Administrator ?? false; - var password = info.Password; - var nickname = info.Nickname; - var newEntity = new UserEntity { Username = username, Password = _passwordService.HashPassword(password), - Roles = UserRoleConvert.ToString(administrator), - Nickname = nickname, + Roles = "", Version = 1 }; _databaseContext.Users.Add(newEntity); await _databaseContext.SaveChangesAsync(); - _logger.LogInformation(Log.Format(LogDatabaseCreate, - ("Id", newEntity.Id), ("Username", username), ("Administrator", administrator))); + _logger.LogInformation(Log.Format(LogDatabaseCreate, ("Id", newEntity.Id), ("Username", username))); - return CreateUserFromEntity(newEntity); + return await CreateUserFromEntity(newEntity); } - private void ValidateModifyUserInfo(User? info) + public async Task ModifyUser(long id, ModifyUserParams? param) { - if (info != null) + if (param != null) { - if (info.Username != null) - CheckUsernameFormat(info.Username, nameof(info)); + if (param.Username != null) + CheckUsernameFormat(param.Username, nameof(param)); - if (info.Password != null) - CheckPasswordFormat(info.Password, nameof(info)); + if (param.Password != null) + CheckPasswordFormat(param.Password, nameof(param)); - if (info.Nickname != null) - CheckNicknameFormat(info.Nickname, nameof(info)); + if (param.Nickname != null) + CheckNicknameFormat(param.Nickname, nameof(param)); } - } - private async Task UpdateUserEntity(UserEntity entity, User? info) - { - if (info != null) + var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); + if (entity == null) + throw new UserNotExistException(id); + + if (param != null) { var now = _clock.GetCurrentTime(); bool updateLastModified = false; - var username = info.Username; + var username = param.Username; if (username != null && username != entity.Username) { var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username); @@ -346,21 +283,14 @@ namespace Timeline.Services updateLastModified = true; } - var password = info.Password; + var password = param.Password; if (password != null) { entity.Password = _passwordService.HashPassword(password); entity.Version += 1; } - var administrator = info.Administrator; - if (administrator.HasValue && UserRoleConvert.ToBool(entity.Roles) != administrator) - { - entity.Roles = UserRoleConvert.ToString(administrator.Value); - updateLastModified = true; - } - - var nickname = info.Nickname; + var nickname = param.Nickname; if (nickname != null && nickname != entity.Nickname) { entity.Nickname = nickname; @@ -371,44 +301,12 @@ namespace Timeline.Services { entity.LastModified = now; } - } - } + await _databaseContext.SaveChangesAsync(); + _logger.LogInformation(LogDatabaseUpdate, ("Id", id)); + } - public async Task 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 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(AuthenticationConstants.Scheme, AuthenticationConstants.DisplayName, o => { }); services.AddAuthorization(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); @@ -85,6 +88,7 @@ namespace Timeline services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); -- cgit v1.2.3 From d3da412fa7e10db8c721846152a2c056dd4ccbcf Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 12 Nov 2020 23:21:31 +0800 Subject: ... --- BackEnd/Timeline.Tests/Helpers/TestDatabase.cs | 21 +++---------- .../IntegratedTests/IntegratedTestBase.cs | 25 +++++---------- .../Timeline.Tests/IntegratedTests/TokenTest.cs | 3 +- BackEnd/Timeline.Tests/IntegratedTests/UserTest.cs | 36 +++++----------------- .../Timeline.Tests/Services/TimelineServiceTest.cs | 9 ++++-- .../Controllers/ControllerAuthExtensions.cs | 5 +-- BackEnd/Timeline/Controllers/TimelineController.cs | 20 ++++++------ .../Timeline/Controllers/UserAvatarController.cs | 6 ++-- BackEnd/Timeline/Controllers/UserController.cs | 24 +++++++-------- BackEnd/Timeline/Models/Http/UserController.cs | 21 ++----------- BackEnd/Timeline/Services/TimelineService.cs | 14 ++++----- BackEnd/Timeline/Services/UserService.cs | 4 +-- 12 files changed, 67 insertions(+), 121 deletions(-) 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.Instance, context, passwordService, new Clock()); + var userService = new UserService(NullLogger.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/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() + 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(); @@ -87,7 +74,9 @@ namespace Timeline.Tests.IntegratedTests var userService = scope.ServiceProvider.GetRequiredService(); 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(s, options)); } diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs index 480d66cd..f4a406d1 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs @@ -103,7 +103,8 @@ namespace Timeline.Tests.IntegratedTests { // create a user for test var userService = scope.ServiceProvider.GetRequiredService(); - 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/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() .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() .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("token/verify", new() { Token = token }); + res.Should().HaveStatusCode(HttpStatusCode.BadRequest); } { @@ -235,14 +234,6 @@ namespace Timeline.Tests.IntegratedTests res.Should().HaveStatusCode(HttpStatusCode.Forbidden); } - [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() { @@ -301,22 +292,16 @@ namespace Timeline.Tests.IntegratedTests { Username = "aaa", Password = "bbb", - Administrator = true, - Nickname = "ccc" }); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody().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().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 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/TimelineServiceTest.cs b/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs index 19d2781a..73fdd32f 100644 --- a/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs +++ b/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs @@ -24,6 +24,8 @@ namespace Timeline.Tests.Services private DataManager _dataManager; + private UserPermissionService _userPermissionService; + private UserService _userService; private TimelineService _timelineService; @@ -37,7 +39,8 @@ namespace Timeline.Tests.Services protected override void OnDatabaseCreated() { _dataManager = new DataManager(Database, _eTagGenerator); - _userService = new UserService(NullLogger.Instance, Database, _passwordService, _clock); + _userPermissionService = new UserPermissionService(Database); + _userService = new UserService(NullLogger.Instance, Database, _passwordService, _clock, _userPermissionService); _timelineService = new TimelineService(NullLogger.Instance, Database, _dataManager, _userService, _imageValidator, _clock); _userDeleteService = new UserDeleteService(NullLogger.Instance, Database, _timelineService); } @@ -207,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/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/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); + /// /// List all timelines. /// @@ -180,7 +182,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task>> 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 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> 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> 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> 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 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 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> 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> 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..44d45b76 100644 --- a/BackEnd/Timeline/Controllers/UserAvatarController.cs +++ b/BackEnd/Timeline/Controllers/UserAvatarController.cs @@ -86,7 +86,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task 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 +149,10 @@ namespace Timeline.Controllers [Authorize] public async Task 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..524e5559 100644 --- a/BackEnd/Timeline/Controllers/UserController.cs +++ b/BackEnd/Timeline/Controllers/UserController.cs @@ -65,7 +65,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 +90,12 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> Patch([FromBody] UserPatchRequest body, [FromRoute][Username] string username) { - if (this.IsAdministrator()) + if (this.UserHasPermission(UserPermission.UserManagement)) { try { - var user = await _userService.ModifyUser(username, _mapper.Map(body)); + var id = await _userService.GetUserIdByUsername(username); + var user = await _userService.ModifyUser(id, _mapper.Map(body)); return Ok(ConvertToUserInfo(user)); } catch (UserNotExistException e) @@ -108,7 +110,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 +122,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(body)); + var user = await _userService.ModifyUser(this.GetUserId(), _mapper.Map(body)); return Ok(ConvertToUserInfo(user)); } } @@ -134,7 +132,7 @@ namespace Timeline.Controllers /// /// Username of the user to delete. /// Info of deletion. - [HttpDelete("users/{username}"), AdminAuthorize] + [HttpDelete("users/{username}"), PermissionAuthorize(UserPermission.UserManagement)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] @@ -151,7 +149,7 @@ namespace Timeline.Controllers /// Create a new user. You have to be administrator. /// /// The new user's info. - [HttpPost("userop/createuser"), AdminAuthorize] + [HttpPost("userop/createuser"), PermissionAuthorize(UserPermission.UserManagement)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] @@ -160,7 +158,7 @@ namespace Timeline.Controllers { try { - var user = await _userService.CreateUser(_mapper.Map(body)); + var user = await _userService.CreateUser(body.Username, body.Password); return Ok(ConvertToUserInfo(user)); } catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.User) @@ -186,7 +184,7 @@ 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. 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 /// [Nickname] public string? Nickname { get; set; } - - /// - /// Whether to be administrator. Null if not change. Need to be administrator. - /// - public bool? Administrator { get; set; } } /// @@ -50,18 +46,6 @@ namespace Timeline.Models.Http /// [Required, MinLength(1)] public string Password { get; set; } = default!; - - /// - /// Whether the new user is administrator. - /// - [Required] - public bool? Administrator { get; set; } - - /// - /// Nickname of the new user. - /// - [Nickname] - public string? Nickname { get; set; } } /// @@ -86,8 +70,7 @@ namespace Timeline.Models.Http { public UserControllerAutoMapperProfile() { - CreateMap(MemberList.Source); - CreateMap(MemberList.Source); + CreateMap(); } } } diff --git a/BackEnd/Timeline/Services/TimelineService.cs b/BackEnd/Timeline/Services/TimelineService.cs index 4bcae596..04870dcf 100644 --- a/BackEnd/Timeline/Services/TimelineService.cs +++ b/BackEnd/Timeline/Services/TimelineService.cs @@ -414,12 +414,12 @@ namespace Timeline.Services /// Remember to include Members when query. private async Task MapTimelineFromEntity(TimelineEntity entity) { - var owner = await _userService.GetUserById(entity.OwnerId); + var owner = await _userService.GetUser(entity.OwnerId); var members = new List(); 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 +441,7 @@ namespace Timeline.Services private async Task 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 +699,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 +742,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 +1098,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/UserService.cs b/BackEnd/Timeline/Services/UserService.cs index ed637ba3..b925742e 100644 --- a/BackEnd/Timeline/Services/UserService.cs +++ b/BackEnd/Timeline/Services/UserService.cs @@ -17,7 +17,7 @@ namespace Timeline.Services /// /// Null means not change. /// - public record ModifyUserParams(string? Username, string? Password, string? Nickname); + public record ModifyUserParams(string? Username = null, string? Password = null, string? Nickname = null); public interface IUserService { @@ -74,7 +74,7 @@ namespace Timeline.Services /// The id of the user. /// The new information. /// The new user info. - /// Thrown when some fields in is bad. + /// Thrown when some fields in is bad. /// Thrown when user with given id does not exist. /// /// Version will increase if password is changed. -- cgit v1.2.3 From 609804f4e7d5d27496c9c31ed1ec84d6e86313c3 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 12 Nov 2020 23:45:00 +0800 Subject: feat: Add REST API for user permission. --- BackEnd/Timeline/Controllers/UserController.cs | 48 +++++++++++++++++++++- .../Models/Converters/JsonDateTimeConverter.cs | 2 +- BackEnd/Timeline/Models/Validation/Validator.cs | 4 +- BackEnd/Timeline/Services/UserService.cs | 7 +++- 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/BackEnd/Timeline/Controllers/UserController.cs b/BackEnd/Timeline/Controllers/UserController.cs index 524e5559..c5d1d4de 100644 --- a/BackEnd/Timeline/Controllers/UserController.cs +++ b/BackEnd/Timeline/Controllers/UserController.cs @@ -26,20 +26,24 @@ namespace Timeline.Controllers { private readonly ILogger _logger; private readonly IUserService _userService; + private readonly IUserPermissionService _userPermissionService; private readonly IUserDeleteService _userDeleteService; private readonly IMapper _mapper; /// - public UserController(ILogger logger, IUserService userService, IUserDeleteService userDeleteService, IMapper mapper) + public UserController(ILogger 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(user); + private bool UserHasUserManagementPermission => this.UserHasPermission(UserPermission.UserManagement); + /// /// Get all users. /// @@ -90,7 +94,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> Patch([FromBody] UserPatchRequest body, [FromRoute][Username] string username) { - if (this.UserHasPermission(UserPermission.UserManagement)) + if (UserHasUserManagementPermission) { try { @@ -189,5 +193,45 @@ namespace Timeline.Controllers } // 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 PutUserPermission([FromRoute] 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 DeleteUserPermission([FromRoute] 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/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/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/Services/UserService.cs b/BackEnd/Timeline/Services/UserService.cs index b925742e..915c9460 100644 --- a/BackEnd/Timeline/Services/UserService.cs +++ b/BackEnd/Timeline/Services/UserService.cs @@ -17,7 +17,12 @@ namespace Timeline.Services /// /// Null means not change. /// - public record ModifyUserParams(string? Username = null, string? Password = null, string? Nickname = null); + public record ModifyUserParams + { + public string? Username { get; set; } + public string? Password { get; set; } + public string? Nickname { get; set; } + } public interface IUserService { -- cgit v1.2.3 From f34d9ccfa729c5367c5169d8461f74df11a5b2fb Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 13 Nov 2020 16:10:12 +0800 Subject: test: Add integrated tests for permission api. --- .../IntegratedTests/UserPermissionTest.cs | 308 +++++++++++++++++++++ BackEnd/Timeline/Controllers/UserController.cs | 4 +- BackEnd/Timeline/GlobalSuppressions.cs | 4 +- .../Resources/Services/UserService.Designer.cs | 4 +- .../Timeline/Resources/Services/UserService.resx | 4 +- BackEnd/Timeline/Services/UserPermissionService.cs | 2 +- 6 files changed, 316 insertions(+), 10 deletions(-) create mode 100644 BackEnd/Timeline.Tests/IntegratedTests/UserPermissionTest.cs 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(); + body.Permissions.Should().BeEquivalentTo(Enum.GetNames()); + } + + [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(); + body.Permissions.Should().BeEmpty(); + } + + public static IEnumerable EveryPermissionTestData() + { + return Enum.GetValues().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(); + body.Permissions.Should().BeEquivalentTo(Enum.GetNames()); + } + + { + 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(); + body.Permissions.Should().BeEquivalentTo(Enum.GetNames()); + } + } + + [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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + body.Code.Should().Be(ErrorCodes.UserCommon.NotExist); + } + } + } +} diff --git a/BackEnd/Timeline/Controllers/UserController.cs b/BackEnd/Timeline/Controllers/UserController.cs index c5d1d4de..bbdb5d57 100644 --- a/BackEnd/Timeline/Controllers/UserController.cs +++ b/BackEnd/Timeline/Controllers/UserController.cs @@ -200,7 +200,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task PutUserPermission([FromRoute] string username, [FromRoute] UserPermission permission) + public async Task PutUserPermission([FromRoute][Username] string username, [FromRoute] UserPermission permission) { try { @@ -220,7 +220,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task DeleteUserPermission([FromRoute] string username, [FromRoute] UserPermission permission) + public async Task DeleteUserPermission([FromRoute][Username] string username, [FromRoute] UserPermission permission) { try { 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/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 { } /// - /// 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}.. /// internal static string ExceptionNicknameBadFormat { get { @@ -106,7 +106,7 @@ namespace Timeline.Resources.Services { } /// - /// 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}.. /// 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 @@ New username is of bad format. - Nickname is of bad format, because {}. + Nickname is of bad format, because {0}. Old username is of bad format. @@ -133,7 +133,7 @@ Password can't be null. - Username is of bad format, because {}. + Username is of bad format, because {0}. A user with given username already exists. diff --git a/BackEnd/Timeline/Services/UserPermissionService.cs b/BackEnd/Timeline/Services/UserPermissionService.cs index deedf0a6..ff09b4ee 100644 --- a/BackEnd/Timeline/Services/UserPermissionService.cs +++ b/BackEnd/Timeline/Services/UserPermissionService.cs @@ -70,7 +70,7 @@ namespace Timeline.Services /// A string list. public List ToStringList() { - return _permissions.Select(p => p.ToString().ToUpperInvariant()).ToList(); + return _permissions.Select(p => p.ToString()).ToList(); } /// -- cgit v1.2.3 From ede745d500d87ce257b117e98202e92890dc1cfe Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 13 Nov 2020 16:15:48 +0800 Subject: refactor(database): Remove roles from user table. --- BackEnd/Timeline/Entities/UserEntity.cs | 3 - .../20201113081411_RemoveRolesFromUser.Designer.cs | 402 +++++++++++++++++++++ .../20201113081411_RemoveRolesFromUser.cs | 24 ++ .../Migrations/DatabaseContextModelSnapshot.cs | 5 - BackEnd/Timeline/Services/UserService.cs | 1 - 5 files changed, 426 insertions(+), 9 deletions(-) create mode 100644 BackEnd/Timeline/Migrations/20201113081411_RemoveRolesFromUser.Designer.cs create mode 100644 BackEnd/Timeline/Migrations/20201113081411_RemoveRolesFromUser.cs diff --git a/BackEnd/Timeline/Entities/UserEntity.cs b/BackEnd/Timeline/Entities/UserEntity.cs index 83fa8f89..6a256a31 100644 --- a/BackEnd/Timeline/Entities/UserEntity.cs +++ b/BackEnd/Timeline/Entities/UserEntity.cs @@ -23,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; } 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 @@ +// +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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("data"); + + b.Property("Ref") + .HasColumnType("INTEGER") + .HasColumnName("ref"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tag"); + + b.HasKey("Id"); + + b.HasIndex("Tag") + .IsUnique(); + + b.ToTable("data"); + }); + + modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Key") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("key"); + + b.HasKey("Id"); + + b.ToTable("jwt_token"); + }); + + modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("CreateTime") + .HasColumnType("TEXT") + .HasColumnName("create_time"); + + b.Property("CurrentPostLocalId") + .HasColumnType("INTEGER") + .HasColumnName("current_post_local_id"); + + b.Property("Description") + .HasColumnType("TEXT") + .HasColumnName("description"); + + b.Property("LastModified") + .HasColumnType("TEXT") + .HasColumnName("last_modified"); + + b.Property("Name") + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.Property("NameLastModified") + .HasColumnType("TEXT") + .HasColumnName("name_last_modified"); + + b.Property("OwnerId") + .HasColumnType("INTEGER") + .HasColumnName("owner"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.Property("UniqueId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("unique_id") + .HasDefaultValueSql("lower(hex(randomblob(16)))"); + + b.Property("Visibility") + .HasColumnType("INTEGER") + .HasColumnName("visibility"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.ToTable("timelines"); + }); + + modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("TimelineId") + .HasColumnType("INTEGER") + .HasColumnName("timeline"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("AuthorId") + .HasColumnType("INTEGER") + .HasColumnName("author"); + + b.Property("Content") + .HasColumnType("TEXT") + .HasColumnName("content"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("content_type"); + + b.Property("ExtraContent") + .HasColumnType("TEXT") + .HasColumnName("extra_content"); + + b.Property("LastUpdated") + .HasColumnType("TEXT") + .HasColumnName("last_updated"); + + b.Property("LocalId") + .HasColumnType("INTEGER") + .HasColumnName("local_id"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("DataTag") + .HasColumnType("TEXT") + .HasColumnName("data_tag"); + + b.Property("LastModified") + .HasColumnType("TEXT") + .HasColumnName("last_modified"); + + b.Property("Type") + .HasColumnType("TEXT") + .HasColumnName("type"); + + b.Property("UserId") + .HasColumnType("INTEGER") + .HasColumnName("user"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("user_avatars"); + }); + + modelBuilder.Entity("Timeline.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("CreateTime") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("create_time") + .HasDefaultValueSql("datetime('now', 'utc')"); + + b.Property("LastModified") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("last_modified") + .HasDefaultValueSql("datetime('now', 'utc')"); + + b.Property("Nickname") + .HasColumnType("TEXT") + .HasColumnName("nickname"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("password"); + + b.Property("UniqueId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("unique_id") + .HasDefaultValueSql("lower(hex(randomblob(16)))"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("username"); + + b.Property("UsernameChangeTime") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("username_change_time") + .HasDefaultValueSql("datetime('now', 'utc')"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Permission") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("permission"); + + b.Property("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( + name: "roles", + table: "users", + type: "TEXT", + nullable: false, + defaultValue: ""); + } + } +} diff --git a/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs b/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs index 95bb0ff5..2f0f75a2 100644 --- a/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs +++ b/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs @@ -251,11 +251,6 @@ namespace Timeline.Migrations .HasColumnType("TEXT") .HasColumnName("password"); - b.Property("Roles") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("roles"); - b.Property("UniqueId") .IsRequired() .ValueGeneratedOnAdd() diff --git a/BackEnd/Timeline/Services/UserService.cs b/BackEnd/Timeline/Services/UserService.cs index 915c9460..f83d2928 100644 --- a/BackEnd/Timeline/Services/UserService.cs +++ b/BackEnd/Timeline/Services/UserService.cs @@ -242,7 +242,6 @@ namespace Timeline.Services { Username = username, Password = _passwordService.HashPassword(password), - Roles = "", Version = 1 }; _databaseContext.Users.Add(newEntity); -- cgit v1.2.3 From 79673878b5427bbedbc4ff4323dce3958d307b49 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 13 Nov 2020 16:17:17 +0800 Subject: Run code clean. --- BackEnd/Timeline.Tests/Helpers/TestApplication.cs | 1 - BackEnd/Timeline.Tests/Helpers/TestClock.cs | 3 --- BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs | 1 - BackEnd/Timeline.Tests/IntegratedTests/UserAvatarTest.cs | 1 - BackEnd/Timeline/Controllers/UserAvatarController.cs | 1 - BackEnd/Timeline/Migrations/20200105150407_Initialize.cs | 4 ++-- BackEnd/Timeline/Migrations/20200221064341_AddJwtToken.cs | 3 +-- BackEnd/Timeline/Migrations/20200229103848_AddPostLocalId.cs | 3 +-- BackEnd/Timeline/Migrations/20200306110049_AddDataTable.cs | 3 +-- .../Timeline/Migrations/20200618064936_TimelineAddModifiedTime.cs | 4 ++-- BackEnd/Timeline/Migrations/20200810155908_AddTimesToUser.cs | 3 +-- BackEnd/Timeline/Models/Http/Common.cs | 4 ++-- BackEnd/Timeline/Services/EntityNames.cs | 7 +------ BackEnd/Timeline/Services/TimelineService.cs | 1 - BackEnd/Timeline/Services/UserDeleteService.cs | 1 - BackEnd/Timeline/Swagger/DocumentDescriptionDocumentProcessor.cs | 2 -- 16 files changed, 11 insertions(+), 31 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/IntegratedTests/TokenTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs index f4a406d1..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; 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/Controllers/UserAvatarController.cs b/BackEnd/Timeline/Controllers/UserAvatarController.cs index 44d45b76..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; 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/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; } } - /// + /// public CommonDeleteResponse() { } - /// + /// public CommonDeleteResponse(int code, string message, bool delete) : base(code, message, new ResponseData(delete)) { 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 04870dcf..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; 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/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 -- cgit v1.2.3