3
\$\begingroup\$

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 (just a sketch).

Does it make sense? Would you personally go with this approach to structure your code?

asked Apr 1, 2016 at 17:56
\$\endgroup\$

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.