前面兩篇文章已經大略說明了 DACL 的概念,而在本文中,我們來實際建構一個 DACL ...
前面兩篇文章已經大略說明了 DACL 的概念,而在本文中,我們來實際建構一個 DACL,這個 ACL 可以存放預設的 Default Entry 以及 Object-based Entry 兩種,一份 DACL 的長度最小為 8 bytes,每個 Entry 的長度為 4 bytes,如果要存到資料庫中,可選用 int 或是 varbinary(4) 來存。
一份 Object Access Control Entry (物件存取控制項目) 擁有兩個部份,前 16 bits 是物件代碼,資料型態為 ushort,最大可存到 65,535 個模組的權限,後 16 bits 分成兩部份,前 8 bits 為存取旗標 (flags),後 8 bit 保留給未來使用 (reserved),它的資料結構如下圖:
我們在上一篇說過,bit stream 排列有分為 Little Endian 與 Big Endian 兩種,而 Intel x86 CPU 使用的是 Little Endian,所以 C, R, U, D 是反向的排列,前四個 bit 代表 C, R, U, D,後四個 bit 則預留未來使用。我們可以直接使用 byte 來定義這 8 個 bits,而後面的 byte 則是以保留方式處理,所以就組成了這樣的資料結構:
	public class ObjectACE
	{
	    // permission bit description:
	    // bit 1 - Create
	    // bit 2 - Read
	    // bit 3 - Update
	    // bit 4 - Delete
	    // bit 5-8 are reserved.
	    private ushort _objectID = 0;
	    private byte _permission = (byte)0x00;
	    private byte _reserved = (byte)0x00;
	}
當然,還不止這些,像是要將二進位資料轉換成 ACE 資料,或是將 ACE 轉換成二進位資料,權限比較與更新權限等,這些輔助方法加一加後就變成了我們的 ObjectACE 類別,其中還包含了一個 bit enumeraton 列舉型別,以方便 bit 的處理:
	[Flags]
	public enum ObjectAccessPermissions
	{
	    Create = 0x01, // bit 00000001
	    Read   = 0x02, // bit 00000010
	    Update = 0x04, // bit 00000100
	    Delete = 0x08  // bit 00001000
	} 
	/// <summary>
	/// Object Access Control Entry (ACE), indicate object access permission.
	/// </summary>
	public class ObjectACE
	{
	    // permission bit description:
	    // bit 1 - Create
	    // bit 2 - Read
	    // bit 3 - Update
	    // bit 4 - Delete
	    // bit 5-8 are reserved.
	    private ushort _objectID = 0;
	    private byte _permission = (byte)0x00;
	    private byte _reserved = (byte)0x00; 
public ushort ObjectID { get { return this._objectID; } }
private ObjectACE() : this(0) { }
	    private ObjectACE(byte[] BinaryData)
	    {
	        this._objectID = BitConverter.ToUInt16(new byte[] { BinaryData[0], BinaryData[1] }, 0);
	        this._permission = BinaryData[2];
	    } 
	    private ObjectACE(ushort ObjectID)
	    {
	        this._objectID = ObjectID;
	    } 
	    public static ObjectACE CreateEmpty()
	    {
	        return new ObjectACE();
	    } 
	    public static ObjectACE CreateEmpty(ushort ObjectID)
	    {
	        return new ObjectACE(ObjectID);
	    } 
	    public static ObjectACE CreateFromBinary(byte[] BinaryData)
	    {
	        return new ObjectACE(BinaryData);
	    } 
	    public bool CanRead { get {
	        return ((int)this._permission & (int)ObjectAccessPermissions.Read) == (int)ObjectAccessPermissions.Read; } }
	    public bool CanCreate { get {
	        return ((int)this._permission & (int)ObjectAccessPermissions.Create) == (int)ObjectAccessPermissions.Create; } }
	    public bool CanUpdate { get {
	        return ((int)this._permission & (int)ObjectAccessPermissions.Update) == (int)ObjectAccessPermissions.Update; } }
	    public bool CanDelete { get {
	        return ((int)this._permission & (int)ObjectAccessPermissions.Delete) == (int)ObjectAccessPermissions.Delete; } } 
	    public void UpdatePermission(byte Permission)
	    {
	        this._permission = Permission;
	    } 
	    public void UpdatePermission(bool CanCreate, bool CanRead, bool CanUpdate, bool CanDelete)
	    {
	        byte perms = (byte)0x00;
	        if (CanCreate)
	            perms = (byte)(((int)perms) | (int)ObjectAccessPermissions.Create);
	        if (CanRead)
	            perms = (byte)(((int)perms) | (int)ObjectAccessPermissions.Read);
	        if (CanUpdate)
	            perms = (byte)(((int)perms) | (int)ObjectAccessPermissions.Update);
	        if (CanDelete)
	            perms = (byte)(((int)perms) | (int)ObjectAccessPermissions.Delete); 
	        this._permission = perms;
	    } 
	    public byte[] GetBinaryData()
	    {
	        MemoryStream ms = new MemoryStream(4);
	        BinaryWriter bw = new BinaryWriter(ms); 
	        bw.Write(this._objectID);
	        bw.Write(this._permission);
	        bw.Write(this._reserved); 
	        bw.Flush();
	        byte[] data = ms.ToArray();
	        bw.Close(); 
	        return data;
	    } 
	    public static ObjectACE operator &(ObjectACE ACE1, ObjectACE ACE2)
	    {
	        ObjectACE resultACE = ObjectACE.CreateEmpty(); 
	        resultACE.UpdatePermission(
	            (ACE1.CanCreate && ACE2.CanCreate), (ACE1.CanRead && ACE2.CanRead),
	            (ACE1.CanUpdate && ACE2.CanUpdate), (ACE1.CanDelete && ACE2.CanDelete)); 
	        return resultACE;
	    }
	}
接下來,我們就可以來定義 DACL 本體了,本文所使用的 DACL 本體包含了標頭 version 與 length 各一個 byte,以及預設存取控制的 ObjectACE (Default ObjectACE) 4 個 bytes,與各物件的 ObjectACE 等,其資料結構如下圖:
為了要避免過度混淆,在階段 1 中我們還不會看到 Object 自己的 ACE,而先聚焦在 Default Object ACE 即可,完整的 DACL 類別如下:
	public class DiscretionaryACL
	{
	    private byte _version = (byte)0x01;
	    private byte _length = (byte)0x01;
	    private byte _reserved1 = (byte)0x00;
	    private byte _reserved2 = (byte)0x00;
	    private ObjectACE _defaultObjectACE = ObjectACE.CreateEmpty();
	    private Dictionary<ushort, ObjectACE> _objectACEs = new Dictionary<ushort, ObjectACE>(); 
	    private DiscretionaryACL(byte[] BinaryData)
	    {
	        MemoryStream ms = new MemoryStream(BinaryData);
	        BinaryReader reader = new BinaryReader(ms); 
	        this._version = reader.ReadByte();
	        this._length = reader.ReadByte();
	        this._reserved1 = reader.ReadByte();
	        this._reserved2 = reader.ReadByte();
	        this._defaultObjectACE = ObjectACE.CreateFromBinary(reader.ReadBytes(4)); 
	        // get object ACEs.
	        while (reader.BaseStream.Position < (reader.BaseStream.Length - 1))
	        {
	            ObjectACE objectACE = ObjectACE.CreateFromBinary(reader.ReadBytes(4));
	            this._objectACEs.Add(objectACE.ObjectID, objectACE);
	        }
	    } 
	    public DiscretionaryACL()
	    {           
	    } 
	    public static DiscretionaryACL CreateFromBinary(byte[] BinaryData)
	    {
	        return new DiscretionaryACL(BinaryData);
	    } 
	    public static DiscretionaryACL CreateEmpty()
	    {
	        return new DiscretionaryACL();
	    } 
	    public ObjectACE GetObjectACE(ushort ObjectID)
	    {
	        if (this._objectACEs.ContainsKey(ObjectID))
	            return this._objectACEs[ObjectID];
	        else
	            return null;
	    } 
	    public ObjectACE GetDefaultACE()
	    {
	        return this._defaultObjectACE;
	    } 
	    public void SetDefaultACE(ObjectACE DefaultACE)
	    {
	        this._defaultObjectACE = DefaultACE;
	    } 
	    public byte[] GetBinaryData()
	    {
	        MemoryStream ms = new MemoryStream();
	        BinaryWriter bw = new BinaryWriter(ms); 
	        bw.Write(this._version);
	        bw.Write(this._length);
	        bw.Write(this._reserved1);
	        bw.Write(this._reserved2);
	        bw.Write(this._defaultObjectACE.GetBinaryData()); 
	        if (this._objectACEs != null && this._objectACEs.Count > 0)
	        {
	            foreach (KeyValuePair<ushort, ObjectACE> objectACE in this._objectACEs)
	                bw.Write(objectACE.Value.GetBinaryData());
	        } 
	        bw.Flush();
	        byte[] data = ms.ToArray();
	        bw.Close(); 
	        return data;
	    } 
	    public void AddObjectACE(ObjectACE ObjectACE)
	    {
	        if (this._objectACEs.ContainsKey(ObjectACE.ObjectID))
	            this._objectACEs[ObjectACE.ObjectID] = ObjectACE;
	        else
	            this._objectACEs.Add(ObjectACE.ObjectID, ObjectACE);
	    } 
	    public void UpdateObjectACE(ObjectACE ObjectACE)
	    {
	        if (this._objectACEs.ContainsKey(ObjectACE.ObjectID))
	            this._objectACEs[ObjectACE.ObjectID] = ObjectACE;
	    } 
	    public void DeleteObjectACE(ObjectACE ObjectACE)
	    {
	        if (this._objectACEs.ContainsKey(ObjectACE.ObjectID))
	            this._objectACEs.Remove(ObjectACE.ObjectID);
	    }
	}
有了 DACL 和 ACE 後,我們就可以利用一個測試專案 (Windows Forms) 來測試 DACL 的功能,在測試專案中,我們加入了 User 和 Group 物件:
	public class Group
	{
	    public string GroupID { get; set; }
	    public string Name { get; set; }
	    public DiscretionaryACL GroupACL { get; set; }
	    public List<User> Users { get; set; } 
	    public Group()
	    {
	        this.Users = new List<User>(); 
	        DiscretionaryACL acl = DiscretionaryACL.CreateEmpty();
	        ObjectACE defaultACE = ObjectACE.CreateEmpty(); 
	        defaultACE.UpdatePermission(false, true, true, false); 
	        acl.SetDefaultACE(defaultACE); 
	        this.GroupACL = acl;
	    }
	}
	public class User
	{
	    public string UserID { get; set; }
	    public string Name { get; set; }
	    public DiscretionaryACL UserACL { get; set; } 
	    public User()
	    {
	        DiscretionaryACL acl = DiscretionaryACL.CreateEmpty();
	        ObjectACE defaultACE = ObjectACE.CreateEmpty(); 
	        defaultACE.UpdatePermission(true, true, false, false); 
	        acl.SetDefaultACE(defaultACE); 
	        this.UserACL = acl;
	    }
	}
User 和 Group 物件均各有自己的 DACL (這也是 Discretionary ACL 的重點之一),且所擁有的權限也是不同的,Group 允許 R 和 U,但不允許 C 和 D,而 User 允許 C 和 R,但不允許 U 和 D。
接著,在專案中加入一個 User Control 模擬模組,這個 User Control 只有 4 個按鈕:
而程式碼也很簡單,只是判斷權限而已:
	private void ucForm1_Load(object sender, EventArgs e)
	{
	    ObjectACE ace = Program.User.UserACL.GetDefaultACE() & Program.Group.GroupACL.GetDefaultACE(); 
	    this.button1.Enabled = ace.CanCreate;
	    this.button2.Enabled = ace.CanRead;
	    this.button3.Enabled = ace.CanUpdate;
	    this.button4.Enabled = ace.CanDelete;
	}
接著,在 Program.cs 中加入初始化的程式碼:
	namespace DACL.WinFormDemo
	{
	    static class Program
	    { 
	        public static User User = null;
	        public static Group Group = null; 
	        /// <summary>
	        /// 應用程式的主要進入點。
	        /// </summary>
	        [STAThread]
	        static void Main()
	        {
	            Application.EnableVisualStyles();
	            Application.SetCompatibleTextRenderingDefault(false); 
	            Program.User = new User() { Name = "User1", UserID = "U1" };
	            Program.Group = new Group() { Name = "Group1", GroupID = "G1" };
	            Program.Group.Users.Add(Program.User); 
	            Application.Run(new MainForm());
	        }
	    }
	}
最後,在 MainForm 中加入剛才新增的 user control,就可以建置它了,而執行出來的畫面是:
因為 user 允許 C, R,而 group 允許 R, U,所以權限組合之下,允許的就是 R,故只有 R 是可用的,其他都不被授權。而這種必須要 user 和 group 都允許才算的,稱為負向授權法 (negative authorization),而相反的則稱為正向授權法 (positive authorization),要採用哪一種授權法則是要由系統開發人員在規劃時決定,而本例使用的是負向授權法,而將 user 和 group 的 ACE 組合的評估,則是靠在 ObjectACE 中覆寫 & 這個運算子的作法:
	public static ObjectACE operator &(ObjectACE ACE1, ObjectACE ACE2)
	{
	    ObjectACE resultACE = ObjectACE.CreateEmpty(); 
	    resultACE.UpdatePermission(
	        (ACE1.CanCreate && ACE2.CanCreate), (ACE1.CanRead && ACE2.CanRead),
	        (ACE1.CanUpdate && ACE2.CanUpdate), (ACE1.CanDelete && ACE2.CanDelete)); 
	    return resultACE;
	}
在程式碼中,使用的是 && 運算子,所以一定要兩邊都是 true 時才會回傳 true,只要有一方 false 就會傳入 false,所以負向授權法才會成立,如果要改用正向授權法,則只要把 && 改成 || 即可,但是授權法有時並沒這麼簡單,例如 Group 和 Group 間要怎麼處理,而如果 Group 之間又有階層 (ex: Administrators 和 Users) 的話,要怎麼處理授權呢?這個我們也會在後面的階段中看到。
Sample Code: https://dotblogsfile.blob.core.windows.net/user/regionbbs/1108/201184152328209.rar