Solution for lots of flags

Hi. When I need to have rights management in my programs, I usually make an enum "Rights" as [Flags] type long and do like Invalid = -1, None = 0, AddGame = 1, DelGame = 2, LaunchGame = 4, EditGame = 8 ..etc I like because it's simple bitwise and I made an extension method so I can just do like function DeleteGame(Game game) { if (!ActiveUser?.Rights.HasPermission(Rights.DeleteGame) ?? false) return; However... I'm doing a big complex app and it's gonna have more rights than I have bits in a long. What is a good way to handle it at that point? I figured I could do enums like Rights_Context1 Rights_Context2 ..etc then I was trying to figure out how to store them in the user. I figured maybe public List<Enum> Rights then load them all into there, but then how do I check them? Loop and check what type each entry is then compare? That seems awkward. Is there any easy solution for having large amounts of flags in an enum? I assume I need a bitwise enum because I want to store the data in the db, and a bigint seemed the easiest way. I suppose I could do a permissions related table then make single entries per flag but that seems very inefficient.
7 Replies
Jimmacle
Jimmacle5d ago
if you have a large amount of flags you shouldn't use an enum, because you're fundamentally limited by the largest integer type you can use as the enum type i would use a permissions table, databases are designed for that type of layout it might seem more inefficient but it's also less likely to break by accident and it would only be a problem if you actually run into performance issues because of it
Lupusregina Beta
Lupusregina BetaOP5d ago
So like when I do a permission check, just have a method that does like SELECT index FROM permissions WHERE uid = ?uid AND permcode = ?perm Then pass the user Guid and maybe a string like "game.delete" then if a row is returned, I know they're authorized else deny? Roundtripping to MySQL that much won't be an issue? Well, it's not like I'm making some high use app anyway lol so I doubt it'd be an issue. Just curious. Hmm I could keep a cache in the perm manager and when an admin changes ppls perms, flush cache and/or flush it every X minutes that may reduce load.
Jimmacle
Jimmacle5d ago
do you necessarily need to or can you cache permissions for some amount of time? you can just load them into a hashset and refresh it if the permissions are changed
Lupusregina Beta
Lupusregina BetaOP5d ago
Thanks, appreciate the advice! I have a followup question. I'm working on implementing this. I have a static Perm class now, with a private Dictionary<Guid, HashSet<string>> Permissions. I have an Init method that populates the dict with the user's ID's then all the nodes to which they have permission. Then I have:
public static bool Has(string node, Guid id = default(Guid)) {
if (string.IsNullOrEmpty(node)) return false;
if (id == Guid.Empty) {
if (cfg.LoggedInUser == Guid.Empty) return false;
id = cfg.LoggedInUser;
}
if (!Permissions.ContainsKey(id)) return false;

return Permissions[id].Contains("administrator") || Permissions[id].Contains(node).ToLower().Trim();
}
public static bool Has(string node, Guid id = default(Guid)) {
if (string.IsNullOrEmpty(node)) return false;
if (id == Guid.Empty) {
if (cfg.LoggedInUser == Guid.Empty) return false;
id = cfg.LoggedInUser;
}
if (!Permissions.ContainsKey(id)) return false;

return Permissions[id].Contains("administrator") || Permissions[id].Contains(node).ToLower().Trim();
}
But I'd like to support inheritance. Like If I have nodes like:
Game.View
Game.Edit
Game.Delete
Game.Launch
Game.View
Game.Edit
Game.Delete
Game.Launch
If I'm checking for Game.View, then if they have edit, delete or launch it stands to reason that view is a given. What would be a good way to handle that?
public static class Perm {
private static Dictionary<Guid, HashSet<string>> Permissions = new Dictionary<Guid, HashSet<string>>();
private static Dictionary<string, HashSet<string>> Inheritance = new Dictionary<string, HashSet<string>>();

public static void Init() {
foreach (User user in DataManager.GetUsers()) {
Permissions[user.Id] = DataManager.GetUserPermissionSet(user.Id);
}
Inheritance = DataManager.GetInheritanceSet();
}

public static bool Has(string node, Guid id = default(Guid)) {
if (string.IsNullOrEmpty(node)) return false;
node = node.ToLower().Trim();

if (id == Guid.Empty) {
if (cfg.LoggedInUser == Guid.Empty) return false;
id = cfg.LoggedInUser;
}

if (!Permissions.ContainsKey(id)) return false;
if (Permissions[id].Contains("administrator") || Permissions[id].Contains(node)) return true;
if (!Inheritance.ContainsKey(node)) return false;

return Inheritance[node].Any(x => Permissions[id].Contains(x));
}
}
public static class Perm {
private static Dictionary<Guid, HashSet<string>> Permissions = new Dictionary<Guid, HashSet<string>>();
private static Dictionary<string, HashSet<string>> Inheritance = new Dictionary<string, HashSet<string>>();

public static void Init() {
foreach (User user in DataManager.GetUsers()) {
Permissions[user.Id] = DataManager.GetUserPermissionSet(user.Id);
}
Inheritance = DataManager.GetInheritanceSet();
}

public static bool Has(string node, Guid id = default(Guid)) {
if (string.IsNullOrEmpty(node)) return false;
node = node.ToLower().Trim();

if (id == Guid.Empty) {
if (cfg.LoggedInUser == Guid.Empty) return false;
id = cfg.LoggedInUser;
}

if (!Permissions.ContainsKey(id)) return false;
if (Permissions[id].Contains("administrator") || Permissions[id].Contains(node)) return true;
if (!Inheritance.ContainsKey(node)) return false;

return Inheritance[node].Any(x => Permissions[id].Contains(x));
}
}
GetInheritanceSet returns a HashSet with all the nodes that would grant the key node, like
Dictionary<string, HashSet<string>> {
{ "game.view", new HashSet<string>() {
"game.edit",
"game.delete",
"game.launch"
}
},
{ "game.edit", new HashSet<string>() {
"game.delete"
}
}
};
Dictionary<string, HashSet<string>> {
{ "game.view", new HashSet<string>() {
"game.edit",
"game.delete",
"game.launch"
}
},
{ "game.edit", new HashSet<string>() {
"game.delete"
}
}
};
So if I do Has("Game.View") and the user doesn't explicitly have that but they do have Game.Launch, it would return true. Does that look workable?
Anton
Anton5d ago
yes there's no reason for it to be a hash set if it's static data though also, what is that ToLower().Trim() doing there? I liked the bitsets. I'd prefer integer ids instead of strings at least if you know the max amount of roles you'll have, you can do a struct with a bunch of integer fields to accommodate the max for the bitset
Lupusregina Beta
Lupusregina BetaOP5d ago
Ahh. What would I use if not the hashset for the static data then? The tolower trim was to normalize the input, but that's gone now as I switched to using an enum for the permission nodes rather than typing them as strings. Roles are variable, users can add them. Thanks!
Anton
Anton5d ago
an array

Did you find this page helpful?