NetSqlAzMan 在 xPort3 中的應用

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 需要多用一點時間,其餘則沒有明顯的延滯。