diff --git a/PythonCode/library.py b/PythonCode/library.py new file mode 100644 index 0000000..1b4573c --- /dev/null +++ b/PythonCode/library.py @@ -0,0 +1,336 @@ +from copy import deepcopy + +class POST: + def __init__(self, state): + self._upa = {} # dictionary (user, set of permissions) + self._permissions = set() + self._orig_ua = dict() # original ua + self._orig_pa = dict() # original pa + self._ua = dict() # post-processed ua + self._pa = dict() # post-processed pa + self._nr = 0 # number of roles + self._state = state # starting RBAC state (file containing a representation of UA and PA) + self._load_ua_pa() + self._users = set(self._orig_ua.keys()) + self._original_users = deepcopy(self._users) + + def _load_ua_pa(self): + f = open(self._state, 'r') + for line in f: + if 'role' in line: + r = int(line.split(':')[1]) + # print('role: ', r) + elif 'permissions' in line: + permissions = line.split(':')[1] + permissions = set(map(int, permissions.split(','))) + self._orig_pa[r] = permissions + # print('permissions: ', pa[r]) + elif 'users' in line: + users = line.split(':')[1] + users = set(map(int, users.split(','))) + # print('users: ', users) + + for u in users: + if u in self._orig_ua.keys(): + self._orig_ua[u].add(r) + else: + self._orig_ua[u] = {r} + + if u in self._upa: + self._upa[u].update(permissions) + else: + self._upa[u] = deepcopy(permissions) + f.close() + + def _update_ua_pa(self, usrs, role): + + if role not in self._pa.values(): + self._nr += 1 + self._pa[self._nr] = role + found = self._nr + else: + found = [r for (r, prms) in self._pa.items() if prms == role][0] + + for u in usrs: + if u in self._ua: + self._ua[u].add(found) + else: + self._ua[u] = {found} + + def _cs(self): + if len(self._ua) != len(self._original_users): + #print('Failed #users', '*mined users', len(self._ua), '#orig users', len(self._original_users)) + return False + + flag = True + for u in self._ua: + perms = set() + for r in self._ua[u]: + perms.update(self._pa[r]) + if perms != self._upa[u]: + # print('user', u) + # print('original', self._upa[u]) + # print('assigned', perms) + # print() + flag = False, u + break + #return False + return flag + + + def check_solution(self): + if set(self._ua.keys()) != set(self._orig_ua.keys()): + return False + + for u in self._ua.keys(): + s1 = set() + s2 = set() + for r in self._orig_ua[u]: + s1.update(self._orig_pa[r]) + for r in self._ua[u]: + s2.update(self._pa[r]) + + if s1 != s2: + return False + + return True + + def _check_soundness_starting_state(self): + if set(self._orig_ua.keys()) != set(self._upa.keys()): + return False + + for u in self._orig_ua.keys(): + s = set() + for r in self._orig_ua[u]: + s.update(self._orig_pa[r]) + + if s != self._upa[u]: + return False + + return True + + def get_wsc(self): + nroles = len(self._pa.keys()) + ua_size = 0 + for roles in self._ua.values(): + ua_size += len(roles) + pa_size = 0 + for prms in self._pa.values(): + pa_size += len(prms) + return nroles + ua_size + pa_size, nroles, ua_size, pa_size + +class Mining: + def __init__(self, dataset, unique = False): + if type(dataset) != str and type(dataset) != dict: + raise Exception('Dataset error: wrong format') + + self._users = set() + self._permissions = set() + self._upa = {} # dictionary (user, set of permissions) + self._upa_unique = {} # dictionary (user, set of permissions) only users with distinct set of permissions + self._pua = {} # dictionary (permission, set of users) + self._ua = {} # dictionary (user, set of roles) + self._pa = {} # dictionary (role, set of permissions) + self._k = 0 # mined roles so far + self._n = 0 # total number of granted access to resources (i.e., number of pairs in dataset) + + if type(dataset) == str: + self._dataset = dataset + self._load_upa() + else: # the dataset is represented by a dictionary (UPA) + self._dataset = '-- direct upa inizialization --' + self._upa = dataset + self._users = set(self._upa.keys()) + for u, prms in self._upa.items(): + self._permissions = self._permissions.union(prms) + self._n += len(prms) + for p in prms: + if p in self._pua: + self._pua[p].add(u) + else: + self._pua[p] = {u} + + if unique: # collapse users having the same set of permissions to just one user + self._unique_users() + + self._unc_upa = deepcopy(self._upa) + self._unc_pua = deepcopy(self._pua) + self._unc_users = deepcopy(self._users) + self._unc_permissions = deepcopy(self._permissions) + + def _load_upa(self): + with open(self._dataset) as f: + for u_p in f: + (user, permission) = u_p.split() + user = int(user.strip()) + permission = int(permission.strip()) + + if user in self._users: + if permission not in self._upa[user]: + self._upa[user].add(permission) + self._n = self._n + 1 + else: + self._users.add(user) + self._upa[user] = {permission} + self._n = self._n + 1 + + if permission in self._permissions: + self._pua[permission].add(user) + else: + self._permissions.add(permission) + self._pua[permission] = {user} + f.close() + + def roles(self): + return self._pa + + def _unique_users(self): + self._users_bk = deepcopy(self._users) # users backup + self._upa_bk = deepcopy(self._upa) # upa backup + self._users_map = dict() #key = user, value=list of users with identical permissions + equal_prms = dict() + for u in self._users: + equal_prms[u] = u + self._upa = dict() + + for u_i in sorted(self._upa_bk.keys()): + for u_j in sorted(self._upa_bk.keys()): + if u_j > u_i and equal_prms[u_j] == u_j and self._upa_bk[u_j] == self._upa_bk[u_i]: + equal_prms[u_j] = u_i #u_j's permissions are identical to u_i's ones + + + for k, v in equal_prms.items(): + if v not in self._users_map: + self._users_map[v] = [k] + else: + self._users_map[v].append(k) + + # reduced user-permission association + for u in self._users_map: + self._upa[u] = deepcopy(self._upa_bk[u]) + + self._users = set(self._users_map.keys()) + + def _update_ua_pa(self, usrs, prms): + idx_f = 0 + for (idx, r) in self._pa.items(): + if r == prms: + idx_f = idx + break + else: + self._k += 1 + self._pa[self._k] = prms + idx_f = self._k + + for u in usrs: + if u in self._ua: + self._ua[u].add(idx_f) + else: + self._ua[u] = {idx_f} + + def _update_unc(self, usrs, prms): + for u in usrs: + self._unc_upa[u] = self._unc_upa[u] - prms + if len(self._unc_upa[u]) == 0: + self._unc_users.remove(u) + for p in prms: + self._unc_pua[p] = self._unc_pua[p] - usrs + if len(self._unc_pua[p]) == 0 and p in self._unc_permissions: + self._unc_permissions.remove(p) + + def get_wsc(self): + nroles = len(self._pa.keys()) + ua_size = 0 + for roles in self._ua.values(): + ua_size += len(roles) + pa_size = 0 + for prms in self._pa.values(): + pa_size += len(prms) + return nroles + ua_size + pa_size, nroles, ua_size, pa_size + + def _check_solution(self): + for u in self._users: + if u not in self._ua.keys(): + return 1, False + perms = set() + for r in self._ua[u]: + perms.update(self._pa[r]) + if perms != self._upa[u]: + return 2, False + return True + + def _check_unused_roles(self): + roles = set() + for r in self._ua.values(): + roles.update(r) + if roles != set(self._pa.keys()): + return True + else: + return False + + def print_roles(self): + sr = sorted(self._pa.items(), key = lambda role : len(role[1])) + for r in sr: + print(r) + + def __str__(self): + to_return = '-- dati dataset/esperimento --\n' + to_return = to_return + self._dataset + '\n' + to_return = to_return + '#utenti:' + str(len(self._users)) + '\n' + to_return = to_return + '#permessi:' + str(len(self._permissions)) + '\n' + to_return = to_return + '|upa|=' + str(self._n) + '\n' + return to_return + + + def check_duplicates(self): + print('-- check duplicates --') + tmp_roles = list(self._pa.values()) + print(' #initial roles', len(tmp_roles)) + roles = [] + for r in tmp_roles: + if r not in roles: + roles.append(r) + print(' #final roles', len(roles)) + if len(tmp_roles) == len(roles): + print(' No duplicated roles') + else: + print(' DUPLICATED roles') + + def duplicated_users(self): + tmp_users = [] + for u in self._upa.values(): + if u not in tmp_users: + tmp_users.insert(1, u) + else: + print(u, 'giĆ  presente') + print(len(self._users), ' ', len(tmp_users)) + if len(tmp_users) != len(self._users): + return True + else: + return False + + def get_dupa(self): + _dupa = 0 + for u in self._users: + if u not in self._ua.keys(): + _dupa = _dupa + len(self._upa[u]) + else: + prms = set() + for r in self._ua[u]: + prms = prms.union(self._pa[r]) + if prms.issubset(self._upa[u]): + _dupa = _dupa + len(self._upa[u] - prms) + else: + print('ERROR!!!') + exit(0) + return _dupa + + def verify(self): + num_perms = 0 + for u in self._ua.keys(): + prms = set() + for r in self._ua[u]: + prms = prms.union(self._pa[r]) + num_perms = num_perms + len(prms) + dupa = self._n - num_perms + return dupa diff --git a/PythonCode/test_udcc.py b/PythonCode/test_udcc.py new file mode 100644 index 0000000..24c972f --- /dev/null +++ b/PythonCode/test_udcc.py @@ -0,0 +1,187 @@ +import sys +import time +from udcc import * + +base_dir = 'decompositions/' + +ds_range = {'americas_large': (278, 556, 834, 1111, 1389, 1667, 1944, 2222, 2500, 2777, 3055, 3333), + 'americas_small': (281, 562, 843, 1124, 1405, 1686, 1967, 2248, 2529, 2809, 3090, 3371), + 'apj': (28, 56, 84, 112, 139, 167, 195, 223, 251, 278, 306, 334), + 'customer': (419, 837, 1256, 1674, 2092, 2511, 2929, 3348, 3766, 4184, 4603, 5021), + 'domino': (6, 11, 16, 21, 26, 31, 36, 41, 46, 51, 57, 62), + 'emea': (1, 2, 3, 4), + 'fire1': (21, 41, 61, 82, 102, 122, 143, 163, 183, 203, 224, 244), + 'fire2': (24, 48, 72, 96, 120, 144, 168, 192, 216, 239, 263, 287), + 'hc': (3, 6, 9, 11, 14, 17, 19, 22, 25, 27, 30, 33) + } + +datasets = ['americas_large', + 'americas_small', + 'apj', + 'customer', + 'domino', + 'emea', + 'fire1', + 'fire2', + 'hc'] + +dataset_name = dict(map(lambda d: (d, ' '.join(d.split('_')).title()), datasets)) +dataset_name['hc'] = 'Healthcare' +dataset_name['fire1'] = 'Firewall 1' +dataset_name['fire2'] = 'Firewall 2' + +decomp_names = ['_optimal_cover.txt', + '_org_row.txt', + '_unc_row.txt', + '_org_col.txt', + '_unc_col.txt', + '_fastMin.txt', + '_fastMin_v2.txt', + '_obmd.txt', + '_biclique.txt'] + +# if heuristics='both', then test_udcc executes the heuristics first on the starting decomposition +# and then on the reduced decomposition +def test_udcc(dataset, decompositions, murs, heuristics='reduced', output='terminal'): + all_wsc = '' + all_nr = '' + all_time = '' + starting_dataset = 'datasets/' + dataset + '.txt' + for mur in murs: + print('mur:', mur) + l_nr = list() + l_wsc = list() + l_time = list() + start = time.process_time_ns() + state = UDCC_1(starting_dataset, mur) + state.mine() + span = time.process_time_ns() - start + wsc, nr, _, _ = state.get_wsc() + l_nr.append(nr) + l_wsc.append(wsc) + l_time.append(span // 1000) + + start = time.process_time_ns() + state = UDCC_2(starting_dataset, mur) + state.mine() + span = time.process_time_ns() - start + wsc, nr, _, _ = state.get_wsc() + l_nr.append(nr) + l_wsc.append(wsc) + l_time.append(span // 1000) + + for decomposition in decompositions: + starting_state = base_dir + dataset + decomposition + if heuristics == 'both': + if dataset == 'customer' and 'optimal' in decomposition: + nr, wsc, span = 0, 0, 0 + else: + start = time.process_time_ns() + state = POST_UDCC(starting_state, mur) + state.mine() + span = time.process_time_ns() - start + wsc, nr, _, _ = state.get_wsc() + l_nr.append(nr) + l_wsc.append(wsc) + l_time.append(span // 1000) + + if dataset == 'customer' and 'optimal' in decomposition: + nr, wsc, span = 0, 0, 0 + else: + start = time.process_time_ns() + state = POST_UDCC(starting_state, mur, True) # remove redundant/unused roles + state.mine() + span = time.process_time_ns() - start + wsc, nr, _, _ = state.get_wsc() + l_nr.append(nr) + l_wsc.append(wsc) + l_time.append(span // 1000) + + m_nr = min(l_nr) + s_nr = f'{mur:>4}' + m_wsc = min(l_wsc) + s_wsc = f'{mur:>4}' + m_tm = min(l_time) + s_tm = f'{mur:>4}' + + for i in range(len(l_wsc)): + if l_nr[i] <= m_nr: + s_nr = s_nr + ' & \\bf ' + f'{l_nr[i]:>6}' + else: + s_nr = s_nr + ' & ' + f'{l_nr[i]:>10}' + + if l_wsc[i] <= m_wsc: + s_wsc = s_wsc + ' & \\bf ' + f'{l_wsc[i]:>6}' + else: + s_wsc = s_wsc + ' & ' + f'{l_wsc[i]:>10}' + + if l_time[i] <= m_tm: + s_tm = s_tm + ' & \\bf ' + f'{l_time[i]:>6}' + else: + s_tm = s_tm + ' & ' + f'{l_time[i]:>10}' + + all_nr = all_nr + s_nr + '\\\ \n' + all_wsc = all_wsc + s_wsc + '\\\ \n' + all_time = all_time + s_tm + '\\\ \n' + + header = '\\begin{table}[h]\n' + \ + '\centering\n' + \ + '\small{\n' + \ + '\\begin{tabular}{c' + 'r' * (len(decompositions) + 1) * 2 + '} \hline \n' + + header += ' mru & ' + fields = ['A1', 'A2'] + for i in range(1, len(decompositions) + 1): + if heuristics == 'both': + fields.append('d' + str(i)) + fields.append('r' + str(i)) + + for h in fields: + header = header + f'{h:^10}' + ' & ' + + if output == 'file': + stdout = sys.stdout + sys.stdout = open('UDCC_experiments/' + dataset + '.tex', 'w') + + print(f'\\section{{Dataset {dataset_name[dataset]}}}') + print() + print(header[:-3] + '\\\ ') + print(all_nr) + footer_r = '\end{tabular}\n' + \ + f'\caption{{Role-set size for dataset {dataset_name[dataset]} }}\n' + \ + f'\label{{tab_{dataset}_r}}' + \ + '\n}\n\end{table}' + print(footer_r) + print('\n') + + print(header[:-3] + '\\\ ') + print(all_wsc) + footer_w = '\end{tabular}\n' + \ + f'\caption{{$WSC$ values for dataset {dataset_name[dataset]} }}\n' + \ + f'\label{{tab_{dataset}_w}}' + \ + '\n}\n\end{table}' + print(footer_w) + print('\\clearpage') + + print(header[:-3] + '\\\ ') + print(all_time) + footer_t = '\end{tabular}\n' + \ + f'\caption{{Execution times for dataset {dataset_name[dataset]} }}\n' + \ + f'\label{{tab_{dataset}_t}}' + \ + '\n}\n\end{table}' + print(footer_t) + print('\\clearpage') + print('\n') + + if output == 'file': + sys.stdout.close() + sys.stdout = stdout + + + +if __name__ == '__main__': + pass + for ds_name, tics in ds_range.items(): + print(ds_name, tics) + if 'hc' in ds_name: + test_udcc(ds_name, decomp_names, tics, output='terminal') \ No newline at end of file diff --git a/PythonCode/udcc.py b/PythonCode/udcc.py new file mode 100644 index 0000000..f70843f --- /dev/null +++ b/PythonCode/udcc.py @@ -0,0 +1,438 @@ +import abc +import random +from copy import deepcopy +from library import POST +from library import Mining + + +class POST_UDCC(POST): + def __init__(self, state, mur, reduce=False): + super().__init__(state) + self._ur = dict() # key: role - values: users assigned to key + self._mur = mur # maximum users per role + self._reduce = reduce + self._ua = deepcopy(self._orig_ua) + self._pa = deepcopy(self._orig_pa) + + def _update_ur(self): + for user, roles in self._ua.items(): + for r in roles: + if r not in self._ur: + self._ur[r] = [user] + else: + self._ur[r].append(user) + + def redundant_roles(self): + self._redundant = dict() + for user, roles in self._ua.items(): + to_check = sorted([(r, self._pa[r]) for r in roles], key=lambda t: len(t[1])) + # print(user, to_check) + for i in range(len(to_check) - 1): + for j in range(i + 1, len(to_check)): + if to_check[i][1] <= to_check[j][1]: + if user in self._redundant: + self._redundant[user].add(to_check[i][0]) + else: + self._redundant[user] = {to_check[i][0]} + + return self._redundant + + def remove_redundant_roles(self): + for user, roles in self._redundant.items(): + if not (roles <= self._ua[user]): + print('ERROR!!!!') + self._ua[user] = self._ua[user] - roles + + def unused_roles(self): + all_roles = set() + for roles in self._ua.values(): + all_roles.update(roles) + + return set(self._pa.keys()) - all_roles if set(self._pa.keys()) != all_roles else {} + + def remove_unused_roles(self, to_remove): + for role in to_remove: + del self._pa[role] + + def mine(self): + if self._reduce: # first remove reduntant roles then remove, if any, unused roles + if self.redundant_roles(): + self.remove_redundant_roles() + if u_r := self.unused_roles(): + self.remove_unused_roles(u_r) + + self._update_ur() + nr = max(self._pa.keys()) + for role, users in self._ur.items(): + if len(users) > self._mur: + i_u = 0 # number of users for which we modified the role assignments + for u in users[self._mur:]: + self._ua[u].remove(role) + if i_u % self._mur == 0: + nr += 1 + self._pa[nr] = deepcopy(self._pa[role]) + i_u += 1 + self._ua[u].add(nr) + + +class STRICT_UDCC(Mining): + def __init__(self, dataset, mur, access_matrix='upa', criterion='min', num_iter=10): + super().__init__(dataset) + self._mur = len(self._users) if mur == 0 else mur # maximum users per role + self._num_iter = num_iter # number of times the heuristic tries to generate pair of roles (see _split) + self._au = dict() # key: role - value: number of users assogned to key + self._forbidden_roles = list() # role assigned to mur users + self._dupa = dict() # direct user-to-permission assignment + + # use the original UPA or the entries left uncovered in UPA + self._matrix = self._upa if access_matrix == 'upa' else self._unc_upa + + # select the minimum weight row (criterion ='min') or the maximum weight row + self._selection = min if criterion == 'min' else max + + def _pick_role(self): + # select a pair (user, role) according the fixed criterion in the specified access_matrix + u, prms = self._selection([(u, self._matrix[u]) for u in self._unc_users], + key=lambda t: len(t[1])) + prms = self._unc_upa[u] + if prms not in self._forbidden_roles: + to_return = [u, prms] # return user and role + else: # split the role as it already reached the UDCC constraint (i.e., mur) + # print('FORBIDEN ROLE', prms) + # Cannot split a role with a single permission it will be handled by DUPA + if len(prms) == 1: + roles = [None, None] + else: + roles = self._split(prms) + # print('returned by _split: ', roles) + to_return = [u, roles[0], roles[1]] + + return to_return + + def _split(self, prms): + # any considered role is a proper subset of prms + all_contained_roles = [(role, self._au[r]) for r, role in self._pa.items() + if role < prms and self._au[r] < self._mur] + + # first check pairs of existing roles satisfying the UDCC constraint + to_check = list() + for i in range(len(all_contained_roles) - 1): + for j in range(i + 1, len(all_contained_roles)): + if all_contained_roles[i][0].union(all_contained_roles[j][0]) == prms: + to_check.append((all_contained_roles[i][0], all_contained_roles[j][0], + all_contained_roles[i][1] + all_contained_roles[j][1])) + + # If no pair of existing roles covers prms, consider any contained role + # in prms and its complement with respect to prms. Consider the complement + # only if it is not a mined role (i.e., it does not appear in PA) + if not to_check: + for (role, nau) in all_contained_roles: + if prms - role not in self._pa.values(): + to_check.append((role, prms - role, nau)) + + if to_check: + # If some roles pair has been found, take the one with least sum mur values (i.e., min nau) + to_return = min(to_check, key=lambda t: t[2])[:2] # take the first two elements (i.e., the roles) + else: + # try num_iter times to generate two random new roles covering prms + i = 0 + while i < self._num_iter: + i += 1 + np = random.randint(1, len(prms) - 1) # len(prms) -1 to avoid the generation of prms + r1 = set(random.sample(list(prms), np)) + r2 = prms - r1 + + if r1 in self._pa.values() or r2 in self._pa.values(): + continue # if either r1 or2 already has been mined, try again + else: + to_return = [r1, r2] + break + else: # If roles generation fails num_iter times, give up and handle prms by DUPA + to_return = [None, None] # no roles found + + # print('SPLIT:', to_return) + return to_return + + def _update_ua_pa(self, u_to_add, prms): # _u_ is not used + usrs = set() + # Look for role's index, if any + idx = 0 + if in_pa := [r for (r, role) in self._pa.items() if role == prms]: + idx = in_pa[0] + + ''' + if idx: + if self._au[idx] >= self._mur: + print('SOMETHING WENT WRONG IN PICKING ROLE', idx) + print('roles:') + for id_r, prms_r in self._pa.items(): + print(id_r, prms_r) + print('au\n', self._au) + ''' + + # If the role induced by prms is new, than add it to PA + if not idx: # prms represents a new role + self._k += 1 + idx = self._k + self._pa[idx] = deepcopy(prms) + self._au[idx] = 0 + + # users possessing all permissions in prms some of that have not been covered yet + user_to_consider = [usr for usr in self._unc_users if prms.issubset(self._upa[usr]) and + prms.intersection(self._unc_upa[usr])] + + # Done to add user u_to_add to the set of users the role induced by prms + user_to_consider.remove(u_to_add) + user_to_consider.insert(0, u_to_add) + + for u in user_to_consider: + if self._au[idx] < self._mur: + usrs.add(u) + self._au[idx] += 1 + if u in self._ua: + self._ua[u].add(idx) + else: + self._ua[u] = {idx} + + # If the role (prms) at index idx has already reached the maximum number of + # allowed users (i.e., mur), then mark it as forbidden and stop searching for + # other usrers to assign prms to + if self._au[idx] == self._mur: + self._forbidden_roles.append(self._pa[idx]) + break + else: + break + + return usrs # users that have been assigned role induced by prms + + def mine(self): + while self._unc_users: + result = self._pick_role() # result = [user, r1, r2] (r2 might be not present) + u = result[0] + if result[1] is not None: # assign roles to u and to at most mur other users containing them + for role in result[1:]: + users = self._update_ua_pa(u, role) + # print('affected users:', users) + self._update_unc(users, role) + else: # assign uncovered permissions through DUPA + # print('FILLING DUPA') + self._dupa[u] = deepcopy(self._unc_upa[u]) + self._update_unc({u}, self._unc_upa[u]) + + def check_solution(self): + covered = True + if self._users != set(self._upa.keys()): + print('ERROR: skipped user in UA') + print(set(self._upa.keys()).symmetric_difference(self._users)) + covered = False + + for u in self._users: + if u not in self._ua: + if u not in self._dupa: + print('ERROR: skipped user', u) + covered = False + # break + if self._dupa[u] != self._upa[u]: + print('ERROR: wrong DUPA assignment') + covered = False + # break + else: + perms = set() + for r in self._ua[u]: + perms.update(self._pa[r]) + if u in state._dupa: + perms.update(self._dupa[u]) + + if perms != self._upa[u]: + print('uncovered permissions for user', u, 'uncovered permissions', self._upa[u] - perms) + covered = False + # break + + return covered + + def get_dupa(self): + dupa = 0 + for u, permissions in self._dupa.items(): + dupa += len(permissions) + return dupa + + def verify_dupa_covering(self): + for u in self._dupa: + prms = deepcopy(self._dupa[u]) + for i, r in self._pa.items(): + if r <= self._dupa[u] and self._au[i] < self._mur: + prms = prms - r + + if not prms: + print('ATTENTION!!!') + print(' permissions assigned to user ', u, ' by DUPA can be covered by mined roles') + + +class STRICT_UDCC_REDUCE(STRICT_UDCC, POST_UDCC): + def mine(self): + super().mine() + wsc, nr, ua, pa = self.get_wsc() + dupa = self.get_dupa() + print(f'{nr:>5} & {wsc:>7} & {ua:>7} & {pa:>7} & {dupa:>5}') + + if self.redundant_roles(): + print('redundant roles') + self.remove_redundant_roles() + if u_r := self.unused_roles(): + self.remove_unused_roles(u_r) + + wsc, nr, ua, pa = self.get_wsc() + dupa = self.get_dupa() + print(f'{nr:>5} & {wsc:>7} & {ua:>7} & {pa:>7} & {dupa:>5}') + + + +# abstract class +class UDCC(Mining, abc.ABC): + def __init__(self, dataset, mur=0): + super().__init__(dataset) + self._mur = len(self._users) if mur == 0 else mur # maximum users per role + + @abc.abstractmethod + def _pick_role(self): + pass + + def _update_ua_pa(self, usrs, prms): + self._k += 1 + self._pa[self._k] = prms + for u in usrs: + if u in self._ua: + self._ua[u].add(self._k) + else: + self._ua[u] = {self._k} + + def _update_unc(self, usrs, prms): + for u in usrs: + self._unc_upa[u] = self._unc_upa[u] - prms + if len(self._unc_upa[u]) == 0: + del self._unc_upa[u] + self._unc_users.remove(u) + for p in prms: + if p in self._unc_pua: + self._unc_pua[p] = self._unc_pua[p] - usrs + if len(self._unc_pua[p]) == 0 and p in self._unc_permissions: + del self._unc_pua[p] + self._unc_permissions.remove(p) + + def mine(self): + while len(self._unc_users) > 0: + usrs, prms = self._pick_role() + if usrs: + self._update_ua_pa(usrs, prms) + self._update_unc(usrs, prms) + + +class UDCC_1(UDCC): + def _pick_role(self): + u, prms = min(self._unc_upa.items(), key=lambda t: len(t[1])) + + all_usrs = [(u, self._unc_upa[u]) for u in self._unc_users if prms <= self._unc_upa[u]] + # try also _unc_upa + # all_usrs = [(u, self._unc_upa[u]) for u in self._unc_users if prms <= self._upa[u]] + + all_usrs.sort(key=lambda t: len(t[1]), reverse=True) + usrs = [t[0] for t in all_usrs] + + if len(usrs) > self._mur: + return set(usrs[:self._mur]), prms + else: + return set(usrs), prms + + +class UDCC_2(UDCC): + def _pick_role(self): + u, u_min = min(self._unc_upa.items(), key=lambda t: len(t[1])) + p, p_min = min(self._unc_pua.items(), key=lambda t: len(t[1])) + + usrs, prms = self._pick_role_u(u) if u_min <= p_min else self._pick_role_p(p) + + return usrs, prms + + def _pick_role_u(self, u): # the selected node is a user + prms = self._unc_upa[u] + usrs = [u for u in self._unc_users if prms <= self._unc_upa[u]] + if len(usrs) > self._mur: + return set(usrs[:self._mur]), prms + else: + return set(usrs), prms + + def _pick_role_p(self, p): # the selected node is a permission + all_usrs = list(self._unc_pua[p]) + if len(all_usrs) > self._mur: + usrs = set(all_usrs[:self._mur]) + else: + usrs = set(all_usrs) + + prms = {p for p in self._unc_permissions if usrs <= self._pua[p]} + + return usrs, prms + + +class UDCC_RM_1(UDCC): + def _pick_role(self): + u, prms = min([(u, self._upa[u]) for u in self._unc_users], key=lambda t: len(t[1])) + # print(u, prms) + all_usrs = {usr for usr in self._unc_users if prms <= self._upa[usr]} + #print(all_usrs) + if len(all_usrs) <= self._mur: + usrs = all_usrs + # print(usrs) + else: + all_usrs.remove(u) + new_set = set(list(all_usrs)[:self._mur - 1]) + new_set.add(u) + usrs = new_set + # print(usrs) + + #input('xxx') + return usrs, prms + + +class UDCC_RM_2(UDCC): + def _pick_role(self): + u, prms = min([(u, self._unc_upa[u]) for u in self._unc_users], key=lambda t: len(t[1])) + all_usrs = {usr for usr in self._unc_users if prms <= self._upa[usr]} + # print(u, prms) + all_usrs = {usr for usr in self._unc_users if prms <= self._upa[usr]} + # print(all_usrs) + if len(all_usrs) <= self._mur: + usrs = all_usrs + #print(usrs) + else: + all_usrs.remove(u) + new_set = set(list(all_usrs)[:self._mur - 1]) + new_set.add(u) + usrs = new_set + # print(usrs) + + # input('xxx') + return usrs, prms + + +if __name__ == '__main__': + pass + + + dataset = 'hc' + mur = 4 + dataset_name = 'datasets/' + dataset + '.txt' + state = STRICT_UDCC(dataset_name, mur, access_matrix='unc_upa', criterion='min') + state.mine() + wsc, nr, ua, pa = state.get_wsc() + print('wsc', wsc, '#roles:', nr, '|ua|:', ua, '|pa|:', pa, '|dupa|:', state.get_dupa()) + print('dupa:', state._dupa) + print('covered:', state.check_solution()) + + + dataset = 'americas_large' + mur = 50 + dataset_name = 'datasets/' + dataset + '.txt' + state = STRICT_UDCC_REDUCE(dataset_name, mur, access_matrix='unc_upa', criterion='min') + state.mine() + print('covered:', state.check_solution()) +