1
0
mirror of https://github.com/kakwa/ldapcherry synced 2024-06-09 12:27:50 +02:00
ldapcherry/ldapcherry/roles.py

264 lines
8.6 KiB
Python
Raw Normal View History

2015-04-15 21:13:14 +02:00
# -*- coding: utf-8 -*-
# vim:set expandtab tabstop=4 shiftwidth=4:
#
# The MIT License (MIT)
# LdapCherry
# Copyright (c) 2014 Carpentier Pierre-Francois
import os
import sys
2015-06-05 00:49:13 +02:00
import copy
2015-04-15 21:13:14 +02:00
2015-05-14 22:10:26 +02:00
from sets import Set
2015-05-12 01:24:16 +02:00
from ldapcherry.pyyamlwrapper import loadNoDump
from ldapcherry.pyyamlwrapper import DumplicatedKey
from ldapcherry.exceptions import DumplicateRoleKey, MissingKey, DumplicateRoleContent, MissingRolesFile, MissingRole
2015-05-15 01:03:31 +02:00
import yaml
2015-05-12 01:24:16 +02:00
2015-05-15 01:03:31 +02:00
class CustomDumper(yaml.SafeDumper):
"A custom YAML dumper that never emits aliases"
def ignore_aliases(self, _data):
return True
2015-04-15 21:13:14 +02:00
class Roles:
def __init__(self, role_file):
2015-05-14 22:10:26 +02:00
self.role_file = role_file
self.backends = Set([])
try:
stream = open(role_file, 'r')
except:
raise MissingRolesFile(role_file)
try:
self.roles_raw = loadNoDump(stream)
except DumplicatedKey as e:
raise DumplicateRoleKey(e.key)
2015-05-12 01:24:16 +02:00
stream.close()
self.graph = {}
2015-05-15 01:03:31 +02:00
self.roles = {}
self.flatten = {}
self.admin_roles = []
2015-05-12 01:24:16 +02:00
self._nest()
2015-04-15 21:13:14 +02:00
def _merge_groups(self, backends_list):
2015-06-05 00:49:13 +02:00
""" merge a list backends_groups"""
ret = {}
for backends in backends_list:
for b in backends:
if not b in ret:
ret[b] = Set([])
for group in backends[b]:
ret[b].add(group)
2015-06-05 00:49:13 +02:00
for b in ret:
ret[b] = list(ret[b])
return ret
2015-06-05 00:49:13 +02:00
def _flatten(self, roles=None, groups=None):
""" flatten a (semi) nest roles structure"""
if roles is None:
2015-06-05 00:49:13 +02:00
roles_in = copy.deepcopy(self.roles_raw)
else:
roles_in = roles
for roleid in roles_in:
role = roles_in[roleid]
if not groups is None:
role['backends_groups'] = self._merge_groups([role['backends_groups'], groups])
if 'subroles' in role:
2015-06-05 00:49:13 +02:00
self._flatten(role['subroles'],
role['backends_groups'])
del role['subroles']
self.flatten[roleid] = role
def _set_admin(self, role):
for r in role['subroles']:
self.admin_roles.append(r)
self._set_admin(role['subroles'][r])
2015-05-15 01:03:31 +02:00
def _is_parent(self, roleid1, roleid2):
"""Test if roleid1 is contained inside roleid2"""
2015-06-05 00:49:13 +02:00
role2 = self.flatten[roleid2]
role1 = self.flatten[roleid1]
2015-05-15 01:03:31 +02:00
if role1 == role2:
return False
2015-05-15 01:03:31 +02:00
# Check if role1 is contained by role2
for b1 in role1['backends_groups']:
if not b1 in role2['backends_groups']:
2015-05-15 01:03:31 +02:00
return False
for group in role1['backends_groups'][b1]:
if not group in role2['backends_groups'][b1]:
2015-05-15 01:03:31 +02:00
return False
# If role2 is inside role1, roles are equal, throw exception
for b2 in role2['backends_groups']:
if not b2 in role1['backends_groups']:
2015-05-15 01:03:31 +02:00
return True
for group in role2['backends_groups'][b2]:
if not group in role1['backends_groups'][b2]:
2015-05-15 01:03:31 +02:00
return True
raise DumplicateRoleContent(roleid1, roleid2)
2015-05-12 01:24:16 +02:00
def _nest(self):
2015-04-15 21:13:14 +02:00
"""nests the roles (creates roles hierarchy)"""
2015-06-05 00:49:13 +02:00
self._flatten()
2015-05-15 01:03:31 +02:00
parents = {}
2015-06-05 00:49:13 +02:00
for roleid in self.flatten:
role = self.flatten[roleid]
2015-05-14 22:10:26 +02:00
# Display name is mandatory
if not 'display_name' in role:
raise MissingKey('display_name', role, self.role_file)
# Backend is mandatory
if not 'backends_groups' in role:
raise MissingKey('backends_groups', role, self.role_file)
2015-05-14 22:10:26 +02:00
# Create the list of backends
for backend in role['backends_groups']:
2015-05-15 01:03:31 +02:00
self.backends.add(backend)
2015-05-14 22:10:26 +02:00
# Create the nested groups
2015-06-05 00:49:13 +02:00
for roleid in self.flatten:
role = self.flatten[roleid]
2015-05-15 01:03:31 +02:00
parents[roleid]=[]
2015-06-05 00:49:13 +02:00
for roleid2 in self.flatten:
role2 = self.flatten[roleid2]
2015-05-15 01:03:31 +02:00
if self._is_parent(roleid, roleid2):
parents[roleid].append(roleid2)
for r in parents:
for p in parents[r]:
for p2 in parents[r]:
if p != p2 and p in parents[p2]:
parents[r].remove(p)
def nest(p):
2015-06-05 00:49:13 +02:00
ret = self.flatten[p]
2015-05-15 01:03:31 +02:00
ret['subroles'] = {}
if len(parents[p]) == 0:
return ret
else:
for i in parents[p]:
sub = nest(i)
ret['subroles'][i] = sub
return ret
2015-04-15 21:13:14 +02:00
2015-05-15 01:03:31 +02:00
for p in parents.keys():
if p in parents:
self.roles[p] = nest(p)
for roleid in self.roles:
role = self.roles[roleid]
# Create the list of roles which are ldapcherry admins
if 'LC_admins' in role and role['LC_admins']:
self.admin_roles.append(roleid)
self._set_admin(role)
def get_admin_roles(self):
return self.admin_roles
2015-05-15 01:03:31 +02:00
def dump_nest(self):
"""dump the nested role hierarchy"""
2015-05-15 01:03:31 +02:00
return yaml.dump(self.roles, Dumper=CustomDumper)
2015-04-15 21:13:14 +02:00
2015-06-05 00:49:13 +02:00
def dump_flatten(self):
"""dump the nested role hierarchy"""
return yaml.dump(self.flatten, Dumper=CustomDumper)
2015-05-16 19:11:52 +02:00
def _check_member(self, role, groups, notroles, roles, parentroles, usedgroups):
# if we have already calculate user is not member of role
# return False
if role in notroles:
return False
# if we have already calculate that user is already member, skip
# role membership calculation
# (parentroles is a list of roles that the user is member of by
# being member of one of their subroles)
if not (role in parentroles or role in roles):
for b in self.roles[role]['backends_groups']:
for g in self.roles[role]['backends_groups'][b]:
if b not in groups:
notroles.add(role)
return False
if not g in groups[b]:
notroles.add(role)
return False
# add groups of the role to usedgroups
for b in self.roles[role]['backends_groups']:
if not b in usedgroups:
usedgroups[b] = Set([])
for g in self.roles[role]['backends_groups'][b]:
usedgroups[b].add(g)
flag = True
# recursively determine if user is member of any subrole
for subrole in self.roles[role]['subroles']:
flag = flag and not self._check_member(subrole, groups, notroles, roles, parentroles, usedgroups)
# if not, add role to the list of roles
if flag:
roles.add(role)
# else remove it from the list of roles and add
# it to the list of parentroles
else:
if role in roles:
roles.remove(role)
parentroles.add(role)
return True
2015-04-15 21:13:14 +02:00
def get_roles(self, groups):
"""get list of roles and list of standalone groups"""
roles = Set([])
parentroles = Set([])
notroles = Set([])
usedgroups = {}
unusedgroups = {}
ret = {}
# determine roles membership
2015-05-16 19:11:52 +02:00
for role in self.roles:
self._check_member(role, groups, notroles, roles, parentroles, usedgroups)
# determine standalone groups not matching any roles
for b in groups:
for g in groups[b]:
if not b in usedgroups or not g in usedgroups[b]:
if b not in unusedgroups:
unusedgroups[b] = Set([])
unusedgroups[b].add(g)
ret['roles'] = roles
ret['unusedgroups'] = unusedgroups
return ret
2015-04-15 21:13:14 +02:00
def get_allroles(self):
"""get the list of roles"""
2015-06-05 00:49:13 +02:00
return self.flatten.keys()
def get_display_name(self, role):
"""get the display name of a role"""
2015-06-05 00:49:13 +02:00
if not role in self.flatten:
raise MissingRole(role)
2015-06-05 00:49:13 +02:00
return self.flatten[role]['display_name']
def get_groups(self, role):
"""get the list of groups from role"""
2015-06-05 00:49:13 +02:00
if not role in self.flatten:
raise MissingRole(role)
2015-06-05 00:49:13 +02:00
return self.flatten[role]['backends_groups']
def is_admin(self, roles):
"""determine from a list of roles if is ldapcherry administrator"""
for r in roles:
if r in self.admin_roles:
return True
return False
def get_backends(self):
"""return the list of backends in roles file"""
return self.backends