UserId
type described here here. GitHub repository (just a sketch).
UserId
type described here. GitHub repository (just a sketch).
UserId
type described here. GitHub repository (just a sketch).
UserId
type described here. GitHub repository (just a sketch).
UserId
type described here. GitHub repository.
UserId
type described here. GitHub repository (just a sketch).
Multitenant app authorization
Role based security does not work for me well in a multitenant app, as I need to query DB to decide on permission allowance. Here is the solution I came to.
All implementations of this interface will be auto registered in IoC container (note covariance/contravariance support):
public interface IResourceAuthorizer<out TResource, in TAccess>
: IResourceAuthorizer where TAccess : Access
{
bool Grants(int resourceId, UserId userId);
}
Where marker interface IResourceAuthorizer
is:
public interface IResourceAuthorizer {}
and Access
is:
public abstract class Access {}
public abstract class Read : Access {}
public abstract class Write : Access {}
public abstract class Create : Write {}
public abstract class Update : Write {}
public abstract class Delete : Write {}
It can be easily extended with Execute
, etc.
Possible implementations might look like this (demo code, some database querying logic is supposed to be here):
public class Company {} // some resource to authorize access to
public class Corporation : Company {}
public class CorporationAuthorizer :
IResourceAuthorizer<Corporation, Access>
{
public bool Grants(int resourceId, UserId userId) =>
resourceId % 2 != 0;
}
or
public class CompanyReadAuthorizer :
IResourceAuthorizer<Company, Read>
{
public bool Grants(int resourceId, UserId userId) =>
true;
}
public class CompanyCreateUpdateAuthorizer :
IResourceAuthorizer<Company, Create>
IResourceAuthorizer<Company, Update>
{
public bool Grants(int resourceId, UserId userId) =>
userId.Authenticated;
}
The following interface and its implementation will be used for permission testing after registering them in IoC container:
public interface IAuthorizer
{
bool Grants<TResource, TAccess>(int resourceId, UserId userId)
where TAccess : Access;
}
public class Authorizer : IAuthorizer
{
public Authorizer(IEnumerable<IResourceAuthorizer> authorizers)
{
Authorizers = authorizers;
}
IEnumerable<IResourceAuthorizer> Authorizers { get; }
public bool Grants<TResource, TAccess>(int resourceId, UserId userId)
where TAccess : Access
{
var authorizer = Authorizers
.OfType<IResourceAuthorizer<TResource, TAccess>>()
.SingleOrDefault();
if (authorizer == null)
throw new NotImplementedException();
return authorizer.Grants(resourceId, userId);
}
}
There are some extensions to help with consuming of this component:
public static class AuthorizerExtensions
{
public static bool Grants<TResource, TAccess>(
this IAuthorizer authorizer)
where TAccess : Access =>
authorizer.Grants<TResource, TAccess>(0, UserId.Current);
public static bool Grants<TResource, TAccess>(
this IAuthorizer authorizer, UserId userId)
where TAccess : Access =>
authorizer.Grants<TResource, TAccess>(0, userId);
public static bool Grants<TResource, TAccess>(
this IAuthorizer authorizer, int resourceId)
where TAccess : Access =>
authorizer.Grants<TResource, TAccess>(resourceId, UserId.Current);
}
public static class AuthorizerGuards
{
public static void Authorize<TResource, TAccess>(
this IAuthorizer authorizer)
where TAccess : Access
{
if (!authorizer.Grants<TResource, TAccess>())
throw new UnauthorizedAccessException();
}
public static void Authorize<TResource, TAccess>(
this IAuthorizer authorizer, UserId userId)
where TAccess : Access
{
if (!authorizer.Grants<TResource, TAccess>(userId))
throw new UnauthorizedAccessException();
}
public static void Authorize<TResource, TAccess>(
this IAuthorizer authorizer, int resourceId)
where TAccess : Access
{
if (!authorizer.Grants<TResource, TAccess>(resourceId))
throw new UnauthorizedAccessException();
}
public static void Authorize<TResource, TAccess>(
this IAuthorizer authorizer, int resourceId, UserId userId)
where TAccess : Access
{
if (!authorizer.Grants<TResource, TAccess>(resourceId, userId))
throw new UnauthorizedAccessException();
}
}
OK, now we can use it:
class CompanyController : Controller
{
public CompanyController(IAuthorizer authorizer)
{
Authorizer = authorizer;
}
IAuthorizer Authorizer { get; }
public void Delete(int id)
{
Authorizer.Authorize<Company, Delete>(id);
// ...
}
}
UserId
type described here. GitHub repository.
Does it make sense? Would you personally go with this approach to structure your code?