Skip to main content
Code Review

Return to Question

Tweeted twitter.com/StackCodeReview/status/1098552920411385857
replaced http://codereview.stackexchange.com/ with https://codereview.stackexchange.com/
Source Link

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).

added 16 characters in body
Source Link
Dmitry Nogin
  • 6.1k
  • 3
  • 21
  • 40

UserId type described here. GitHub repository (just a sketch).

UserId type described here. GitHub repository.

UserId type described here. GitHub repository (just a sketch).

Source Link
Dmitry Nogin
  • 6.1k
  • 3
  • 21
  • 40

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?

lang-cs

AltStyle によって変換されたページ (->オリジナル) /