题目背景
为了响应国家发展新基建的倡议,西西艾弗岛上兴建了西西艾弗数据中心,并以此为基础运营了西西艾弗云。作为数据中心的运营和维护方,
西西艾弗云公司十分重视西西艾弗云的网络安全管理工作。众所周知,安全性和便捷性难以兼得,同时,
一个混乱的权限模型可能会导致人员被授予不必要的权限,从而造成安全风险。因此在西西艾弗云公司的网络安全部工作的小 C
专门设计了一种科学的权限模型。
这种安全模型将验证流程分为两个步骤。第一步是验证用户的身份(鉴别),第二步是验证用户的权限(授权)。在第一步,
首先验证一个用户是否是该用户所声称的那个身份。例如,通过验证用户提供的口令(Password)是否正确,或者通过验证用户提供的智能卡是否合法有效。
接下来,在授权的步骤中,权限策略会被检索以便判断来访的用户是否能够操作系统中的某个资源。
为了能够灵活地表达用户和授权之间的关系,西西艾弗云公司设计了一种简洁而灵活的授权模型:基于角色的授权模型。它的思路是:首先设定若干角色,
每个角色中指明了一个清单,表明允许访问的资源的种类、资源的名称和对资源的操作;然后将被前一步骤已经鉴别过的用户和一个或多个角色相关联。
某个用户能够执行的操作,即为与其关联的全部角色中允许的操作的并集。
小 C 将实现授权模型的工作交给了你,希望你能够把它们实现出来。
问题描述
用户表示授权模型中的一个已识别的主体,该识别过程由此前的鉴别过程完成。一个用户具有下列要素:
- 名称:是一个字符串,用于唯一标识一个用户;
- 用户组:是一个数组,包含若干个字符串,表示该用户所属的用户组。
一个待授权的行为,包括下列要素:
- 主体:是一个用户,包括试图进行该行为的用户的名称和该用户所属的用户组;
- 操作:是一个字符串,一般是一个动词,例如
Read
、Open
、Close
等; - 资源:表示该行为的操作对象,由资源种类和资源名称描述。资源种类例如
Door
、File
等;在一个特定的资源种类中,资源名称唯一确定了一个资源。
需要注意的是,一个待授权的行为的主体信息,即用户名称和所属用户组,是由前一步骤的鉴别过程完成的。因此,每次授权过程中,
每个待授权的行为都会包含主体用户和其关联的用户组的信息。由于鉴权过程中的其它因素,同一个名称的用户在先后两次待授权的行为中所属的用户组可能有区别,
不能存储或记忆此前每个待授权的行为中,用户与用户组的关联情况,而是要按照每次待授权的行为中给出的信息独立判断。
角色是这种授权模型的基本单位,它指明了一个用户可以执行的操作,角色的清单中描述了角色所允许的操作。一个角色包含下列要素:
- 名称,是一个字符串,用于唯一标识一个角色;
- 操作清单,是一个数组,包含一个或多个操作,表示该角色允许执行的操作集合;
- 资源种类清单,是一个数组,包含一个或多个资源种类,表示该角色允许操作的资源的种类集合;
- 资源名称清单,是一个数组,包含若干个资源名称,表示该角色允许操作的资源的名称集合。
判断一个角色能否对某个资源执行某个操作的过程是:
- 检查该角色的操作清单,如果该角色的操作清单中不包含该操作,且该角色的操作清单中也不包含字符串
*
,那么不能执行该操作; - 检查该角色的资源种类清单,如果该角色的资源种类清单中不包含该资源的种类,且该角色的资源种类清单中也不包含字符串
*
,那么不能执行该操作; - 检查该角色的资源名称清单,如果该角色的资源名称清单中不包含该资源的名称,且该角色的资源名称清单不是空数组,那么不能执行该操作;
- 允许执行该操作。
例如,假设有某个角色 Doorman
,其允许执行的操作有 Open
和 Close
,其允许操作的资源类型有 Door
,其允许操作的资源名称有 FrontDoor
和 BackDoor
。
如果某用户与这个角色关联,那么该用户可以对名为 FrontDoor
的 Door
执行 Open
操作,但是不能对 BackDoor
的 Door
执行 Delete
操作。
同时,一个角色能允许进行的操作可以用通配符来表示。例如,另有一个角色 Admin
,其允许执行的操作有 *
,允许操作的资源类型是 *
,其允许操作的资源名称列表为空,
那么与该角色关联的所有用户可以执行任何操作。值得注意的是,一个角色的操作清单,只能用允许列表的方式列举该角色允许进行的操作,而不能禁止角色进行某个操作。
角色关联指明了一个用户和一个或多个角色之间的关系。一个角色关联包含下列要素:
- 角色名称,是一个字符串,用于指明一个角色;
- 授权对象清单,是一个数组,包含一个或多个用户名称或者用户组名称,表示该角色关联的用户和用户组的集合。
判断一个用户能否执行某个操作的过程是:
- 检查所有的角色关联的授权对象清单,如果清单中包含该用户的名称,或者该清单中包含该用户所属的某一个用户组的名称,那么选取该角色关联所关联的角色;
- 对于所有被选取的角色,判断这些角色是否能对该资源执行该操作,如果所有角色都不能执行该操作,那么不能执行该操作;
- 允许执行该操作。
由此可见,一个角色关联可以将一个角色与多个用户或用户组关联起来。例如,如果有一个角色关联,其关联的角色名称为 Doorman
,其关联的用户和用户组清单为
用户 foo1
、用户 foo2
、用户组 bar
。那么这些用户会与 Doorman
角色关联:
- 名为
foo1
的用户,属于用户组bar
; - 名为
foo2
的用户,属于用户组barz
; - 名为
foo3
的用户,属于用户组bar
和barz
。
但是,属于用户组 barz
的名为 foo4
的用户不能与 Doorman
的角色关联。
从上述判断规则可以知道,一个用户可能与多个角色相关联,在这种情况下,该用户允许进行的操作是这些角色被允许进行的操作集合的并集。
输入格式
从标准输入读入数据。
输入的第一行包含三个正整数 n、m、q,分别表示角色数量、角色关联数量和待检查的操作数量。
输入接下来的 n 行中,每行表示一个角色,包括空格分隔的若干元素,依次为:
- 一个字符串,表示该角色的名称;
- 一个正整数 nv,表示操作清单中包含的操作数量;
- nv 个字符串,依次表示操作清单中的操作;
- 一个正整数 no,表示资源种类清单中包含的资源种类的数量;
- no 个字符串,依次表示资源种类清单中的资源种类;
- 一个非负整数 nn,表示资源名称清单中包含的资源名称的数量;
- nn 个字符串,依次表示资源名称清单中的资源名称。
输入接下来的 m 行中,每行表示一个角色关联,包括空格分隔的若干元素,依次为:
- 一个字符串,表示该角色关联的角色名称;
- 一个正整数 ns,表示授权对象清单中包含的授权对象的数量;
- 2ns 个字符串,每两个表示授权对象清单中的授权对象,前一个字符串为
u
或g
,分别表示这个授权对象是一个用户名称或者用户组名称,后一个字符串为用户名称或者用户组名称。
输入接下来的 q 行中,每行表示一个待授权的行为,包括空格分隔的若干元素,依次为:
- 一个字符串,表示执行该操作的用户名称;
- 一个正整数 ng,表示该用户所属的用户组的数量;
- ng 个字符串,依次表示该用户所属的用户组的名称;
- 一个字符串,表示待查操作的名称;
- 一个字符串,表示被操作的资源种类;
- 一个字符串,表示被操作的资源名称。
输出格式
输出到标准输出。
输出 q 行,每行表示一个操作是否可以被执行,0
表示不能执行,1
表示可以执行。
样例输入
1. 1 2 3 2. op 1 open 1 door 0 3. op 1 g sre 4. op 1 u xiaop 5. xiaoc 2 sre ops open door room302 6. xiaop 1 ops open door room501 7. xiaoc 2 sre ops remove door room302
Data
样例输出
1. 1 2. 1 3. 0
Data
样例解释
在本例中,定义了一个名为 op
的角色,授予了对任意 door
类型的对象的 open
操作的权限,同时定义了两个指向 op
的角色关联。
注意,可以针对一个角色定义多于一个角色关联。本例给出了三个待授权的行为。其中,第一个行为,授权的主体用户是 xiaoc
,
该用户所属的用户组 sre
被关联 op
角色,因此可以执行开门动作。第二个行为中,授权的主体用户是 xiaop
,
该用户被直接关联了 op
角色,因此也可以执行开门动作。第三个行为中,授权的主体用户仍是 xiaoc
,关联的角色仍为 op
。但是,
由于 op
角色并未被授予 remove
操作的权限,因此该动作被拒绝。
解题思路
这里设计三个对象,分别是角色role,小组group还有用户user。
其中role中包含三个set集合,分别对应opl操作清单,opt资源种类清单,opn资源名称清单。
group中包含一个set对象rol,即这个组所对应的角色
user中包含两个set对象,分别为rol表示这个这个用户所拥有的角色,group这个用户所对应的组名。以及一个内置函数check,用来判断该用户是否可以执行某项操作
在check函数中,我们先判断这个用户自己对应的角色可不可以执行这个操作,可以就返回true。如果不可以,则寻找这个用户对应的组,检查这个组是否有权限执行这项操作。都不可以的话就返回false,该用户不可以执行这个操作。
我们还设计了三个map,这样就可以通过role,group,user的名字,找到对应的对象了。
对于前n个输入,我们只需要增加mapr中的值即可。
对于之后m个输入,我们依据输入的是用户还是用户组,分别存储到对应的mpg和mpu中。
对于之后q个输入,我们在m个输入中已经输入了用户名所对应的角色名,user类中的rol已经写好了,下一步填充grp即可。在接收到这个用户所属的用户组后,就可以进行判断了,我们把判断结果打印出来,就可以得到我们想要的结果。不过要注意的是,每次输入后要清理一下group,因为不用的用户在不同的输入组中对应的用户组不同,避免信息混乱。
代码
1. #include<bits/stdc++.h> 2. using namespace std; 3. 4. class role{ 5. public: 6. set<string> opl; 7. set<string> opt; 8. set<string> opn; 9. }; 10. 11. class group{ 12. public: 13. set<string> rol; 14. }; 15. 16. map<string,role> mpr; 17. map<string,group> mpg; 18. 19. class user{ 20. public: 21. set<string> rol; 22. set<string> grp; 23. 24. bool check(string opl,string opt,string opn){ 25. for(auto it:rol) 26. if(mpr[it].opl.count("*") || mpr[it].opl.count(opl)) 27. if(mpr[it].opt.count("*") || mpr[it].opt.count(opt)) 28. if(mpr[it].opn.empty() || mpr[it].opn.count(opn)) 29. return true; 30. 31. for(auto it:grp) 32. if(mpg.count(it)) 33. for(auto it1:mpg[it].rol) 34. if(mpr[it1].opl.count("*") || mpr[it1].opl.count(opl)) 35. if(mpr[it1].opt.count("*") || mpr[it1].opt.count(opt)) 36. if(mpr[it1].opn.empty() || mpr[it1].opn.count(opn)) 37. return true; 38. return false; 39. } 40. 41. }; 42. map<string,user> mpu; 43. 44. signed main(){ 45. //提高cin,cout的速度 46. ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); 47. 48. int n,m,q,nv,no,nn,k; 49. string name,rname,uname,x,y,z,ch; 50. cin>>n>>m>>q; 51. 52. for(int i=0;i<n;i++){ 53. cin>>name>>nv; 54. while(nv--) 55. cin>>x,mpr[name].opl.emplace(x); 56. cin>>no; 57. while(no--) 58. cin>>x,mpr[name].opt.emplace(x); 59. cin>>nn; 60. while(nn--) 61. cin>>x,mpr[name].opn.emplace(x); 62. } 63. 64. for(int i=0;i<m;i++){ 65. cin>>rname>>k; 66. for(int j=0;j<k;j++){ 67. cin>>ch>>name; 68. if(ch == "g") 69. mpg[name].rol.emplace(rname); 70. else 71. mpu[name].rol.emplace(rname); 72. } 73. } 74. 75. for(int i=0;i<q;i++){ 76. cin>>uname>>k; 77. for(int j=0;j<k;j++) 78. cin>>name,mpu[uname].grp.emplace(name); 79. cin>>x>>y>>z; 80. cout<<mpu[uname].check(x,y,z)<<endl; 81. mpu[uname].grp.clear(); 82. } 83. 84. }