Merge pull request #6 from leonhoppe/feature/relations

Feature/relations
This commit is contained in:
leonhoppe
2024-09-28 12:18:58 +02:00
committed by GitHub
50 changed files with 733 additions and 781 deletions

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
namespace HopFrame.Security;
namespace HopFrame.Database;
public static class EncryptionManager {

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -1,4 +1,4 @@
namespace HopFrame.Security.Authorization;
namespace HopFrame.Database;
public static class PermissionValidator {

View File

@@ -0,0 +1,21 @@
using HopFrame.Database.Models;
namespace HopFrame.Database.Repositories;
public interface IGroupRepository {
Task<IList<PermissionGroup>> GetPermissionGroups();
Task<IList<PermissionGroup>> GetDefaultGroups();
Task<IList<PermissionGroup>> GetUserGroups(User user);
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);
}

View 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);
}

View 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);
}

View File

@@ -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,13 +11,8 @@ public interface IUserService {
Task<User> GetUserByUsername(string username);
Task<User> AddUser(UserRegister user);
/// <summary>
/// IMPORTANT:<br/>
/// This function does not add or remove any permissions to the user.
/// For that please use <see cref="IPermissionService"/>
/// </summary>
Task<User> AddUser(User user);
Task UpdateUser(User user);
Task DeleteUser(User user);

View File

@@ -0,0 +1,79 @@
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 Task<IList<PermissionGroup>> GetUserGroups(User user) {
return Task.FromResult((IList<PermissionGroup>) context.Groups
.Include(g => g.Permissions)
.AsEnumerable()
.Where(g => user.Permissions.Any(p => p.PermissionName == g.Name))
.ToList());
}
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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,113 @@
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)
.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 ?? new List<Permission>(),
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,
GrantedAt = DateTime.Now
});
}
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();
}
}

View 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;
}
}

View File

@@ -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();

View File

@@ -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;

View File

@@ -1,3 +1,4 @@
using HopFrame.Database;
using HopFrame.Security.Claims;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;

View File

@@ -20,5 +20,5 @@ public interface ITokenContext {
/// <summary>
/// The access token the user provided
/// </summary>
Guid AccessToken { get; }
Token AccessToken { get; }
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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;
}
@@ -176,7 +177,8 @@
}
_group.Permissions.Add(new Permission {
PermissionName = _permissionToAdd
PermissionName = _permissionToAdd,
GrantedAt = DateTime.Now
});
_permissionToAdd = null;
@@ -184,8 +186,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 +203,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 +220,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 +240,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 +256,12 @@
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(new PermissionGroup {
Description = _group.Description,
IsDefaultGroup = _group.IsDefaultGroup,
Permissions = _group.Permissions,
Name = "group." + _group.GroupName
});
if (ReloadPage is not null)
await ReloadPage.Invoke();

View File

@@ -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);

View File

@@ -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">
@@ -57,7 +57,7 @@
<option selected>Select group</option>
@foreach (var group in _allGroups) {
@if (_userGroups.All(g => g.Name != group.Name)) {
@if (_userGroups?.All(g => g.Name != group.Name) == true) {
<option value="@group.Name">@group.Name.Replace("group.", "")</option>
}
}
@@ -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 = await Groups.GetUserGroups(user);
_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;
}

View File

@@ -1,4 +1,4 @@
@using HopFrame.Security.Authorization
@using HopFrame.Database
@using HopFrame.Security.Claims
@using Microsoft.AspNetCore.Http

View File

@@ -61,7 +61,7 @@
_hasError = true;
return;
}
Navigator.NavigateTo(string.IsNullOrEmpty(RedirectAfter) ? DefaultRedirect : RedirectAfter, true);
}
}

View File

@@ -6,7 +6,7 @@
.field-wrapper {
margin-top: 25vh;
min-width: 30vw;
min-width: 500px;
padding: 30px;
border: 2px solid #ced4da;

View File

@@ -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 {

View File

@@ -20,7 +20,7 @@
<BSNavbarToggle/>
</Toggler>
<Content>
<BSNav MarginEnd="Margins.Auto" MarginBottom="Margins.Small" Class="mb-lg-0">
<BSNav MarginEnd="Margins.Auto" Class="mb-lg-0">
<BSNavItem IsActive="IsDashboardActive()" OnClick="NavigateToDashboard">Dashboard</BSNavItem>
@foreach (var nav in Subpages) {

View File

@@ -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,9 @@
</BSTBody>
</BSTable>
@inject IUserService UserService
@inject IPermissionService PermissionsService
@inject IUserRepository UserService
@inject IPermissionRepository PermissionsService
@inject IGroupRepository Groups
@inject SweetAlertService Alerts
@inject ITokenContext Auth
@@ -119,12 +120,12 @@
_users = await UserService.GetUsers();
foreach (var user in _users) {
var groups = await PermissionsService.GetUserPermissionGroups(user);
var groups = await Groups.GetUserGroups(user);
_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 +135,7 @@
_users = await UserService.GetUsers();
foreach (var user in _users) {
var groups = await PermissionsService.GetUserPermissionGroups(user);
var groups = await Groups.GetUserGroups(user);
_userGroups.Add(user.Id, groups.LastOrDefault());
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View 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; }
}

View 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; }
}

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<Nullable>disable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>