diff options
41 files changed, 573 insertions, 775 deletions
diff --git a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs index 24f4a922..9bd690a2 100644 --- a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs +++ b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs @@ -26,12 +26,12 @@ namespace Timeline.Tests.Helpers var userService = new UserService(NullLogger<UserService>.Instance, context, new PasswordService(), new Clock());
- await userService.ModifyUser(
- await userService.GetUserIdByUsername("administrator"),
+ await userService.ModifyUserAsync(
+ await userService.GetUserIdByUsernameAsync("administrator"),
new ModifyUserParams() { Username = "admin", Password = "adminpw", Nickname = "administrator" });
- var user = await userService.CreateUser("user", "userpw");
- await userService.ModifyUser(user.Id, new ModifyUserParams() { Nickname = "imuser" });
+ var user = await userService.CreateUserAsync(new CreateUserParams("user", "userpw"));
+ await userService.ModifyUserAsync(user.Id, new ModifyUserParams() { Nickname = "imuser" });
}
public async Task DisposeAsync()
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs b/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs index 427881a0..588f2f93 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs @@ -65,7 +65,7 @@ namespace Timeline.Tests.IntegratedTests var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
- await userService.ModifyUser(await userService.GetUserIdByUsername("administrator"), new ModifyUserParams
+ await userService.ModifyUserAsync(await userService.GetUserIdByUsernameAsync("administrator"), new ModifyUserParams
{
Username = "admin",
Password = "adminpw",
@@ -75,8 +75,8 @@ namespace Timeline.Tests.IntegratedTests foreach (var user in users)
{
var (username, password, nickname) = user;
- var u = await userService.CreateUser(username, password);
- await userService.ModifyUser(u.Id, new ModifyUserParams() { Nickname = nickname });
+ var u = await userService.CreateUserAsync(new CreateUserParams(username, password));
+ await userService.ModifyUserAsync(u.Id, new ModifyUserParams() { Nickname = nickname });
}
}
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs index 7206dab8..4d4835ca 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs @@ -92,8 +92,8 @@ namespace Timeline.Tests.IntegratedTests {
// create a user for test
var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
- var id = await userService.GetUserIdByUsername("user1");
- await userService.ModifyUser(id, new ModifyUserParams { Password = "user1pw" });
+ var id = await userService.GetUserIdByUsernameAsync("user1");
+ await userService.ModifyUserAsync(id, new ModifyUserParams { Password = "user1pw" });
}
await client.TestPostAssertErrorAsync(VerifyTokenUrl,
diff --git a/BackEnd/Timeline.Tests/Services/SearchServiceTest.cs b/BackEnd/Timeline.Tests/Services/SearchServiceTest.cs index 0aa412b2..1e94076e 100644 --- a/BackEnd/Timeline.Tests/Services/SearchServiceTest.cs +++ b/BackEnd/Timeline.Tests/Services/SearchServiceTest.cs @@ -35,10 +35,10 @@ namespace Timeline.Tests.Services [Fact]
public async Task UserSearch_Should_Work()
{
- await UserService.CreateUser("hahaha", "p");
- var u2 = await UserService.CreateUser("bababa", "p");
- await UserService.ModifyUser(u2.Id, new ModifyUserParams { Nickname = "hahaha" });
- await UserService.CreateUser("bbbbbb", "p");
+ await UserService.CreateUserAsync(new CreateUserParams("hahaha", "p"));
+ var u2 = await UserService.CreateUserAsync(new CreateUserParams("bababa", "p"));
+ await UserService.ModifyUserAsync(u2.Id, new ModifyUserParams { Nickname = "hahaha" });
+ await UserService.CreateUserAsync(new CreateUserParams("bbbbbb", "p"));
var searchResult = await _service.SearchUser("hah");
searchResult.Items.Should().HaveCount(2);
diff --git a/BackEnd/Timeline.Tests/Services/ServiceTestBase.cs b/BackEnd/Timeline.Tests/Services/ServiceTestBase.cs index 0f4efe95..2c1bdea8 100644 --- a/BackEnd/Timeline.Tests/Services/ServiceTestBase.cs +++ b/BackEnd/Timeline.Tests/Services/ServiceTestBase.cs @@ -37,8 +37,8 @@ namespace Timeline.Tests.Services UserService = new UserService(NullLogger<UserService>.Instance, Database, new PasswordService(), Clock);
TimelineService = new TimelineService(Database, UserService, Clock);
- UserId = await UserService.GetUserIdByUsername("user");
- AdminId = await UserService.GetUserIdByUsername("admin");
+ UserId = await UserService.GetUserIdByUsernameAsync("user");
+ AdminId = await UserService.GetUserIdByUsernameAsync("admin");
await OnInitializeAsync();
OnInitialize();
diff --git a/BackEnd/Timeline/Controllers/TimelineController.cs b/BackEnd/Timeline/Controllers/TimelineController.cs index fc28daa4..2feea476 100644 --- a/BackEnd/Timeline/Controllers/TimelineController.cs +++ b/BackEnd/Timeline/Controllers/TimelineController.cs @@ -101,7 +101,7 @@ namespace Timeline.Controllers {
try
{
- var relatedUserId = await _userService.GetUserIdByUsername(relate);
+ var relatedUserId = await _userService.GetUserIdByUsernameAsync(relate);
var relationType = relateType is null ? TimelineUserRelationshipType.Default : Enum.Parse<TimelineUserRelationshipType>(relateType, true);
@@ -190,7 +190,7 @@ namespace Timeline.Controllers try
{
- var userId = await _userService.GetUserIdByUsername(member);
+ var userId = await _userService.GetUserIdByUsernameAsync(member);
var create = await _service.AddMember(timelineId, userId);
return Ok(CommonPutResponse.Create(create));
}
@@ -222,7 +222,7 @@ namespace Timeline.Controllers try
{
- var userId = await _userService.GetUserIdByUsername(member);
+ var userId = await _userService.GetUserIdByUsernameAsync(member);
var delete = await _service.RemoveMember(timelineId, userId);
return Ok(CommonDeleteResponse.Create(delete));
}
diff --git a/BackEnd/Timeline/Controllers/TokenController.cs b/BackEnd/Timeline/Controllers/TokenController.cs index 1de45754..fc19c64a 100644 --- a/BackEnd/Timeline/Controllers/TokenController.cs +++ b/BackEnd/Timeline/Controllers/TokenController.cs @@ -23,16 +23,14 @@ namespace Timeline.Controllers [ProducesErrorResponseType(typeof(CommonResponse))]
public class TokenController : Controller
{
- private readonly IUserCredentialService _userCredentialService;
private readonly IUserTokenManager _userTokenManager;
private readonly ILogger<TokenController> _logger;
private readonly UserMapper _userMapper;
private readonly IClock _clock;
/// <summary></summary>
- public TokenController(IUserCredentialService userCredentialService, IUserTokenManager userTokenManager, ILogger<TokenController> logger, UserMapper userMapper, IClock clock)
+ public TokenController(IUserTokenManager userTokenManager, ILogger<TokenController> logger, UserMapper userMapper, IClock clock)
{
- _userCredentialService = userCredentialService;
_userTokenManager = userTokenManager;
_logger = logger;
_userMapper = userMapper;
diff --git a/BackEnd/Timeline/Controllers/UserAvatarController.cs b/BackEnd/Timeline/Controllers/UserAvatarController.cs index d0998fa7..5d4c70f3 100644 --- a/BackEnd/Timeline/Controllers/UserAvatarController.cs +++ b/BackEnd/Timeline/Controllers/UserAvatarController.cs @@ -55,7 +55,7 @@ namespace Timeline.Controllers long id;
try
{
- id = await _userService.GetUserIdByUsername(username);
+ id = await _userService.GetUserIdByUsernameAsync(username);
}
catch (UserNotExistException e)
{
@@ -91,7 +91,7 @@ namespace Timeline.Controllers long id;
try
{
- id = await _userService.GetUserIdByUsername(username);
+ id = await _userService.GetUserIdByUsernameAsync(username);
}
catch (UserNotExistException e)
{
@@ -150,7 +150,7 @@ namespace Timeline.Controllers long id;
try
{
- id = await _userService.GetUserIdByUsername(username);
+ id = await _userService.GetUserIdByUsernameAsync(username);
}
catch (UserNotExistException e)
{
diff --git a/BackEnd/Timeline/Controllers/UserController.cs b/BackEnd/Timeline/Controllers/UserController.cs index 95f65d25..a48a1b73 100644 --- a/BackEnd/Timeline/Controllers/UserController.cs +++ b/BackEnd/Timeline/Controllers/UserController.cs @@ -26,18 +26,16 @@ namespace Timeline.Controllers {
private readonly ILogger<UserController> _logger;
private readonly IUserService _userService;
- private readonly IUserCredentialService _userCredentialService;
private readonly IUserPermissionService _userPermissionService;
private readonly IUserDeleteService _userDeleteService;
private readonly UserMapper _userMapper;
private readonly IMapper _mapper;
/// <summary></summary>
- public UserController(ILogger<UserController> logger, IUserService userService, IUserCredentialService userCredentialService, IUserPermissionService userPermissionService, IUserDeleteService userDeleteService, UserMapper userMapper, IMapper mapper)
+ public UserController(ILogger<UserController> logger, IUserService userService, IUserPermissionService userPermissionService, IUserDeleteService userDeleteService, UserMapper userMapper, IMapper mapper)
{
_logger = logger;
_userService = userService;
- _userCredentialService = userCredentialService;
_userPermissionService = userPermissionService;
_userDeleteService = userDeleteService;
_userMapper = userMapper;
@@ -54,7 +52,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<List<HttpUser>>> List()
{
- var users = await _userService.GetUsers();
+ var users = await _userService.GetUsersAsync();
var result = await _userMapper.MapToHttp(users, Url);
return result;
}
@@ -72,7 +70,8 @@ namespace Timeline.Controllers {
try
{
- var user = await _userService.CreateUser(body.Username, body.Password);
+ var user = await _userService.CreateUserAsync(
+ new CreateUserParams(body.Username, body.Password) { Nickname = body.Nickname });
return await _userMapper.MapToHttp(user, Url);
}
catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.User)
@@ -93,8 +92,8 @@ namespace Timeline.Controllers {
try
{
- var id = await _userService.GetUserIdByUsername(username);
- var user = await _userService.GetUser(id);
+ var id = await _userService.GetUserIdByUsernameAsync(username);
+ var user = await _userService.GetUserAsync(id);
return await _userMapper.MapToHttp(user, Url);
}
catch (UserNotExistException e)
@@ -122,8 +121,8 @@ namespace Timeline.Controllers {
try
{
- var id = await _userService.GetUserIdByUsername(username);
- var user = await _userService.ModifyUser(id, _mapper.Map<ModifyUserParams>(body));
+ var id = await _userService.GetUserIdByUsernameAsync(username);
+ var user = await _userService.ModifyUserAsync(id, _mapper.Map<ModifyUserParams>(body));
return await _userMapper.MapToHttp(user, Url);
}
catch (UserNotExistException e)
@@ -150,7 +149,7 @@ namespace Timeline.Controllers return StatusCode(StatusCodes.Status403Forbidden,
ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Password));
- var user = await _userService.ModifyUser(this.GetUserId(), _mapper.Map<ModifyUserParams>(body));
+ var user = await _userService.ModifyUserAsync(this.GetUserId(), _mapper.Map<ModifyUserParams>(body));
return await _userMapper.MapToHttp(user, Url);
}
}
@@ -192,7 +191,7 @@ namespace Timeline.Controllers {
try
{
- await _userCredentialService.ChangePassword(this.GetUserId(), request.OldPassword, request.NewPassword);
+ await _userService.ChangePassword(this.GetUserId(), request.OldPassword, request.NewPassword);
return Ok();
}
catch (BadPasswordException e)
@@ -214,7 +213,7 @@ namespace Timeline.Controllers {
try
{
- var id = await _userService.GetUserIdByUsername(username);
+ var id = await _userService.GetUserIdByUsernameAsync(username);
await _userPermissionService.AddPermissionToUserAsync(id, permission);
return Ok();
}
@@ -238,7 +237,7 @@ namespace Timeline.Controllers {
try
{
- var id = await _userService.GetUserIdByUsername(username);
+ var id = await _userService.GetUserIdByUsernameAsync(username);
await _userPermissionService.RemovePermissionFromUserAsync(id, permission);
return Ok();
}
diff --git a/BackEnd/Timeline/Models/Http/HttpUserPostRequest.cs b/BackEnd/Timeline/Models/Http/HttpUserPostRequest.cs index c8cc004b..4eb02928 100644 --- a/BackEnd/Timeline/Models/Http/HttpUserPostRequest.cs +++ b/BackEnd/Timeline/Models/Http/HttpUserPostRequest.cs @@ -20,5 +20,8 @@ namespace Timeline.Models.Http /// </summary>
[Required, MinLength(1)]
public string Password { get; set; } = default!;
+
+ [Nickname]
+ public string? Nickname { get; set; }
}
}
diff --git a/BackEnd/Timeline/Resources/Services/UserService.Designer.cs b/BackEnd/Timeline/Resources/Services/UserService.Designer.cs deleted file mode 100644 index 564dd26c..00000000 --- a/BackEnd/Timeline/Resources/Services/UserService.Designer.cs +++ /dev/null @@ -1,162 +0,0 @@ -//------------------------------------------------------------------------------
-// <auto-generated>
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-// </auto-generated>
-//------------------------------------------------------------------------------
-
-namespace Timeline.Resources.Services {
- using System;
-
-
- /// <summary>
- /// A strongly-typed resource class, for looking up localized strings, etc.
- /// </summary>
- // This class was auto-generated by the StronglyTypedResourceBuilder
- // class via a tool like ResGen or Visual Studio.
- // To add or remove a member, edit your .ResX file then rerun ResGen
- // with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class UserService {
-
- private static global::System.Resources.ResourceManager resourceMan;
-
- private static global::System.Globalization.CultureInfo resourceCulture;
-
- [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal UserService() {
- }
-
- /// <summary>
- /// Returns the cached ResourceManager instance used by this class.
- /// </summary>
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Resources.ResourceManager ResourceManager {
- get {
- if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Services.UserService", typeof(UserService).Assembly);
- resourceMan = temp;
- }
- return resourceMan;
- }
- }
-
- /// <summary>
- /// Overrides the current thread's CurrentUICulture property for all
- /// resource lookups using this strongly typed resource class.
- /// </summary>
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture {
- get {
- return resourceCulture;
- }
- set {
- resourceCulture = value;
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to New username is of bad format..
- /// </summary>
- internal static string ExceptionNewUsernameBadFormat {
- get {
- return ResourceManager.GetString("ExceptionNewUsernameBadFormat", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Nickname is of bad format, because {0}..
- /// </summary>
- internal static string ExceptionNicknameBadFormat {
- get {
- return ResourceManager.GetString("ExceptionNicknameBadFormat", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Old username is of bad format..
- /// </summary>
- internal static string ExceptionOldUsernameBadFormat {
- get {
- return ResourceManager.GetString("ExceptionOldUsernameBadFormat", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Password can't be empty..
- /// </summary>
- internal static string ExceptionPasswordEmpty {
- get {
- return ResourceManager.GetString("ExceptionPasswordEmpty", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Password can't be null..
- /// </summary>
- internal static string ExceptionPasswordNull {
- get {
- return ResourceManager.GetString("ExceptionPasswordNull", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Username is of bad format, because {0}..
- /// </summary>
- internal static string ExceptionUsernameBadFormat {
- get {
- return ResourceManager.GetString("ExceptionUsernameBadFormat", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to A user with given username already exists..
- /// </summary>
- internal static string ExceptionUsernameConflict {
- get {
- return ResourceManager.GetString("ExceptionUsernameConflict", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Username can't be null..
- /// </summary>
- internal static string ExceptionUsernameNull {
- get {
- return ResourceManager.GetString("ExceptionUsernameNull", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to A new user entry is added to the database..
- /// </summary>
- internal static string LogDatabaseCreate {
- get {
- return ResourceManager.GetString("LogDatabaseCreate", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to A user entry is removed from the database..
- /// </summary>
- internal static string LogDatabaseRemove {
- get {
- return ResourceManager.GetString("LogDatabaseRemove", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to A user entry is updated to the database..
- /// </summary>
- internal static string LogDatabaseUpdate {
- get {
- return ResourceManager.GetString("LogDatabaseUpdate", resourceCulture);
- }
- }
- }
-}
diff --git a/BackEnd/Timeline/Resources/Services/UserService.resx b/BackEnd/Timeline/Resources/Services/UserService.resx deleted file mode 100644 index 1f3c0011..00000000 --- a/BackEnd/Timeline/Resources/Services/UserService.resx +++ /dev/null @@ -1,153 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?>
-<root>
- <!--
- Microsoft ResX Schema
-
- Version 2.0
-
- The primary goals of this format is to allow a simple XML format
- that is mostly human readable. The generation and parsing of the
- various data types are done through the TypeConverter classes
- associated with the data types.
-
- Example:
-
- ... ado.net/XML headers & schema ...
- <resheader name="resmimetype">text/microsoft-resx</resheader>
- <resheader name="version">2.0</resheader>
- <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
- <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
- <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
- <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
- <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
- <value>[base64 mime encoded serialized .NET Framework object]</value>
- </data>
- <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
- <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
- <comment>This is a comment</comment>
- </data>
-
- There are any number of "resheader" rows that contain simple
- name/value pairs.
-
- Each data row contains a name, and value. The row also contains a
- type or mimetype. Type corresponds to a .NET class that support
- text/value conversion through the TypeConverter architecture.
- Classes that don't support this are serialized and stored with the
- mimetype set.
-
- The mimetype is used for serialized objects, and tells the
- ResXResourceReader how to depersist the object. This is currently not
- extensible. For a given mimetype the value must be set accordingly:
-
- Note - application/x-microsoft.net.object.binary.base64 is the format
- that the ResXResourceWriter will generate, however the reader can
- read any of the formats listed below.
-
- mimetype: application/x-microsoft.net.object.binary.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.soap.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.bytearray.base64
- value : The object must be serialized into a byte array
- : using a System.ComponentModel.TypeConverter
- : and then encoded with base64 encoding.
- -->
- <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
- <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
- <xsd:element name="root" msdata:IsDataSet="true">
- <xsd:complexType>
- <xsd:choice maxOccurs="unbounded">
- <xsd:element name="metadata">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" />
- </xsd:sequence>
- <xsd:attribute name="name" use="required" type="xsd:string" />
- <xsd:attribute name="type" type="xsd:string" />
- <xsd:attribute name="mimetype" type="xsd:string" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="assembly">
- <xsd:complexType>
- <xsd:attribute name="alias" type="xsd:string" />
- <xsd:attribute name="name" type="xsd:string" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="data">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
- <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
- <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="resheader">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" />
- </xsd:complexType>
- </xsd:element>
- </xsd:choice>
- </xsd:complexType>
- </xsd:element>
- </xsd:schema>
- <resheader name="resmimetype">
- <value>text/microsoft-resx</value>
- </resheader>
- <resheader name="version">
- <value>2.0</value>
- </resheader>
- <resheader name="reader">
- <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <resheader name="writer">
- <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <data name="ExceptionNewUsernameBadFormat" xml:space="preserve">
- <value>New username is of bad format.</value>
- </data>
- <data name="ExceptionNicknameBadFormat" xml:space="preserve">
- <value>Nickname is of bad format, because {0}.</value>
- </data>
- <data name="ExceptionOldUsernameBadFormat" xml:space="preserve">
- <value>Old username is of bad format.</value>
- </data>
- <data name="ExceptionPasswordEmpty" xml:space="preserve">
- <value>Password can't be empty.</value>
- </data>
- <data name="ExceptionPasswordNull" xml:space="preserve">
- <value>Password can't be null.</value>
- </data>
- <data name="ExceptionUsernameBadFormat" xml:space="preserve">
- <value>Username is of bad format, because {0}.</value>
- </data>
- <data name="ExceptionUsernameConflict" xml:space="preserve">
- <value>A user with given username already exists.</value>
- </data>
- <data name="ExceptionUsernameNull" xml:space="preserve">
- <value>Username can't be null.</value>
- </data>
- <data name="LogDatabaseCreate" xml:space="preserve">
- <value>A new user entry is added to the database.</value>
- </data>
- <data name="LogDatabaseRemove" xml:space="preserve">
- <value>A user entry is removed from the database.</value>
- </data>
- <data name="LogDatabaseUpdate" xml:space="preserve">
- <value>A user entry is updated to the database.</value>
- </data>
-</root>
\ No newline at end of file diff --git a/BackEnd/Timeline/Services/Api/BookmarkTimelineService.cs b/BackEnd/Timeline/Services/Api/BookmarkTimelineService.cs index 0d4cc0a6..cabc1db2 100644 --- a/BackEnd/Timeline/Services/Api/BookmarkTimelineService.cs +++ b/BackEnd/Timeline/Services/Api/BookmarkTimelineService.cs @@ -93,7 +93,7 @@ namespace Timeline.Services.Api public async Task<bool> AddBookmark(long userId, long timelineId)
{
- if (!await _userService.CheckUserExistence(userId))
+ if (!await _userService.CheckUserExistenceAsync(userId))
throw new UserNotExistException(userId);
if (!await _timelineService.CheckExistence(timelineId))
@@ -115,7 +115,7 @@ namespace Timeline.Services.Api public async Task<List<long>> GetBookmarks(long userId)
{
- if (!await _userService.CheckUserExistence(userId))
+ if (!await _userService.CheckUserExistenceAsync(userId))
throw new UserNotExistException(userId);
var entities = await _database.BookmarkTimelines.Where(t => t.UserId == userId).OrderBy(t => t.Rank).Select(t => new { t.TimelineId }).ToListAsync();
@@ -125,7 +125,7 @@ namespace Timeline.Services.Api public async Task<bool> IsBookmark(long userId, long timelineId, bool checkUserExistence = true, bool checkTimelineExistence = true)
{
- if (checkUserExistence && !await _userService.CheckUserExistence(userId))
+ if (checkUserExistence && !await _userService.CheckUserExistenceAsync(userId))
throw new UserNotExistException(userId);
if (checkTimelineExistence && !await _timelineService.CheckExistence(timelineId))
@@ -136,7 +136,7 @@ namespace Timeline.Services.Api public async Task MoveBookmark(long userId, long timelineId, long newPosition)
{
- if (!await _userService.CheckUserExistence(userId))
+ if (!await _userService.CheckUserExistenceAsync(userId))
throw new UserNotExistException(userId);
if (!await _timelineService.CheckExistence(timelineId))
@@ -178,7 +178,7 @@ namespace Timeline.Services.Api public async Task<bool> RemoveBookmark(long userId, long timelineId)
{
- if (!await _userService.CheckUserExistence(userId))
+ if (!await _userService.CheckUserExistenceAsync(userId))
throw new UserNotExistException(userId);
if (!await _timelineService.CheckExistence(timelineId))
diff --git a/BackEnd/Timeline/Services/Api/HighlightTimelineService.cs b/BackEnd/Timeline/Services/Api/HighlightTimelineService.cs index 9ef8ea84..419aa68d 100644 --- a/BackEnd/Timeline/Services/Api/HighlightTimelineService.cs +++ b/BackEnd/Timeline/Services/Api/HighlightTimelineService.cs @@ -95,7 +95,7 @@ namespace Timeline.Services.Api if (!await _timelineService.CheckExistence(timelineId))
throw new TimelineNotExistException(timelineId);
- if (operatorId.HasValue && !await _userService.CheckUserExistence(operatorId.Value))
+ if (operatorId.HasValue && !await _userService.CheckUserExistenceAsync(operatorId.Value))
{
throw new UserNotExistException(null, operatorId.Value, "User with given operator id does not exist.", null);
}
@@ -121,7 +121,7 @@ namespace Timeline.Services.Api if (!await _timelineService.CheckExistence(timelineId))
throw new TimelineNotExistException(timelineId);
- if (operatorId.HasValue && !await _userService.CheckUserExistence(operatorId.Value))
+ if (operatorId.HasValue && !await _userService.CheckUserExistenceAsync(operatorId.Value))
{
throw new UserNotExistException(null, operatorId.Value, "User with given operator id does not exist.", null);
}
diff --git a/BackEnd/Timeline/Services/BasicServicesServiceCollectionExtensions.cs b/BackEnd/Timeline/Services/BasicServicesServiceCollectionExtensions.cs index 6465fb9f..e9221fef 100644 --- a/BackEnd/Timeline/Services/BasicServicesServiceCollectionExtensions.cs +++ b/BackEnd/Timeline/Services/BasicServicesServiceCollectionExtensions.cs @@ -13,6 +13,7 @@ namespace Timeline.Services {
services.TryAddSingleton<IPathProvider, PathProvider>();
services.TryAddTransient<IClock, Clock>();
+ return services;
}
}
}
diff --git a/BackEnd/Timeline/Services/Imaging/ImageException.cs b/BackEnd/Timeline/Services/Imaging/ImageException.cs index 12aefa0a..18c8a4d3 100644 --- a/BackEnd/Timeline/Services/Imaging/ImageException.cs +++ b/BackEnd/Timeline/Services/Imaging/ImageException.cs @@ -38,7 +38,7 @@ namespace Timeline.Services.Imaging System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
private static string MakeMessage(ErrorReason? reason) =>
- string.Format(CultureInfo.InvariantCulture, Resource.ExceptionImage, reason switch
+ string.Format(CultureInfo.CurrentCulture, Resource.ExceptionImage, reason switch
{
ErrorReason.CantDecode => Resource.ExceptionImageReasonCantDecode,
ErrorReason.UnmatchedFormat => Resource.ExceptionImageReasonUnmatchedFormat,
diff --git a/BackEnd/Timeline/Services/Timeline/BasicTimelineService.cs b/BackEnd/Timeline/Services/Timeline/BasicTimelineService.cs index f917b176..d633be4d 100644 --- a/BackEnd/Timeline/Services/Timeline/BasicTimelineService.cs +++ b/BackEnd/Timeline/Services/Timeline/BasicTimelineService.cs @@ -94,7 +94,7 @@ namespace Timeline.Services.Timeline long userId;
try
{
- userId = await _basicUserService.GetUserIdByUsername(timelineName);
+ userId = await _basicUserService.GetUserIdByUsernameAsync(timelineName);
}
catch (UserNotExistException e)
{
diff --git a/BackEnd/Timeline/Services/Timeline/TimelinePostNotExistException.cs b/BackEnd/Timeline/Services/Timeline/TimelinePostNotExistException.cs index e0e819aa..87c190ab 100644 --- a/BackEnd/Timeline/Services/Timeline/TimelinePostNotExistException.cs +++ b/BackEnd/Timeline/Services/Timeline/TimelinePostNotExistException.cs @@ -22,7 +22,7 @@ namespace Timeline.Services.Timeline private static string MakeMessage(bool isDelete)
{
- return string.Format(CultureInfo.InvariantCulture, Resource.ExceptionTimelinePostNoExist, isDelete ? Resource.ExceptionTimelinePostNoExistReasonDeleted : Resource.ExceptionTimelinePostNoExistReasonNotCreated);
+ return string.Format(CultureInfo.CurrentCulture, Resource.ExceptionTimelinePostNoExist, isDelete ? Resource.ExceptionTimelinePostNoExistReasonDeleted : Resource.ExceptionTimelinePostNoExistReasonNotCreated);
}
public long? TimelineId { get; set; }
diff --git a/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs b/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs index 6a6273c5..d18e65e0 100644 --- a/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs +++ b/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs @@ -190,7 +190,7 @@ namespace Timeline.Services.Timeline private async Task CheckUserExistence(long userId)
{
- if (!await _basicUserService.CheckUserExistence(userId))
+ if (!await _basicUserService.CheckUserExistenceAsync(userId))
throw new UserNotExistException(userId);
}
diff --git a/BackEnd/Timeline/Services/Timeline/TimelineService.cs b/BackEnd/Timeline/Services/Timeline/TimelineService.cs index 342ce234..0086726e 100644 --- a/BackEnd/Timeline/Services/Timeline/TimelineService.cs +++ b/BackEnd/Timeline/Services/Timeline/TimelineService.cs @@ -284,7 +284,7 @@ namespace Timeline.Services.Timeline if (!await CheckExistence(timelineId))
throw new TimelineNotExistException(timelineId);
- if (!await _userService.CheckUserExistence(userId))
+ if (!await _userService.CheckUserExistenceAsync(userId))
throw new UserNotExistException(userId);
if (await _database.TimelineMembers.AnyAsync(m => m.TimelineId == timelineId && m.UserId == userId))
@@ -306,7 +306,7 @@ namespace Timeline.Services.Timeline if (!await CheckExistence(timelineId))
throw new TimelineNotExistException(timelineId);
- if (!await _userService.CheckUserExistence(userId))
+ if (!await _userService.CheckUserExistenceAsync(userId))
throw new UserNotExistException(userId);
var entity = await _database.TimelineMembers.SingleOrDefaultAsync(m => m.TimelineId == timelineId && m.UserId == userId);
diff --git a/BackEnd/Timeline/Services/Token/UserTokenManager.cs b/BackEnd/Timeline/Services/Token/UserTokenManager.cs index 4a5f08d2..31cc70f2 100644 --- a/BackEnd/Timeline/Services/Token/UserTokenManager.cs +++ b/BackEnd/Timeline/Services/Token/UserTokenManager.cs @@ -48,16 +48,14 @@ namespace Timeline.Services.Token private readonly ILogger<UserTokenManager> _logger;
private readonly IOptionsMonitor<TokenOptions> _tokenOptionsMonitor;
private readonly IUserService _userService;
- private readonly IUserCredentialService _userCredentialService;
private readonly IUserTokenHandler _userTokenService;
private readonly IClock _clock;
- public UserTokenManager(ILogger<UserTokenManager> logger, IOptionsMonitor<TokenOptions> tokenOptionsMonitor, IUserService userService, IUserCredentialService userCredentialService, IUserTokenHandler userTokenService, IClock clock)
+ public UserTokenManager(ILogger<UserTokenManager> logger, IOptionsMonitor<TokenOptions> tokenOptionsMonitor, IUserService userService, IUserTokenHandler userTokenService, IClock clock)
{
_logger = logger;
_tokenOptionsMonitor = tokenOptionsMonitor;
_userService = userService;
- _userCredentialService = userCredentialService;
_userTokenService = userTokenService;
_clock = clock;
}
@@ -71,8 +69,8 @@ namespace Timeline.Services.Token if (password == null)
throw new ArgumentNullException(nameof(password));
- var userId = await _userCredentialService.VerifyCredential(username, password);
- var user = await _userService.GetUser(userId);
+ var userId = await _userService.VerifyCredential(username, password);
+ var user = await _userService.GetUserAsync(userId);
var token = _userTokenService.GenerateToken(new UserTokenInfo
{
@@ -98,7 +96,7 @@ namespace Timeline.Services.Token try
{
- var user = await _userService.GetUser(tokenInfo.Id);
+ var user = await _userService.GetUserAsync(tokenInfo.Id);
if (tokenInfo.Version < user.Version)
throw new UserTokenVersionExpiredException(token, tokenInfo.Version, user.Version);
diff --git a/BackEnd/Timeline/Services/User/BasicUserService.cs b/BackEnd/Timeline/Services/User/BasicUserService.cs index a3763ef6..1f1b25f5 100644 --- a/BackEnd/Timeline/Services/User/BasicUserService.cs +++ b/BackEnd/Timeline/Services/User/BasicUserService.cs @@ -7,37 +7,6 @@ using Timeline.Models.Validation; namespace Timeline.Services.User
{
- /// <summary>
- /// This service provide some basic user features, which should be used internally for other services.
- /// </summary>
- public interface IBasicUserService
- {
- /// <summary>
- /// Check if a user exists.
- /// </summary>
- /// <param name="id">The id of the user.</param>
- /// <returns>True if exists. Otherwise false.</returns>
- Task<bool> CheckUserExistence(long id);
-
- /// <summary>
- /// Get the user id of given username.
- /// </summary>
- /// <param name="username">Username of the user.</param>
- /// <returns>The id of the user.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format.</exception>
- /// <exception cref="UserNotExistException">Thrown when the user with given username does not exist.</exception>
- Task<long> GetUserIdByUsername(string username);
-
- /// <summary>
- /// Get the username modified time of a user.
- /// </summary>
- /// <param name="userId">User id.</param>
- /// <returns>The time.</returns>
- /// <exception cref="UserNotExistException">Thrown when user does not exist.</exception>
- Task<DateTime> GetUsernameLastModifiedTime(long userId);
- }
-
public class BasicUserService : IBasicUserService
{
private readonly DatabaseContext _database;
@@ -49,12 +18,12 @@ namespace Timeline.Services.User _database = database;
}
- public async Task<bool> CheckUserExistence(long id)
+ public async Task<bool> CheckUserExistenceAsync(long id)
{
return await _database.Users.AnyAsync(u => u.Id == id);
}
- public async Task<long> GetUserIdByUsername(string username)
+ public async Task<long> GetUserIdByUsernameAsync(string username)
{
if (username == null)
throw new ArgumentNullException(nameof(username));
@@ -70,7 +39,7 @@ namespace Timeline.Services.User return entity.Id;
}
- public async Task<DateTime> GetUsernameLastModifiedTime(long userId)
+ public async Task<DateTime> GetUsernameLastModifiedTimeAsync(long userId)
{
var entity = await _database.Users.Where(u => u.Id == userId).Select(u => new { u.UsernameChangeTime }).SingleOrDefaultAsync();
@@ -85,7 +54,7 @@ namespace Timeline.Services.User {
public static async Task ThrowIfUserNotExist(this IBasicUserService service, long userId)
{
- if (!await service.CheckUserExistence(userId))
+ if (!await service.CheckUserExistenceAsync(userId))
{
throw new UserNotExistException(userId);
}
diff --git a/BackEnd/Timeline/Services/User/CreateUserParams.cs b/BackEnd/Timeline/Services/User/CreateUserParams.cs new file mode 100644 index 00000000..e66f83dc --- /dev/null +++ b/BackEnd/Timeline/Services/User/CreateUserParams.cs @@ -0,0 +1,17 @@ +using System;
+
+namespace Timeline.Services.User
+{
+ public class CreateUserParams
+ {
+ public CreateUserParams(string username, string password)
+ {
+ Username = username ?? throw new ArgumentNullException(nameof(username));
+ Password = password ?? throw new ArgumentNullException(nameof(password));
+ }
+
+ public string Username { get; set; }
+ public string Password { get; set; }
+ public string? Nickname { get; set; }
+ }
+}
diff --git a/BackEnd/Timeline/Services/User/IBasicUserService.cs b/BackEnd/Timeline/Services/User/IBasicUserService.cs new file mode 100644 index 00000000..0e30d733 --- /dev/null +++ b/BackEnd/Timeline/Services/User/IBasicUserService.cs @@ -0,0 +1,36 @@ +using System;
+using System.Threading.Tasks;
+
+namespace Timeline.Services.User
+{
+ /// <summary>
+ /// This service provide some basic user features, which should be used internally for other services.
+ /// </summary>
+ public interface IBasicUserService
+ {
+ /// <summary>
+ /// Check if a user exists.
+ /// </summary>
+ /// <param name="id">The id of the user.</param>
+ /// <returns>True if exists. Otherwise false.</returns>
+ Task<bool> CheckUserExistenceAsync(long id);
+
+ /// <summary>
+ /// Get the user id of given username.
+ /// </summary>
+ /// <param name="username">Username of the user.</param>
+ /// <returns>The id of the user.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format.</exception>
+ /// <exception cref="UserNotExistException">Thrown when the user with given username does not exist.</exception>
+ Task<long> GetUserIdByUsernameAsync(string username);
+
+ /// <summary>
+ /// Get the username modified time of a user.
+ /// </summary>
+ /// <param name="userId">User id.</param>
+ /// <returns>The time.</returns>
+ /// <exception cref="UserNotExistException">Thrown when user does not exist.</exception>
+ Task<DateTime> GetUsernameLastModifiedTimeAsync(long userId);
+ }
+}
diff --git a/BackEnd/Timeline/Services/User/IUserDeleteService.cs b/BackEnd/Timeline/Services/User/IUserDeleteService.cs new file mode 100644 index 00000000..ce9448ac --- /dev/null +++ b/BackEnd/Timeline/Services/User/IUserDeleteService.cs @@ -0,0 +1,18 @@ +using System;
+using System.Threading.Tasks;
+
+namespace Timeline.Services.User
+{
+ public interface IUserDeleteService
+ {
+ /// <summary>
+ /// Delete a user of given username.
+ /// </summary>
+ /// <param name="username">Username of the user to delete. Can't be null.</param>
+ /// <returns>True if user is deleted, false if user not exist.</returns>
+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="username"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format.</exception>
+ /// <exception cref="InvalidOperationOnRootUserException">Thrown when deleting root user.</exception>
+ Task<bool> DeleteUser(string username);
+ }
+}
diff --git a/BackEnd/Timeline/Services/User/IUserPermissionService.cs b/BackEnd/Timeline/Services/User/IUserPermissionService.cs new file mode 100644 index 00000000..7ff1275b --- /dev/null +++ b/BackEnd/Timeline/Services/User/IUserPermissionService.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks;
+
+namespace Timeline.Services.User
+{
+ public interface IUserPermissionService
+ {
+ /// <summary>
+ /// Get permissions of a user.
+ /// </summary>
+ /// <param name="userId">The id of the user.</param>
+ /// <param name="checkUserExistence">Whether check the user's existence.</param>
+ /// <returns>The permission list.</returns>
+ /// <exception cref="UserNotExistException">Thrown when <paramref name="checkUserExistence"/> is true and user does not exist.</exception>
+ Task<UserPermissions> GetPermissionsOfUserAsync(long userId, bool checkUserExistence = true);
+
+ /// <summary>
+ /// Add a permission to user.
+ /// </summary>
+ /// <param name="userId">The id of the user.</param>
+ /// <param name="permission">The new permission.</param>
+ /// <exception cref="UserNotExistException">Thrown when user does not exist.</exception>
+ /// <exception cref="InvalidOperationOnRootUserException">Thrown when change root user's permission.</exception>
+ Task AddPermissionToUserAsync(long userId, UserPermission permission);
+
+ /// <summary>
+ /// Remove a permission from user.
+ /// </summary>
+ /// <param name="userId">The id of the user.</param>
+ /// <param name="permission">The permission.</param>
+ /// <param name="checkUserExistence">Whether check the user's existence.</param>
+ /// <exception cref="UserNotExistException">Thrown when <paramref name="checkUserExistence"/> is true and user does not exist.</exception>
+ /// <exception cref="InvalidOperationOnRootUserException">Thrown when change root user's permission.</exception>
+ Task RemovePermissionFromUserAsync(long userId, UserPermission permission, bool checkUserExistence = true);
+ }
+}
diff --git a/BackEnd/Timeline/Services/User/IUserService.cs b/BackEnd/Timeline/Services/User/IUserService.cs new file mode 100644 index 00000000..06155c55 --- /dev/null +++ b/BackEnd/Timeline/Services/User/IUserService.cs @@ -0,0 +1,71 @@ +using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Timeline.Entities;
+
+namespace Timeline.Services.User
+{
+ public interface IUserService : IBasicUserService
+ {
+ /// <summary>
+ /// Try to get a user by id.
+ /// </summary>
+ /// <param name="id">The id of the user.</param>
+ /// <returns>The user info.</returns>
+ /// <exception cref="UserNotExistException">Thrown when the user with given id does not exist.</exception>
+ Task<UserEntity> GetUserAsync(long id);
+
+ /// <summary>
+ /// List all users.
+ /// </summary>
+ /// <returns>The user info of users.</returns>
+ Task<List<UserEntity>> GetUsersAsync();
+
+ /// <summary>
+ /// Create a user with given info.
+ /// </summary>
+ /// <param name="param">Info of new user.</param>
+ /// <returns>The the new user.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="param"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when param field is illegal.</exception>
+ /// <exception cref="EntityAlreadyExistException">Thrown when a user with given username already exists.</exception>
+ Task<UserEntity> CreateUserAsync(CreateUserParams param);
+
+ /// <summary>
+ /// Modify a user.
+ /// </summary>
+ /// <param name="id">The id of the user.</param>
+ /// <param name="param">The new information.</param>
+ /// <returns>The new user info.</returns>
+ /// <exception cref="ArgumentException">Thrown when some fields in <paramref name="param"/> is bad.</exception>
+ /// <exception cref="UserNotExistException">Thrown when user with given id does not exist.</exception>
+ /// <remarks>
+ /// Version will increase if password is changed.
+ /// </remarks>
+ Task<UserEntity> ModifyUserAsync(long id, ModifyUserParams? param);
+
+ /// <summary>
+ /// Try to verify the given username and password.
+ /// </summary>
+ /// <param name="username">The username of the user to verify.</param>
+ /// <param name="password">The password of the user to verify.</param>
+ /// <returns>User id.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format or <paramref name="password"/> is empty.</exception>
+ /// <exception cref="UserNotExistException">Thrown when the user with given username does not exist.</exception>
+ /// <exception cref="BadPasswordException">Thrown when password is wrong.</exception>
+ Task<long> VerifyCredential(string username, string password);
+
+ /// <summary>
+ /// Try to change a user's password with old password.
+ /// </summary>
+ /// <param name="id">The id of user to change password of.</param>
+ /// <param name="oldPassword">Old password.</param>
+ /// <param name="newPassword">New password.</param>
+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="oldPassword"/> or <paramref name="newPassword"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown if <paramref name="oldPassword"/> or <paramref name="newPassword"/> is empty.</exception>
+ /// <exception cref="UserNotExistException">Thrown if the user with given username does not exist.</exception>
+ /// <exception cref="BadPasswordException">Thrown if the old password is wrong.</exception>
+ Task ChangePassword(long id, string oldPassword, string newPassword);
+ }
+}
diff --git a/BackEnd/Timeline/Services/User/InvalidOperationOnRootUserException.cs b/BackEnd/Timeline/Services/User/InvalidOperationOnRootUserException.cs index c432febd..636985d0 100644 --- a/BackEnd/Timeline/Services/User/InvalidOperationOnRootUserException.cs +++ b/BackEnd/Timeline/Services/User/InvalidOperationOnRootUserException.cs @@ -6,7 +6,7 @@ namespace Timeline.Services.User [Serializable]
public class InvalidOperationOnRootUserException : InvalidOperationException
{
- public InvalidOperationOnRootUserException() { }
+ public InvalidOperationOnRootUserException() : base(Resource.ExceptionInvalidOperationOnRootUser) { }
public InvalidOperationOnRootUserException(string message) : base(message) { }
public InvalidOperationOnRootUserException(string message, Exception inner) : base(message, inner) { }
protected InvalidOperationOnRootUserException(
diff --git a/BackEnd/Timeline/Services/User/ModifyUserParams.cs b/BackEnd/Timeline/Services/User/ModifyUserParams.cs new file mode 100644 index 00000000..296c3212 --- /dev/null +++ b/BackEnd/Timeline/Services/User/ModifyUserParams.cs @@ -0,0 +1,12 @@ +namespace Timeline.Services.User
+{
+ /// <summary>
+ /// Null means not change.
+ /// </summary>
+ public class ModifyUserParams
+ {
+ public string? Username { get; set; }
+ public string? Password { get; set; }
+ public string? Nickname { get; set; }
+ }
+}
diff --git a/BackEnd/Timeline/Services/User/PasswordService.cs b/BackEnd/Timeline/Services/User/PasswordService.cs index 1c14875f..5c2062dd 100644 --- a/BackEnd/Timeline/Services/User/PasswordService.cs +++ b/BackEnd/Timeline/Services/User/PasswordService.cs @@ -19,7 +19,7 @@ namespace Timeline.Services.User public HashedPasswordBadFromatException(string message, Exception inner) : base(message, inner) { }
public HashedPasswordBadFromatException(string hashedPassword, string reason, Exception? inner = null)
- : base(string.Format(CultureInfo.InvariantCulture, Resource.ExceptionHashedPasswordBadFormat, reason), inner) { HashedPassword = hashedPassword; }
+ : base(string.Format(CultureInfo.CurrentCulture, Resource.ExceptionHashedPasswordBadFormat, reason), inner) { HashedPassword = hashedPassword; }
protected HashedPasswordBadFromatException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
diff --git a/BackEnd/Timeline/Services/User/Resource.Designer.cs b/BackEnd/Timeline/Services/User/Resource.Designer.cs index 4f75b055..908e2732 100644 --- a/BackEnd/Timeline/Services/User/Resource.Designer.cs +++ b/BackEnd/Timeline/Services/User/Resource.Designer.cs @@ -70,6 +70,24 @@ namespace Timeline.Services.User { }
/// <summary>
+ /// Looks up a localized string similar to Can't change root user's permission..
+ /// </summary>
+ internal static string ExceptionChangeRootUserPermission {
+ get {
+ return ResourceManager.GetString("ExceptionChangeRootUserPermission", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Can't delete root user..
+ /// </summary>
+ internal static string ExceptionDeleteRootUser {
+ get {
+ return ResourceManager.GetString("ExceptionDeleteRootUser", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The hashes password is of bad format. It might not be created by server. Reason: {0}.
/// </summary>
internal static string ExceptionHashedPasswordBadFormat {
@@ -133,6 +151,15 @@ namespace Timeline.Services.User { }
/// <summary>
+ /// Looks up a localized string similar to Can't perform such operation on root user..
+ /// </summary>
+ internal static string ExceptionInvalidOperationOnRootUser {
+ get {
+ return ResourceManager.GetString("ExceptionInvalidOperationOnRootUser", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Nickname is of bad format. {0}.
/// </summary>
internal static string ExceptionNicknameBadFormat {
@@ -151,6 +178,15 @@ namespace Timeline.Services.User { }
/// <summary>
+ /// Looks up a localized string similar to Password can't be null..
+ /// </summary>
+ internal static string ExceptionPasswordNull {
+ get {
+ return ResourceManager.GetString("ExceptionPasswordNull", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to User with given constraints already exists..
/// </summary>
internal static string ExceptionUserAlreadyExist {
@@ -169,6 +205,15 @@ namespace Timeline.Services.User { }
/// <summary>
+ /// Looks up a localized string similar to Username can't be null..
+ /// </summary>
+ internal static string ExceptionUsernameNull {
+ get {
+ return ResourceManager.GetString("ExceptionUsernameNull", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Requested user does not exist..
/// </summary>
internal static string ExceptionUserNotExist {
@@ -176,5 +221,41 @@ namespace Timeline.Services.User { return ResourceManager.GetString("ExceptionUserNotExist", resourceCulture);
}
}
+
+ /// <summary>
+ /// Looks up a localized string similar to User with username = {0}, id ={1} changed password..
+ /// </summary>
+ internal static string LogChangePassowrd {
+ get {
+ return ResourceManager.GetString("LogChangePassowrd", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A user is deleted with username = {0}, id = {1}..
+ /// </summary>
+ internal static string LogDeleteUser {
+ get {
+ return ResourceManager.GetString("LogDeleteUser", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A new user is created with username = {0}, id = {1}..
+ /// </summary>
+ internal static string LogUserCreated {
+ get {
+ return ResourceManager.GetString("LogUserCreated", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A user is modified with username = {0}, id = {1}..
+ /// </summary>
+ internal static string LogUserModified {
+ get {
+ return ResourceManager.GetString("LogUserModified", resourceCulture);
+ }
+ }
}
}
diff --git a/BackEnd/Timeline/Services/User/Resource.resx b/BackEnd/Timeline/Services/User/Resource.resx index 28e75b19..a734bd70 100644 --- a/BackEnd/Timeline/Services/User/Resource.resx +++ b/BackEnd/Timeline/Services/User/Resource.resx @@ -120,6 +120,12 @@ <data name="ExceptionBadPassword" xml:space="preserve">
<value>Password is wrong.</value>
</data>
+ <data name="ExceptionChangeRootUserPermission" xml:space="preserve">
+ <value>Can't change root user's permission.</value>
+ </data>
+ <data name="ExceptionDeleteRootUser" xml:space="preserve">
+ <value>Can't delete root user.</value>
+ </data>
<data name="ExceptionHashedPasswordBadFormat" xml:space="preserve">
<value>The hashes password is of bad format. It might not be created by server. Reason: {0}</value>
</data>
@@ -141,19 +147,40 @@ <data name="ExceptionHashedPasswordBadFormatReasonUnknownMarker" xml:space="preserve">
<value>Unknown format marker.</value>
</data>
+ <data name="ExceptionInvalidOperationOnRootUser" xml:space="preserve">
+ <value>Can't perform such operation on root user.</value>
+ </data>
<data name="ExceptionNicknameBadFormat" xml:space="preserve">
<value>Nickname is of bad format. {0}</value>
</data>
<data name="ExceptionPasswordEmpty" xml:space="preserve">
<value>Password can't be empty.</value>
</data>
+ <data name="ExceptionPasswordNull" xml:space="preserve">
+ <value>Password can't be null.</value>
+ </data>
<data name="ExceptionUserAlreadyExist" xml:space="preserve">
<value>User with given constraints already exists.</value>
</data>
<data name="ExceptionUsernameBadFormat" xml:space="preserve">
<value>Username is of bad format. {0}</value>
</data>
+ <data name="ExceptionUsernameNull" xml:space="preserve">
+ <value>Username can't be null.</value>
+ </data>
<data name="ExceptionUserNotExist" xml:space="preserve">
<value>Requested user does not exist.</value>
</data>
+ <data name="LogChangePassowrd" xml:space="preserve">
+ <value>User with username = {0}, id ={1} changed password.</value>
+ </data>
+ <data name="LogDeleteUser" xml:space="preserve">
+ <value>A user is deleted with username = {0}, id = {1}.</value>
+ </data>
+ <data name="LogUserCreated" xml:space="preserve">
+ <value>A new user is created with username = {0}, id = {1}.</value>
+ </data>
+ <data name="LogUserModified" xml:space="preserve">
+ <value>A user is modified with username = {0}, id = {1}.</value>
+ </data>
</root>
\ No newline at end of file diff --git a/BackEnd/Timeline/Services/User/UserAvatarService.cs b/BackEnd/Timeline/Services/User/UserAvatarService.cs index e18a0560..9f59624d 100644 --- a/BackEnd/Timeline/Services/User/UserAvatarService.cs +++ b/BackEnd/Timeline/Services/User/UserAvatarService.cs @@ -144,7 +144,7 @@ namespace Timeline.Services.User public async Task<ICacheableDataDigest> GetAvatarDigest(long userId)
{
- var usernameChangeTime = await _basicUserService.GetUsernameLastModifiedTime(userId);
+ var usernameChangeTime = await _basicUserService.GetUsernameLastModifiedTimeAsync(userId);
var entity = await _database.UserAvatars.Where(a => a.UserId == userId).Select(a => new { a.DataTag, a.LastModified }).SingleOrDefaultAsync();
diff --git a/BackEnd/Timeline/Services/User/UserCredentialService.cs b/BackEnd/Timeline/Services/User/UserCredentialService.cs deleted file mode 100644 index 6becc469..00000000 --- a/BackEnd/Timeline/Services/User/UserCredentialService.cs +++ /dev/null @@ -1,101 +0,0 @@ -using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Logging;
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-using Timeline.Entities;
-using Timeline.Helpers;
-using Timeline.Models.Validation;
-
-namespace Timeline.Services.User
-{
- public interface IUserCredentialService
- {
- /// <summary>
- /// Try to verify the given username and password.
- /// </summary>
- /// <param name="username">The username of the user to verify.</param>
- /// <param name="password">The password of the user to verify.</param>
- /// <returns>User id.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format or <paramref name="password"/> is empty.</exception>
- /// <exception cref="UserNotExistException">Thrown when the user with given username does not exist.</exception>
- /// <exception cref="BadPasswordException">Thrown when password is wrong.</exception>
- Task<long> VerifyCredential(string username, string password);
-
- /// <summary>
- /// Try to change a user's password with old password.
- /// </summary>
- /// <param name="id">The id of user to change password of.</param>
- /// <param name="oldPassword">Old password.</param>
- /// <param name="newPassword">New password.</param>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="oldPassword"/> or <paramref name="newPassword"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown if <paramref name="oldPassword"/> or <paramref name="newPassword"/> is empty.</exception>
- /// <exception cref="UserNotExistException">Thrown if the user with given username does not exist.</exception>
- /// <exception cref="BadPasswordException">Thrown if the old password is wrong.</exception>
- Task ChangePassword(long id, string oldPassword, string newPassword);
- }
-
- public class UserCredentialService : IUserCredentialService
- {
- private readonly ILogger<UserCredentialService> _logger;
- private readonly DatabaseContext _database;
- private readonly IPasswordService _passwordService;
-
- private readonly UsernameValidator _usernameValidator = new UsernameValidator();
-
- public UserCredentialService(ILogger<UserCredentialService> logger, DatabaseContext database, IPasswordService passwordService)
- {
- _logger = logger;
- _database = database;
- _passwordService = passwordService;
- }
-
- public async Task<long> VerifyCredential(string username, string password)
- {
- if (username == null)
- throw new ArgumentNullException(nameof(username));
- if (password == null)
- throw new ArgumentNullException(nameof(password));
- if (!_usernameValidator.Validate(username, out var message))
- throw new ArgumentException(message);
- if (password.Length == 0)
- throw new ArgumentException("Password can't be empty.");
-
- var entity = await _database.Users.Where(u => u.Username == username).Select(u => new { u.Id, u.Password }).SingleOrDefaultAsync();
-
- if (entity == null)
- throw new UserNotExistException(username);
-
- if (!_passwordService.VerifyPassword(entity.Password, password))
- throw new BadPasswordException(password);
-
- return entity.Id;
- }
-
- public async Task ChangePassword(long id, string oldPassword, string newPassword)
- {
- if (oldPassword == null)
- throw new ArgumentNullException(nameof(oldPassword));
- if (newPassword == null)
- throw new ArgumentNullException(nameof(newPassword));
- if (oldPassword.Length == 0)
- throw new ArgumentException("Old password can't be empty.");
- if (newPassword.Length == 0)
- throw new ArgumentException("New password can't be empty.");
-
- var entity = await _database.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
-
- if (entity == null)
- throw new UserNotExistException(id);
-
- if (!_passwordService.VerifyPassword(entity.Password, oldPassword))
- throw new BadPasswordException(oldPassword);
-
- entity.Password = _passwordService.HashPassword(newPassword);
- entity.Version += 1;
- await _database.SaveChangesAsync();
- _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate, ("Id", id), ("Operation", "Change password")));
- }
- }
-}
diff --git a/BackEnd/Timeline/Services/User/UserDeleteService.cs b/BackEnd/Timeline/Services/User/UserDeleteService.cs index 8da4678a..94b15d33 100644 --- a/BackEnd/Timeline/Services/User/UserDeleteService.cs +++ b/BackEnd/Timeline/Services/User/UserDeleteService.cs @@ -10,19 +10,6 @@ using Timeline.Services.Timeline; namespace Timeline.Services.User
{
- public interface IUserDeleteService
- {
- /// <summary>
- /// Delete a user of given username.
- /// </summary>
- /// <param name="username">Username of the user to delete. Can't be null.</param>
- /// <returns>True if user is deleted, false if user not exist.</returns>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="username"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format.</exception>
- /// <exception cref="InvalidOperationOnRootUserException">Thrown when deleting root user.</exception>
- Task<bool> DeleteUser(string username);
- }
-
public class UserDeleteService : IUserDeleteService
{
private readonly ILogger<UserDeleteService> _logger;
@@ -55,13 +42,14 @@ namespace Timeline.Services.User return false;
if (user.Id == 1)
- throw new InvalidOperationOnRootUserException("Can't delete root user.");
+ throw new InvalidOperationOnRootUserException(Resource.ExceptionDeleteRootUser);
await _timelinePostService.DeleteAllPostsOfUser(user.Id);
_databaseContext.Users.Remove(user);
await _databaseContext.SaveChangesAsync();
+ _logger.LogWarning(Resource.LogDeleteUser, user.Username, user.Id);
return true;
}
diff --git a/BackEnd/Timeline/Services/User/UserPermission.cs b/BackEnd/Timeline/Services/User/UserPermission.cs new file mode 100644 index 00000000..1404cdcd --- /dev/null +++ b/BackEnd/Timeline/Services/User/UserPermission.cs @@ -0,0 +1,18 @@ +namespace Timeline.Services.User
+{
+ public enum UserPermission
+ {
+ /// <summary>
+ /// This permission allows to manage user (creating, deleting or modifying).
+ /// </summary>
+ UserManagement,
+ /// <summary>
+ /// This permission allows to view and modify all timelines.
+ /// </summary>
+ AllTimelineManagement,
+ /// <summary>
+ /// This permission allow to add or remove highlight timelines.
+ /// </summary>
+ HighlightTimelineManagement
+ }
+}
diff --git a/BackEnd/Timeline/Services/User/UserPermissionService.cs b/BackEnd/Timeline/Services/User/UserPermissionService.cs index f292142d..f9911c7f 100644 --- a/BackEnd/Timeline/Services/User/UserPermissionService.cs +++ b/BackEnd/Timeline/Services/User/UserPermissionService.cs @@ -1,172 +1,10 @@ using Microsoft.EntityFrameworkCore;
-using System;
-using System.Collections;
-using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
namespace Timeline.Services.User
{
- public enum UserPermission
- {
- /// <summary>
- /// This permission allows to manage user (creating, deleting or modifying).
- /// </summary>
- UserManagement,
- /// <summary>
- /// This permission allows to view and modify all timelines.
- /// </summary>
- AllTimelineManagement,
- /// <summary>
- /// This permission allow to add or remove highlight timelines.
- /// </summary>
- HighlightTimelineManagement
- }
-
- /// <summary>
- /// Represents a user's permissions.
- /// </summary>
- public class UserPermissions : IEnumerable<UserPermission>, IEquatable<UserPermissions>
- {
- public static UserPermissions AllPermissions { get; } = new UserPermissions(Enum.GetValues<UserPermission>());
-
- /// <summary>
- /// Create an instance containing given permissions.
- /// </summary>
- /// <param name="permissions">Permission list.</param>
- public UserPermissions(params UserPermission[] permissions) : this(permissions as IEnumerable<UserPermission>)
- {
-
- }
-
- /// <summary>
- /// Create an instance containing given permissions.
- /// </summary>
- /// <param name="permissions">Permission list.</param>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="permissions"/> is null.</exception>
- public UserPermissions(IEnumerable<UserPermission> permissions)
- {
- if (permissions == null) throw new ArgumentNullException(nameof(permissions));
- _permissions = new SortedSet<UserPermission>(permissions);
- }
-
- private readonly SortedSet<UserPermission> _permissions = new();
-
- /// <summary>
- /// Check if a permission is contained in the list.
- /// </summary>
- /// <param name="permission">The permission to check.</param>
- /// <returns>True if contains. Otherwise false.</returns>
- public bool Contains(UserPermission permission)
- {
- return _permissions.Contains(permission);
- }
-
- /// <summary>
- /// To a serializable string list.
- /// </summary>
- /// <returns>A string list.</returns>
- public List<string> ToStringList()
- {
- return _permissions.Select(p => p.ToString()).ToList();
- }
-
- /// <summary>
- /// Convert a string list to user permissions.
- /// </summary>
- /// <param name="list">The string list.</param>
- /// <returns>An instance.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="list"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when there is unknown permission name.</exception>
- public static UserPermissions FromStringList(IEnumerable<string> list)
- {
- List<UserPermission> permissions = new();
-
- foreach (var value in list)
- {
- if (Enum.TryParse<UserPermission>(value, false, out var result))
- {
- permissions.Add(result);
- }
- else
- {
- throw new ArgumentException("Unknown permission name.", nameof(list));
- }
- }
-
- return new UserPermissions(permissions);
- }
-
- public IEnumerator<UserPermission> GetEnumerator()
- {
- return _permissions.GetEnumerator();
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return ((IEnumerable)_permissions).GetEnumerator();
- }
-
- public bool Equals(UserPermissions? other)
- {
- if (other == null)
- return false;
-
- return _permissions.SequenceEqual(other._permissions);
- }
-
- public override bool Equals(object? obj)
- {
- return Equals(obj as UserPermissions);
- }
-
- public override int GetHashCode()
- {
- int result = 0;
- foreach (var permission in Enum.GetValues<UserPermission>())
- {
- if (_permissions.Contains(permission))
- {
- result += 1;
- }
- result <<= 1;
- }
- return result;
- }
- }
-
- public interface IUserPermissionService
- {
- /// <summary>
- /// Get permissions of a user.
- /// </summary>
- /// <param name="userId">The id of the user.</param>
- /// <param name="checkUserExistence">Whether check the user's existence.</param>
- /// <returns>The permission list.</returns>
- /// <exception cref="UserNotExistException">Thrown when <paramref name="checkUserExistence"/> is true and user does not exist.</exception>
- Task<UserPermissions> GetPermissionsOfUserAsync(long userId, bool checkUserExistence = true);
-
- /// <summary>
- /// Add a permission to user.
- /// </summary>
- /// <param name="userId">The id of the user.</param>
- /// <param name="permission">The new permission.</param>
- /// <exception cref="UserNotExistException">Thrown when user does not exist.</exception>
- /// <exception cref="InvalidOperationOnRootUserException">Thrown when change root user's permission.</exception>
- Task AddPermissionToUserAsync(long userId, UserPermission permission);
-
- /// <summary>
- /// Remove a permission from user.
- /// </summary>
- /// <param name="userId">The id of the user.</param>
- /// <param name="permission">The permission.</param>
- /// <param name="checkUserExistence">Whether check the user's existence.</param>
- /// <exception cref="UserNotExistException">Thrown when <paramref name="checkUserExistence"/> is true and user does not exist.</exception>
- /// <exception cref="InvalidOperationOnRootUserException">Thrown when change root user's permission.</exception>
- Task RemovePermissionFromUserAsync(long userId, UserPermission permission, bool checkUserExistence = true);
- }
-
public class UserPermissionService : IUserPermissionService
{
private readonly DatabaseContext _database;
@@ -205,7 +43,7 @@ namespace Timeline.Services.User public async Task AddPermissionToUserAsync(long userId, UserPermission permission)
{
if (userId == 1)
- throw new InvalidOperationOnRootUserException("Can't change root user's permission.");
+ throw new InvalidOperationOnRootUserException(Resource.ExceptionChangeRootUserPermission);
await CheckUserExistence(userId, true);
@@ -222,7 +60,7 @@ namespace Timeline.Services.User public async Task RemovePermissionFromUserAsync(long userId, UserPermission permission, bool checkUserExistence = true)
{
if (userId == 1)
- throw new InvalidOperationOnRootUserException("Can't change root user's permission.");
+ throw new InvalidOperationOnRootUserException(Resource.ExceptionChangeRootUserPermission);
await CheckUserExistence(userId, checkUserExistence);
diff --git a/BackEnd/Timeline/Services/User/UserPermissions.cs b/BackEnd/Timeline/Services/User/UserPermissions.cs new file mode 100644 index 00000000..1c27b4b8 --- /dev/null +++ b/BackEnd/Timeline/Services/User/UserPermissions.cs @@ -0,0 +1,119 @@ +using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Timeline.Services.User
+{
+ /// <summary>
+ /// Represents a user's permissions.
+ /// </summary>
+ public class UserPermissions : IEnumerable<UserPermission>, IEquatable<UserPermissions>
+ {
+ public static UserPermissions AllPermissions { get; } = new UserPermissions(Enum.GetValues<UserPermission>());
+
+ /// <summary>
+ /// Create an instance containing given permissions.
+ /// </summary>
+ /// <param name="permissions">Permission list.</param>
+ public UserPermissions(params UserPermission[] permissions) : this(permissions as IEnumerable<UserPermission>)
+ {
+
+ }
+
+ /// <summary>
+ /// Create an instance containing given permissions.
+ /// </summary>
+ /// <param name="permissions">Permission list.</param>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="permissions"/> is null.</exception>
+ public UserPermissions(IEnumerable<UserPermission> permissions)
+ {
+ if (permissions == null) throw new ArgumentNullException(nameof(permissions));
+ _permissions = new SortedSet<UserPermission>(permissions);
+ }
+
+ private readonly SortedSet<UserPermission> _permissions = new();
+
+ /// <summary>
+ /// Check if a permission is contained in the list.
+ /// </summary>
+ /// <param name="permission">The permission to check.</param>
+ /// <returns>True if contains. Otherwise false.</returns>
+ public bool Contains(UserPermission permission)
+ {
+ return _permissions.Contains(permission);
+ }
+
+ /// <summary>
+ /// To a serializable string list.
+ /// </summary>
+ /// <returns>A string list.</returns>
+ public List<string> ToStringList()
+ {
+ return _permissions.Select(p => p.ToString()).ToList();
+ }
+
+ /// <summary>
+ /// Convert a string list to user permissions.
+ /// </summary>
+ /// <param name="list">The string list.</param>
+ /// <returns>An instance.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="list"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when there is unknown permission name.</exception>
+ public static UserPermissions FromStringList(IEnumerable<string> list)
+ {
+ List<UserPermission> permissions = new();
+
+ foreach (var value in list)
+ {
+ if (Enum.TryParse<UserPermission>(value, false, out var result))
+ {
+ permissions.Add(result);
+ }
+ else
+ {
+ throw new ArgumentException("Unknown permission name.", nameof(list));
+ }
+ }
+
+ return new UserPermissions(permissions);
+ }
+
+ public IEnumerator<UserPermission> GetEnumerator()
+ {
+ return _permissions.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)_permissions).GetEnumerator();
+ }
+
+ public bool Equals(UserPermissions? other)
+ {
+ if (other == null)
+ return false;
+
+ return _permissions.SequenceEqual(other._permissions);
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return Equals(obj as UserPermissions);
+ }
+
+ public override int GetHashCode()
+ {
+ int result = 0;
+ foreach (var permission in Enum.GetValues<UserPermission>())
+ {
+ if (_permissions.Contains(permission))
+ {
+ result += 1;
+ }
+ result <<= 1;
+ }
+ return result;
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Services/User/UserService.cs b/BackEnd/Timeline/Services/User/UserService.cs index bbbe15b0..6496b55b 100644 --- a/BackEnd/Timeline/Services/User/UserService.cs +++ b/BackEnd/Timeline/Services/User/UserService.cs @@ -10,57 +10,6 @@ using Timeline.Models.Validation; namespace Timeline.Services.User
{
- /// <summary>
- /// Null means not change.
- /// </summary>
- public class ModifyUserParams
- {
- public string? Username { get; set; }
- public string? Password { get; set; }
- public string? Nickname { get; set; }
- }
-
- public interface IUserService : IBasicUserService
- {
- /// <summary>
- /// Try to get a user by id.
- /// </summary>
- /// <param name="id">The id of the user.</param>
- /// <returns>The user info.</returns>
- /// <exception cref="UserNotExistException">Thrown when the user with given id does not exist.</exception>
- Task<UserEntity> GetUser(long id);
-
- /// <summary>
- /// List all users.
- /// </summary>
- /// <returns>The user info of users.</returns>
- Task<List<UserEntity>> GetUsers();
-
- /// <summary>
- /// Create a user with given info.
- /// </summary>
- /// <param name="username">The username of new user.</param>
- /// <param name="password">The password of new user.</param>
- /// <returns>The the new user.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> or <paramref name="password"/> is of bad format.</exception>
- /// <exception cref="EntityAlreadyExistException">Thrown when a user with given username already exists.</exception>
- Task<UserEntity> CreateUser(string username, string password);
-
- /// <summary>
- /// Modify a user.
- /// </summary>
- /// <param name="id">The id of the user.</param>
- /// <param name="param">The new information.</param>
- /// <returns>The new user info.</returns>
- /// <exception cref="ArgumentException">Thrown when some fields in <paramref name="param"/> is bad.</exception>
- /// <exception cref="UserNotExistException">Thrown when user with given id does not exist.</exception>
- /// <remarks>
- /// Version will increase if password is changed.
- /// </remarks>
- Task<UserEntity> ModifyUser(long id, ModifyUserParams? param);
- }
-
public class UserService : BasicUserService, IUserService
{
private readonly ILogger<UserService> _logger;
@@ -110,58 +59,63 @@ namespace Timeline.Services.User throw new UserAlreadyExistException(user);
}
- public async Task<UserEntity> GetUser(long id)
+ public async Task<UserEntity> GetUserAsync(long id)
{
var user = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
- if (user == null)
+ if (user is null)
throw new UserNotExistException(id);
return user;
}
- public async Task<List<UserEntity>> GetUsers()
+ public async Task<List<UserEntity>> GetUsersAsync()
{
return await _databaseContext.Users.ToListAsync();
}
- public async Task<UserEntity> CreateUser(string username, string password)
+ public async Task<UserEntity> CreateUserAsync(CreateUserParams param)
{
- if (username == null)
- throw new ArgumentNullException(nameof(username));
- if (password == null)
- throw new ArgumentNullException(nameof(password));
-
- CheckUsernameFormat(username, nameof(username));
- CheckPasswordFormat(password, nameof(password));
-
- var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username);
+ if (param is null)
+ throw new ArgumentNullException(nameof(param));
+ if (param.Username is null)
+ throw new ArgumentException(Resource.ExceptionUsernameNull, nameof(param));
+ if (param.Password is null)
+ throw new ArgumentException(Resource.ExceptionPasswordNull, nameof(param));
+ CheckUsernameFormat(param.Username, nameof(param));
+ CheckPasswordFormat(param.Password, nameof(param));
+ if (param.Nickname is not null)
+ CheckNicknameFormat(param.Nickname, nameof(param));
+
+ var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == param.Username);
if (conflict)
ThrowUsernameConflict(null);
var newEntity = new UserEntity
{
- Username = username,
- Password = _passwordService.HashPassword(password),
+ Username = param.Username,
+ Password = _passwordService.HashPassword(param.Password),
+ Nickname = param.Nickname,
Version = 1
};
_databaseContext.Users.Add(newEntity);
await _databaseContext.SaveChangesAsync();
+ _logger.LogInformation(Resource.LogUserCreated, param.Username, newEntity.Id);
return newEntity;
}
- public async Task<UserEntity> ModifyUser(long id, ModifyUserParams? param)
+ public async Task<UserEntity> ModifyUserAsync(long id, ModifyUserParams? param)
{
- if (param != null)
+ if (param is not null)
{
- if (param.Username != null)
+ if (param.Username is not null)
CheckUsernameFormat(param.Username, nameof(param));
- if (param.Password != null)
+ if (param.Password is not null)
CheckPasswordFormat(param.Password, nameof(param));
- if (param.Nickname != null)
+ if (param.Nickname is not null)
CheckNicknameFormat(param.Nickname, nameof(param));
}
@@ -169,13 +123,13 @@ namespace Timeline.Services.User if (entity == null)
throw new UserNotExistException(id);
- if (param != null)
+ if (param is not null)
{
var now = _clock.GetCurrentTime();
bool updateLastModified = false;
var username = param.Username;
- if (username != null && username != entity.Username)
+ if (username is not null && username != entity.Username)
{
var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username);
if (conflict)
@@ -187,14 +141,14 @@ namespace Timeline.Services.User }
var password = param.Password;
- if (password != null)
+ if (password is not null)
{
entity.Password = _passwordService.HashPassword(password);
entity.Version += 1;
}
var nickname = param.Nickname;
- if (nickname != null && nickname != entity.Nickname)
+ if (nickname is not null && nickname != entity.Nickname)
{
entity.Nickname = nickname;
updateLastModified = true;
@@ -206,9 +160,53 @@ namespace Timeline.Services.User }
await _databaseContext.SaveChangesAsync();
+ _logger.LogInformation(Resource.LogUserModified, entity.Username, id);
}
return entity;
}
+
+ public async Task<long> VerifyCredential(string username, string password)
+ {
+ if (username is null)
+ throw new ArgumentNullException(nameof(username));
+ if (password is null)
+ throw new ArgumentNullException(nameof(password));
+ CheckUsernameFormat(username, nameof(username));
+ CheckPasswordFormat(password, nameof(password));
+
+ var entity = await _databaseContext.Users.Where(u => u.Username == username).Select(u => new { u.Id, u.Password }).SingleOrDefaultAsync();
+
+ if (entity is null)
+ throw new UserNotExistException(username);
+
+ if (!_passwordService.VerifyPassword(entity.Password, password))
+ throw new BadPasswordException(password);
+
+ return entity.Id;
+ }
+
+ public async Task ChangePassword(long id, string oldPassword, string newPassword)
+ {
+ if (oldPassword == null)
+ throw new ArgumentNullException(nameof(oldPassword));
+ if (newPassword == null)
+ throw new ArgumentNullException(nameof(newPassword));
+ CheckPasswordFormat(oldPassword, nameof(oldPassword));
+ CheckPasswordFormat(newPassword, nameof(newPassword));
+
+ var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
+
+ if (entity is null)
+ throw new UserNotExistException(id);
+
+ if (!_passwordService.VerifyPassword(entity.Password, oldPassword))
+ throw new BadPasswordException(oldPassword);
+
+ entity.Password = _passwordService.HashPassword(newPassword);
+ entity.Version += 1;
+ await _databaseContext.SaveChangesAsync();
+ _logger.LogInformation(Resource.LogChangePassowrd, entity.Username, id);
+ }
}
}
diff --git a/BackEnd/Timeline/Startup.cs b/BackEnd/Timeline/Startup.cs index 58438d4c..c0873113 100644 --- a/BackEnd/Timeline/Startup.cs +++ b/BackEnd/Timeline/Startup.cs @@ -2,11 +2,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
using System;
using System.ComponentModel;
using System.Net.Mime;
@@ -105,7 +103,6 @@ namespace Timeline services.AddTransient<IPasswordService, PasswordService>();
services.AddScoped<IBasicUserService, BasicUserService>();
services.AddScoped<IUserService, UserService>();
- services.AddScoped<IUserCredentialService, UserCredentialService>();
services.AddScoped<IUserDeleteService, UserDeleteService>();
services.AddScoped<IUserPermissionService, UserPermissionService>();
services.AddUserAvatarService();
diff --git a/BackEnd/Timeline/Timeline.csproj b/BackEnd/Timeline/Timeline.csproj index 15771977..dc41a7f8 100644 --- a/BackEnd/Timeline/Timeline.csproj +++ b/BackEnd/Timeline/Timeline.csproj @@ -133,11 +133,6 @@ <AutoGen>True</AutoGen>
<DependentUpon>UserAvatarService.resx</DependentUpon>
</Compile>
- <Compile Update="Resources\Services\UserService.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>UserService.resx</DependentUpon>
- </Compile>
<Compile Update="Resources\Services\UserTokenService.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
@@ -250,10 +245,6 @@ <Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>UserAvatarService.Designer.cs</LastGenOutput>
</EmbeddedResource>
- <EmbeddedResource Update="Resources\Services\UserService.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>UserService.Designer.cs</LastGenOutput>
- </EmbeddedResource>
<EmbeddedResource Update="Resources\Services\UserTokenService.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>UserTokenService.Designer.cs</LastGenOutput>
|