We’ve been kicking around some ideas of how to revamp user permissions in our application, and I was reflecting on how I’ve done it in the past. Previously, I worked on a client/server application where each user had a permission string that tracked all the rights they’d been granted, and global enumerations were used to identify which permissions and rights. I was explaining the approach to colleagues, and there was enough interest that I decided to recreate it.
Here’s the summary:
- Each user has a Base64-encoded permission string
- There’s an enumeration for the different modules; this identifies which byte in the permission string contains the rights
- There’s an enumeration for the different rights which can be granted for a given module; these are used in bitwise operations to check or modify a user’s rights to a module
Enumerations
public enum Modules
{
ModuleOne = 0,
ModuleTwo = 1,
ModuleThree = 2
}
public enum Permissions
{
Create = 1, // 0000 0001
Read = 2, // 0000 0010
Update = 4, // 0000 0100
Delete = 8, // 0000 1000
Execute = 16 // 0001 0000
}
Granting Permission
Granting a permission is a simple as specifying the module and permission to grant. The module is used to identify the correct byte, and then a bitwise OR
is performed to enable the corresponding permission bit.
string GrantPermission(string permissionString, Modules module, Permissions permission)
{
if (permissionString == null)
{
permissionString = string.Empty;
}
var bytes = Convert.FromBase64String(permissionString);
if (bytes.Length <= (int)module)
{
Array.Resize(ref bytes, (int)module + 1);
}
bytes[(int)module] |= (byte)permission;
return Convert.ToBase64String(bytes);
}
Checking Permission
Checking a permission is very similar to granting except that we AND
the permission mask. If the result isn’t zero, that means the user has the specified permission.
bool HasPermission(string permissionString, Modules module, Permissions permission)
{
if (string.IsNullOrWhiteSpace(permissionString))
{
return false;
}
var bytes = Convert.FromBase64String(permissionString);
if (permissionString.Length < (int)module)
{
return false;
}
var b = bytes[(int)module];
return (b & (byte)permission) != 0;
}
Revoking Permission
Revoking a permission is similar as well. This time, we’ll invert the bits on the permission mask and AND
it to the module permission byte so that all non-zero bits except the specified permission will remain the same.
string RevokePermission(string permissionString, Modules module, Permissions permission)
{
if (permissionString == null)
{
permissionString = string.Empty;
}
var bytes = Convert.FromBase64String(permissionString);
if (bytes.Length <= (int)module)
{
return permissionString;
}
bytes[(int)module] &= (byte)~permission;
return Convert.ToBase64String(bytes);
}
Conclusion
This was just a quick & dirty re-implementation of something from the past. I haven’t put this code through the wringer, so there may be some bugs, and you could probably make it more developer-friendly by extracting it to its own class or moving this logic into some nice extension methods. It’s an efficient way to store and check permissions for a collection of modules. Depending on your needs, it might be a good fit. I’m also wondering if this could be used in a hybrid system–for example, perhaps it would make sense for different applications or scopes to have their own permission string within a larger system.