python 满分解CCF-CSP 真题: 202206-3 角色授权

题目如下:

试题编号:

202206-3
试题名称: 角色授权
时间限制: 5.0s
内存限制: 512.0MB
问题描述:

题目背景

为了响应国家发展新基建的倡议,西西艾弗岛上兴建了西西艾弗数据中心,并以此为基础运营了西西艾弗云。作为数据中心的运营和维护方,
西西艾弗云公司十分重视西西艾弗云的网络安全管理工作。众所周知,安全性和便捷性难以兼得,同时,
一个混乱的权限模型可能会导致人员被授予不必要的权限,从而造成安全风险。因此在西西艾弗云公司的网络安全部工作的小 C
专门设计了一种科学的权限模型。

这种安全模型将验证流程分为两个步骤。第一步是验证用户的身份(鉴别),第二步是验证用户的权限(授权)。在第一步,
首先验证一个用户是否是该用户所声称的那个身份。例如,通过验证用户提供的口令(Password)是否正确,或者通过验证用户提供的智能卡是否合法有效。
接下来,在授权的步骤中,权限策略会被检索以便判断来访的用户是否能够操作系统中的某个资源。

为了能够灵活地表达用户和授权之间的关系,西西艾弗云公司设计了一种简洁而灵活的授权模型:基于角色的授权模型。它的思路是:首先设定若干角色,
每个角色中指明了一个清单,表明允许访问的资源的种类、资源的名称和对资源的操作;然后将被前一步骤已经鉴别过的用户和一个或多个角色相关联。
某个用户能够执行的操作,即为与其关联的全部角色中允许的操作的并集。

小 C 将实现授权模型的工作交给了你,希望你能够把它们实现出来。

问题描述

用户表示授权模型中的一个已识别的主体,该识别过程由此前的鉴别过程完成。一个用户具有下列要素:

  • 名称:是一个字符串,用于唯一标识一个用户;
  • 用户组:是一个数组,包含若干个字符串,表示该用户所属的用户组。

一个待授权的行为,包括下列要素:

  • 主体:是一个用户,包括试图进行该行为的用户的名称和该用户所属的用户组;
  • 操作:是一个字符串,一般是一个动词,例如 ReadOpenClose 等;
  • 资源:表示该行为的操作对象,由资源种类和资源名称描述。资源种类例如 DoorFile 等;在一个特定的资源种类中,资源名称唯一确定了一个资源。

需要注意的是,一个待授权的行为的主体信息,即用户名称和所属用户组,是由前一步骤的鉴别过程完成的。因此,每次授权过程中,
每个待授权的行为都会包含主体用户和其关联的用户组的信息。由于鉴权过程中的其它因素,同一个名称的用户在先后两次待授权的行为中所属的用户组可能有区别,
不能存储或记忆此前每个待授权的行为中,用户与用户组的关联情况,而是要按照每次待授权的行为中给出的信息独立判断。

角色是这种授权模型的基本单位,它指明了一个用户可以执行的操作,角色的清单中描述了角色所允许的操作。一个角色包含下列要素:

  • 名称,是一个字符串,用于唯一标识一个角色;
  • 操作清单,是一个数组,包含一个或多个操作,表示该角色允许执行的操作集合;
  • 资源种类清单,是一个数组,包含一个或多个资源种类,表示该角色允许操作的资源的种类集合;
  • 资源名称清单,是一个数组,包含若干个资源名称,表示该角色允许操作的资源的名称集合。

判断一个角色能否对某个资源执行某个操作的过程是:

  1. 检查该角色的操作清单,如果该角色的操作清单中不包含该操作,且该角色的操作清单中也不包含字符串 *,那么不能执行该操作;
  2. 检查该角色的资源种类清单,如果该角色的资源种类清单中不包含该资源的种类,且该角色的资源种类清单中也不包含字符串 *,那么不能执行该操作;
  3. 检查该角色的资源名称清单,如果该角色的资源名称清单中不包含该资源的名称,且该角色的资源名称清单不是空数组,那么不能执行该操作;
  4. 允许执行该操作。

例如,假设有某个角色 Doorman,其允许执行的操作有 Open 和 Close,其允许操作的资源类型有 Door,其允许操作的资源名称有 FrontDoor 和 BackDoor
如果某用户与这个角色关联,那么该用户可以对名为 FrontDoor 的 Door 执行 Open 操作,但是不能对 BackDoor 的 Door 执行 Delete 操作。
同时,一个角色能允许进行的操作可以用通配符来表示。例如,另有一个角色 Admin,其允许执行的操作有 *,允许操作的资源类型是 *,其允许操作的资源名称列表为空,
那么与该角色关联的所有用户可以执行任何操作。值得注意的是,一个角色的操作清单,只能用允许列表的方式列举该角色允许进行的操作,而不能禁止角色进行某个操作。

角色关联指明了一个用户和一个或多个角色之间的关系。一个角色关联包含下列要素:

  • 角色名称,是一个字符串,用于指明一个角色;
  • 授权对象清单,是一个数组,包含一个或多个用户名称或者用户组名称,表示该角色关联的用户和用户组的集合。

判断一个用户能否执行某个操作的过程是:

  1. 检查所有的角色关联的授权对象清单,如果清单中包含该用户的名称,或者该清单中包含该用户所属的某一个用户组的名称,那么选取该角色关联所关联的角色;
  2. 对于所有被选取的角色,判断这些角色是否能对该资源执行该操作,如果所有角色都不能执行该操作,那么不能执行该操作;
  3. 允许执行该操作。

由此可见,一个角色关联可以将一个角色与多个用户或用户组关联起来。例如,如果有一个角色关联,其关联的角色名称为 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 2 3
op 1 open 1 door 0
op 1 g sre
op 1 u xiaop
xiaoc 2 sre ops open door room302
xiaop 1 ops open door room501
xiaoc 2 sre ops remove door room302

Data

样例输出

1
1
0

Data

样例解释

在本例中,定义了一个名为 op 的角色,授予了对任意 door 类型的对象的 open 操作的权限,同时定义了两个指向 op 的角色关联。
注意,可以针对一个角色定义多于一个角色关联。本例给出了三个待授权的行为。其中,第一个行为,授权的主体用户是 xiaoc
该用户所属的用户组 sre 被关联 op 角色,因此可以执行开门动作。第二个行为中,授权的主体用户是 xiaop
该用户被直接关联了 op 角色,因此也可以执行开门动作。第三个行为中,授权的主体用户仍是 xiaoc,关联的角色仍为 op。但是,
由于 op 角色并未被授予 remove 操作的权限,因此该动作被拒绝。

子任务

对于 20% 的数据,有 n=m=1,且给出的角色类似于题目正文中用于举例的 Admin,允许执行任何操作,且 nv=no=ns=ng=1、nn=0。

对于 40% 的数据,有 1≤n,m≤50,且 nv=no=ns=1、ng≤40、nn=0。

对于 70% 的数据,有 1≤n,m≤50,且 nv,no,ns,ng≤40、nn≤400。

对于 100% 的数据,有:

  • 1≤n,m≤500;
  • 1≤q≤5000;
  • 1≤nv,no,ns,ng≤400;
  • 0≤nn≤400;
  • 全部字符串或为 *,或仅包含大写字母、小写字母、数字(A-Za-z0-9),且字符数目不超过 10。

原题链接:角色授权

先说成果,第一次提交运行超时70分,第二次提交满分通过(虽然显示运行时间超过规定的5s,至于原因我也不得清楚)

试题名称 提交时间 代码长度 编程语言 评测结果 得分 时间使用 空间使用
角色授权 07-20 22:17 1.421KB PYTHON 正确 100 11.82s 79.86MB
角色授权 07-20 19:44 2.257KB PYTHON 运行超时 70 运行超时 54.07MB

下面分析解题思路:其实解题思路题目已经给出来了,如下:

判断一个用户能否执行某个操作的过程是:

  1. 检查所有的角色关联的授权对象清单,如果清单中包含该用户的名称,或者该清单中包含该用户所属的某一个用户组的名称,那么选取该角色关联所关联的角色;
  2. 对于所有被选取的角色,判断这些角色是否能对该资源执行该操作,如果所有角色都不能执行该操作,那么不能执行该操作;
  3. 允许执行该操作。

按照这个思路,我的解题做法是创建两个类分别对应角色和角色关联,每个角色实例有四个属性:角色名、操作、资源种类、资源名称,后个属性都用列表表示。每个角色关联有两个属性:关联角色名、关联用户、关联用户组,同样后两个属性用列表表示。

将所有输入的角色实例和角色关联实例创建实例对象,然后对每个输入的待授权的操作,遍历所有角色关联实例,先、后判断用户、用户组是否存在于当前角色关联实例的关联用户属性、关联用户组中,只要满足一个存在则继续判断当前角色关联实例的关联角色属性对应的角色实例能否授权操作。如果能,记录当前操作为授权(也就是1),然后接受下一个待授权操作,否则,遍历下一个角色关联实例,重复步骤直到所有角色关联实例都遍历完,若此时当前操作依然未被授权,则记录当前操作为不授权(也就是0),然后接受下一个待授权操作。

下面为代码实现(70分)

def pd(jsm,czm,zylm,zym):
    jsm = jname[jsm]
    if jsm.pcz(czm) == 0:
        return 0
    if jsm.pzyl(zylm) == 0:
        return 0
    if jsm.pzy(zym) == 0:
        return 0
    return 1
class js:
    def __init__(self,name):
        self.name = name
        self.czj0 = 0
        self.zylj0 = 0
    def cz(self,czj):
        self.czj = czj
        if '*' in czj:
            self.czj0 = 1
    def zyl(self,zylj):
        self.zylj = zylj
        if '*' in zylj:
            self.zylj0 = 1
    def zy(self,zyj):
        self.zyj = zyj
    def pcz(self,cz):
        if self.czj0 == 1:
            return 1
        if cz in self.czj:
            return 1
        else:return 0
    def pzyl(self,zyl):
        if self.zylj0 == 1:
            return 1
        if zyl in self.zylj:
            return 1
        else:return 0
    def pzy(self,zyj):
        if self.zyj == []:
            return 1
        if zyj in self.zyj:
            return 1
        else:return 0
class jsl:
    def __init__(self,name):
        self.name = name
        self.u = []
        self.g = []
    def uyh(self,udx):
        self.u.append(udx)
    def gyh(self, gdx):
            self.g.append(gdx)
    def udx(self,yh):
        if yh in self.u:
            return 1
        else:return 0
    def gdx(self,yhz):
        for yh in yhz:
            if yh in self.g:
                return 1
        return 0
n,m,q = map(int,input().split())
jsj = []
jslj = []
jname = {}
for i in range(n):
    L = list(input().split())
    jsj.append(js(L[0]))
    jname[L[0]] = jsj[i]
    nv = int(L[1])
    no = int(L[2 + nv])
    nn = int(L[3 + nv + no])
    jsj[i].cz(L[2:2+nv])
    jsj[i].zyl(L[3+nv:3+nv+no])
    jsj[i].zy(L[4+nv+no:])
for i in range(m):
    L = list(input().split())
    jslj.append(jsl(L[0]))
    ns = int(L[1])
    for j in range(ns):
        if L[2*(j+1)] == 'u':
            jslj[i].uyh(L[2*(j+1)+1])
        else:jslj[i].gyh(L[2*(j+1)+1])
sc = [0]*q
for i in range(q):
    L = list(input().split())
    yh = L[0]
    ng = int(L[1])
    yhz = L[2:ng+2]
    dcz = L[-3]
    dzyl = L[-2]
    dzy = L[-1]
    for j in jslj:
        if j.udx(yh) == 1 or j.gdx(yhz) == 1:
            if pd(j.name,dcz,dzyl,dzy) == 1:
                sc[i] = 1
                break
for i in sc:
    print(i)

提交后显示运行超时,分析原因:在最坏情况下,对每个待授权操作,都是要遍历所有角色关联实例,继而遍历角色关联实例下所有用户和用户组。当数据量巨大时,超时是必然的。

解决方法:可以将角色关联实例反过来做成用户关联实例和用户组关联实例,这样可以直接找到关联当前用户或用户组的角色。

另外优化:每个角色都要创建实例对象,这同样会耗费大量时间,单从解题目的出发,其实可以不创建实例对象,直接对输入的信息操作。

代码实现如下(满分):

def pc(x,y):
    if '*' in x or y in x:
        return 1
    return 0
def pzy(x,y):
    if x == [] or y in x:
        return 1
    return 0
def pd(jsm,czm,zylm,zym):
    if jsm in jname:
        p = jname[jsm]
    else:return 0
    nv = int(p[1])
    no = int(p[2 + nv])
    if pc(p[2:2+nv],czm) == 0:
        return 0
    if pc(p[3+nv:3+nv+no],zylm) == 0:
        return 0
    if pzy(p[4+nv+no:],zym) == 0:
        return 0
    return 1
n,m,q = map(int,input().split())
jsg = {}
jsu = {}
jname = {}
for i in range(n):
    L = list(input().split())
    jname[L[0]] = L
for i in range(m):
    L = list(input().split())
    ns = int(L[1])
    for j in range(ns):
        if L[2*(j+1)] == 'u':
            if L[2*(j+1)+1] in jsu:
                jsu[L[2*(j+1)+1]].add(L[0])
            else:
                jsu[L[2*(j+1)+1]] = {L[0]}
        else:
            if L[2*(j+1)+1] in jsg:
                jsg[L[2*(j+1)+1]].add(L[0])
            else:
                jsg[L[2*(j+1)+1]] = {L[0]}

sc = [0]*q
for j in range(q):
    L = list(input().split())
    yh = L[0]
    ng = int(L[1])
    yhz = L[2:ng+2]
    dcz = L[-3]
    dzyl = L[-2]
    dzy = L[-1]
    if yh in jsu:
        jsj = jsu[yh]
    else:
        jsj = set()
    for i in yhz:
        if i in jsg:
            jsj = jsj.union(jsg[i])
    if len(jsj) == 0:
        continue
    for i in jsj:
        if pd(i, dcz, dzyl, dzy) == 1:
            sc[j] = 1
            break
for i in sc:
    print(i)

你可能感兴趣的:(CSP解题,python,算法)