Skip to content

Commit

Permalink
Merge pull request #225 from fluentcms/200-implement-resource-base-au…
Browse files Browse the repository at this point in the history
…thorization-instead-of-current-authorization-method

200 implement resource base authorization instead of current authorization method
  • Loading branch information
pournasserian authored Nov 25, 2023
2 parents f3e10e8 + f026eae commit 4534362
Show file tree
Hide file tree
Showing 35 changed files with 388 additions and 294 deletions.
19 changes: 0 additions & 19 deletions src/FluentCMS.Api/ApiApplicationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,4 @@ public class CurrentContext : ICurrentContext
public string UserName { get; set; } = string.Empty;
public bool IsSuperAdmin { get; set; }
public bool IsAuthenticated => !string.IsNullOrEmpty(UserName);

public bool IsInRole(Guid roleId)
{
if (IsSuperAdmin)
return true;

if (RoleIds == null || !RoleIds.Any())
return false;

return RoleIds.Any(x => x == roleId);
}

public bool IsInRole(IEnumerable<Guid> roleIds)
{
if (IsSuperAdmin)
return true;

return roleIds.Any(IsInRole);
}
}
2 changes: 0 additions & 2 deletions src/FluentCMS.Api/DefaultData/DefaultData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ public class DefaultData
{
public required Host Host { get; set; }
public required DefaultUser SuperAdmin { get; set; }
public required DefaultUser Admin { get; set; }
public required Role AdminRole { get; set; }
public required Site Site { get; set; }
public required List<Page> Pages { get; set; }

Expand Down
22 changes: 1 addition & 21 deletions src/FluentCMS.Api/DefaultData/DefaultDataLoaderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ public static void LoadInitialDataFrom(this IServiceProvider provider, string da
var siteService = scope.ServiceProvider.GetRequiredService<ISiteService>();
var pageService = scope.ServiceProvider.GetRequiredService<IPageService>();
var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
var roleService = scope.ServiceProvider.GetRequiredService<IRoleService>();
var appContext = scope.ServiceProvider.GetRequiredService<IApplicationContext>();

if (!hostService.IsInitialized().GetAwaiter().GetResult())
Expand All @@ -42,12 +41,6 @@ public static void LoadInitialDataFrom(this IServiceProvider provider, string da
Email = defaultData.SuperAdmin.Email
};

var adminUser = new User
{
UserName = defaultData.Admin.UserName,
Email = defaultData.Admin.Email
};

appContext.Current = new CurrentContext
{
UserName = superUser.UserName,
Expand All @@ -56,26 +49,13 @@ public static void LoadInitialDataFrom(this IServiceProvider provider, string da

// Default users creation
userService.Create(superUser, defaultData.SuperAdmin.Password).GetAwaiter().GetResult();
userService.Create(adminUser, defaultData.Admin.Password).GetAwaiter().GetResult();

// Host creation
hostService.Create(defaultData.Host).GetAwaiter().GetResult();

// Site creation
siteService.Create(defaultData.Site).GetAwaiter().GetResult();

// Admin Role creation
defaultData.AdminRole.SiteId = defaultData.Site.Id;
roleService.Create(defaultData.AdminRole).GetAwaiter().GetResult();

// Add admin role to admin user
adminUser.RoleIds = [defaultData.AdminRole.Id];
userService.Update(adminUser).GetAwaiter().GetResult();

// Updating site with admin role
defaultData.Site.AdminRoleIds = [defaultData.AdminRole.Id];
siteService.Update(defaultData.Site).GetAwaiter().GetResult();


// Pages creation: adding a few default pages
foreach (var page in defaultData.Pages)
{
Expand Down
7 changes: 1 addition & 6 deletions src/FluentCMS.Api/JwtServiceExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
using FluentCMS.Providers.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FluentCMS.Api;
namespace Microsoft.Extensions.DependencyInjection;

public static class JwtServiceExtensions
{
Expand Down
2 changes: 1 addition & 1 deletion src/FluentCMS.Api/Models/Host/HostResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ public class HostResponse
public string LastUpdatedBy { get; set; } = string.Empty; // UserName
public DateTime LastUpdatedAt { get; set; }

public List<Guid> SuperUserIds { get; set; } = [];
public List<string> SuperUsers { get; set; } = [];
}
2 changes: 1 addition & 1 deletion src/FluentCMS.Api/Models/Host/HostUpdateRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

public class HostUpdateRequest
{
public List<Guid> SuperUserIds { get; set; } = [];
public List<string> SuperUsers { get; set; } = [];
}
1 change: 0 additions & 1 deletion src/FluentCMS.Api/Models/Sites/SiteResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,5 @@ public class SiteResponse
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public List<string> Urls { get; set; } = [];
public Guid RoleId { get; set; }
public IEnumerable<PageResponse> Pages { get; set; } = [];
}
6 changes: 6 additions & 0 deletions src/FluentCMS.Entities/IAuthorizeEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace FluentCMS.Entities;

public interface IAuthorizeEntity : IEntity
{
public Guid SiteId { get; set; }
}
4 changes: 1 addition & 3 deletions src/FluentCMS.Entities/Page.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
namespace FluentCMS.Entities;

public class Page : AuditEntity
public class Page : AuditEntity, IAuthorizeEntity
{
public Guid SiteId { get; set; }
public required string Title { get; set; }
public Guid? ParentId { get; set; }
public int Order { get; set; }
public required string Path { get; set; }
public IEnumerable<Guid> AdminRoleIds { get; set; } = new List<Guid>();
public IEnumerable<Guid> ViewRoleIds { get; set; } = new List<Guid>();
}
10 changes: 10 additions & 0 deletions src/FluentCMS.Entities/Permission.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace FluentCMS.Entities;

public class Permission : AuditEntity
{
public Guid SiteId { get; set; }
public Guid RoleId { get; set; }
public Guid EntityId { get; set; }
public string EntityType { get; set; } = default!;
public string Policy { get; set; } = default!;
}
11 changes: 10 additions & 1 deletion src/FluentCMS.Entities/Role.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
namespace FluentCMS.Entities;

public class Role : AuditEntity
public class Role : AuditEntity, IAuthorizeEntity
{
public Guid SiteId { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public RoleType Type { get; set; }
}

public enum RoleType
{
Normal = 0,
Authenticated = 1,
Guest = 2,
All = 3
}
8 changes: 4 additions & 4 deletions src/FluentCMS.Entities/Site.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
namespace FluentCMS.Entities;

public class Site : AuditEntity
public class Site : AuditEntity, IAuthorizeEntity
{
public required string Name { get; set; }
public string? Description { get; set; }
public required string Name { get; set; }
public string? Description { get; set; }
public List<string> Urls { get; set; } = [];
public List<Guid> AdminRoleIds { get; set; } = [];
public Guid SiteId { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using FluentCMS.Entities;

namespace FluentCMS.Repositories;

public interface IPermissionRepository : IGenericRepository<Permission>
{
Task<IEnumerable<Permission>> GetPermissions(Guid siteId, IEnumerable<Guid> roleIds, CancellationToken cancellationToken = default);
}
1 change: 0 additions & 1 deletion src/FluentCMS.Repositories/Abstractions/ISiteRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@ namespace FluentCMS.Repositories;
public interface ISiteRepository : IGenericRepository<Site>
{
Task<Site?> GetByUrl(string url, CancellationToken cancellationToken = default);
Task<bool> CheckUrls(IList<string> urls, CancellationToken cancellationToken = default);
}
5 changes: 0 additions & 5 deletions src/FluentCMS.Repositories/LiteDb/SiteRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ public SiteRepository(LiteDbContext dbContext, IApplicationContext applicationCo
{
}

public Task<bool> CheckUrls(IList<string> urls, CancellationToken cancellationToken = default)
{
return Task.FromResult(false);
}

public async Task<Site?> GetByUrl(string url, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
Expand Down
6 changes: 3 additions & 3 deletions src/FluentCMS.Repositories/MongoDB/GenericRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public virtual async Task<IEnumerable<TEntity>> GetByIds(IEnumerable<Guid> ids,
if (entity is IAuditEntity audit)
{
audit.CreatedAt = DateTime.UtcNow;
audit.CreatedAt = DateTime.UtcNow;
audit.CreatedBy = AppContext.Current.UserName;
audit.LastUpdatedAt = DateTime.UtcNow;
audit.LastUpdatedBy = AppContext.Current.UserName;
}
Expand All @@ -87,7 +87,7 @@ public virtual async Task<IEnumerable<TEntity>> CreateMany(IEnumerable<TEntity>
foreach (var audit in entities.Cast<IAuditEntity>())
{
audit.CreatedAt = DateTime.UtcNow;
audit.CreatedAt = DateTime.UtcNow;
audit.CreatedBy = AppContext.Current.UserName;
audit.LastUpdatedAt = DateTime.UtcNow;
audit.LastUpdatedBy = AppContext.Current.UserName;
}
Expand All @@ -105,7 +105,7 @@ public virtual async Task<IEnumerable<TEntity>> CreateMany(IEnumerable<TEntity>
{
audit.LastUpdatedAt = DateTime.UtcNow;
audit.LastUpdatedBy = AppContext.Current.UserName;
}
}

var idFilter = Builders<TEntity>.Filter.Eq(x => x.Id, entity.Id);

Expand Down
23 changes: 23 additions & 0 deletions src/FluentCMS.Repositories/MongoDB/PermissionRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using FluentCMS.Entities;
using MongoDB.Driver;

namespace FluentCMS.Repositories.MongoDB;

public class PermissionRepository : GenericRepository<Permission>, IPermissionRepository
{
public PermissionRepository(IMongoDBContext mongoDbContext, IApplicationContext applicationContext) : base(mongoDbContext, applicationContext)
{
}

public async Task<IEnumerable<Permission>> GetPermissions(Guid siteId, IEnumerable<Guid> roleIds, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();

var filter = Builders<Permission>.Filter.Eq(x => x.SiteId, siteId);
filter &= Builders<Permission>.Filter.In(x => x.RoleId, roleIds);

var findResult = await Collection.FindAsync(filter, null, cancellationToken);

return findResult.ToEnumerable(cancellationToken);
}
}
1 change: 1 addition & 0 deletions src/FluentCMS.Repositories/MongoDB/ServiceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public static IServiceCollection AddMongoDbRepositories(this IServiceCollection
services.AddScoped<IHostRepository, HostRepository>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IRoleRepository, RoleRepository>();
services.AddScoped<IPermissionRepository, PermissionRepository>();

return services;
}
Expand Down
15 changes: 0 additions & 15 deletions src/FluentCMS.Repositories/MongoDB/SiteRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,6 @@ namespace FluentCMS.Repositories.MongoDB;

public class SiteRepository(IMongoDBContext mongoDbContext, IApplicationContext applicationContext) : GenericRepository<Site>(mongoDbContext, applicationContext), ISiteRepository
{
public async Task<bool> CheckUrls(IList<string> urls, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();

var builder = Builders<Site>.Filter;
var filters = new List<FilterDefinition<Site>>();

foreach (var url in urls)
filters.Add(builder.AnyEq(x => x.Urls, url));

var findResult = await Collection.FindAsync(Builders<Site>.Filter.Or(filters), null, cancellationToken);

return findResult.Any(cancellationToken);
}

public async Task<Site?> GetByUrl(string url, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
Expand Down
83 changes: 83 additions & 0 deletions src/FluentCMS.Services/AuthorizationProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using FluentCMS.Entities;
using FluentCMS.Repositories;

namespace FluentCMS.Services;

public interface IAuthorizationProvider
{
bool Authorize(Site site, IEnumerable<string> policyNames);
bool IsSuperAdmin();
Task<Permission> Create<T>(T entity, string policyName, CancellationToken cancellationToken = default) where T : IAuthorizeEntity;
}

public class AuthorizationProvider(
IPermissionRepository permissionRepository,
IApplicationContext applicationContext,
IRoleRepository roleRepository) : IAuthorizationProvider
{
private readonly IEnumerable<Guid> _userRoleIds = applicationContext.Current.RoleIds;
private IEnumerable<Permission> _permissions = null!;

private async Task Load(Guid siteId, CancellationToken cancellationToken = default)
{
if (_permissions != null)
return;

_permissions = await permissionRepository.GetPermissions(siteId, _userRoleIds, cancellationToken);
}

public bool IsSuperAdmin()
{
// super admins have access to everything
return applicationContext.Current.IsSuperAdmin;
}

public bool Authorize(Site site, IEnumerable<string> policyNames)
{
ArgumentNullException.ThrowIfNull(site);
ArgumentNullException.ThrowIfNull(policyNames);

// super admins have access to everything
if (applicationContext.Current.IsSuperAdmin)
return true;

Load(site.Id).Wait();

var entityType = typeof(Site).Name;
var userRoleIds = applicationContext.Current.RoleIds;

if (_permissions.Any(x => x.Id == site.Id && x.EntityType == typeof(Site).Name && policyNames.Contains(x.Policy)))
return true;

return false;
}

public async Task<Permission> Create<T>(T entity, string policyName, CancellationToken cancellationToken = default) where T : IAuthorizeEntity
{
ArgumentNullException.ThrowIfNull(entity);

var siteId = entity.GetType() == typeof(Site) ? entity.Id : entity.SiteId;

var role = new Role
{
SiteId = siteId,
Name = policyName,
Description = "Default " + policyName
};

_ = await roleRepository.Create(role, cancellationToken) ??
throw new AppException(ExceptionCodes.RoleUnableToCreate);

var permission = new Permission
{
SiteId = siteId,
EntityType = typeof(T).Name,
Policy = policyName,
EntityId = entity.Id,
RoleId = role.Id
};

return await permissionRepository.Create(permission, cancellationToken) ??
throw new AppException(ExceptionCodes.PermissionUnableToCreate);
}
}
22 changes: 0 additions & 22 deletions src/FluentCMS.Services/BaseService.cs

This file was deleted.

Loading

0 comments on commit 4534362

Please sign in to comment.