Rebuild data storage system so that database dependencies get taken into account
This commit is contained in:
2
.idea/.idea.HopFrame/.idea/dataSources.xml
generated
2
.idea/.idea.HopFrame/.idea/dataSources.xml
generated
@@ -5,7 +5,7 @@
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/RestApiTest/bin/Debug/net8.0/test.db</jdbc-url>
|
||||
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/test/RestApiTest/bin/Debug/net8.0/test.db</jdbc-url>
|
||||
<jdbc-additional-properties>
|
||||
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||
</jdbc-additional-properties>
|
||||
|
||||
@@ -27,10 +27,11 @@ public static class ServiceCollectionExtensions {
|
||||
/// <param name="services">The service provider to add the services to</param>
|
||||
/// <typeparam name="TDbContext">The data source for all HopFrame entities</typeparam>
|
||||
public static void AddHopFrameNoEndpoints<TDbContext>(this IServiceCollection services) where TDbContext : HopDbContextBase {
|
||||
services.AddHopFrameRepositories<TDbContext>();
|
||||
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
services.AddScoped<IAuthLogic, AuthLogic<TDbContext>>();
|
||||
services.AddScoped<IAuthLogic, AuthLogic>();
|
||||
|
||||
services.AddHopFrameAuthentication<TDbContext>();
|
||||
services.AddHopFrameAuthentication();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
using HopFrame.Api.Models;
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Database.Models.Entries;
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Database.Repositories;
|
||||
using HopFrame.Security.Authentication;
|
||||
using HopFrame.Security.Claims;
|
||||
using HopFrame.Security.Models;
|
||||
using HopFrame.Security.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Api.Logic.Implementation;
|
||||
|
||||
public class AuthLogic<TDbContext>(TDbContext context, IUserService users, ITokenContext tokenContext, IHttpContextAccessor accessor) : IAuthLogic where TDbContext : HopDbContextBase {
|
||||
public class AuthLogic(IUserRepository users, ITokenRepository tokens, ITokenContext tokenContext, IHttpContextAccessor accessor) : IAuthLogic {
|
||||
|
||||
public async Task<LogicResult<SingleValueResult<string>>> Login(UserLogin login) {
|
||||
var user = await users.GetUserByEmail(login.Email);
|
||||
@@ -21,34 +19,21 @@ public class AuthLogic<TDbContext>(TDbContext context, IUserService users, IToke
|
||||
if (!await users.CheckUserPassword(user, login.Password))
|
||||
return LogicResult<SingleValueResult<string>>.Forbidden("The provided password is not correct");
|
||||
|
||||
var refreshToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.RefreshTokenType,
|
||||
UserId = user.Id.ToString()
|
||||
};
|
||||
var accessToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.AccessTokenType,
|
||||
UserId = user.Id.ToString()
|
||||
};
|
||||
var refreshToken = await tokens.CreateToken(Token.RefreshTokenType, user);
|
||||
var accessToken = await tokens.CreateToken(Token.AccessTokenType, user);
|
||||
|
||||
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Token, new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication<TDbContext>.RefreshTokenTime,
|
||||
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Content.ToString(), new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication.RefreshTokenTime,
|
||||
HttpOnly = true,
|
||||
Secure = true
|
||||
});
|
||||
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication<TDbContext>.AccessTokenTime,
|
||||
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication.AccessTokenTime,
|
||||
HttpOnly = true,
|
||||
Secure = true
|
||||
});
|
||||
|
||||
await context.Tokens.AddRangeAsync(refreshToken, accessToken);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return LogicResult<SingleValueResult<string>>.Ok(accessToken.Token);
|
||||
return LogicResult<SingleValueResult<string>>.Ok(accessToken.Content.ToString());
|
||||
}
|
||||
|
||||
public async Task<LogicResult<SingleValueResult<string>>> Register(UserRegister register) {
|
||||
@@ -59,36 +44,27 @@ public class AuthLogic<TDbContext>(TDbContext context, IUserService users, IToke
|
||||
if (allUsers.Any(user => user.Username == register.Username || user.Email == register.Email))
|
||||
return LogicResult<SingleValueResult<string>>.Conflict("Username or Email is already registered");
|
||||
|
||||
var user = await users.AddUser(register);
|
||||
var user = await users.AddUser(new User {
|
||||
Username = register.Username,
|
||||
Email = register.Email,
|
||||
Password = register.Password
|
||||
});
|
||||
|
||||
var refreshToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.RefreshTokenType,
|
||||
UserId = user.Id.ToString()
|
||||
};
|
||||
var accessToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.AccessTokenType,
|
||||
UserId = user.Id.ToString()
|
||||
};
|
||||
var refreshToken = await tokens.CreateToken(Token.RefreshTokenType, user);
|
||||
var accessToken = await tokens.CreateToken(Token.AccessTokenType, user);
|
||||
|
||||
await context.Tokens.AddRangeAsync(refreshToken, accessToken);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Token, new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication<TDbContext>.RefreshTokenTime,
|
||||
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Content.ToString(), new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication.RefreshTokenTime,
|
||||
HttpOnly = true,
|
||||
Secure = true
|
||||
});
|
||||
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication<TDbContext>.AccessTokenTime,
|
||||
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication.AccessTokenTime,
|
||||
HttpOnly = false,
|
||||
Secure = true
|
||||
});
|
||||
|
||||
return LogicResult<SingleValueResult<string>>.Ok(accessToken.Token);
|
||||
return LogicResult<SingleValueResult<string>>.Ok(accessToken.Content.ToString());
|
||||
}
|
||||
|
||||
public async Task<LogicResult<SingleValueResult<string>>> Authenticate() {
|
||||
@@ -97,31 +73,26 @@ public class AuthLogic<TDbContext>(TDbContext context, IUserService users, IToke
|
||||
if (string.IsNullOrEmpty(refreshToken))
|
||||
return LogicResult<SingleValueResult<string>>.Conflict("Refresh token not provided");
|
||||
|
||||
var token = await context.Tokens.SingleOrDefaultAsync(token => token.Token == refreshToken && token.Type == TokenEntry.RefreshTokenType);
|
||||
|
||||
var token = await tokens.GetToken(refreshToken);
|
||||
|
||||
if (token.Type != Token.RefreshTokenType)
|
||||
return LogicResult<SingleValueResult<string>>.BadRequest("The provided token is not a refresh token");
|
||||
|
||||
if (token is null)
|
||||
return LogicResult<SingleValueResult<string>>.NotFound("Refresh token not valid");
|
||||
|
||||
if (token.CreatedAt + HopFrameAuthentication<TDbContext>.RefreshTokenTime < DateTime.Now)
|
||||
if (token.CreatedAt + HopFrameAuthentication.RefreshTokenTime < DateTime.Now)
|
||||
return LogicResult<SingleValueResult<string>>.Conflict("Refresh token is expired");
|
||||
|
||||
var accessToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.AccessTokenType,
|
||||
UserId = token.UserId
|
||||
};
|
||||
|
||||
await context.Tokens.AddAsync(accessToken);
|
||||
await context.SaveChangesAsync();
|
||||
var accessToken = await tokens.CreateToken(Token.AccessTokenType, token.Owner);
|
||||
|
||||
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication<TDbContext>.AccessTokenTime,
|
||||
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication.AccessTokenTime,
|
||||
HttpOnly = false,
|
||||
Secure = true
|
||||
});
|
||||
|
||||
return LogicResult<SingleValueResult<string>>.Ok(accessToken.Token);
|
||||
return LogicResult<SingleValueResult<string>>.Ok(accessToken.Content.ToString());
|
||||
}
|
||||
|
||||
public async Task<LogicResult> Logout() {
|
||||
@@ -131,17 +102,7 @@ public class AuthLogic<TDbContext>(TDbContext context, IUserService users, IToke
|
||||
if (string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(refreshToken))
|
||||
return LogicResult.Conflict("access or refresh token not provided");
|
||||
|
||||
var tokenEntries = await context.Tokens.Where(token =>
|
||||
(token.Token == accessToken && token.Type == TokenEntry.AccessTokenType) ||
|
||||
(token.Token == refreshToken && token.Type == TokenEntry.RefreshTokenType))
|
||||
.ToArrayAsync();
|
||||
|
||||
if (tokenEntries.Length != 2)
|
||||
return LogicResult.NotFound("One or more of the provided tokens was not found");
|
||||
|
||||
context.Tokens.Remove(tokenEntries[0]);
|
||||
context.Tokens.Remove(tokenEntries[1]);
|
||||
await context.SaveChangesAsync();
|
||||
await tokens.DeleteUserTokens(tokenContext.User);
|
||||
|
||||
accessor.HttpContext?.Response.Cookies.Delete(ITokenContext.RefreshTokenType);
|
||||
accessor.HttpContext?.Response.Cookies.Delete(ITokenContext.AccessTokenType);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
|
||||
namespace HopFrame.Security;
|
||||
namespace HopFrame.Database;
|
||||
|
||||
public static class EncryptionManager {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using HopFrame.Database.Models.Entries;
|
||||
using HopFrame.Database.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Database;
|
||||
@@ -8,25 +8,27 @@ namespace HopFrame.Database;
|
||||
/// </summary>
|
||||
public abstract class HopDbContextBase : DbContext {
|
||||
|
||||
public virtual DbSet<UserEntry> Users { get; set; }
|
||||
public virtual DbSet<PermissionEntry> Permissions { get; set; }
|
||||
public virtual DbSet<TokenEntry> Tokens { get; set; }
|
||||
public virtual DbSet<GroupEntry> Groups { get; set; }
|
||||
public virtual DbSet<User> Users { get; set; }
|
||||
public virtual DbSet<Permission> Permissions { get; set; }
|
||||
public virtual DbSet<Token> Tokens { get; set; }
|
||||
public virtual DbSet<PermissionGroup> Groups { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder) {
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<UserEntry>();
|
||||
modelBuilder.Entity<PermissionEntry>();
|
||||
modelBuilder.Entity<TokenEntry>();
|
||||
modelBuilder.Entity<GroupEntry>();
|
||||
}
|
||||
modelBuilder.Entity<User>()
|
||||
.HasMany(u => u.Tokens)
|
||||
.WithOne(t => t.Owner)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
/// <summary>
|
||||
/// Gets executed when a user is deleted through the IUserService from the
|
||||
/// HopFrame.Security package. You can override this method to also delete
|
||||
/// related user specific entries in the database
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
public virtual void OnUserDelete(UserEntry user) {}
|
||||
modelBuilder.Entity<User>()
|
||||
.HasMany(u => u.Permissions)
|
||||
.WithOne(p => p.User)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<PermissionGroup>()
|
||||
.HasMany(g => g.Permissions)
|
||||
.WithOne(p => p.Group)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace HopFrame.Database.Models.Entries;
|
||||
|
||||
public class GroupEntry {
|
||||
[Key, Required, MaxLength(50)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Required, DefaultValue(false)]
|
||||
public bool Default { get; set; }
|
||||
|
||||
[MaxLength(500)]
|
||||
public string Description { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace HopFrame.Database.Models.Entries;
|
||||
|
||||
public sealed class PermissionEntry {
|
||||
[Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public long RecordId { get; set; }
|
||||
|
||||
[Required, MaxLength(255)]
|
||||
public string PermissionText { get; set; }
|
||||
|
||||
[Required, MinLength(36), MaxLength(36)]
|
||||
public string UserId { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime GrantedAt { get; set; }
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace HopFrame.Database.Models.Entries;
|
||||
|
||||
public class UserEntry {
|
||||
[Key, Required, MinLength(36), MaxLength(36)]
|
||||
public string Id { get; set; }
|
||||
|
||||
[MaxLength(50)]
|
||||
public string Username { get; set; }
|
||||
|
||||
[Required, MaxLength(50), EmailAddress]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required, MinLength(8), MaxLength(255)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using HopFrame.Database.Models.Entries;
|
||||
|
||||
namespace HopFrame.Database.Models;
|
||||
|
||||
public static class ModelExtensions {
|
||||
|
||||
/// <summary>
|
||||
/// Converts the database model to a friendly user model
|
||||
/// </summary>
|
||||
/// <param name="entry">the database model</param>
|
||||
/// <param name="contextBase">the data source for the permissions and users</param>
|
||||
/// <returns></returns>
|
||||
public static User ToUserModel(this UserEntry entry, HopDbContextBase contextBase) {
|
||||
var user = new User {
|
||||
Id = Guid.Parse(entry.Id),
|
||||
Username = entry.Username,
|
||||
Email = entry.Email,
|
||||
CreatedAt = entry.CreatedAt
|
||||
};
|
||||
|
||||
user.Permissions = contextBase.Permissions
|
||||
.Where(perm => perm.UserId == entry.Id)
|
||||
.Select(perm => perm.ToPermissionModel())
|
||||
.ToList();
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
public static Permission ToPermissionModel(this PermissionEntry entry) {
|
||||
Guid.TryParse(entry.UserId, out var userId);
|
||||
|
||||
return new Permission {
|
||||
Owner = userId,
|
||||
PermissionName = entry.PermissionText,
|
||||
GrantedAt = entry.GrantedAt,
|
||||
Id = entry.RecordId
|
||||
};
|
||||
}
|
||||
|
||||
public static PermissionGroup ToPermissionGroup(this GroupEntry entry, HopDbContextBase contextBase) {
|
||||
var group = new PermissionGroup {
|
||||
Name = entry.Name,
|
||||
IsDefaultGroup = entry.Default,
|
||||
Description = entry.Description,
|
||||
CreatedAt = entry.CreatedAt
|
||||
};
|
||||
|
||||
group.Permissions = contextBase.Permissions
|
||||
.Where(perm => perm.UserId == group.Name)
|
||||
.Select(perm => perm.ToPermissionModel())
|
||||
.ToList();
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,26 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace HopFrame.Database.Models;
|
||||
|
||||
public sealed class Permission {
|
||||
public class Permission {
|
||||
|
||||
[Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public long Id { get; init; }
|
||||
|
||||
[Required, MaxLength(255)]
|
||||
public string PermissionName { get; set; }
|
||||
public Guid Owner { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime GrantedAt { get; set; }
|
||||
|
||||
[ForeignKey("UserId"), JsonIgnore]
|
||||
public virtual User User { get; set; }
|
||||
|
||||
[ForeignKey("GroupName"), JsonIgnore]
|
||||
public virtual PermissionGroup Group { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public interface IPermissionOwner {}
|
||||
public interface IPermissionOwner;
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace HopFrame.Database.Models;
|
||||
|
||||
public class PermissionGroup : IPermissionOwner {
|
||||
|
||||
[Key, Required, MaxLength(50)]
|
||||
public string Name { get; init; }
|
||||
|
||||
[Required, DefaultValue(false)]
|
||||
public bool IsDefaultGroup { get; set; }
|
||||
|
||||
[MaxLength(500)]
|
||||
public string Description { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public IList<Permission> Permissions { get; set; }
|
||||
|
||||
public virtual IList<Permission> Permissions { get; set; }
|
||||
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace HopFrame.Database.Models.Entries;
|
||||
namespace HopFrame.Database.Models;
|
||||
|
||||
public class TokenEntry {
|
||||
public class Token {
|
||||
public const int RefreshTokenType = 0;
|
||||
public const int AccessTokenType = 1;
|
||||
|
||||
@@ -15,11 +17,11 @@ public class TokenEntry {
|
||||
public int Type { get; set; }
|
||||
|
||||
[Key, Required, MinLength(36), MaxLength(36)]
|
||||
public string Token { get; set; }
|
||||
|
||||
[Required, MinLength(36), MaxLength(36)]
|
||||
public string UserId { get; set; }
|
||||
public Guid Content { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
[ForeignKey("UserId"), JsonIgnore]
|
||||
public virtual User Owner { get; set; }
|
||||
}
|
||||
@@ -1,9 +1,28 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace HopFrame.Database.Models;
|
||||
|
||||
public sealed class User : IPermissionOwner {
|
||||
public class User : IPermissionOwner {
|
||||
|
||||
[Key, Required, MinLength(36), MaxLength(36)]
|
||||
public Guid Id { get; init; }
|
||||
|
||||
[MaxLength(50)]
|
||||
public string Username { get; set; }
|
||||
|
||||
[Required, MaxLength(50), EmailAddress]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required, MinLength(8), MaxLength(255), JsonIgnore]
|
||||
public string Password { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public IList<Permission> Permissions { get; set; }
|
||||
|
||||
public virtual IList<Permission> Permissions { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual IList<Token> Tokens { get; set; }
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace HopFrame.Security.Authorization;
|
||||
namespace HopFrame.Database;
|
||||
|
||||
public static class PermissionValidator {
|
||||
|
||||
19
src/HopFrame.Database/Repositories/IGroupRepository.cs
Normal file
19
src/HopFrame.Database/Repositories/IGroupRepository.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using HopFrame.Database.Models;
|
||||
|
||||
namespace HopFrame.Database.Repositories;
|
||||
|
||||
public interface IGroupRepository {
|
||||
Task<IList<PermissionGroup>> GetPermissionGroups();
|
||||
|
||||
Task<IList<PermissionGroup>> GetDefaultGroups();
|
||||
|
||||
Task<PermissionGroup> GetPermissionGroup(string name);
|
||||
|
||||
Task EditPermissionGroup(PermissionGroup group);
|
||||
|
||||
Task<PermissionGroup> CreatePermissionGroup(PermissionGroup group);
|
||||
|
||||
Task DeletePermissionGroup(PermissionGroup group);
|
||||
|
||||
internal Task<IList<string>> GetFullGroupPermissions(string group);
|
||||
}
|
||||
23
src/HopFrame.Database/Repositories/IPermissionRepository.cs
Normal file
23
src/HopFrame.Database/Repositories/IPermissionRepository.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using HopFrame.Database.Models;
|
||||
|
||||
namespace HopFrame.Database.Repositories;
|
||||
|
||||
public interface IPermissionRepository {
|
||||
Task<bool> HasPermission(IPermissionOwner owner, params string[] permissions);
|
||||
|
||||
/// <summary>
|
||||
/// permission system:<br/>
|
||||
/// - "*" -> all rights<br/>
|
||||
/// - "group.[name]" -> group member<br/>
|
||||
/// - "[namespace].[name]" -> single permission<br/>
|
||||
/// - "[namespace].*" -> all permissions in the namespace
|
||||
/// </summary>
|
||||
/// <param name="owner"></param>
|
||||
/// <param name="permission"></param>
|
||||
/// <returns></returns>
|
||||
Task<Permission> AddPermission(IPermissionOwner owner, string permission);
|
||||
|
||||
Task RemovePermission(IPermissionOwner owner, string permission);
|
||||
|
||||
public Task<IList<string>> GetFullPermissions(IPermissionOwner owner);
|
||||
}
|
||||
9
src/HopFrame.Database/Repositories/ITokenRepository.cs
Normal file
9
src/HopFrame.Database/Repositories/ITokenRepository.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using HopFrame.Database.Models;
|
||||
|
||||
namespace HopFrame.Database.Repositories;
|
||||
|
||||
public interface ITokenRepository {
|
||||
public Task<Token> GetToken(string content);
|
||||
public Task<Token> CreateToken(int type, User owner);
|
||||
public Task DeleteUserTokens(User owner);
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Security.Models;
|
||||
|
||||
namespace HopFrame.Security.Services;
|
||||
namespace HopFrame.Database.Repositories;
|
||||
|
||||
public interface IUserService {
|
||||
public interface IUserRepository {
|
||||
Task<IList<User>> GetUsers();
|
||||
|
||||
Task<User> GetUser(Guid userId);
|
||||
@@ -12,12 +11,12 @@ public interface IUserService {
|
||||
|
||||
Task<User> GetUserByUsername(string username);
|
||||
|
||||
Task<User> AddUser(UserRegister user);
|
||||
Task<User> AddUser(User user);
|
||||
|
||||
/// <summary>
|
||||
/// IMPORTANT:<br/>
|
||||
/// This function does not add or remove any permissions to the user.
|
||||
/// For that please use <see cref="IPermissionService"/>
|
||||
/// For that please use <see cref="IPermissionRepository"/>
|
||||
/// </summary>
|
||||
Task UpdateUser(User user);
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
using HopFrame.Database.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Database.Repositories.Implementation;
|
||||
|
||||
internal sealed class GroupRepository<TDbContext>(TDbContext context) : IGroupRepository where TDbContext : HopDbContextBase {
|
||||
public async Task<IList<PermissionGroup>> GetPermissionGroups() {
|
||||
return await context.Groups
|
||||
.Include(g => g.Permissions)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IList<PermissionGroup>> GetDefaultGroups() {
|
||||
return await context.Groups
|
||||
.Include(g => g.Permissions)
|
||||
.Where(g => g.IsDefaultGroup)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<PermissionGroup> GetPermissionGroup(string name) {
|
||||
return await context.Groups
|
||||
.Include(g => g.Permissions)
|
||||
.Where(g => g.Name == name)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task EditPermissionGroup(PermissionGroup group) {
|
||||
var orig = await context.Groups.SingleOrDefaultAsync(g => g.Name == group.Name);
|
||||
|
||||
if (orig is null) return;
|
||||
|
||||
var entity = context.Groups.Update(orig);
|
||||
|
||||
entity.Entity.IsDefaultGroup = group.IsDefaultGroup;
|
||||
entity.Entity.Description = group.Description;
|
||||
entity.Entity.Permissions = group.Permissions;
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<PermissionGroup> CreatePermissionGroup(PermissionGroup group) {
|
||||
group.CreatedAt = DateTime.Now;
|
||||
await context.Groups.AddAsync(group);
|
||||
await context.SaveChangesAsync();
|
||||
return group;
|
||||
}
|
||||
|
||||
public async Task DeletePermissionGroup(PermissionGroup group) {
|
||||
context.Groups.Remove(group);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<IList<string>> GetFullGroupPermissions(string group) {
|
||||
var permissions = await context.Permissions
|
||||
.Include(p => p.Group)
|
||||
.Where(p => p.Group != null)
|
||||
.Where(p => p.Group.Name == group)
|
||||
.Select(p => p.PermissionName)
|
||||
.ToListAsync();
|
||||
|
||||
var groups = permissions
|
||||
.Where(p => p.StartsWith("group."))
|
||||
.ToList();
|
||||
|
||||
foreach (var subgroup in groups) {
|
||||
permissions.AddRange(await GetFullGroupPermissions(subgroup));
|
||||
}
|
||||
|
||||
return permissions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using HopFrame.Database.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Database.Repositories.Implementation;
|
||||
|
||||
internal sealed class PermissionRepository<TDbContext>(TDbContext context, IGroupRepository groupRepository) : IPermissionRepository where TDbContext : HopDbContextBase {
|
||||
public async Task<bool> HasPermission(IPermissionOwner owner, params string[] permissions) {
|
||||
var perms = (await GetFullPermissions(owner)).ToArray();
|
||||
|
||||
foreach (var permission in permissions) {
|
||||
if (!PermissionValidator.IncludesPermission(permission, perms)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<Permission> AddPermission(IPermissionOwner owner, string permission) {
|
||||
var entry = new Permission {
|
||||
GrantedAt = DateTime.Now,
|
||||
PermissionName = permission
|
||||
};
|
||||
|
||||
if (owner is User user) {
|
||||
entry.User = user;
|
||||
}else if (owner is PermissionGroup group) {
|
||||
entry.Group = group;
|
||||
}
|
||||
|
||||
await context.Permissions.AddAsync(entry);
|
||||
await context.SaveChangesAsync();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public async Task RemovePermission(IPermissionOwner owner, string permission) {
|
||||
Permission entry = null;
|
||||
|
||||
if (owner is User user) {
|
||||
entry = await context.Permissions
|
||||
.Include(p => p.User)
|
||||
.Where(p => p.User != null)
|
||||
.Where(p => p.User.Id == user.Id)
|
||||
.Where(p => p.PermissionName == permission)
|
||||
.SingleOrDefaultAsync();
|
||||
}else if (owner is PermissionGroup group) {
|
||||
entry = await context.Permissions
|
||||
.Include(p => p.Group)
|
||||
.Where(p => p.Group != null)
|
||||
.Where(p =>p.Group.Name == group.Name)
|
||||
.Where(p => p.PermissionName == permission)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
if (entry is not null) {
|
||||
context.Permissions.Remove(entry);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IList<string>> GetFullPermissions(IPermissionOwner owner) {
|
||||
var permissions = new List<string>();
|
||||
|
||||
if (owner is User user) {
|
||||
var perms = await context.Permissions
|
||||
.Include(p => p.User)
|
||||
.Where(p => p.User != null)
|
||||
.Where(p => p.User.Id == user.Id)
|
||||
.ToListAsync();
|
||||
|
||||
permissions.AddRange(perms.Select(p => p.PermissionName));
|
||||
}else if (owner is PermissionGroup group) {
|
||||
var perms = await context.Permissions
|
||||
.Include(p => p.Group)
|
||||
.Where(p => p.Group != null)
|
||||
.Where(p =>p.Group.Name == group.Name)
|
||||
.ToListAsync();
|
||||
|
||||
permissions.AddRange(perms.Select(p => p.PermissionName));
|
||||
}
|
||||
|
||||
var groups = permissions
|
||||
.Where(p => p.StartsWith("group."))
|
||||
.ToList();
|
||||
foreach (var group in groups) {
|
||||
permissions.AddRange(await groupRepository.GetFullGroupPermissions(group));
|
||||
}
|
||||
|
||||
return permissions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using HopFrame.Database.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Database.Repositories.Implementation;
|
||||
|
||||
internal sealed class TokenRepository<TDbContext>(TDbContext context) : ITokenRepository where TDbContext : HopDbContextBase {
|
||||
|
||||
public async Task<Token> GetToken(string content) {
|
||||
var success = Guid.TryParse(content, out Guid guid);
|
||||
if (!success) return null;
|
||||
|
||||
return await context.Tokens
|
||||
.Include(t => t.Owner)
|
||||
.Where(t => t.Content == guid)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<Token> CreateToken(int type, User owner) {
|
||||
var token = new Token {
|
||||
CreatedAt = DateTime.Now,
|
||||
Content = Guid.NewGuid(),
|
||||
Type = type,
|
||||
Owner = owner
|
||||
};
|
||||
|
||||
await context.Tokens.AddAsync(token);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
public async Task DeleteUserTokens(User owner) {
|
||||
var tokens = await context.Tokens
|
||||
.Include(t => t.Owner)
|
||||
.Where(t => t.Owner.Id == owner.Id)
|
||||
.ToListAsync();
|
||||
|
||||
context.Tokens.RemoveRange(tokens);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using HopFrame.Database.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Query;
|
||||
|
||||
namespace HopFrame.Database.Repositories.Implementation;
|
||||
|
||||
internal sealed class UserRepository<TDbContext>(TDbContext context, IGroupRepository groupRepository) : IUserRepository where TDbContext : HopDbContextBase {
|
||||
|
||||
private IIncludableQueryable<User, IList<Token>> IncludeReferences() {
|
||||
return context.Users
|
||||
.Include(u => u.Permissions)
|
||||
.ThenInclude(p => p.Group)
|
||||
.Include(u => u.Tokens);
|
||||
}
|
||||
|
||||
public async Task<IList<User>> GetUsers() {
|
||||
return await IncludeReferences()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<User> GetUser(Guid userId) {
|
||||
return await IncludeReferences()
|
||||
.Where(u => u.Id == userId)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<User> GetUserByEmail(string email) {
|
||||
return await IncludeReferences()
|
||||
.Where(u => u.Email == email)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<User> GetUserByUsername(string username) {
|
||||
return await IncludeReferences()
|
||||
.Where(u => u.Username == username)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<User> AddUser(User user) {
|
||||
if (await GetUserByEmail(user.Email) is not null) return null;
|
||||
if (await GetUserByUsername(user.Username) is not null) return null;
|
||||
|
||||
var entry = new User {
|
||||
Id = Guid.NewGuid(),
|
||||
Email = user.Email,
|
||||
Username = user.Username,
|
||||
CreatedAt = DateTime.Now,
|
||||
Permissions = user.Permissions,
|
||||
Tokens = user.Tokens
|
||||
};
|
||||
entry.Password = EncryptionManager.Hash(user.Password, Encoding.Default.GetBytes(entry.CreatedAt.ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
var defaultGroups = await groupRepository.GetDefaultGroups();
|
||||
foreach (var group in defaultGroups) {
|
||||
entry.Permissions.Add(new Permission {
|
||||
PermissionName = group.Name,
|
||||
//TODO: Check if user needs to be set
|
||||
});
|
||||
}
|
||||
|
||||
await context.Users.AddAsync(entry);
|
||||
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public async Task UpdateUser(User user) {
|
||||
var entry = await IncludeReferences()
|
||||
.SingleOrDefaultAsync(entry => entry.Id == user.Id);
|
||||
if (entry is null) return;
|
||||
|
||||
entry.Email = user.Email;
|
||||
entry.Username = user.Username;
|
||||
entry.Permissions = user.Permissions;
|
||||
entry.Tokens = user.Tokens;
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteUser(User user) {
|
||||
var entry = await context.Users
|
||||
.SingleOrDefaultAsync(entry => entry.Id == user.Id);
|
||||
|
||||
if (entry is null) return;
|
||||
|
||||
context.Users.Remove(entry);
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> CheckUserPassword(User user, string password) {
|
||||
var hash = EncryptionManager.Hash(password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
var entry = await context.Users
|
||||
.Where(entry => entry.Id == user.Id)
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
return entry.Password == hash;
|
||||
}
|
||||
|
||||
public async Task ChangePassword(User user, string password) {
|
||||
var entry = await context.Users
|
||||
.Where(entry => entry.Id == user.Id)
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
if (entry is null) return;
|
||||
|
||||
var hash = EncryptionManager.Hash(password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
|
||||
entry.Password = hash;
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
}
|
||||
18
src/HopFrame.Database/ServiceCollectionExtensions.cs
Normal file
18
src/HopFrame.Database/ServiceCollectionExtensions.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using HopFrame.Database.Repositories;
|
||||
using HopFrame.Database.Repositories.Implementation;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace HopFrame.Database;
|
||||
|
||||
public static class ServiceCollectionExtensions {
|
||||
|
||||
public static IServiceCollection AddHopFrameRepositories<TDbContext>(this IServiceCollection services) where TDbContext : HopDbContextBase {
|
||||
services.AddScoped<IGroupRepository, GroupRepository<TDbContext>>();
|
||||
services.AddScoped<IPermissionRepository, PermissionRepository<TDbContext>>();
|
||||
services.AddScoped<IUserRepository, UserRepository<TDbContext>>();
|
||||
services.AddScoped<ITokenRepository, TokenRepository<TDbContext>>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Database.Repositories;
|
||||
using HopFrame.Security.Claims;
|
||||
using HopFrame.Security.Services;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@@ -13,15 +11,15 @@ using Microsoft.Extensions.Options;
|
||||
|
||||
namespace HopFrame.Security.Authentication;
|
||||
|
||||
public class HopFrameAuthentication<TDbContext>(
|
||||
public class HopFrameAuthentication(
|
||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock,
|
||||
TDbContext context,
|
||||
IPermissionService perms)
|
||||
: AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder, clock)
|
||||
where TDbContext : HopDbContextBase {
|
||||
ITokenRepository tokens,
|
||||
IUserRepository users,
|
||||
IPermissionRepository perms)
|
||||
: AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder, clock) {
|
||||
|
||||
public const string SchemeName = "HopCore.Authentication";
|
||||
public static readonly TimeSpan AccessTokenTime = new(0, 0, 5, 0);
|
||||
@@ -32,21 +30,21 @@ public class HopFrameAuthentication<TDbContext>(
|
||||
if (string.IsNullOrEmpty(accessToken)) accessToken = Request.Headers[SchemeName];
|
||||
if (string.IsNullOrEmpty(accessToken)) accessToken = Request.Headers["Token"];
|
||||
if (string.IsNullOrEmpty(accessToken)) return AuthenticateResult.Fail("No Access Token provided");
|
||||
|
||||
var tokenEntry = await context.Tokens.SingleOrDefaultAsync(token => token.Token == accessToken);
|
||||
|
||||
var tokenEntry = await tokens.GetToken(accessToken);
|
||||
|
||||
if (tokenEntry is null) return AuthenticateResult.Fail("The provided Access Token does not exist");
|
||||
if (tokenEntry.CreatedAt + AccessTokenTime < DateTime.Now) return AuthenticateResult.Fail("The provided Access Token is expired");
|
||||
|
||||
if (!await context.Users.AnyAsync(user => user.Id == tokenEntry.UserId))
|
||||
if (tokenEntry.Owner is null)
|
||||
return AuthenticateResult.Fail("The provided Access Token does not match any user");
|
||||
|
||||
var claims = new List<Claim> {
|
||||
new(HopFrameClaimTypes.AccessTokenId, accessToken),
|
||||
new(HopFrameClaimTypes.UserId, tokenEntry.UserId)
|
||||
new(HopFrameClaimTypes.UserId, tokenEntry.Owner.Id.ToString())
|
||||
};
|
||||
|
||||
var permissions = await perms.GetFullPermissions(tokenEntry.UserId);
|
||||
var permissions = await perms.GetFullPermissions(tokenEntry.Owner);
|
||||
claims.AddRange(permissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm)));
|
||||
|
||||
var principal = new ClaimsPrincipal();
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Security.Claims;
|
||||
using HopFrame.Security.Services;
|
||||
using HopFrame.Security.Services.Implementation;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -17,13 +14,11 @@ public static class HopFrameAuthenticationExtensions {
|
||||
/// <param name="service">The service provider to add the services to</param>
|
||||
/// <typeparam name="TDbContext">The database object that saves all entities that are important for the security api</typeparam>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddHopFrameAuthentication<TDbContext>(this IServiceCollection service) where TDbContext : HopDbContextBase {
|
||||
public static IServiceCollection AddHopFrameAuthentication(this IServiceCollection service) {
|
||||
service.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
service.AddScoped<ITokenContext, TokenContextImplementor<TDbContext>>();
|
||||
service.AddScoped<IPermissionService, PermissionService<TDbContext>>();
|
||||
service.AddScoped<IUserService, UserService<TDbContext>>();
|
||||
service.AddScoped<ITokenContext, TokenContextImplementor>();
|
||||
|
||||
service.AddAuthentication(HopFrameAuthentication<TDbContext>.SchemeName).AddScheme<AuthenticationSchemeOptions, HopFrameAuthentication<TDbContext>>(HopFrameAuthentication<TDbContext>.SchemeName, _ => {});
|
||||
service.AddAuthentication(HopFrameAuthentication.SchemeName).AddScheme<AuthenticationSchemeOptions, HopFrameAuthentication>(HopFrameAuthentication.SchemeName, _ => {});
|
||||
service.AddAuthorization();
|
||||
|
||||
return service;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Security.Claims;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Authorization;
|
||||
|
||||
@@ -20,5 +20,5 @@ public interface ITokenContext {
|
||||
/// <summary>
|
||||
/// The access token the user provided
|
||||
/// </summary>
|
||||
Guid AccessToken { get; }
|
||||
Token AccessToken { get; }
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Database.Repositories;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace HopFrame.Security.Claims;
|
||||
|
||||
internal sealed class TokenContextImplementor<TDbContext>(IHttpContextAccessor accessor, TDbContext context) : ITokenContext where TDbContext : HopDbContextBase {
|
||||
internal sealed class TokenContextImplementor(IHttpContextAccessor accessor, IUserRepository users, ITokenRepository tokens) : ITokenContext {
|
||||
public bool IsAuthenticated => !string.IsNullOrEmpty(accessor.HttpContext?.User.GetAccessTokenId());
|
||||
|
||||
public User User => context.Users
|
||||
.SingleOrDefault(user => user.Id == accessor.HttpContext.User.GetUserId())?
|
||||
.ToUserModel(context);
|
||||
|
||||
public Guid AccessToken => Guid.Parse(accessor.HttpContext?.User.GetAccessTokenId() ?? Guid.Empty.ToString());
|
||||
public User User => users.GetUser(Guid.Parse(accessor.HttpContext?.User.GetUserId() ?? Guid.Empty.ToString())).GetAwaiter().GetResult();
|
||||
|
||||
public Token AccessToken => tokens.GetToken(accessor.HttpContext?.User.GetAccessTokenId()).GetAwaiter().GetResult();
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using HopFrame.Database.Models;
|
||||
|
||||
namespace HopFrame.Security.Services;
|
||||
|
||||
/// <summary>
|
||||
/// permission system:<br/>
|
||||
/// - "*" -> all rights<br/>
|
||||
/// - "group.[name]" -> group member<br/>
|
||||
/// - "[namespace].[name]" -> single permission<br/>
|
||||
/// - "[namespace].*" -> all permissions in the namespace
|
||||
/// </summary>
|
||||
public interface IPermissionService {
|
||||
|
||||
Task<bool> HasPermission(string permission, Guid user);
|
||||
|
||||
Task<IList<PermissionGroup>> GetPermissionGroups();
|
||||
|
||||
Task<PermissionGroup> GetPermissionGroup(string name);
|
||||
|
||||
Task EditPermissionGroup(PermissionGroup group);
|
||||
|
||||
Task<IList<PermissionGroup>> GetUserPermissionGroups(User user);
|
||||
|
||||
Task RemoveGroupFromUser(User user, PermissionGroup group);
|
||||
|
||||
Task<PermissionGroup> CreatePermissionGroup(string name, bool isDefault = false, string description = null);
|
||||
|
||||
Task DeletePermissionGroup(PermissionGroup group);
|
||||
|
||||
Task<Permission> GetPermission(string name, IPermissionOwner owner);
|
||||
|
||||
/// <summary>
|
||||
/// permission system:<br/>
|
||||
/// - "*" -> all rights<br/>
|
||||
/// - "group.[name]" -> group member<br/>
|
||||
/// - "[namespace].[name]" -> single permission<br/>
|
||||
/// - "[namespace].*" -> all permissions in the namespace
|
||||
/// </summary>
|
||||
/// <param name="owner"></param>
|
||||
/// <param name="permission"></param>
|
||||
/// <returns></returns>
|
||||
Task AddPermission(IPermissionOwner owner, string permission);
|
||||
|
||||
Task RemovePermission(Permission permission);
|
||||
|
||||
Task<string[]> GetFullPermissions(string user);
|
||||
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Database.Models.Entries;
|
||||
using HopFrame.Security.Authorization;
|
||||
using HopFrame.Security.Claims;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Security.Services.Implementation;
|
||||
|
||||
internal sealed class PermissionService<TDbContext>(TDbContext context, ITokenContext current) : IPermissionService where TDbContext : HopDbContextBase {
|
||||
public async Task<bool> HasPermission(string permission) {
|
||||
return await HasPermission(permission, current.User.Id);
|
||||
}
|
||||
|
||||
public async Task<bool> HasPermissions(params string[] permissions) {
|
||||
var user = current.User.Id.ToString();
|
||||
var perms = await GetFullPermissions(user);
|
||||
|
||||
foreach (var permission in permissions) {
|
||||
if (!PermissionValidator.IncludesPermission(permission, perms)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> HasAnyPermission(params string[] permissions) {
|
||||
var user = current.User.Id.ToString();
|
||||
var perms = await GetFullPermissions(user);
|
||||
|
||||
foreach (var permission in permissions) {
|
||||
if (PermissionValidator.IncludesPermission(permission, perms)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> HasPermission(string permission, Guid user) {
|
||||
var permissions = await GetFullPermissions(user.ToString());
|
||||
|
||||
return PermissionValidator.IncludesPermission(permission, permissions);
|
||||
}
|
||||
|
||||
public async Task<IList<PermissionGroup>> GetPermissionGroups() {
|
||||
return await context.Groups
|
||||
.Select(group => group.ToPermissionGroup(context))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public Task<PermissionGroup> GetPermissionGroup(string name) {
|
||||
return context.Groups
|
||||
.Where(group => group.Name == name)
|
||||
.Select(group => group.ToPermissionGroup(context))
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task EditPermissionGroup(PermissionGroup group) {
|
||||
var orig = await context.Groups.SingleOrDefaultAsync(g => g.Name == group.Name);
|
||||
|
||||
if (orig is null) return;
|
||||
|
||||
var entity = context.Groups.Update(orig);
|
||||
|
||||
entity.Entity.Default = group.IsDefaultGroup;
|
||||
entity.Entity.Description = group.Description;
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<IList<PermissionGroup>> GetUserPermissionGroups(User user) {
|
||||
var groups = await context.Groups.ToListAsync();
|
||||
var perms = await GetFullPermissions(user.Id.ToString());
|
||||
|
||||
return groups
|
||||
.Where(group => perms.Contains(group.Name))
|
||||
.Select(group => group.ToPermissionGroup(context))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task RemoveGroupFromUser(User user, PermissionGroup group) {
|
||||
var entry = await context.Permissions
|
||||
.Where(perm => perm.PermissionText == group.Name && perm.UserId == user.Id.ToString())
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
if (entry is null) return;
|
||||
|
||||
context.Permissions.Remove(entry);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<PermissionGroup> CreatePermissionGroup(string name, bool isDefault = false, string description = null) {
|
||||
var group = new GroupEntry {
|
||||
Name = name,
|
||||
Description = description,
|
||||
Default = isDefault,
|
||||
CreatedAt = DateTime.Now
|
||||
};
|
||||
|
||||
await context.Groups.AddAsync(group);
|
||||
|
||||
if (isDefault) {
|
||||
var users = await context.Users.ToListAsync();
|
||||
|
||||
foreach (var user in users) {
|
||||
await context.Permissions.AddAsync(new PermissionEntry {
|
||||
GrantedAt = DateTime.Now,
|
||||
PermissionText = group.Name,
|
||||
UserId = user.Id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return group.ToPermissionGroup(context);
|
||||
}
|
||||
|
||||
public async Task DeletePermissionGroup(PermissionGroup group) {
|
||||
var entry = await context.Groups.SingleOrDefaultAsync(entry => entry.Name == group.Name);
|
||||
context.Groups.Remove(entry);
|
||||
|
||||
var permissions = await context.Permissions
|
||||
.Where(perm => perm.UserId == group.Name || perm.PermissionText == group.Name)
|
||||
.ToListAsync();
|
||||
|
||||
if (permissions.Count > 0) {
|
||||
context.Permissions.RemoveRange(permissions);
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<Permission> GetPermission(string name, IPermissionOwner owner) {
|
||||
var ownerId = (owner is User user) ? user.Id.ToString() : ((PermissionGroup)owner).Name;
|
||||
|
||||
return await context.Permissions
|
||||
.Where(perm => perm.PermissionText == name && perm.UserId == ownerId)
|
||||
.Select(perm => perm.ToPermissionModel())
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task AddPermission(IPermissionOwner owner, string permission) {
|
||||
var userId = owner is User user ? user.Id.ToString() : (owner as PermissionGroup)?.Name;
|
||||
|
||||
await context.Permissions.AddAsync(new PermissionEntry {
|
||||
UserId = userId,
|
||||
PermissionText = permission,
|
||||
GrantedAt = DateTime.Now
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task RemovePermission(Permission permission) {
|
||||
var entry = await context.Permissions.SingleOrDefaultAsync(entry => entry.RecordId == permission.Id);
|
||||
context.Permissions.Remove(entry);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<string[]> GetFullPermissions(string user) {
|
||||
var permissions = await context.Permissions
|
||||
.Where(perm => perm.UserId == user)
|
||||
.Select(perm => perm.PermissionText)
|
||||
.ToListAsync();
|
||||
|
||||
var groups = permissions
|
||||
.Where(perm => perm.StartsWith("group."))
|
||||
.ToList();
|
||||
|
||||
var groupPerms = new List<string>();
|
||||
foreach (var group in groups) {
|
||||
var perms = await GetFullPermissions(group);
|
||||
groupPerms.AddRange(perms);
|
||||
}
|
||||
|
||||
permissions.AddRange(groupPerms);
|
||||
|
||||
return permissions.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Database.Models.Entries;
|
||||
using HopFrame.Security.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Security.Services.Implementation;
|
||||
|
||||
internal sealed class UserService<TDbContext>(TDbContext context) : IUserService where TDbContext : HopDbContextBase {
|
||||
public async Task<IList<User>> GetUsers() {
|
||||
return await context.Users
|
||||
.Select(user => user.ToUserModel(context))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public Task<User> GetUser(Guid userId) {
|
||||
var id = userId.ToString();
|
||||
|
||||
return context.Users
|
||||
.Where(user => user.Id == id)
|
||||
.Select(user => user.ToUserModel(context))
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public Task<User> GetUserByEmail(string email) {
|
||||
return context.Users
|
||||
.Where(user => user.Email == email)
|
||||
.Select(user => user.ToUserModel(context))
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public Task<User> GetUserByUsername(string username) {
|
||||
return context.Users
|
||||
.Where(user => user.Username == username)
|
||||
.Select(user => user.ToUserModel(context))
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<User> AddUser(UserRegister user) {
|
||||
if (await GetUserByEmail(user.Email) is not null) return null;
|
||||
if (await GetUserByUsername(user.Username) is not null) return null;
|
||||
|
||||
var entry = new UserEntry {
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Email = user.Email,
|
||||
Username = user.Username,
|
||||
CreatedAt = DateTime.Now
|
||||
};
|
||||
entry.Password = EncryptionManager.Hash(user.Password, Encoding.Default.GetBytes(entry.CreatedAt.ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
await context.Users.AddAsync(entry);
|
||||
|
||||
var defaultGroups = await context.Groups
|
||||
.Where(group => group.Default)
|
||||
.Select(group => "group." + group.Name)
|
||||
.ToListAsync();
|
||||
|
||||
await context.Permissions.AddRangeAsync(defaultGroups.Select(group => new PermissionEntry {
|
||||
GrantedAt = DateTime.Now,
|
||||
PermissionText = group,
|
||||
UserId = entry.Id
|
||||
}));
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
return entry.ToUserModel(context);
|
||||
}
|
||||
|
||||
public async Task UpdateUser(User user) {
|
||||
var id = user.Id.ToString();
|
||||
var entry = await context.Users
|
||||
.SingleOrDefaultAsync(entry => entry.Id == id);
|
||||
if (entry is null) return;
|
||||
|
||||
entry.Email = user.Email;
|
||||
entry.Username = user.Username;
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteUser(User user) {
|
||||
var id = user.Id.ToString();
|
||||
var entry = await context.Users
|
||||
.SingleOrDefaultAsync(entry => entry.Id == id);
|
||||
|
||||
if (entry is null) return;
|
||||
|
||||
context.Users.Remove(entry);
|
||||
|
||||
var userTokens = await context.Tokens
|
||||
.Where(token => token.UserId == id)
|
||||
.ToArrayAsync();
|
||||
context.Tokens.RemoveRange(userTokens);
|
||||
|
||||
var userPermissions = await context.Permissions
|
||||
.Where(perm => perm.UserId == id)
|
||||
.ToArrayAsync();
|
||||
context.Permissions.RemoveRange(userPermissions);
|
||||
|
||||
context.OnUserDelete(entry);
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> CheckUserPassword(User user, string password) {
|
||||
var id = user.Id.ToString();
|
||||
var hash = EncryptionManager.Hash(password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
var entry = await context.Users
|
||||
.Where(entry => entry.Id == id)
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
return entry.Password == hash;
|
||||
}
|
||||
|
||||
public async Task ChangePassword(User user, string password) {
|
||||
var entry = await context.Users
|
||||
.Where(entry => entry.Id == user.Id.ToString())
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
if (entry is null) return;
|
||||
|
||||
var hash = EncryptionManager.Hash(password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
|
||||
entry.Password = hash;
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
using System.Security.Claims;
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Database.Repositories;
|
||||
using HopFrame.Security.Authentication;
|
||||
using HopFrame.Security.Claims;
|
||||
using HopFrame.Security.Services;
|
||||
using HopFrame.Web.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace HopFrame.Web;
|
||||
|
||||
public sealed class AuthMiddleware(IAuthService auth, IPermissionService perms) : IMiddleware {
|
||||
public sealed class AuthMiddleware(IAuthService auth, IPermissionRepository perms) : IMiddleware {
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next) {
|
||||
var loggedIn = await auth.IsLoggedIn();
|
||||
|
||||
@@ -20,14 +19,14 @@ public sealed class AuthMiddleware(IAuthService auth, IPermissionService perms)
|
||||
}
|
||||
|
||||
var claims = new List<Claim> {
|
||||
new(HopFrameClaimTypes.AccessTokenId, token.Token),
|
||||
new(HopFrameClaimTypes.UserId, token.UserId)
|
||||
new(HopFrameClaimTypes.AccessTokenId, token.Content.ToString()),
|
||||
new(HopFrameClaimTypes.UserId, token.Owner.Id.ToString())
|
||||
};
|
||||
|
||||
var permissions = await perms.GetFullPermissions(token.UserId);
|
||||
var permissions = await perms.GetFullPermissions(token.Owner);
|
||||
claims.AddRange(permissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm)));
|
||||
|
||||
context.User.AddIdentity(new ClaimsIdentity(claims, HopFrameAuthentication<HopDbContextBase>.SchemeName));
|
||||
context.User.AddIdentity(new ClaimsIdentity(claims, HopFrameAuthentication.SchemeName));
|
||||
}
|
||||
|
||||
await next?.Invoke(context);
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
@using BlazorStrap.V5
|
||||
@using CurrieTechnologies.Razor.SweetAlert2
|
||||
@using HopFrame.Database.Models
|
||||
@using HopFrame.Database.Repositories
|
||||
@using HopFrame.Security.Claims
|
||||
@using HopFrame.Security.Services
|
||||
@using HopFrame.Web.Model
|
||||
|
||||
<BSModal DataId="add-group-modal" HideOnValidSubmit="true" IsStaticBackdrop="true" @ref="_modal">
|
||||
@@ -115,7 +115,8 @@
|
||||
</BSForm>
|
||||
</BSModal>
|
||||
|
||||
@inject IPermissionService Permissions
|
||||
@inject IGroupRepository Groups
|
||||
@inject IPermissionRepository Permissions
|
||||
@inject SweetAlertService Alerts
|
||||
@inject ITokenContext Context
|
||||
|
||||
@@ -133,7 +134,7 @@
|
||||
private bool _isEdit;
|
||||
|
||||
public async Task ShowAsync(PermissionGroup group = null) {
|
||||
_allGroups = await Permissions.GetPermissionGroups();
|
||||
_allGroups = await Groups.GetPermissionGroups();
|
||||
|
||||
if (group is not null) {
|
||||
_group = new PermissionGroupAdd {
|
||||
@@ -167,7 +168,7 @@
|
||||
}
|
||||
|
||||
if (_isEdit) {
|
||||
if (!(await Permissions.HasPermission(Security.AdminPermissions.EditGroup, Context.User.Id))) {
|
||||
if (!await Permissions.HasPermission(Context.User, Security.AdminPermissions.EditGroup)) {
|
||||
await NoEditPermissions();
|
||||
return;
|
||||
}
|
||||
@@ -184,8 +185,7 @@
|
||||
|
||||
private async Task RemovePermission(Permission permission) {
|
||||
if (_isEdit) {
|
||||
var perm = await Permissions.GetPermission(permission.PermissionName, _group);
|
||||
await Permissions.RemovePermission(perm);
|
||||
await Permissions.RemovePermission(_group, permission.PermissionName);
|
||||
}
|
||||
|
||||
_group.Permissions.Remove(permission);
|
||||
@@ -202,7 +202,7 @@
|
||||
}
|
||||
|
||||
if (_isEdit) {
|
||||
if (!(await Permissions.HasPermission(Security.AdminPermissions.EditGroup, Context.User.Id))) {
|
||||
if (!await Permissions.HasPermission(Context.User, Security.AdminPermissions.EditGroup)) {
|
||||
await NoEditPermissions();
|
||||
return;
|
||||
}
|
||||
@@ -219,12 +219,12 @@
|
||||
|
||||
private async Task AddGroup() {
|
||||
if (_isEdit) {
|
||||
if (!(await Permissions.HasPermission(Security.AdminPermissions.EditGroup, Context.User.Id))) {
|
||||
if (!await Permissions.HasPermission(Context.User, Security.AdminPermissions.EditGroup)) {
|
||||
await NoEditPermissions();
|
||||
return;
|
||||
}
|
||||
|
||||
await Permissions.EditPermissionGroup(_group);
|
||||
await Groups.EditPermissionGroup(_group);
|
||||
|
||||
if (ReloadPage is not null)
|
||||
await ReloadPage.Invoke();
|
||||
@@ -239,7 +239,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await Permissions.HasPermission(Security.AdminPermissions.AddGroup, Context.User.Id))) {
|
||||
if (!await Permissions.HasPermission(Context.User, Security.AdminPermissions.AddGroup)) {
|
||||
await NoAddPermissions();
|
||||
return;
|
||||
}
|
||||
@@ -255,11 +255,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var dbGroup = await Permissions.CreatePermissionGroup("group." + _group.GroupName, _group.IsDefaultGroup, _group.Description);
|
||||
|
||||
foreach (var permission in _group.Permissions) {
|
||||
await Permissions.AddPermission(dbGroup, permission.PermissionName);
|
||||
}
|
||||
await Groups.CreatePermissionGroup(_group);
|
||||
|
||||
if (ReloadPage is not null)
|
||||
await ReloadPage.Invoke();
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
@using BlazorStrap.V5
|
||||
@using CurrieTechnologies.Razor.SweetAlert2
|
||||
@using HopFrame.Database.Models
|
||||
@using HopFrame.Database.Repositories
|
||||
@using HopFrame.Security.Claims
|
||||
@using HopFrame.Security.Services
|
||||
@using HopFrame.Web.Model
|
||||
|
||||
<BSModal DataId="add-user-modal" HideOnValidSubmit="true" IsStaticBackdrop="true" OnShow="() => _user = new()" @ref="_modal">
|
||||
@@ -47,8 +47,9 @@
|
||||
</BSForm>
|
||||
</BSModal>
|
||||
|
||||
@inject IUserService Users
|
||||
@inject IPermissionService Permissions
|
||||
@inject IUserRepository Users
|
||||
@inject IPermissionRepository Permissions
|
||||
@inject IGroupRepository Groups
|
||||
@inject SweetAlertService Alerts
|
||||
@inject ITokenContext Auth
|
||||
|
||||
@@ -62,14 +63,14 @@
|
||||
private BSModalBase _modal;
|
||||
|
||||
public async Task ShowAsync() {
|
||||
_allGroups = await Permissions.GetPermissionGroups();
|
||||
_allGroups = await Groups.GetPermissionGroups();
|
||||
_allUsers = await Users.GetUsers();
|
||||
|
||||
await _modal.ShowAsync();
|
||||
}
|
||||
|
||||
private async Task AddUser() {
|
||||
if (!(await Permissions.HasPermission(Security.AdminPermissions.AddUser, Auth.User.Id))) {
|
||||
if (!(await Permissions.HasPermission(Auth.User, Security.AdminPermissions.AddUser))) {
|
||||
await NoAddPermissions();
|
||||
return;
|
||||
}
|
||||
@@ -104,7 +105,11 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await Users.AddUser(_user);
|
||||
var user = await Users.AddUser(new User {
|
||||
Username = _user.Username,
|
||||
Email = _user.Email,
|
||||
Password = _user.Password
|
||||
});
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_user.Group)) {
|
||||
await Permissions.AddPermission(user, _user.Group);
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
@using BlazorStrap.V5
|
||||
@using CurrieTechnologies.Razor.SweetAlert2
|
||||
@using HopFrame.Database.Models
|
||||
@using HopFrame.Database.Repositories
|
||||
@using HopFrame.Security.Claims
|
||||
@using HopFrame.Security.Services
|
||||
@using HopFrame.Web.Model
|
||||
|
||||
<BSModal DataId="edit-user-modal" HideOnValidSubmit="true" IsStaticBackdrop="true" @ref="_modal">
|
||||
@@ -100,8 +100,9 @@
|
||||
</BSForm>
|
||||
</BSModal>
|
||||
|
||||
@inject IUserService Users
|
||||
@inject IPermissionService Permissions
|
||||
@inject IUserRepository Users
|
||||
@inject IPermissionRepository Permissions
|
||||
@inject IGroupRepository Groups
|
||||
@inject SweetAlertService Alerts
|
||||
@inject ITokenContext Auth
|
||||
|
||||
@@ -118,19 +119,19 @@
|
||||
private string _permissionToAdd;
|
||||
|
||||
public async Task ShowAsync(User user) {
|
||||
if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
|
||||
if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) {
|
||||
await NoEditPermissions();
|
||||
return;
|
||||
}
|
||||
|
||||
_user = user;
|
||||
_userGroups = await Permissions.GetUserPermissionGroups(_user);
|
||||
_allGroups = await Permissions.GetPermissionGroups();
|
||||
_userGroups = _user.Permissions.Where(p => p.PermissionName.StartsWith("group.")).Select(p => p.Group).ToList();
|
||||
_allGroups = await Groups.GetPermissionGroups();
|
||||
await _modal.ShowAsync();
|
||||
}
|
||||
|
||||
private async Task AddGroup() {
|
||||
if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
|
||||
if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) {
|
||||
await NoEditPermissions();
|
||||
return;
|
||||
}
|
||||
@@ -158,7 +159,7 @@
|
||||
}
|
||||
|
||||
private async Task RemoveGroup(PermissionGroup group) {
|
||||
if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
|
||||
if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) {
|
||||
await NoEditPermissions();
|
||||
return;
|
||||
}
|
||||
@@ -172,7 +173,7 @@
|
||||
});
|
||||
|
||||
if (result.IsConfirmed) {
|
||||
await Permissions.RemoveGroupFromUser(_user, group);
|
||||
await Permissions.RemovePermission(_user, group.Name);
|
||||
_userGroups.Remove(group);
|
||||
StateHasChanged();
|
||||
|
||||
@@ -186,7 +187,7 @@
|
||||
}
|
||||
|
||||
private async Task AddPermission() {
|
||||
if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
|
||||
if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) {
|
||||
await NoEditPermissions();
|
||||
return;
|
||||
}
|
||||
@@ -200,8 +201,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
await Permissions.AddPermission(_user, _permissionToAdd);
|
||||
_user.Permissions.Add(await Permissions.GetPermission(_permissionToAdd, _user));
|
||||
_user.Permissions.Add(await Permissions.AddPermission(_user, _permissionToAdd));
|
||||
_permissionToAdd = "";
|
||||
|
||||
await Alerts.FireAsync(new SweetAlertOptions {
|
||||
@@ -213,7 +213,7 @@
|
||||
}
|
||||
|
||||
private async Task RemovePermission(Permission perm) {
|
||||
if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
|
||||
if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) {
|
||||
await NoEditPermissions();
|
||||
return;
|
||||
}
|
||||
@@ -227,7 +227,7 @@
|
||||
});
|
||||
|
||||
if (result.IsConfirmed) {
|
||||
await Permissions.RemovePermission(perm);
|
||||
await Permissions.RemovePermission(perm.User, perm.PermissionName);
|
||||
_user.Permissions.Remove(perm);
|
||||
StateHasChanged();
|
||||
|
||||
@@ -241,7 +241,7 @@
|
||||
}
|
||||
|
||||
private async void EditUser() {
|
||||
if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
|
||||
if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) {
|
||||
await NoEditPermissions();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@using HopFrame.Security.Authorization
|
||||
@using HopFrame.Database
|
||||
@using HopFrame.Security.Claims
|
||||
@using Microsoft.AspNetCore.Http
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
@using BlazorStrap.V5
|
||||
@using CurrieTechnologies.Razor.SweetAlert2
|
||||
@using HopFrame.Database.Models
|
||||
@using HopFrame.Database.Repositories
|
||||
@using HopFrame.Security.Claims
|
||||
@using HopFrame.Security.Services
|
||||
@using HopFrame.Web.Pages.Administration.Layout
|
||||
|
||||
<PageTitle>Groups</PageTitle>
|
||||
@@ -94,7 +94,8 @@
|
||||
</BSTBody>
|
||||
</BSTable>
|
||||
|
||||
@inject IPermissionService Permissions
|
||||
@inject IGroupRepository Groups
|
||||
@inject IPermissionRepository Permissions
|
||||
@inject ITokenContext Auth
|
||||
@inject SweetAlertService Alerts
|
||||
|
||||
@@ -110,23 +111,23 @@
|
||||
private GroupAddModal _groupAddModal;
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
_groups = await Permissions.GetPermissionGroups();
|
||||
_groups = await Groups.GetPermissionGroups();
|
||||
|
||||
_hasEditPrivileges = await Permissions.HasPermission(Security.AdminPermissions.EditGroup, Auth.User.Id);
|
||||
_hasDeletePrivileges = await Permissions.HasPermission(Security.AdminPermissions.DeleteGroup, Auth.User.Id);
|
||||
_hasEditPrivileges = await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditGroup);
|
||||
_hasDeletePrivileges = await Permissions.HasPermission(Auth.User, Security.AdminPermissions.DeleteGroup);
|
||||
}
|
||||
|
||||
private async Task Reload() {
|
||||
_groups = new List<PermissionGroup>();
|
||||
|
||||
_groups = await Permissions.GetPermissionGroups();
|
||||
_groups = await Groups.GetPermissionGroups();
|
||||
|
||||
OrderBy(_currentOrder, false);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task Search() {
|
||||
var groups = await Permissions.GetPermissionGroups();
|
||||
var groups = await Groups.GetPermissionGroups();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_searchText)) {
|
||||
groups = groups
|
||||
@@ -166,7 +167,7 @@
|
||||
});
|
||||
|
||||
if (result.IsConfirmed) {
|
||||
await Permissions.DeletePermissionGroup(group);
|
||||
await Groups.DeletePermissionGroup(group);
|
||||
await Reload();
|
||||
|
||||
await Alerts.FireAsync(new SweetAlertOptions {
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
@using CurrieTechnologies.Razor.SweetAlert2
|
||||
@using HopFrame.Database.Models
|
||||
@using HopFrame.Security.Claims
|
||||
@using HopFrame.Security.Services
|
||||
@using HopFrame.Web.Pages.Administration.Layout
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using HopFrame.Web.Components
|
||||
@using BlazorStrap.V5
|
||||
@using HopFrame.Database.Repositories
|
||||
@using HopFrame.Web.Components.Administration
|
||||
|
||||
<PageTitle>Users</PageTitle>
|
||||
@@ -95,8 +95,8 @@
|
||||
</BSTBody>
|
||||
</BSTable>
|
||||
|
||||
@inject IUserService UserService
|
||||
@inject IPermissionService PermissionsService
|
||||
@inject IUserRepository UserService
|
||||
@inject IPermissionRepository PermissionsService
|
||||
@inject SweetAlertService Alerts
|
||||
@inject ITokenContext Auth
|
||||
|
||||
@@ -119,12 +119,12 @@
|
||||
_users = await UserService.GetUsers();
|
||||
|
||||
foreach (var user in _users) {
|
||||
var groups = await PermissionsService.GetUserPermissionGroups(user);
|
||||
var groups = user.Permissions.Where(p => p.PermissionName.StartsWith("group.")).Select(p => p.Group).ToList();
|
||||
_userGroups.Add(user.Id, groups.LastOrDefault());
|
||||
}
|
||||
|
||||
_hasEditPrivileges = await PermissionsService.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id);
|
||||
_hasDeletePrivileges = await PermissionsService.HasPermission(Security.AdminPermissions.DeleteUser, Auth.User.Id);
|
||||
_hasEditPrivileges = await PermissionsService.HasPermission(Auth.User, Security.AdminPermissions.EditUser);
|
||||
_hasDeletePrivileges = await PermissionsService.HasPermission(Auth.User, Security.AdminPermissions.DeleteUser);
|
||||
}
|
||||
|
||||
private async Task Reload() {
|
||||
@@ -134,7 +134,7 @@
|
||||
_users = await UserService.GetUsers();
|
||||
|
||||
foreach (var user in _users) {
|
||||
var groups = await PermissionsService.GetUserPermissionGroups(user);
|
||||
var groups = user.Permissions.Where(p => p.PermissionName.StartsWith("group.")).Select(p => p.Group).ToList();
|
||||
_userGroups.Add(user.Id, groups.LastOrDefault());
|
||||
}
|
||||
|
||||
|
||||
@@ -12,14 +12,15 @@ namespace HopFrame.Web;
|
||||
public static class ServiceCollectionExtensions {
|
||||
public static IServiceCollection AddHopFrame<TDbContext>(this IServiceCollection services) where TDbContext : HopDbContextBase {
|
||||
services.AddHttpClient();
|
||||
services.AddScoped<IAuthService, AuthService<TDbContext>>();
|
||||
services.AddHopFrameRepositories<TDbContext>();
|
||||
services.AddScoped<IAuthService, AuthService>();
|
||||
services.AddTransient<AuthMiddleware>();
|
||||
|
||||
// Component library's
|
||||
services.AddSweetAlert2();
|
||||
services.AddBlazorStrap();
|
||||
|
||||
services.AddHopFrameAuthentication<TDbContext>();
|
||||
services.AddHopFrameAuthentication();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using HopFrame.Database.Models.Entries;
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Security.Models;
|
||||
|
||||
namespace HopFrame.Web.Services;
|
||||
@@ -8,6 +8,6 @@ public interface IAuthService {
|
||||
Task<bool> Login(UserLogin login);
|
||||
Task Logout();
|
||||
|
||||
Task<TokenEntry> RefreshLogin();
|
||||
Task<Token> RefreshLogin();
|
||||
Task<bool> IsLoggedIn();
|
||||
}
|
||||
@@ -1,47 +1,38 @@
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Database.Models.Entries;
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Database.Repositories;
|
||||
using HopFrame.Security.Authentication;
|
||||
using HopFrame.Security.Claims;
|
||||
using HopFrame.Security.Models;
|
||||
using HopFrame.Security.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Web.Services.Implementation;
|
||||
|
||||
internal class AuthService<TDbContext>(
|
||||
IUserService userService,
|
||||
internal class AuthService(
|
||||
IUserRepository userService,
|
||||
IHttpContextAccessor httpAccessor,
|
||||
TDbContext context)
|
||||
: IAuthService where TDbContext : HopDbContextBase {
|
||||
ITokenRepository tokens,
|
||||
ITokenContext context)
|
||||
: IAuthService {
|
||||
|
||||
public async Task Register(UserRegister register) {
|
||||
var user = await userService.AddUser(register);
|
||||
var user = await userService.AddUser(new User {
|
||||
Username = register.Username,
|
||||
Email = register.Email,
|
||||
Password = register.Password
|
||||
});
|
||||
|
||||
if (user is null) return;
|
||||
|
||||
var refreshToken = await tokens.CreateToken(Token.RefreshTokenType, user);
|
||||
var accessToken = await tokens.CreateToken(Token.AccessTokenType, user);
|
||||
|
||||
var refreshToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.RefreshTokenType,
|
||||
UserId = user.Id.ToString()
|
||||
};
|
||||
var accessToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.AccessTokenType,
|
||||
UserId = user.Id.ToString()
|
||||
};
|
||||
|
||||
context.Tokens.AddRange(refreshToken, accessToken);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Token, new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication<HopDbContextBase>.RefreshTokenTime,
|
||||
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Content.ToString(), new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication.RefreshTokenTime,
|
||||
HttpOnly = true,
|
||||
Secure = true
|
||||
});
|
||||
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication<TDbContext>.AccessTokenTime,
|
||||
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication.AccessTokenTime,
|
||||
HttpOnly = false,
|
||||
Secure = true
|
||||
});
|
||||
@@ -53,29 +44,16 @@ internal class AuthService<TDbContext>(
|
||||
if (user == null) return false;
|
||||
if (await userService.CheckUserPassword(user, login.Password) == false) return false;
|
||||
|
||||
var refreshToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.RefreshTokenType,
|
||||
UserId = user.Id.ToString()
|
||||
};
|
||||
var accessToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.AccessTokenType,
|
||||
UserId = user.Id.ToString()
|
||||
};
|
||||
var refreshToken = await tokens.CreateToken(Token.RefreshTokenType, user);
|
||||
var accessToken = await tokens.CreateToken(Token.AccessTokenType, user);
|
||||
|
||||
context.Tokens.AddRange(refreshToken, accessToken);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Token, new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication<HopDbContextBase>.RefreshTokenTime,
|
||||
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Content.ToString(), new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication.RefreshTokenTime,
|
||||
HttpOnly = true,
|
||||
Secure = true
|
||||
});
|
||||
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication<TDbContext>.AccessTokenTime,
|
||||
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication.AccessTokenTime,
|
||||
HttpOnly = false,
|
||||
Secure = true
|
||||
});
|
||||
@@ -84,67 +62,27 @@ internal class AuthService<TDbContext>(
|
||||
}
|
||||
|
||||
public async Task Logout() {
|
||||
var accessToken = httpAccessor.HttpContext?.Request.Cookies[ITokenContext.AccessTokenType];
|
||||
var refreshToken = httpAccessor.HttpContext?.Request.Cookies[ITokenContext.RefreshTokenType];
|
||||
|
||||
var tokenEntries = await context.Tokens.Where(token =>
|
||||
(token.Token == accessToken && token.Type == TokenEntry.AccessTokenType) ||
|
||||
(token.Token == refreshToken && token.Type == TokenEntry.RefreshTokenType))
|
||||
.ToArrayAsync();
|
||||
|
||||
context.Tokens.Remove(tokenEntries[0]);
|
||||
context.Tokens.Remove(tokenEntries[1]);
|
||||
await context.SaveChangesAsync();
|
||||
await tokens.DeleteUserTokens(context.User);
|
||||
|
||||
httpAccessor.HttpContext?.Response.Cookies.Delete(ITokenContext.RefreshTokenType);
|
||||
httpAccessor.HttpContext?.Response.Cookies.Delete(ITokenContext.AccessTokenType);
|
||||
}
|
||||
|
||||
public async Task<TokenEntry> RefreshLogin() {
|
||||
public async Task<Token> RefreshLogin() {
|
||||
var refreshToken = httpAccessor.HttpContext?.Request.Cookies[ITokenContext.RefreshTokenType];
|
||||
|
||||
if (string.IsNullOrWhiteSpace(refreshToken)) return null;
|
||||
|
||||
var token = await context.Tokens.SingleOrDefaultAsync(token => token.Token == refreshToken && token.Type == TokenEntry.RefreshTokenType);
|
||||
|
||||
if (token is null) return null;
|
||||
var token = await tokens.GetToken(refreshToken);
|
||||
|
||||
var oldAccessTokens = context.Tokens
|
||||
.AsEnumerable()
|
||||
.Where(old =>
|
||||
old.Type == TokenEntry.AccessTokenType &&
|
||||
old.UserId == token.UserId &&
|
||||
old.CreatedAt + HopFrameAuthentication<TDbContext>.AccessTokenTime < DateTime.Now)
|
||||
.ToList();
|
||||
if (oldAccessTokens.Count != 0)
|
||||
context.Tokens.RemoveRange(oldAccessTokens);
|
||||
if (token is null || token.Type != Token.RefreshTokenType) return null;
|
||||
|
||||
var oldRefreshTokens = context.Tokens
|
||||
.AsEnumerable()
|
||||
.Where(old =>
|
||||
old.Type == TokenEntry.RefreshTokenType &&
|
||||
old.UserId == token.UserId &&
|
||||
old.CreatedAt + HopFrameAuthentication<TDbContext>.RefreshTokenTime < DateTime.Now)
|
||||
.ToList();
|
||||
if (oldRefreshTokens.Count != 0)
|
||||
context.Tokens.RemoveRange(oldRefreshTokens);
|
||||
if (token.CreatedAt + HopFrameAuthentication.RefreshTokenTime < DateTime.Now) return null;
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
var accessToken = await tokens.CreateToken(Token.AccessTokenType, token.Owner);
|
||||
|
||||
if (token.CreatedAt + HopFrameAuthentication<TDbContext>.RefreshTokenTime < DateTime.Now) return null;
|
||||
|
||||
var accessToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.AccessTokenType,
|
||||
UserId = token.UserId
|
||||
};
|
||||
|
||||
await context.Tokens.AddAsync(accessToken);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication<TDbContext>.AccessTokenTime,
|
||||
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication.AccessTokenTime,
|
||||
HttpOnly = false,
|
||||
Secure = true
|
||||
});
|
||||
@@ -155,12 +93,13 @@ internal class AuthService<TDbContext>(
|
||||
public async Task<bool> IsLoggedIn() {
|
||||
var accessToken = httpAccessor.HttpContext?.Request.Cookies[ITokenContext.AccessTokenType];
|
||||
if (string.IsNullOrEmpty(accessToken)) return false;
|
||||
|
||||
var tokenEntry = await context.Tokens.SingleOrDefaultAsync(token => token.Token == accessToken);
|
||||
|
||||
var tokenEntry = await tokens.GetToken(accessToken);
|
||||
|
||||
if (tokenEntry is null) return false;
|
||||
if (tokenEntry.CreatedAt + HopFrameAuthentication<TDbContext>.AccessTokenTime < DateTime.Now) return false;
|
||||
if (!await context.Users.AnyAsync(user => user.Id == tokenEntry.UserId)) return false;
|
||||
if (tokenEntry.Type != Token.AccessTokenType) return false;
|
||||
if (tokenEntry.CreatedAt + HopFrameAuthentication.AccessTokenTime < DateTime.Now) return false;
|
||||
if (tokenEntry.Owner is null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,54 @@
|
||||
using HopFrame.Api.Logic;
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Security.Authorization;
|
||||
using HopFrame.Security.Claims;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RestApiTest.Models;
|
||||
|
||||
namespace RestApiTest.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("test")]
|
||||
public class TestController(ITokenContext userContext) : ControllerBase {
|
||||
public class TestController(ITokenContext userContext, DatabaseContext context) : ControllerBase {
|
||||
|
||||
[HttpGet("permissions"), Authorized]
|
||||
public ActionResult<IList<Permission>> Permissions() {
|
||||
return new ActionResult<IList<Permission>>(userContext.User.Permissions);
|
||||
}
|
||||
|
||||
[HttpGet("generate")]
|
||||
public async Task<ActionResult> GenerateData() {
|
||||
var employee = new Employee() {
|
||||
Name = "Max Mustermann"
|
||||
};
|
||||
|
||||
await context.AddAsync(employee);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var address = new Address() {
|
||||
City = "Musterstadt",
|
||||
Country = "Musterland",
|
||||
State = "Musterbundesland",
|
||||
ZipCode = 12345,
|
||||
AddressDetails = "Musterstraße 5",
|
||||
Employee = employee
|
||||
};
|
||||
|
||||
await context.AddAsync(address);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return LogicResult.Ok();
|
||||
}
|
||||
|
||||
[HttpGet("employees")]
|
||||
public async Task<ActionResult<IList<Employee>>> GetEmployees() {
|
||||
return LogicResult<IList<Employee>>.Ok(await context.Employees.Include(e => e.Address).ToListAsync());
|
||||
}
|
||||
|
||||
[HttpGet("addresses")]
|
||||
public async Task<ActionResult<IList<Address>>> GetAddresses() {
|
||||
return LogicResult<IList<Address>>.Ok(await context.Addresses.Include(e => e.Employee).ToListAsync());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,12 +1,25 @@
|
||||
using HopFrame.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RestApiTest.Models;
|
||||
|
||||
namespace RestApiTest;
|
||||
|
||||
public class DatabaseContext : HopDbContextBase {
|
||||
|
||||
public DbSet<Employee> Employees { get; set; }
|
||||
public DbSet<Address> Addresses { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
|
||||
optionsBuilder.UseSqlite("Data Source=C:\\Users\\Remote\\Documents\\Projekte\\HopFrame\\test\\RestApiTest\\bin\\Debug\\net8.0\\test.db;Mode=ReadWrite;");
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder) {
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<Employee>()
|
||||
.HasOne(e => e.Address)
|
||||
.WithOne(a => a.Employee);
|
||||
}
|
||||
}
|
||||
18
test/RestApiTest/Models/Address.cs
Normal file
18
test/RestApiTest/Models/Address.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace RestApiTest.Models;
|
||||
|
||||
public class Address {
|
||||
[ForeignKey("Employee")]
|
||||
public int AddressId { get; set; }
|
||||
public string AddressDetails { get; set; }
|
||||
public string City { get; set; }
|
||||
public int ZipCode { get; set; }
|
||||
public string State { get; set; }
|
||||
public string Country { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Employee Employee { get; set; }
|
||||
}
|
||||
8
test/RestApiTest/Models/Employee.cs
Normal file
8
test/RestApiTest/Models/Employee.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace RestApiTest.Models;
|
||||
|
||||
public class Employee {
|
||||
public int EmployeeId { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public virtual Address Address { get; set; }
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<Nullable>disable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user