I am trying to write a class for privileges/permissions.
I am trying to use the Binary base permission System in C#.
I got it where I believe it should work. However, it is not working as expected.
After, I stepped thru my code I can see one issue in the method called _mapPermissions
the line if (dic.ContainsKey(vv))
returns false every time.
the key is an instance of a class which have 2 values (ie. secionName
, KeyIndex
)
This is my entire class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using MySql.Data.MySqlClient;
namespace POS
{
class Roles
{
private Dictionary<PermissionsKey, int> systemPermissions = new Dictionary<PermissionsKey, int>();
private Dictionary<PermissionsKey, int> userPermissions = new Dictionary<PermissionsKey, int>();
public Roles()
{
this._getSystemPrivleges();
this._getUserPrivleges();
this._display(systemPermissions);
this._display(userPermissions);
}
public bool hasAccess(string sectionName, string[] keys)
{
if ( this.systemPermissions.Count == 0
|| this.userPermissions.Count == 0
|| keys.Count() == 0
|| String.IsNullOrEmpty(sectionName)
)
{
return false;
}
int systemReq = this._mapPermissions(sectionName, keys, systemPermissions); ;
int userPerm = this._mapPermissions(sectionName, keys, userPermissions);
int check = systemReq & userPerm;
Common.Alert("System Value" + systemReq + Environment.NewLine +"User Value " + userPerm + Environment.NewLine + "AND Results " + check);
if (check == 0)
{
return false;
}
return true;
}
private int _mapPermissions(string secName, string[] keys, Dictionary<PermissionsKey, int> dic)
{
int newKey = 0;
var vv = new PermissionsKey();
foreach (string k in keys)
{
vv.sectionName = secName;
vv.KeyIndex = k;
if (dic.ContainsKey(vv))
{
newKey |= dic[vv] ;
}
}
return newKey;
}
private void _getSystemPrivleges()
{
var db = new dbConnetion();
string sql = " SELECT SQL_CACHE "
+ " KeyIndex, Value, sectionName "
+ " FROM role_keys "
+ " WHERE status = 'active' ";
foreach (var i in db.getData(sql, null, r => new SystemPermissions()
{
pIndex = r["KeyIndex"].ToString()
, pSec = r["sectionName"].ToString()
, pValue = r["Value"].ToString()
}
)
)
{
var vv = new PermissionsKey();
vv.sectionName = i.pSec;
vv.KeyIndex = i.pIndex;
systemPermissions.Add(vv, Convert.ToInt32(i.pValue));
}
}
private void _getUserPrivleges()
{
var db = new dbConnetion();
string sql = " SELECT SQL_CACHE k.sectionName, k.KeyIndex, k.value "
+" FROM users AS su "
+" INNER JOIN role_relation AS r ON r.roleID = su.roleID "
+" INNER JOIN role_keys AS k ON k.KeyID = r.KeyID "
+" WHERE su.status = 'active' AND su.userID = @userid ";
var parms = new List<MySqlParameter>();
parms.Add(new MySqlParameter("@userid", UserInfo.UserID));
foreach (var i in db.getData(sql, parms , r => new SystemPermissions()
{
pIndex = r["KeyIndex"].ToString()
, pSec = r["sectionName"].ToString()
, pValue = r["Value"].ToString()
}
)
)
{
var vv = new PermissionsKey();
vv.sectionName = i.pSec;
vv.KeyIndex = i.pIndex;
userPermissions.Add(vv, Convert.ToInt32(i.pValue));
}
}
private void _display(Dictionary<PermissionsKey, int> dic)
{
string str = "";
foreach (KeyValuePair<PermissionsKey, int> d in dic)
{
var vv = new PermissionsKey();
var c = d.Key;
str += c.sectionName + "_" + c.KeyIndex + " => " + d.Value.ToString() + Environment.NewLine;
}
Common.Alert(str);
}
private int _BinToInt(string str)
{
Regex binary = new Regex("^[01]{1,32}$", RegexOptions.Compiled);
int val = -1;
if (binary.IsMatch(str))
{
val = Convert.ToInt32(str, 2);
}
return val;
}
private string _IntToBin(int number)
{
return Convert.ToString(number, 2);
}
}
class SystemPermissions{
public string pIndex;
public string pSec;
public string pValue;
}
class PermissionsKey
{
public string sectionName;
public string KeyIndex;
}
}
And I use this class like so
var role = new Roles();
string[] aa = { "view", "use" };
if (role.hasAccess("system", aa))
{
Common.Alert("Welcome");
}
else
{
Common.Alert("NOP!");
}
Note that the method Common.Alert()
it simply display a Message (ie. MessageBox
)
The above code displays the following when it runs
1) the content of the systemPermissions
dictionary in the following order PermissionsKey.sectionName
_PermissionsKey.KeyIndex
=> the value (ie. 1 , 2, 4, 8, 16, 32, 64 .....)
system_do = > 1
system_use => 2
system_view => 4
test_use => 1
test_view => 2
2) The content of the usePermissions
dictionary
system_do = > 1
test_use => 1
The issue is that the 2 variables systemReq
and userPerm
return 0 every time. I am not sure why
int systemReq = this._mapPermissions(sectionName, keys, systemPermissions); ;
int userPerm = this._mapPermissions(sectionName, keys, userPermissions);
In order to use something as a dictionary key and expect it to follow you expectations of what is considered equals, you actually need to define what is considered the same.
In your PermissionsKey
class, you do not override Equals
, which means that the default Equals method is still in play (which is the object reference, and is unique for each specific object).
To fix, you need to actually tell the dictionary how to evaluate equality. And anytime you override Equals
, you also should also override GetHashCode
:
class PermissionsKey
{
private string sectionName;
private string keyIndex;
public string SectionName { get { return sectionName; } }
public string KeyIndex { get { return keyIndex; } }
public PermissionsKey(string sectionName, string keyIndex)
{
this.sectionName = sectionName;
this.keyIndex = keyIndex;
}
public override bool Equals(object obj)
{
var key = obj as PermissionsKey;
if (key == null)
return false;
return sectionName.Equals(key.sectionName) &&
keyIndex.Equals(key.keyIndex);
}
//Credit to Jon Skeet from https://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode
// for inspiration for this method
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = (int) 2166136261;
// Suitable nullity checks etc, of course :)
hash = hash * 16777619 ^ sectionName.GetHashCode();
hash = hash * 16777619 ^ keyIndex.GetHashCode();
return hash;
}
}
}
You are overriding Equals to actually define the equality comparison. Anytime you try to compare 2 objects (specifically in the Dictionary
object, but any other time you do it by calling .Equals
), you should define your own comparison. If you don't, then you are just using the default equality comparison, which is a pure objection comparison and 2 objects unless created from the same exact object will never be equals.
Similarly, when you override Equals, you are strongly advised to also override GetHashCode
. The HashCode defines which "bucket" the object falls into. A good hashcode that follows the Equals implementation will help speed up comparisons by placing only objects with the same hash code into the same buckets. The standard recommendation is 2 objects that are Equals will always have the same hashcode, but it is possible for 2 objects that are not equals to have the same hashcode. Effectively it is a quick way for the framework to check for inequality. If you get 2 different hashcodes, the framework knows the objects aren't equal, but if the hashcodes are the same, then it goes and checks equality.
If you can't or don't want to override Equals and GetHashCode, then you also have the option of definining an IEqualityComparer<T>
in the constructor of the dictionary,
You just have to pass an object that implements IEqualityComparer<T>
and within that object you can define the equality comparisons you want.
In addition, anything you plan to use as a dictionary key should never be mutable, specifically the fields that are used to calculate equality and calculate the hashcode, otherwise your keys can get lost as the hash code for the object does not match what was originally put into the dictionary. I have modified your original example to change your fields in PermissionsKey to immutable properties and added a constructor to allow you to set them once when initially created.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With