NetSqlAzMan 是基於 .Net 的一個 RBAC (Role-Based Access Control) 軟件,屬於開源的項目,可以在這裡找到,因為 xPort3 是建基於 .Net 3.5,所以我們用的 NetSqlAzMan 是 v3.6.0.5。
xPort3 則是我們自主開發的 web 應用軟件,是 .Net 3.5 + C# + Visual WebGUI v6.3.17,Visual WebGUI 也是開源的,可以在這裡找到。
NetSqlAzMan 最主要的功能是控制 CURD:
C = Create
U = Update
R = Read
D = Delete
就是說,誰可以有:新增/存檔/觀看/刪除 這四項基本權限,再因應 xPort3 增加一些特殊的權限控制的要求(例如 Approve 批核,Print 打印,等等)。
先來看看 login 後的畫面:
這三張圖片可以看到 xPort3 的菜單 (menu),xPort3 分作四個 modules(工廠/訂單/管理/設定),每個 module 以 TreeView 顯示不同的頁面(Pages)。同時,每個 module 的 menubar 上配有一個下拉鍵(dropdown list)可以新增(C = Create)不同的資料。
當用戶打開(R = Read)其中的頁面時,例如選定工廠|產品,會出現以下的畫面:
系統會以 ListView 的方式顯示相關的數據列表,在 ListView 的 Toolbar(功能鍵) 上會有不同的按鈕,而且當用戶雙擊 (double-click) 其中的一行(例如第 7 行),就會彈出輸入產品資料的畫面,如下圖:
從這兩張圖片中可以看到 Toolbar 上的按鈕主要是 存檔(U = Update),刪除(D = Delete),也有一項特殊的功能 — 批核(Approve)。
這就是 CRUD 在 xPort3 中的分佈。
再下來就是利用 NetSqlAzMan Console 設定 NetSqlAzMan 的數據內容以配合以上的安排:
從上面的畫面可以看到 NetSqlAzMan 內的:
Store = xPort3
Application = 工廠 Factory/訂單 Order/管理 Admin/設定 Settings,四項
Operation = 每個 module 之下的每個 pages(頁面)的功能:CRUD + Approve + Print
在上面的圖中可以看到,Document 祇分為 CRUD,而 Product 則分為 CRUD + Approve + Print
輪到介紹 Roles,xPort3 的 Roles = NetSqlAzMan 的 Store Groups,分為 0 至 7(七級),一級比一級高,另外獨立的 Roles 有 A = Customer 和 B = Supplier 兩種。
看看實例 工廠:
對應 Store Groups,工廠的 Roles 也有 3 至 7 和 B,即是 工廠 的 menu 祇出現於擁有這些權限的用戶的畫面,從上面的圖可以看到,Factory.B 可以有 CRUD + Print 的權限,卻沒有 Approve 的權限。那麼誰人可以 Approve?
Factory.3 擁有 Factory.B + Approve,就是說 Factory.3 包含 Factory.B 的所有權限,另加 Approve 權限。如此類推 Factory.4 包含 Factory.3,而 Factory.5 則包含 Facotry.6 …
好了,Definitions 搞定了,我們可以 Authorize 那些用戶屬於那個 Role 了。
Factory.B 准許(Allow)Supplier(Store Groups):
Factory.3 准許(Allow)Supervisor(Store Groups):
Factory.5 准許(Allow)Administrator(Store Groups):
如此類推。
好了,Authorization 也搞定了,是時候把用戶放進不同的 Store Groups,首先是 Administrator:
Administrator (Store Groups) 包括用戶 admin 和 CLARA。看多一個例子:
Supervisor (Store Groups) 包括用戶 ting 和 mandy。那麼,如何多加一個用戶?
可以利用 NetSqlAzMan 的 Console 直接增加:
不過 xPort3 已經內置代碼,可以直接在 xPort3 新增/刪除 Staff/Customer/Supplier 時更改 NetSqlAzMan Store Groups 內的 Members,即是說,在正常的情形下是不需要 NetSqlAzMan Console 的。
什麼是不正常情況?
例如:
Supervisor 本來是有 Factory.Product.Approve 的,用戶 ting 是 Supervisor 應該擁有這個權限的,可是由於工作上的安排,我不希望用戶 ting 擁有 Approve 的權限。
設定:
這時候就要用 NetSqlAzMan Console 在 Item Authorizations 之下的 Factory.Product.Approve 增加一個 item,指明用戶 ting 不可以使用(Deny)此功能,如圖:
一同來看看內碼,首先建立一個 static function 用作測試用戶有沒有 Read 的權限:
using NetSqlAzMan; using NetSqlAzMan.Interfaces; public static bool IsAccessAuthorized(String application, String operation) { bool result = false; String storeName = "xPort3"; String cs = System.Web.Configuration.WebConfigurationManager.ConnectionStrings["NetSqlAzManDb"].ConnectionString; using (IAzManStorage storage = new SqlAzManStorage(cs)) { IAzManSid userSid = new SqlAzManSID(Common.Config.CurrentUserId.ToString()); IAzManDBUser dbUser = storage.GetDBUser(userSid); try { AuthorizationType authorization = storage.CheckAccess(storeName, application, operation, dbUser, DateTime.Now, false); switch (authorization) { case AuthorizationType.Allow: result = true; break; } } catch { } } return result; }
接著就是在組建每個 module 的 TreeView menu 的時候測試一下權限:
using NetSqlAzMan; using NetSqlAzMan.Interfaces; private static Gizmox.WebGUI.Forms.TreeNode CreateTreeNodeFromXmlNode(XmlNode node, String navPaneName) { TreeNode tmptreenode = null; bool buildNode = true; // 如果有採用 NetSqlAzMan, // 就要用 IsAccessAuthorized check 下這位 user 是否有 permission if (xPort3.DAL.Common.Config.UseNetSqlAzMan) { String tagName = String.Empty; if (node.Attributes["Tag"] != null) tagName = node.Attributes["Tag"].Value; else tagName = node.Attributes["Caption"].Value; buildNode = xPort3.Controls.Utility.NetSqlAzMan.IsAccessAuthorized(navPaneName, tagName); } if (buildNode) { // 根據 XmlNode 的資料組成 TreeNode } return tmptreenode; }
根據用戶有沒有 Create 的權限組成 新增 按鈕的下拉鍵(這是 工廠|新增 按鍵的組成代碼):
#region 根據 NetSqlAzMan 的 permission 組成 String app = "Factory"; ContextMenu ddlNew = new ContextMenu(); if (xPort3.Controls.Utility.NetSqlAzMan.IsAccessAuthorized(app, "Product.Create")) ddlNew.MenuItems.Add(new MenuItem(oDict.GetWord("product"), string.Empty, "Product")); if (ddlNew.MenuItems.Count > 0) { ToolBarButton cmdNew = new ToolBarButton("New", oDict.GetWord("addnew")); cmdNew.Style = ToolBarButtonStyle.DropDownButton; cmdNew.Image = new IconResourceHandle("Icons.16x16.ico_16_3.gif"); cmdNew.DropDownMenu = ddlNew; this.atsCoding.Buttons.Add(cmdNew); cmdNew.MenuClick += new MenuEventHandler(AtsMenuClick); }
搞定 C 和 R,到 U 和 D:
if (xPort3.DAL.Common.Config.UseNetSqlAzMan) { #region 用 NetSqlAzMan 就要 check permissions // cmdSave ToolBarButton cmdSave = new ToolBarButton("Save", oDict.GetWord("Save")); cmdSave.Tag = "Save"; cmdSave.Image = new IconResourceHandle("Icons.16x16.16_L_save.gif"); // cmdSaveClose ToolBarButton cmdSaveClose = new ToolBarButton("Save & Close", oDict.GetWord("Save_Close").Replace("%26", "&")); cmdSaveClose.Tag = "Save & Close"; cmdSaveClose.Image = new IconResourceHandle("Icons.16x16.16_saveClose.gif"); // cmdSaveDup ToolBarButton cmdSaveDup = new ToolBarButton("Save & Dup", oDict.GetWord("Save_Dup").Replace("%26", "&")); cmdSaveDup.Tag = "Save & Dup"; cmdSaveDup.Image = new IconResourceHandle("Icons.16x16.16_L_saveDup.gif"); // cmdDelete ToolBarButton cmdDelete = new ToolBarButton("Delete", oDict.GetWord("Delete")); cmdDelete.Tag = "Delete"; cmdDelete.Image = new IconResourceHandle("Icons.16x16.16_L_remove.gif"); if (editMode != Common.Enums.EditMode.Read) { if (xPort3.Controls.Utility.NetSqlAzMan.IsAccessAuthorized(application, operation + ".Update")) { target.Buttons.Add(cmdSave); target.Buttons.Add(cmdSaveClose); target.Buttons.Add(cmdSaveDup); } if (editMode != Common.Enums.EditMode.Add) { if (xPort3.Controls.Utility.NetSqlAzMan.IsAccessAuthorized(application, operation + ".Delete")) { target.Buttons.Add(cmdDelete); } } } #endregion }
剩下來的就是 Print 和 Approve:
if (xPort3.DAL.Common.Config.UseNetSqlAzMan) { if (xPort3.Controls.Utility.NetSqlAzMan.IsAccessAuthorized("Factory", "Product.Delete")) this.ansProductList.Buttons.Add(cmdDelete); if (xPort3.Controls.Utility.NetSqlAzMan.IsAccessAuthorized("Factory", "Product.Approve")) this.ansProductList.Buttons.Add(cmdApprove); }
以上的 source code 都是在組成按鈕時先行 check 一下用戶是否有權限才決定,實際執行時,login 需要多用一點時間,其餘則沒有明顯的延滯。