Skip to content

Latest commit

 

History

History
359 lines (255 loc) · 15.1 KB

7.发给朋友看看,理解CYFS权限系统.md

File metadata and controls

359 lines (255 loc) · 15.1 KB

发给好友测试一下

把上一个章节 实现前端 中的 CYFS App Install Link 对应的 Dec App 安装链接发给你的好友,然后,根据上一章的 去 CYFS 浏览器中安装 Dec App 并查看 的步骤指引,让你的好友也成功安装上我们的留言板 Dec App!

发布留言试试

你跟你的好友可以试试发布留言,你们各自会在自己的留言板上看到自己最新发布的留言信息。

想看看好友的留言板

相信你肯定不只满足于看自己的留言板,那确实有些无聊不是吗?你肯定会好奇你的好友们都在自己的留言板写了什么,想看看自己好友的留言板。 要做到这点,你必须在一个地方能设置好友的 peopleId,为了能尽可能简单的实现,我们将在页面的 URL 中的 search 中设置 peopleid 值为好友的 peopleId。 总的来说,要实现这个功能,只需 2 步:

  1. 在 Service 开放必要的权限。
  2. 前端页面增加 URL 检测功能及修改请求方法。

在 Service 开放必要的权限

首次面对权限问题

在 CYFS 中,每个请求里面都会包含两大块关键信息:来源和目标,协议栈通过根据来源,然后获取目标的 acl 配置,从而并来判断是否匹配。 对于一个请求,包含关键四元组信息:(source-device-id, source-dec-id) -> (target-device-id, target-dec-id),权限系统基于四元组,判断该请求是否满足权限,从而决定是否放行。

权限系统的分类

在 CYFS 中,Dec App 的权限控制分为 2 类:

  1. 申请自己的路径权限。 如果是自己给自己开放权限,可以在 acl.toml 配置文件里设置,也可以在代码里动态注册。 动态注册就是在 dec_ervice 运行中针对指定的 req_path 配置对应的权限。为 req_path 配置权限,就是设置不同的 req_path 对应的 access-string。
  2. 申请其他 App 给自己开放权限。 如果是想要请求其他 app 开放权限给自己,那么必须在 acl.toml 配置文件里写明,无法在代码中动态注册。 配置好 acl.toml 文件之后,该文件会在 app 安装时,被 app-manager 读取,按照配置的各项值,向系统注册对应的权限。
申请自己的路径权限

access-string 是参考 LINUX 下的文件系统的权限来设计的,使用 u32 来表示特定 group 的特定权限.

权限分类

目前权限分为 Read、Write、Call 三种。 在设置权限的时候,可以单个权限去设置,即 AccessPermission,也可以通过组合多个权限去设置,减少代码量,即 AccessPermissions。 查看cyfs-sdk的声明文件,可以看到 AccessPermission 和 AccessPermissions 对应的源码如下:

export class AccessPermission {
	static Call: AccessPermission;
	static Write: AccessPermission;
	static Read: AccessPermission;
}
export class AccessPermissions {
	static None: AccessPermissions;
	static CallOnly: AccessPermissions;
	static WriteOnly: AccessPermissions;
	static WirteAndCall: AccessPermissions;
	static ReadOnly: AccessPermissions;
	static ReadAndCall: AccessPermissions;
	static ReadAndWrite: AccessPermissions;
	static Full: AccessPermissions;
}

权限分组

目前根据 zone、device 和 dec 共分为六个组:

  1. CurrentZone:当前 zone 内设备(同一个 people 下绑定的所有设备)
  2. FriendZone:好友设备,这个是好友列表里面的 People 下的所有设备
  3. OthersZone:除当前 zone 和 friend zone 外的所有未知设备
  4. CurrentDevice:当前设备
  5. OwnerDec:当前 Zone 内 people 的所有 dec
  6. OthersDec: Zone 外所有其他位置的 dec

每个分组都可以可以设置上一小节权限分类中的 3 个独立的权限:

export class AccessGroup {
	static CurrentDevice: AccessGroup;
	static CurrentZone: AccessGroup;
	static FriendZone: AccessGroup;
	static OthersZone: AccessGroup;
	static OwnerDec: AccessGroup;
	static OthersDec: AccessGroup;
}

操作方法

我们可以使用 AccessString 来生成对应的权限。比如想设置权限为同 zone 的所有 dec 可以 Read,那么应该参照如下代码去设置:

import * as cyfs from "cyfs-sdk";

const access = new cyfs.AccessString(0);
access.set_group_permissions(
	cyfs.AccessGroup.CurrentZone,
	cyfs.AccessPermissions.ReadOnly
);
access.set_group_permissions(
	cyfs.AccessGroup.CurrentDevice,
	cyfs.AccessPermissions.ReadOnly
);
access.set_group_permissions(
	cyfs.AccessGroup.OwnerDec,
	cyfs.AccessPermissions.ReadOnly
);
access.set_group_permissions(
	cyfs.AccessGroup.OtherDec,
	cyfs.AccessPermissions.ReadOnly
);

申请其他 App 给自己开放权限

为什么要申请权限

如果是同 Zone 内,同 Dec 之间的 root-state 读写,add-handler 和 post_object,是不需要配置任何额外的权限的。

如果有跨 Zone,或者跨 Dec 的需求,就需要明确的配置权限:

  • 对 root-state 的操作:根据读/写需求,配置 r 或 w 权限

  • add-handler:如果要跨 Zone,或者跨 Dec 的 add-handler,需要给 add-handler 的虚路径配置对应的写权限

  • post_object:如果要跨 Zone,或者跨 Dec 去 post_object,接收方必须给这个虚路径配置执行权限,即 x 权限

配置方法

目前,有两种方法在 acl.toml 配置文件中表示一个 AccessString:

  1. 完整的字符串,用一个 18 位字符串来表示一个完整权限,组内用 linux 的"rwx-",表示每一位的权限。组和组之间可以用空格,或下划线分隔

    例:给 CurrentDevice,CurrentZone 的 OwnerDec 完整权限,给 FriendZone 的 OwnerDec 读写权限,给 OthersZone 的 OthersDec 读权限:

    表示上述权限的字符串为"rwxrwxrw-r--rwxr--", 它和"rwx rwx rw- r-- rwx r--", 还有"rwx_rwx_rw-_r--_rwx_r--"是等价的

  2. 以默认权限为基础,单独为某几组标注权限: 表示为一个数组,数组内是{group, access},group 为 AccessGroup 的枚举名,access 为三位的"rwx-"字符串

    默认的 AccessString 权限:"rwxrwxrwx---rwx"

    还是以上述的权限为例,表示为[{group = "FriendZone", access = "rw-"}, {group = "OthersZone", access = "r--"}, {group = "OthersDec", access = "r--"}]

一个完整的 acl.toml 实例

顶层的 key 为需要申请权限的 dec id,self 是特殊的 key,代表自己的 dec id。


[self]

[self.access]   // 配置自己三个路径的权限

// /test3 使用单独表示法配置权限

"/test3" = [{group = "OthersDec", access = "-wx"}, {group = "CurrentDevice", access = "---"}]

// 下边两个路径使用完整的字符串表示法配置权限

"/test2" = "rwxrwxrwx---rwx---"

"/test1" = "rwxrwxrwx---rwx--x"



[self.specified]    // 自己开放权限给其他的dec

"/test3" = {access = "--x", dec_id = "9tGpLNnDpa8deXEk2NaWGccEu4yFQ2DrTZJPLYLT7gj4"}    // 开放/test3的call权限给特定的dec

"/test2" = {access = "--x", zone_category = "current-zone", dec_id = "9tGpLNnDpa8deXEk2NaWGccEu4yFQ2DrTZJPLYLT7gj4"} // 开放/test2的call权限给特定的dec,并且只能是当前zone内调用

// 开放/test1的call权限,给特定zone内所有的dec

"/test1" = {access = "--x", zone = "5aSixgLwnWbmcDKwBtTBd7p9U4bmqwNU2C6h6SCvfMMh"}



// 为自己申请DECID_A的权限

[DECID_A.specified]

// 下边的SpecifiedGroup配置,不允许填写dec_id,这里的dec_id限定为自己。填写dec_id字段会导致当条配置无效

"/test3" = {access = "--x"} // 为自己申请特定dec的/test3 call权限

"/test2" = {access = "--x", zone_category = "current-zone"} // 为自己申请特定dec的/test2 call权限,只允许本zone内调用

"/test1" = {access = "--x", zone = "5aSixgLwnWbmcDKwBtTBd7p9U4bmqwNU2C6h6SCvfMMh"}// 为自己申请特定dec的/test2 call权限,只允许特定的zone发起调用



[DECID_A.config]    //由于目前config字段为空,这个配置段写不写都可以

如何添加合理的权限

通过前面章节首次面对权限问题的学习,我们已经掌握了配置权限的基本知识。但是,我们还不是很清楚:应该怎么样去合理的配置不同 req_path 的权限? 要知道,随意开放写权限是非常危险的行为——自己的 dec app 数据被别的恶意 dec app 改的一团糟甚至被全部清空,这会给自己造成严重的损失! 从权限分类中我们了解到,权限分为三大类:Read、Write、Call。 从权限分组中我们了解到,一共有 6 个分组:CurrentZone、FriendZone、OthersZone、CurrentDevice、OwnerDec 和 OthersDec。 现在我们来讲解如何针对这三种权限进行设置,便于我们在开发中正确的设置权限,为自己的 dec app 中的数据建立牢固的防火墙。


针对不同的分组开放不同等级的权限,依据的是我们对来源分组的信赖程度。


Call 权限

  1. 一般来说,我们是可以完全信赖 OwnerDec 发起的 Call 调用
  2. 对于 CurrentZone、CurrentDevice、FriendZone、OthersZone 和 OthersDec,我们可以有选择性开放 Call 权限给部分或全部可信赖的来源分组,也可以认为这些来源都不可信,拒绝开放 Call 权限。

Read 权限

  1. 一般来说,我们是可以完全信赖 OwnerDec 发起的 Read 调用
  2. 对于 CurrentZone、CurrentDevice、FriendZone、OthersZone 和 OthersDec,我们可以有选择性开放 Read 权限给部分或全部可信赖的来源分组,也可以认为这些来源都不可信,拒绝开放 Read 权限。

Write 权限

  1. 一般来说,我们是可以完全信赖 OwnerDec 发起的 Write 调用
  2. 对于 CurrentZone、CurrentDevice、FriendZone、OthersZone 和 OthersDec,我们要谨慎的开放 Write 权限给部分可信赖的来源分组,也可以认为这些来源都不可信,拒绝开放 Write 权限。

为留言板设置权限:为好友开放权限

学习了前面的有关 权限 的知识之后,我们现在来实现如何为好友开放权限。 为好友开放权限需要 2 步:

  1. 为查询留言请求路径 /messages/retrieve 开放 FriendZone 分组的 CallOnly 权限,这样,我们可以直接跨 Zone 调用好友的 Service 上的查询留言请求路径的接口。 打开 src/service/entry/app_startup.ts 文件,在addRouters方法中,放开如下代码段的注释即可:

    if (routerObj.reqPath.endsWith("/messages/retrieve")) {
    	access.set_group_permissions(
    		cyfs.AccessGroup.FriendZone,
    		cyfs.AccessPermissions.CallOnly
    	);
    }
  2. 为 RootState 上的 /messages_list路径开放 OthersZone、CurrentDevice 和 OwnerDec 分组的只读权限 ReadOnly。 打开 src/service/entry/app_startup.ts 文件,在addRouters方法中,放开如下代码段的注释即可:

    const access = new cyfs.AccessString(0);
    access.set_group_permissions(
    	cyfs.AccessGroup.OthersZone,
    	cyfs.AccessPermissions.ReadOnly
    );
    access.set_group_permissions(
    	cyfs.AccessGroup.CurrentDevice,
    	cyfs.AccessPermissions.ReadOnly
    );
    access.set_group_permissions(
    	cyfs.AccessGroup.OwnerDec,
    	cyfs.AccessPermissions.ReadOnly
    );
    const r = await stack
    	.root_state_meta_stub()
    	.add_access(cyfs.GlobalStatePathAccessItem.new("/messages_list", access));
    if (r.err) {
    	console.error(`path /messages_list add access error: ${r}`);
    } else {
    	console.log("add sccess /messages_list success.");
    }

前端页面增加 URL 检测功能及修改请求方法

修改 src/www/pages/MessageBoard/MessageBoard.tsx 页面文件

  1. 修改 queryMessageRecords 方法,去除方法中第 2-3 行的代码注释。接着,修改调用 listMessagesByPage 方法的参数。这样,我们就可以指定要查看哪个好友的留言板(URL 中指定设置 peopleid 为好友 poepleId)。修改后的代码如下:

    const target = extractPeopleIdFromSearch();
    const to =
    	target === "self"
    		? undefined
    		: cyfs.ObjectId.from_base_58(target).unwrap();
    const list = await listMessagesByPage(0, to);
  2. 去除 extractPeopleIdFromSearch 方法的代码注释。extractPeopleIdFromSearch 方法的功能是检测当前 URL 中的 peopleid 是否指定,如果指定了好友的 peopleid 就返回好友的 peopleid 字符串。给代码如下:

    const extractPeopleIdFromSearch = () => {
    	let target = "self";
    	if (location.search.includes("peopleid=")) {
    		target = location.search.substring(10);
    	}
    	console.log("target: ", target);
    	return target;
    };

修改 src/www/apis/message.ts 文件

  1. 修改 retrieveMessage 方法,增加可选入参 target,类型为 cyfs.ObjectId。然后,增加 postObjectoptions 对象的 target参数。为 postObject 增加 target 参数后,就可以实现跨 Zone 发起请求。代码如下:

    export async function retrieveMessage(
    	msgKey: string,
    	target?: cyfs.ObjectId
    ) {
    	const stackWraper = checkStack();
    	// Create a new Message object
    	const messageObj = Message.create({
    		key: msgKey,
    		content: "",
    		decId: DEC_ID,
    		owner: stackWraper.checkOwner(),
    	});
    	// make a request
    	const ret = await stackWraper.postObject(messageObj, MessageDecoder, {
    		reqPath: ROUTER_PATHS.RETRIEVE_MESSAGE,
    		decId: DEC_ID,
    		target,
    	});
    	// ...ignore
    }
  2. 修改 listMessagesByPage 方法,增加可选入参 to ,类型为 cyfs.ObjectId。接着,修改调用 root_state_access_stub 的传参为 target。最后,修改代码中调用 retrieveMessage 方法的入参 target。为 listMessagesByPage 方法增加可选入参 to后,就可以发起跨 Zone 分页读取好友 RootState 上的 /messages_list 路径下的对象列表的请求。代码如下:

export async function listMessagesByPage(
	pageIndex: number,
	to?: cyfs.ObjectId
) {
	const stack = checkStack();
	// Get your own OwnerId
	const target = to ? to : stack.checkOwner();
	// Get an instance of cyfs.GlobalStateAccessStub
	const access = stack.check().root_state_access_stub(target);
	// ...ignore
	const msgList = await Promise.all(
		keyList.map(async (item) => {
			const msg = await retrieveMessage(item, target);
			return msg;
		})
	);
	// ...ignore
}

试试能不能看到好友的留言板

发布新代码到 OOD

经过前面的代码修改后,我们的留言板已经具备了在自己的留言板上查看好友的留言板信息的功能。 在执行发布流程前,请打开根目录下的 cyfs.config.json 文件,修改 version 的版本号,比如由原来的 1.0.1 升级为 1.0.2。修改完成后,重新发布更新后的代码到 OOD。

发布完成后,自己跟好友就可以在 CYFS 浏览器的应用商店中对留言板 App 进行升级安装,参考上一个章节中的去 CYFS 浏览器中升级 Dec App 并查看的内容。

可以看好友的留言板了

打开 CYFS 浏览器,进入留言板前端页面,在页面 URL 追加 peopleid 的值,如: cyfs://xxxx/meessage_baord/index.html?peopleid=xxxx,就可以看到好友的留言板内容啦!是不是非常炫酷?