9

What is the best design for this scenario?
I have different Object types: User, Channel, MessageBox, UserGroup, etc.
User and Channel can have permission on other objects. For example User has the following enum defined as its permissions for MessageBox:

CanRead,
CanWrite,
CanDelete,
...

Other enums are defined for User as the owner of other object types.

Also, Channel has different enum values on these objects. For example, consider Channel as the owner and MessageBox as the object:

CanDispath
CanRetrieve
...

All the permissions are saved and retrieved from a specific table in database using bitwise comparison:

OwnerID........OwnerType........ObjectID........ObjectType........AccessLevel  
  1              User              10           MessageBox            38     
  5             Channel            12           MessageBox            18  

Now in code behind, What's the best way to implement permission classes?

1- Define PermissionManager, UserPermissionManager, ChannelPermissionManager classes separately from each other. Other classes just call PermissionManager like:

if (new PermissionManager.HasAccess(CurrentUser,  
                                    CurrentMessageBox, 
                                    UserPermissions.CanReadMessages))  

Then PermissionManager decides what class this is related to based on the OwnerType (UserPermissionManager or ChannelPermissionManager) and calls its HasAccess method. This way, PermissionManager.HasAccess is always being called and I think it can make the code more maintainable and extensible. This is my preferred solution but since PermissionManager, UserPermissionManager and ChannelPermissionManager refer to the same context, I think there should be a hierarchy or possibly an interface so these 3 classes become more integrated. But I don't know how to relate them together.

2- Define IPermissionManager interface and implement UserPermissionManager and ChannelPermissionManager from it. Add PermissionManagerTypes enum. Create a factory class and call Managers like:

IPermissionManager UserManager =   
    PermissionFactory.Create(PermissionsManagerTypes.User);
if (UserManager.HasAccess(CurrentUser,  
                          CurrentMessageBox, 
                          UserPermissions.CanReadMessages))  

This is a kind of failed try to relate classes together. But I thought It'd be good to mention it here to let you know what I'm trying to achieve.

P.S. I cannot define classes as static since they need to have a private variable of type ObjectContext (Entity Framework).

Is there a better solution to achieve this?
Thank you and Apologies for the very lengthy question.

Kamyar
  • 18,639
  • 9
  • 97
  • 171

2 Answers2

3

Well, this is pretty hard to answer. You could try to create a base interface IPermission;

interface IPermission<TOwner>
{
}

Then you implement this interface for the types you want to be able to own a permission.

class UserPermission : IPermission<User>
{
    public UserPermission(CustomerPermissionType type)
    {
        // Store the type
    }
}

class ChannelPermission : IPermission<Channel>
{
    public ChannelPermission (ChannelPermissionType type)
    {
        // Store the type
    }
}

Now you need an interface that provides permissions for specific objects.

interface IPermissionProvider
{
    bool HasPermission<TOwner>(IPermission<TOwner> permission, TOwner owner, object target);
}

At this point you have the basic functionality to query for permissions. The problem is how to manage the different handling for User and Channel permissions. You could implement something like this:

class PermissionDispatcher : IPermissionProvider
{
    public void RegisterPermissionProvider<TOwner>(IPermissionProvider     permissionProvider)
    {
        // Store it somewhere
    }

    public IPermissionProvider GetPermissionProvider<TOwner>()
    {
        // Look up a permission provider that is registered for the specified type TOwner and return it.
    }

    bool IPermissionProvider.HasPermission<TOwner>(IPermission<TOwner> permission,     TOwner owner, object target)
    {
        IPermissionProvider  permissionProvider = GetPermissionProvider<TOwner>();
        return permissionProvider .HasPermission<TOwner>(permission, owner, target);
    }
}

The last step would then be to create specific implementations of IPermissionProvider for User and Channel and register them to the PermissionDispatcher at the startup of your application / service.

The usage would be as simple as this:

void foo()
{
    IPermissionProvider permissionProvider = ... the dispatcher, could be a singleton ...;
    User user = ...;
    MessageBox messageBox = ...;
    UserPermission userCanReadPermission = new UserPermission(UserPermissionType.CanRead);
    bool hasUserCanReadPermission = permissionProvider.HasPermission(userCanReadPermission, user, messageBox);

}

Something like this will be the only way to solve this without taking a dependency on permission handling in your domain types. Nevertheless I'm absolutely sure this is no perfect solution.

Florian Greinacher
  • 14,478
  • 1
  • 35
  • 53
  • Very inspiring... I'll wait up to see if a better solution is proposed. If nothing shows up, I'll mark yours as accepted. Thanks. – Kamyar Jun 06 '11 at 11:55
  • Thank you. If you find a combination of your, BobTurbos and my solution works best do not hesitate to combine them to a new answer and accept it! – Florian Greinacher Jun 06 '11 at 11:57
1

I put no thought into this at all, but:

interface HasPermissions {
   getPermissionsFor(object)
}

User implements HasPermissions

Channel implements HasPermissions

etc..


User user = new User();

if user.getPermissionsFor(object).contains(canRead)
BobTurbo
  • 11
  • 1
  • 13
    `I put no thought into this at all` - that's fantastic first words for an answer – Snowbear Jun 06 '11 at 10:01
  • Thanks Bob. However, I think it's better if I separate permission class from the user class and isolate it in its own class. – Kamyar Jun 06 '11 at 10:04
  • As far as I can see, the User/Channel/etc's permissions are owned by them and hence the expert pattern says they should be the ones to determine if they can access things as they have the information available to perform the action. – BobTurbo Jun 06 '11 at 10:12
  • I think it's better if I use strategy pattern and separate `Permission` behavior from the object itself than to implement permission directly in each class. – Kamyar Jun 06 '11 at 10:19
  • What happens when you have a channel in state = crazy and you want its state to override its permissions? Maybe that situation won't occur though :) – BobTurbo Jun 06 '11 at 10:20
  • I think you got me wrong. I didn't mean "I'm using strategy right now" I meant "Maybe it's better if I use strategy pattern with a method like `CheckStatus` and implement it for `Channel`, `User`, etc – Kamyar Jun 06 '11 at 10:29
  • Also, I couldn't understand what would be the problem in my proposed solutions if a channel is in state = crazy. – Kamyar Jun 06 '11 at 10:30
  • If the channel is in state = crazy (as in, it has some property like channel.crazy == true), and you want to temporarily remove all of its permissions during this time, using your method, your permissions classes will have to look into the channel class and inspect its property, which is a dependency issue. Also, Microsoft themselves appears to implement roles and so on in a similar way to that which I suggested:if (User.IsInRole("members")) { buttonMembersArea.Visible = True; } From http://msdn.microsoft.com/en-us/library/5k850zwb.aspx#Y1991 – BobTurbo Jun 06 '11 at 10:33
  • The idea behind the expert pattern (which is how you usually want to decide who does what), is that the object with the knowledge to perform a particular method, does so. This reduces dependencies. – BobTurbo Jun 06 '11 at 10:35
  • @BobTurbo: Maybe I'm still missing something. But I still do not see any conflicts with the solutions I have stated. About the expert pattern, it seems very interesting. I should study more since I have to make sure the best approach is taken for extensibility and right now, I think solution 1 is the best approach considering extensibility. However, thank you for your guides. – Kamyar Jun 06 '11 at 10:46