mirror of
https://github.com/hmaxnl/SharpRSS.git
synced 2024-11-10 16:04:19 +01:00
Implemented groups, sessions & authorization basics.
This commit is contained in:
parent
f95e985c7b
commit
9338d8c106
|
@ -1,10 +1,10 @@
|
|||
using System;
|
||||
|
||||
namespace SharpRSS.API.Contracts.DTO
|
||||
namespace SharpRSS.API.Contracts.DTOs.Groups
|
||||
{
|
||||
public class Group
|
||||
{
|
||||
public string Gid { get; set; } = string.Empty;
|
||||
public string Gid { get; set; } = Guid.NewGuid().ToString();
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public bool Administrator { get; set; }
|
||||
public DateTime DateCreated { get; set; } = DateTime.Now;
|
9
SharpRSS.API.Contracts/DTOs/Groups/GroupItem.cs
Normal file
9
SharpRSS.API.Contracts/DTOs/Groups/GroupItem.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace SharpRSS.API.Contracts.DTOs.Groups
|
||||
{
|
||||
public class GroupItem
|
||||
{
|
||||
public string Gid { get; set; } = string.Empty;
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public bool Administrator { get; set; }
|
||||
}
|
||||
}
|
9
SharpRSS.API.Contracts/DTOs/Groups/InsertGroup.cs
Normal file
9
SharpRSS.API.Contracts/DTOs/Groups/InsertGroup.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace SharpRSS.API.Contracts.DTOs.Groups
|
||||
{
|
||||
public class InsertGroup
|
||||
{
|
||||
public string Gid { get; set; } = string.Empty;
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public bool Administrator { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace SharpRSS.API.Contracts.DTO
|
||||
namespace SharpRSS.API.Contracts.DTOs.Sessions
|
||||
{
|
||||
public class Session
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
namespace SharpRSS.API.Contracts.Payloads
|
||||
namespace SharpRSS.API.Contracts.DTOs.Users
|
||||
{
|
||||
public abstract class AuthenticateUser
|
||||
public class AuthenticateUser
|
||||
{
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
public string Password { get; set; } = string.Empty;
|
|
@ -1,8 +1,8 @@
|
|||
namespace SharpRSS.API.Contracts.Payloads
|
||||
namespace SharpRSS.API.Contracts.DTOs.Users
|
||||
{
|
||||
public class ModifyUser
|
||||
public class InsertUser
|
||||
{
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
public string Uid { get; set; } = string.Empty;
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public string Password { get; set; } = string.Empty;
|
||||
public string Email { get; set; } = string.Empty;
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace SharpRSS.API.Contracts.DTO
|
||||
namespace SharpRSS.API.Contracts.DTOs.Users
|
||||
{
|
||||
public class User
|
||||
{
|
11
SharpRSS.API.Contracts/DTOs/Users/UserItem.cs
Normal file
11
SharpRSS.API.Contracts/DTOs/Users/UserItem.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace SharpRSS.API.Contracts.DTOs.Users
|
||||
{
|
||||
// Used for returning users in a list.
|
||||
public class UserItem
|
||||
{
|
||||
public string Uid { get; set; } = string.Empty;
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public string Email { get; set; } = string.Empty;
|
||||
public bool Active { get; set; }
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ namespace SharpRSS.API.Contracts.Models
|
|||
|
||||
public TValue? Value { get; }
|
||||
|
||||
public static ResultOr<TValue> Ok(TValue value) => new ResultOr<TValue>(value, "", ResultStatus.Ok);
|
||||
public static ResultOr<TValue> Ok(TValue? value) => new ResultOr<TValue>(value, "", ResultStatus.Ok);
|
||||
public static ResultOr<TValue> Failed(string message = "Failed", ResultStatus status = ResultStatus.Failed) => new ResultOr<TValue>(default, message, status);
|
||||
}
|
||||
}
|
|
@ -11,8 +11,4 @@
|
|||
<ProjectReference Include="..\ToolQit\ToolQit\ToolQit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Auth" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -4,7 +4,10 @@ using Microsoft.AspNetCore.Authorization;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using SharpRSS.API.Contracts.Models;
|
||||
using SharpRSS.API.Data;
|
||||
using ToolQit;
|
||||
using ToolQit.Extensions;
|
||||
using ToolQit.Logging;
|
||||
|
||||
namespace SharpRSS.API.Auth
|
||||
|
@ -12,31 +15,49 @@ namespace SharpRSS.API.Auth
|
|||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
|
||||
public class SessionAuthorizeAttribute : Attribute, IAuthorizationFilter
|
||||
{
|
||||
public SessionAuthorizeAttribute(string group = "")
|
||||
public SessionAuthorizeAttribute(bool admin = true)
|
||||
{
|
||||
_admin = admin;
|
||||
_log = LogManager.CreateLogger(typeof(SessionAuthorizeAttribute));
|
||||
_group = group;
|
||||
}
|
||||
|
||||
|
||||
private readonly ILog _log;
|
||||
private readonly string _group;
|
||||
private readonly bool _admin;
|
||||
|
||||
public void OnAuthorization(AuthorizationFilterContext context)
|
||||
public async void OnAuthorization(AuthorizationFilterContext context)
|
||||
{
|
||||
if (context.ActionDescriptor.EndpointMetadata.Any(obj => obj.GetType() == typeof(AllowAnonymousAttribute)))
|
||||
return;
|
||||
var authService = context.HttpContext.RequestServices.GetService(typeof(AuthService)) as AuthService;
|
||||
if (authService == null)
|
||||
{
|
||||
//context.Result = new OkResult();
|
||||
context.Result = new UnauthorizedObjectResult(new Result("Failed to initialize service!", ResultStatus.InternalFail));
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
if (context.HttpContext.Request.Headers.TryGetValue("SRSS-Session", out StringValues val))
|
||||
if (context.HttpContext.Request.Headers.TryGetValue("SRSS-Session", out var val))
|
||||
{
|
||||
//TODO: if no permission check for valid session, permission check if session has access!
|
||||
string? headerVal = val.ToString();
|
||||
if (headerVal == null || headerVal.IsNullEmptyWhiteSpace())
|
||||
{
|
||||
context.Result = new UnauthorizedObjectResult(new Result("Invalid session ID"));
|
||||
return;
|
||||
}
|
||||
var authSet = await authService.ValidateSession(headerVal);
|
||||
if (!authSet.Success || authSet.Value == null)
|
||||
{
|
||||
context.Result = new UnauthorizedResult();
|
||||
return;
|
||||
}
|
||||
if (authSet.Value.Session is { Expired: true })
|
||||
{
|
||||
context.Result = new UnauthorizedObjectResult(new Result("Session expired", ResultStatus.Failed));
|
||||
return;
|
||||
}
|
||||
authSet.Value.AdminRequired = _admin;
|
||||
context.HttpContext.Items["auth"] = authSet.Value;
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO: Check session ID!
|
||||
context.Result = new UnauthorizedResult();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SharpRSS.API.Auth;
|
||||
using SharpRSS.API.Contracts.DTO;
|
||||
using SharpRSS.API.Contracts.DTOs.Groups;
|
||||
using SharpRSS.API.Contracts.DTOs.Sessions;
|
||||
using SharpRSS.API.Contracts.DTOs.Users;
|
||||
using SharpRSS.API.Contracts.Models;
|
||||
using SharpRSS.API.Contracts.Payloads;
|
||||
using SharpRSS.API.Data;
|
||||
using SharpRSS.API.Models;
|
||||
using ToolQit;
|
||||
using ToolQit.Logging;
|
||||
|
||||
namespace SharpRSS.API.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[SessionAuthorize]
|
||||
[SessionAuthorize(false)]
|
||||
[Route("api/[controller]")]
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
|
@ -25,29 +28,37 @@ namespace SharpRSS.API.Controllers
|
|||
|
||||
private readonly AuthService _authService;
|
||||
|
||||
[HttpPost("createuser")]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<ResultOr<User>>> CreateUser(ModifyUser payload)
|
||||
{
|
||||
var createdUserResult = await _authService.CreateUser(payload);
|
||||
return createdUserResult.Success ? Created("", createdUserResult) : createdUserResult.Status == ResultStatus.Failed ? BadRequest(createdUserResult) : StatusCode(StatusCodes.Status500InternalServerError, createdUserResult);
|
||||
}
|
||||
|
||||
[HttpPost("updateuser")]
|
||||
[HttpPost("authenticate")]
|
||||
[AllowAnonymous]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<ResultOr<User>>> UpdateUser(ModifyUser payload)
|
||||
public async Task<ActionResult<ResultOr<Session>>> Authenticate(AuthenticateUser auth)
|
||||
{
|
||||
var updatedUserResult = await _authService.UpdateUser(payload);
|
||||
return updatedUserResult.Success ? Ok(updatedUserResult) : updatedUserResult.Status == ResultStatus.Failed ? BadRequest(updatedUserResult) : StatusCode(StatusCodes.Status500InternalServerError, updatedUserResult);
|
||||
var sessionResult = await _authService.Authenticate(auth);
|
||||
return sessionResult.Success ? Ok(sessionResult) :
|
||||
sessionResult.Status == ResultStatus.Failed ? BadRequest(sessionResult) :
|
||||
StatusCode(StatusCodes.Status500InternalServerError, sessionResult);
|
||||
}
|
||||
|
||||
// To update only fill the values that need to be updated.
|
||||
[HttpPost("user")]
|
||||
[SessionAuthorize]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<ResultOr<User>>> InsertUser(InsertUser payload)
|
||||
{
|
||||
var createdUserResult = await _authService.InsertUserAsync(payload);
|
||||
return createdUserResult.Success ? Created("", createdUserResult) :
|
||||
createdUserResult.Status == ResultStatus.Failed ? BadRequest(createdUserResult) :
|
||||
StatusCode(StatusCodes.Status500InternalServerError, createdUserResult);
|
||||
}
|
||||
|
||||
[HttpDelete("deleteuser")]
|
||||
[HttpDelete("user")]
|
||||
[SessionAuthorize]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
|
@ -55,10 +66,13 @@ namespace SharpRSS.API.Controllers
|
|||
public async Task<ActionResult<Result>> DeleteUser(string userId)
|
||||
{
|
||||
var removedUserResult = await _authService.RemoveUserAsync(userId);
|
||||
return removedUserResult.Success ? Ok(removedUserResult) : removedUserResult.Status == ResultStatus.Failed ? BadRequest(removedUserResult) : StatusCode(StatusCodes.Status500InternalServerError, removedUserResult);
|
||||
return removedUserResult.Success ? Ok(removedUserResult) :
|
||||
removedUserResult.Status == ResultStatus.Failed ? BadRequest(removedUserResult) :
|
||||
StatusCode(StatusCodes.Status500InternalServerError, removedUserResult);
|
||||
}
|
||||
|
||||
[HttpGet("user")]
|
||||
[SessionAuthorize]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
|
@ -69,14 +83,72 @@ namespace SharpRSS.API.Controllers
|
|||
}
|
||||
|
||||
[HttpGet("users")]
|
||||
[SessionAuthorize]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<ListResult<User>>> GetUsers(string search = "", int results = 20, int skip = 0)
|
||||
public async Task<ActionResult<ListResult<UserItem>>> GetUsers(string search = "", int results = 20, int skip = 0)
|
||||
{
|
||||
var authSet = HttpContext.Items["auth"] as AuthorizationSet;
|
||||
var usersResult = await _authService.GetUsersAsync(results, skip, search);
|
||||
return usersResult.Success ? Ok(usersResult) : usersResult.Status == ResultStatus.Failed ? BadRequest(usersResult) : StatusCode(StatusCodes.Status500InternalServerError, usersResult);
|
||||
return usersResult.Success ? Ok(usersResult) :
|
||||
usersResult.Status == ResultStatus.Failed ? BadRequest(usersResult) :
|
||||
StatusCode(StatusCodes.Status500InternalServerError, usersResult);
|
||||
}
|
||||
|
||||
[HttpPost("group")]
|
||||
[SessionAuthorize]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<ResultOr<Group>>> InsertGroup(InsertGroup @group)
|
||||
{
|
||||
var groupInsertResult = await _authService.InsertGroupAsync(group);
|
||||
return groupInsertResult.Success ? Ok(groupInsertResult) :
|
||||
groupInsertResult.Status == ResultStatus.Failed ? BadRequest(groupInsertResult) :
|
||||
StatusCode(StatusCodes.Status500InternalServerError, groupInsertResult);
|
||||
}
|
||||
|
||||
[HttpDelete("group")]
|
||||
[SessionAuthorize]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<Result>> RemoveGroup(string groupId)
|
||||
{
|
||||
var removeResult = await _authService.RemoveGroup(groupId);
|
||||
return removeResult.Success ? Ok(removeResult) :
|
||||
removeResult.Status == ResultStatus.Failed ? BadRequest(removeResult) :
|
||||
StatusCode(StatusCodes.Status500InternalServerError, removeResult);
|
||||
}
|
||||
|
||||
[HttpGet("groups")]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<ListResult<Group>>> GetGroups(string search = "", int results = 20, int skip = 0)
|
||||
{ //TODO: Change DTO to GroupItem!
|
||||
var groupsResult = await _authService.GetGroupsAsync(results, skip, search);
|
||||
return groupsResult.Success ? Ok(groupsResult) :
|
||||
groupsResult.Status == ResultStatus.Failed ? BadRequest(groupsResult) :
|
||||
StatusCode(StatusCodes.Status500InternalServerError, groupsResult);
|
||||
}
|
||||
|
||||
[HttpGet("group")]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<ResultOr<Group>>> GetGroup(string groupId)
|
||||
{
|
||||
var groupResult = await _authService.GetGroupAsync(groupId);
|
||||
return groupResult.Success ? Ok(groupResult) :
|
||||
groupResult.Status == ResultStatus.Failed ? BadRequest(groupResult) :
|
||||
StatusCode(StatusCodes.Status500InternalServerError, groupResult);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using SharpRSS.API.Contracts.DTO;
|
||||
using SharpRSS.API.Contracts.DTOs.Groups;
|
||||
using SharpRSS.API.Contracts.DTOs.Sessions;
|
||||
using SharpRSS.API.Contracts.DTOs.Users;
|
||||
using SharpRSS.API.Contracts.Models;
|
||||
using SharpRSS.API.Contracts.Payloads;
|
||||
using SharpRSS.API.Cryptography;
|
||||
using SharpRSS.API.Models;
|
||||
using ToolQit;
|
||||
using ToolQit.Extensions;
|
||||
|
@ -25,61 +28,98 @@ namespace SharpRSS.API.Data
|
|||
private readonly IConfiguration _configuration;
|
||||
private readonly ILog _log;
|
||||
|
||||
public async Task<ResultOr<User>> CreateUser(ModifyUser user)
|
||||
public async Task<ResultOr<Session>> Authenticate(AuthenticateUser authUser)
|
||||
{
|
||||
if (user.UserName.IsNullEmptyWhiteSpace() || user.Password.IsNullEmptyWhiteSpace())
|
||||
return ResultOr<User>.Failed("Username or password cannot be empty!");
|
||||
DbUser newUser;
|
||||
if (authUser.UserName.IsNullEmptyWhiteSpace() || authUser.Password.IsNullEmptyWhiteSpace())
|
||||
return ResultOr<Session>.Failed("Username/Password cannot be empty!");
|
||||
try
|
||||
{
|
||||
DbAccess access = new DbAccess(_configuration);
|
||||
bool userExists = access.Users.Any(u => u.Uid == user.UserName);
|
||||
if (userExists)
|
||||
return ResultOr<User>.Failed("Username already exists!");
|
||||
newUser = new DbUser(user);
|
||||
access.Users.Add(newUser);
|
||||
var user = await access.Users.Where(u => u.Uid == authUser.UserName).FirstOrDefaultAsync();
|
||||
if (user == null)
|
||||
return ResultOr<Session>.Failed("Unknown user");
|
||||
if (!user.Active) return ResultOr<Session>.Failed("User is not active, contact your administrator to enable this account!");
|
||||
if (!Hasher.ComparePasswords(authUser.Password, user.PswHash, user.Salt))
|
||||
return ResultOr<Session>.Failed($"Failed to authenticate user: '{user.Uid}'");
|
||||
DbSession session = DbSession.CreateSession(user.Uid);
|
||||
access.Sessions.Add(session);
|
||||
bool saved = await access.SaveChangesAsync() > 0;
|
||||
return saved ? ResultOr<Session>.Ok(Utilities.ConvertFrom<Session, DbSession>(session)) : ResultOr<Session>.Failed($"Could create session for user: '{user.Uid}'");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.Error(e, e.Message);
|
||||
return ResultOr<Session>.Failed(e.Message, ResultStatus.InternalFail);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<ResultOr<AuthorizationSet>> ValidateSession(string sessionId)
|
||||
{
|
||||
if (sessionId.IsNullEmptyWhiteSpace()) return ResultOr<AuthorizationSet>.Failed("Session ID is empty!");
|
||||
try
|
||||
{
|
||||
DbAccess access = new DbAccess(_configuration);
|
||||
var dbSession = await access.Sessions.Where(s => s.Sid == sessionId).FirstOrDefaultAsync();
|
||||
if (dbSession == null) return ResultOr<AuthorizationSet>.Failed($"Could not get session: '{sessionId}'");
|
||||
var dbUser = await access.Users.Where(u => u.Uid == dbSession.Uid).FirstOrDefaultAsync();
|
||||
if (dbUser == null) return ResultOr<AuthorizationSet>.Failed($"Could not get user: '{dbSession.Uid}'");
|
||||
var dbGroup = await access.Groups.Where(g => g.Gid == dbUser.Gid).FirstOrDefaultAsync();
|
||||
var set = new AuthorizationSet()
|
||||
{
|
||||
Session = dbSession,
|
||||
User = dbUser,
|
||||
Group = dbGroup
|
||||
};
|
||||
return ResultOr<AuthorizationSet>.Ok(set);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.Error(e, e.Message);
|
||||
return ResultOr<AuthorizationSet>.Failed(e.Message, ResultStatus.InternalFail);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ResultOr<User>> InsertUserAsync(InsertUser user)
|
||||
{
|
||||
User? retUser;
|
||||
try
|
||||
{
|
||||
var dbNew = Utilities.ConvertFrom<DbUser, InsertUser>(user);
|
||||
if (dbNew == null) return ResultOr<User>.Failed("Failed to map user!", ResultStatus.InternalFail);
|
||||
|
||||
DbAccess access = new DbAccess(_configuration);
|
||||
|
||||
if (dbNew.Uid.IsNullEmptyWhiteSpace())
|
||||
{
|
||||
if (user.Password.IsNullEmptyWhiteSpace()) return ResultOr<User>.Failed("To create a new user, create a password!");
|
||||
dbNew.Uid = Guid.NewGuid().ToString();
|
||||
retUser = Utilities.ConvertFrom<User, DbUser>(dbNew);
|
||||
access.Users.Add(dbNew);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dbUser = await access.Users.Where(u => u.Uid == user.Uid).FirstOrDefaultAsync();
|
||||
if (dbUser == null)
|
||||
return ResultOr<User>.Failed($"Failed to get user by id: '{user.Uid}'");
|
||||
dbUser.Update(user);
|
||||
retUser = Utilities.ConvertFrom<User, DbUser>(dbUser);
|
||||
access.Users.Update(dbUser);
|
||||
}
|
||||
bool saved = await access.SaveChangesAsync() > 0;
|
||||
if (!saved)
|
||||
{
|
||||
_log.Warning("Failed to save user: {UserName} to database", user.UserName);
|
||||
_log.Warning("Failed to save user: {UserName} to database", user.Uid);
|
||||
return ResultOr<User>.Failed("Could not save user to database!");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.Error(e, "Failed to create user: {UserName}", user.UserName);
|
||||
return ResultOr<User>.Failed($"Failed to create user: {user.UserName}", ResultStatus.InternalFail);
|
||||
_log.Error(e, "Failed to insert user: {UserName}", user.Uid);
|
||||
return ResultOr<User>.Failed($"Failed to insert user: {user.Uid}", ResultStatus.InternalFail);
|
||||
}
|
||||
return ResultOr<User>.Ok(newUser.ToDto());
|
||||
return retUser == null ? ResultOr<User>.Failed("Could not return user!") : ResultOr<User>.Ok(retUser);
|
||||
}
|
||||
|
||||
public async Task<ResultOr<User>> UpdateUser(ModifyUser user)
|
||||
{
|
||||
if (user.UserName.IsNullEmptyWhiteSpace())
|
||||
return ResultOr<User>.Failed("Username cannot be empty!");
|
||||
try
|
||||
{
|
||||
DbAccess access = new DbAccess(_configuration);
|
||||
DbUser? dbUser = await access.Users.Where(u => u.Uid == user.UserName).FirstOrDefaultAsync();
|
||||
if (dbUser == null)
|
||||
return ResultOr<User>.Failed($"User{user.UserName} does not exists, first create an user then modify!");
|
||||
dbUser.Update(user);
|
||||
access.Update(dbUser);
|
||||
bool saved = await access.SaveChangesAsync() > 0;
|
||||
if (!saved)
|
||||
{
|
||||
_log.Warning("Failed to save user: '{UserName}'", dbUser.Uid);
|
||||
return ResultOr<User>.Failed($"Could not save user: '{dbUser.Uid}'");
|
||||
}
|
||||
return ResultOr<User>.Ok(dbUser.ToDto());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.Error(e, e.Message);
|
||||
return ResultOr<User>.Failed(e.Message, ResultStatus.InternalFail);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<Result> RemoveUserAsync(string userId)
|
||||
{
|
||||
if (userId.IsNullEmptyWhiteSpace())
|
||||
|
@ -101,11 +141,13 @@ namespace SharpRSS.API.Data
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<ListResult<User>> GetUsersAsync(int results = 20, int skip = 0, string searchQuery = "")
|
||||
public async Task<ListResult<UserItem>> GetUsersAsync(int results = 20, int skip = 0, string searchQuery = "")
|
||||
{
|
||||
if (results is 0 or > 20) results = 20;
|
||||
try
|
||||
{
|
||||
int totalCount;
|
||||
IEnumerable<DbUser> items;
|
||||
DbAccess access = new DbAccess(_configuration);
|
||||
if (!searchQuery.IsNullEmptyWhiteSpace())
|
||||
{
|
||||
|
@ -113,16 +155,20 @@ namespace SharpRSS.API.Data
|
|||
EF.Functions.Like(u.Uid, $"%{searchQuery}%") ||
|
||||
EF.Functions.Like(u.Email, $"%{searchQuery}%") ||
|
||||
EF.Functions.Like(u.DisplayName, $"%{searchQuery}%"));
|
||||
var searchResults = await queryResult.Skip(skip).Take(results).ToListAsync();
|
||||
return new ListResult<User>(searchResults, queryResult.Count());
|
||||
items = await queryResult.Skip(skip).Take(results).ToListAsync();
|
||||
totalCount = queryResult.Count();
|
||||
}
|
||||
var users = await access.Users.Skip(skip).Take(results).ToListAsync();
|
||||
return new ListResult<User>(users, access.Users.Count());
|
||||
else
|
||||
{
|
||||
items = await access.Users.Skip(skip).Take(results).ToListAsync();
|
||||
totalCount = access.Users.Count();
|
||||
}
|
||||
return new ListResult<UserItem>(items.Select(du => Utilities.ConvertFrom<UserItem, DbUser>(du) ?? new UserItem() { Uid = "NULL" }).Where(ui => ui.Uid != "NULL"), totalCount);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.Error(e, "Error while loading users from database!");
|
||||
return new ListResult<User>(null, 0, "Error while loading users from database!", ResultStatus.Failed);
|
||||
_log.Error(e, e.Message);
|
||||
return new ListResult<UserItem>(null, 0, e.Message, ResultStatus.InternalFail);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,13 +178,114 @@ namespace SharpRSS.API.Data
|
|||
try
|
||||
{
|
||||
DbAccess access = new DbAccess(_configuration);
|
||||
User? user = await access.Users.Where(u => u.Uid == userId).FirstOrDefaultAsync();
|
||||
var dbUser = await access.Users.Where(u => u.Uid == userId).FirstOrDefaultAsync();
|
||||
var user = dbUser?.ToDto();
|
||||
return new ResultOr<User>(user, user == null ? $"Could not find user with id: {userId}!" : "", user == null ? ResultStatus.Failed : ResultStatus.Ok);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.Error(e, e.Message);
|
||||
return new ResultOr<User>(null, e.Message, ResultStatus.Failed);
|
||||
return new ResultOr<User>(null, e.Message, ResultStatus.InternalFail);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ResultOr<Group>> InsertGroupAsync(InsertGroup group)
|
||||
{
|
||||
if (group.DisplayName.IsNullEmptyWhiteSpace()) return ResultOr<Group>.Failed("Display name is empty!");
|
||||
try
|
||||
{
|
||||
var dbNew = Utilities.ConvertFrom<DbGroup, InsertGroup>(group);
|
||||
if (dbNew == null) return ResultOr<Group>.Failed("Failed to map group!");
|
||||
|
||||
DbAccess access = new DbAccess(_configuration);
|
||||
if (dbNew.Gid.IsNullEmptyWhiteSpace())
|
||||
{
|
||||
dbNew.Gid = Guid.NewGuid().ToString();
|
||||
access.Groups.Add(dbNew);
|
||||
}
|
||||
else
|
||||
{
|
||||
var existingGroup = await access.Groups.Where(g => g.Gid == group.Gid).FirstOrDefaultAsync();
|
||||
if (existingGroup == null)
|
||||
return ResultOr<Group>.Failed($"Could not find a group with id: '{group.Gid}'");
|
||||
access.Groups.Update(dbNew);
|
||||
}
|
||||
bool saved = await access.SaveChangesAsync() > 0;
|
||||
return saved ? ResultOr<Group>.Ok(Utilities.ConvertFrom<Group, DbGroup>(dbNew)) : ResultOr<Group>.Failed($"Failed to save group: '{@group.DisplayName}'");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.Error(e, e.Message);
|
||||
return ResultOr<Group>.Failed(e.Message, ResultStatus.InternalFail);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result> RemoveGroup(string groupId)
|
||||
{
|
||||
if (groupId.IsNullEmptyWhiteSpace()) return new Result($"'{nameof(groupId)} is empty!'", ResultStatus.Failed);
|
||||
try
|
||||
{
|
||||
DbAccess access = new DbAccess(_configuration);
|
||||
DbGroup? dbGroup = await access.Groups.Where(g => g.Gid == groupId).FirstOrDefaultAsync();
|
||||
if (dbGroup == null)
|
||||
return new Result($"Group with id: '{groupId}' does not exists!", ResultStatus.Failed);
|
||||
access.Groups.Remove(dbGroup);
|
||||
bool removed = await access.SaveChangesAsync() > 0;
|
||||
if (removed)
|
||||
return new Result();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.Error(e, e.Message);
|
||||
return new Result(e.Message, ResultStatus.InternalFail);
|
||||
}
|
||||
|
||||
return new Result($"Failed to remove group: '{groupId}'", ResultStatus.Failed);
|
||||
}
|
||||
|
||||
public async Task<ListResult<GroupItem>> GetGroupsAsync(int results = 20, int skip = 0, string searchQuery = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
int totalCount;
|
||||
IEnumerable<DbGroup> items;
|
||||
DbAccess access = new DbAccess(_configuration);
|
||||
if (!searchQuery.IsNullEmptyWhiteSpace())
|
||||
{
|
||||
IQueryable<DbGroup> queryResult = access.Groups.Where(u =>
|
||||
EF.Functions.Like(u.Gid, $"%{searchQuery}%") ||
|
||||
EF.Functions.Like(u.DateCreated, $"%{searchQuery}%") ||
|
||||
EF.Functions.Like(u.DisplayName, $"%{searchQuery}%"));
|
||||
items = await queryResult.Skip(skip).Take(results).ToListAsync();
|
||||
totalCount = queryResult.Count();
|
||||
}
|
||||
else
|
||||
{
|
||||
items = await access.Groups.Skip(skip).Take(results).ToListAsync();
|
||||
totalCount = access.Groups.Count();
|
||||
}
|
||||
return new ListResult<GroupItem>(items.Select(dg => Utilities.ConvertFrom<GroupItem, DbGroup>(dg) ?? new GroupItem() { Gid = "NULL" }).Where(gi => gi.Gid != "NULL"), totalCount);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.Error(e, e.Message);
|
||||
return new ListResult<GroupItem>(null, 0, e.Message, ResultStatus.InternalFail);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ResultOr<Group>> GetGroupAsync(string groupId)
|
||||
{
|
||||
if (groupId.IsNullEmptyWhiteSpace()) return ResultOr<Group>.Failed($"Parameter '{nameof(groupId)}' is empty!");
|
||||
try
|
||||
{
|
||||
DbAccess access = new DbAccess(_configuration);
|
||||
var group = Utilities.ConvertFrom<Group, DbGroup>(await access.Groups.Where(g => g.Gid == groupId).FirstOrDefaultAsync());
|
||||
return group == null ? ResultOr<Group>.Failed($"Failed to get group from id: {groupId}") : ResultOr<Group>.Ok(group);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.Error(e, e.Message);
|
||||
return ResultOr<Group>.Failed(e.Message, ResultStatus.InternalFail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
10
SharpRSS.API/Models/AuthorizationSet.cs
Normal file
10
SharpRSS.API/Models/AuthorizationSet.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace SharpRSS.API.Models
|
||||
{
|
||||
internal class AuthorizationSet
|
||||
{
|
||||
public DbSession? Session { get; set; }
|
||||
public DbUser? User { get; set; }
|
||||
public DbGroup? Group { get; set; }
|
||||
public bool AdminRequired { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,9 +1,14 @@
|
|||
using SharpRSS.API.Contracts.DTO;
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using SharpRSS.API.Contracts.DTOs.Groups;
|
||||
|
||||
namespace SharpRSS.API.Models
|
||||
{
|
||||
internal class DbGroup : Group
|
||||
internal class DbGroup
|
||||
{
|
||||
|
||||
public string Gid { get; set; } = Guid.NewGuid().ToString();
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public bool Administrator { get; set; }
|
||||
public DateTime DateCreated { get; set; } = DateTime.Now;
|
||||
}
|
||||
}
|
|
@ -2,11 +2,12 @@ using System;
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Serilog;
|
||||
using SharpRSS.API.Contracts.DTO;
|
||||
using SharpRSS.API.Contracts.DTOs;
|
||||
using SharpRSS.API.Contracts.DTOs.Sessions;
|
||||
|
||||
namespace SharpRSS.API.Models
|
||||
{
|
||||
internal class DbSession : Session
|
||||
internal class DbSession
|
||||
{
|
||||
public DbSession() { }
|
||||
private DbSession(string uid, double expiresMinutes)
|
||||
|
@ -24,6 +25,11 @@ namespace SharpRSS.API.Models
|
|||
Sid = $"{new DateTimeOffset(Created).ToUnixTimeMilliseconds()}.{uidHash}.{new DateTimeOffset(Expires).ToUnixTimeMilliseconds()}";
|
||||
}
|
||||
public string Uid { get; set; }
|
||||
public string Sid { get; set; } = string.Empty;
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime Expires { get; set; }
|
||||
|
||||
public bool Expired => Expires < DateTime.Now;
|
||||
|
||||
public static DbSession CreateSession(string uid, double expiresMinutes = 10080)
|
||||
{
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using SharpRSS.API.Contracts.DTO;
|
||||
using SharpRSS.API.Contracts.Payloads;
|
||||
using SharpRSS.API.Contracts.DTOs.Users;
|
||||
using SharpRSS.API.Cryptography;
|
||||
using ToolQit.Extensions;
|
||||
|
||||
namespace SharpRSS.API.Models
|
||||
{
|
||||
// Database model
|
||||
internal class DbUser : User
|
||||
internal class DbUser
|
||||
{
|
||||
public DbUser() { }
|
||||
public DbUser(ModifyUser user)
|
||||
public DbUser(InsertUser user)
|
||||
{
|
||||
Uid = user.UserName;
|
||||
Uid = user.Uid;
|
||||
DisplayName = user.DisplayName;
|
||||
Email = user.Email;
|
||||
Gid = user.GroupId;
|
||||
|
@ -20,6 +20,12 @@ namespace SharpRSS.API.Models
|
|||
PswHash = Hasher.HashPassword(user.Password, out byte[] salt);
|
||||
Salt = salt;
|
||||
}
|
||||
public string Uid { get; set; } = string.Empty;
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public string Email { get; set; } = string.Empty;
|
||||
public string Gid { get; set; } = string.Empty;
|
||||
public bool Active { get; set; }
|
||||
public DateTime DateCreated { get; set; } = DateTime.Now;
|
||||
public byte[] PswHash { get; set; }
|
||||
public byte[] Salt { get; set; }
|
||||
|
||||
|
@ -29,9 +35,9 @@ namespace SharpRSS.API.Models
|
|||
return JsonConvert.DeserializeObject<User>(json) ?? new User();
|
||||
}
|
||||
|
||||
public bool Update(ModifyUser user)
|
||||
public void Update(InsertUser user)
|
||||
{
|
||||
if (user == null) return false;
|
||||
if (user == null) return;
|
||||
if (!user.DisplayName.IsNullEmptyWhiteSpace())
|
||||
DisplayName = user.DisplayName;
|
||||
if (!user.Email.IsNullEmptyWhiteSpace())
|
||||
|
@ -44,7 +50,6 @@ namespace SharpRSS.API.Models
|
|||
PswHash = Hasher.HashPassword(user.Password, out byte[] salt);
|
||||
Salt = salt;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user