Merge patch 16002
This commit is contained in:
@@ -18,128 +18,207 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
"""
|
"""
|
||||||
Fake LDAP server for test harnesses.
|
Fake LDAP server for test harnesses.
|
||||||
|
|
||||||
|
This class does very little error checking, and knows nothing about ldap
|
||||||
|
class definitions. It implements the minimum emulation of the python ldap
|
||||||
|
library to work with nova.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import json
|
||||||
|
|
||||||
from nova import datastore
|
from nova import datastore
|
||||||
|
|
||||||
SCOPE_SUBTREE = 1
|
|
||||||
|
SCOPE_SUBTREE = 2
|
||||||
MOD_ADD = 0
|
MOD_ADD = 0
|
||||||
MOD_DELETE = 1
|
MOD_DELETE = 1
|
||||||
|
|
||||||
SUBS = {
|
|
||||||
'groupOfNames': ['novaProject']
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class NO_SUCH_OBJECT(Exception):
|
class NO_SUCH_OBJECT(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OBJECT_CLASS_VIOLATION(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def initialize(uri):
|
def initialize(uri):
|
||||||
return FakeLDAP(uri)
|
return FakeLDAP()
|
||||||
|
|
||||||
|
|
||||||
|
def _match_query(query, attrs):
|
||||||
|
"""Match an ldap query to an attribute dictionary.
|
||||||
|
|
||||||
|
&, |, and ! are supported in the query. No syntax checking is performed,
|
||||||
|
so malformed querys will not work correctly.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# cut off the parentheses
|
||||||
|
inner = query[1:-1]
|
||||||
|
if inner.startswith('&'):
|
||||||
|
# cut off the &
|
||||||
|
l, r = _paren_groups(inner[1:])
|
||||||
|
return _match_query(l, attrs) and _match_query(r, attrs)
|
||||||
|
if inner.startswith('|'):
|
||||||
|
# cut off the |
|
||||||
|
l, r = _paren_groups(inner[1:])
|
||||||
|
return _match_query(l, attrs) or _match_query(r, attrs)
|
||||||
|
if inner.startswith('!'):
|
||||||
|
# cut off the ! and the nested parentheses
|
||||||
|
return not _match_query(query[2:-1], attrs)
|
||||||
|
|
||||||
|
(k, sep, v) = inner.partition('=')
|
||||||
|
return _match(k, v, attrs)
|
||||||
|
|
||||||
|
|
||||||
|
def _paren_groups(source):
|
||||||
|
"""Split a string into parenthesized groups."""
|
||||||
|
count = 0
|
||||||
|
start = 0
|
||||||
|
result = []
|
||||||
|
for pos in xrange(len(source)):
|
||||||
|
if source[pos] == '(':
|
||||||
|
if count == 0:
|
||||||
|
start = pos
|
||||||
|
count += 1
|
||||||
|
if source[pos] == ')':
|
||||||
|
count -= 1
|
||||||
|
if count == 0:
|
||||||
|
result.append(source[start:pos+1])
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _match(k, v, attrs):
|
||||||
|
"""Match a given key and value against an attribute list."""
|
||||||
|
if k not in attrs:
|
||||||
|
return False
|
||||||
|
if k != "objectclass":
|
||||||
|
return v in attrs[k]
|
||||||
|
# it is an objectclass check, so check subclasses
|
||||||
|
values = _subs(v)
|
||||||
|
for value in values:
|
||||||
|
if value in attrs[k]:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _subs(value):
|
||||||
|
"""Returns a list of subclass strings.
|
||||||
|
|
||||||
|
The strings represent the ldap objectclass plus any subclasses that
|
||||||
|
inherit from it. Fakeldap doesn't know about the ldap object structure,
|
||||||
|
so subclasses need to be defined manually in the dictionary below.
|
||||||
|
|
||||||
|
"""
|
||||||
|
subs = {'groupOfNames': ['novaProject']}
|
||||||
|
if value in subs:
|
||||||
|
return [value] + subs[value]
|
||||||
|
return [value]
|
||||||
|
|
||||||
|
|
||||||
|
def _from_json(encoded):
|
||||||
|
"""Convert attribute values from json representation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
encoded -- a json encoded string
|
||||||
|
|
||||||
|
Returns a list of strings
|
||||||
|
|
||||||
|
"""
|
||||||
|
return [str(x) for x in json.loads(encoded)]
|
||||||
|
|
||||||
|
|
||||||
|
def _to_json(unencoded):
|
||||||
|
"""Convert attribute values into json representation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
unencoded -- an unencoded string or list of strings. If it
|
||||||
|
is a single string, it will be converted into a list.
|
||||||
|
|
||||||
|
Returns a json string
|
||||||
|
|
||||||
|
"""
|
||||||
|
return json.dumps(list(unencoded))
|
||||||
|
|
||||||
|
|
||||||
class FakeLDAP(object):
|
class FakeLDAP(object):
|
||||||
def __init__(self, _uri):
|
#TODO(vish): refactor this class to use a wrapper instead of accessing
|
||||||
self.keeper = datastore.Keeper('fakeldap')
|
# redis directly
|
||||||
if self.keeper['objects'] is None:
|
|
||||||
self.keeper['objects'] = {}
|
|
||||||
|
|
||||||
def simple_bind_s(self, dn, password):
|
def simple_bind_s(self, dn, password):
|
||||||
|
"""This method is ignored, but provided for compatibility."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def unbind_s(self):
|
def unbind_s(self):
|
||||||
|
"""This method is ignored, but provided for compatibility."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _paren_groups(self, source):
|
def add_s(self, dn, attr):
|
||||||
count = 0
|
"""Add an object with the specified attributes at dn."""
|
||||||
start = 0
|
key = "%s%s" % (self.__redis_prefix, dn)
|
||||||
result = []
|
|
||||||
for pos in xrange(len(source)):
|
|
||||||
if source[pos] == '(':
|
|
||||||
if count == 0:
|
|
||||||
start = pos
|
|
||||||
count += 1
|
|
||||||
if source[pos] == ')':
|
|
||||||
count -= 1
|
|
||||||
if count == 0:
|
|
||||||
result.append(source[start:pos+1])
|
|
||||||
|
|
||||||
def _match_query(self, query, attrs):
|
value_dict = dict([(k, _to_json(v)) for k, v in attr])
|
||||||
inner = query[1:-1]
|
datastore.Redis.instance().hmset(key, value_dict)
|
||||||
if inner.startswith('&'):
|
|
||||||
l, r = self._paren_groups(inner[1:])
|
|
||||||
return self._match_query(l, attrs) and self._match_query(r, attrs)
|
|
||||||
if inner.startswith('|'):
|
|
||||||
l, r = self._paren_groups(inner[1:])
|
|
||||||
return self._match_query(l, attrs) or self._match_query(r, attrs)
|
|
||||||
if inner.startswith('!'):
|
|
||||||
return not self._match_query(query[2:-1], attrs)
|
|
||||||
|
|
||||||
(k, sep, v) = inner.partition('=')
|
def delete_s(self, dn):
|
||||||
return self._match(k, v, attrs)
|
"""Remove the ldap object at specified dn."""
|
||||||
|
datastore.Redis.instance().delete("%s%s" % (self.__redis_prefix, dn))
|
||||||
|
|
||||||
def _subs(self, v):
|
def modify_s(self, dn, attrs):
|
||||||
if v in SUBS:
|
"""Modify the object at dn using the attribute list.
|
||||||
return [v] + SUBS[v]
|
|
||||||
return [v]
|
|
||||||
|
|
||||||
def _match(self, k, v, attrs):
|
Args:
|
||||||
if attrs.has_key(k):
|
dn -- a dn
|
||||||
for v in self._subs(v):
|
attrs -- a list of tuples in the following form:
|
||||||
if (v in attrs[k]):
|
([MOD_ADD | MOD_DELETE], attribute, value)
|
||||||
return True
|
|
||||||
return False
|
"""
|
||||||
|
redis = datastore.Redis.instance()
|
||||||
|
key = "%s%s" % (self.__redis_prefix, dn)
|
||||||
|
|
||||||
|
for cmd, k, v in attrs:
|
||||||
|
values = _from_json(redis.hget(key, k))
|
||||||
|
if cmd == MOD_ADD:
|
||||||
|
values.append(v)
|
||||||
|
else:
|
||||||
|
values.remove(v)
|
||||||
|
values = redis.hset(key, k, _to_json(values))
|
||||||
|
|
||||||
def search_s(self, dn, scope, query=None, fields=None):
|
def search_s(self, dn, scope, query=None, fields=None):
|
||||||
#logging.debug("searching for %s" % dn)
|
"""Search for all matching objects under dn using the query.
|
||||||
filtered = {}
|
|
||||||
d = self.keeper['objects'] or {}
|
Args:
|
||||||
for cn, attrs in d.iteritems():
|
dn -- dn to search under
|
||||||
if cn[-len(dn):] == dn:
|
scope -- only SCOPE_SUBTREE is supported
|
||||||
filtered[cn] = attrs
|
query -- query to filter objects by
|
||||||
objects = filtered
|
fields -- fields to return. Returns all fields if not specified
|
||||||
if query:
|
|
||||||
objects = {}
|
"""
|
||||||
for cn, attrs in filtered.iteritems():
|
if scope != SCOPE_SUBTREE:
|
||||||
if self._match_query(query, attrs):
|
raise NotImplementedError(str(scope))
|
||||||
objects[cn] = attrs
|
redis = datastore.Redis.instance()
|
||||||
if objects == {}:
|
keys = redis.keys("%s*%s" % (self.__redis_prefix, dn))
|
||||||
|
objects = []
|
||||||
|
for key in keys:
|
||||||
|
# get the attributes from redis
|
||||||
|
attrs = redis.hgetall(key)
|
||||||
|
# turn the values from redis into lists
|
||||||
|
attrs = dict([(k, _from_json(v))
|
||||||
|
for k, v in attrs.iteritems()])
|
||||||
|
# filter the objects by query
|
||||||
|
if not query or _match_query(query, attrs):
|
||||||
|
# filter the attributes by fields
|
||||||
|
attrs = dict([(k, v) for k, v in attrs.iteritems()
|
||||||
|
if not fields or k in fields])
|
||||||
|
objects.append((key[len(self.__redis_prefix):], attrs))
|
||||||
|
if objects == []:
|
||||||
raise NO_SUCH_OBJECT()
|
raise NO_SUCH_OBJECT()
|
||||||
return objects.items()
|
return objects
|
||||||
|
|
||||||
def add_s(self, cn, attr):
|
|
||||||
#logging.debug("adding %s" % cn)
|
|
||||||
stored = {}
|
|
||||||
for k, v in attr:
|
|
||||||
if type(v) is list:
|
|
||||||
stored[k] = v
|
|
||||||
else:
|
|
||||||
stored[k] = [v]
|
|
||||||
d = self.keeper['objects']
|
|
||||||
d[cn] = stored
|
|
||||||
self.keeper['objects'] = d
|
|
||||||
|
|
||||||
def delete_s(self, cn):
|
|
||||||
logging.debug("deleting %s" % cn)
|
|
||||||
d = self.keeper['objects']
|
|
||||||
del d[cn]
|
|
||||||
self.keeper['objects'] = d
|
|
||||||
|
|
||||||
def modify_s(self, cn, attr):
|
|
||||||
logging.debug("modifying %s" % cn)
|
|
||||||
d = self.keeper['objects']
|
|
||||||
for cmd, k, v in attr:
|
|
||||||
logging.debug("command %s" % cmd)
|
|
||||||
if cmd == MOD_ADD:
|
|
||||||
d[cn][k].append(v)
|
|
||||||
else:
|
|
||||||
d[cn][k].remove(v)
|
|
||||||
self.keeper['objects'] = d
|
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def __redis_prefix(self):
|
||||||
|
return 'ldap:'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -52,15 +52,21 @@ from nova import objectstore # for flags
|
|||||||
|
|
||||||
FLAGS = flags.FLAGS
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
flags.DEFINE_string('ldap_url', 'ldap://localhost', 'Point this at your ldap server')
|
flags.DEFINE_string('ldap_url', 'ldap://localhost',
|
||||||
|
'Point this at your ldap server')
|
||||||
flags.DEFINE_string('ldap_password', 'changeme', 'LDAP password')
|
flags.DEFINE_string('ldap_password', 'changeme', 'LDAP password')
|
||||||
flags.DEFINE_string('user_dn', 'cn=Manager,dc=example,dc=com', 'DN of admin user')
|
flags.DEFINE_string('user_dn', 'cn=Manager,dc=example,dc=com',
|
||||||
|
'DN of admin user')
|
||||||
flags.DEFINE_string('user_unit', 'Users', 'OID for Users')
|
flags.DEFINE_string('user_unit', 'Users', 'OID for Users')
|
||||||
flags.DEFINE_string('user_ldap_subtree', 'ou=Users,dc=example,dc=com', 'OU for Users')
|
flags.DEFINE_string('user_ldap_subtree', 'ou=Users,dc=example,dc=com',
|
||||||
flags.DEFINE_string('project_ldap_subtree', 'ou=Groups,dc=example,dc=com', 'OU for Projects')
|
'OU for Users')
|
||||||
flags.DEFINE_string('role_ldap_subtree', 'ou=Groups,dc=example,dc=com', 'OU for Roles')
|
flags.DEFINE_string('project_ldap_subtree', 'ou=Groups,dc=example,dc=com',
|
||||||
|
'OU for Projects')
|
||||||
|
flags.DEFINE_string('role_ldap_subtree', 'ou=Groups,dc=example,dc=com',
|
||||||
|
'OU for Roles')
|
||||||
|
|
||||||
# mapping with these flags is necessary because we're going to tie in to an existing ldap schema
|
# NOTE(vish): mapping with these flags is necessary because we're going
|
||||||
|
# to tie in to an existing ldap schema
|
||||||
flags.DEFINE_string('ldap_cloudadmin',
|
flags.DEFINE_string('ldap_cloudadmin',
|
||||||
'cn=cloudadmins,ou=Groups,dc=example,dc=com', 'cn for Cloud Admins')
|
'cn=cloudadmins,ou=Groups,dc=example,dc=com', 'cn for Cloud Admins')
|
||||||
flags.DEFINE_string('ldap_itsec',
|
flags.DEFINE_string('ldap_itsec',
|
||||||
@@ -72,11 +78,15 @@ flags.DEFINE_string('ldap_netadmin',
|
|||||||
flags.DEFINE_string('ldap_developer',
|
flags.DEFINE_string('ldap_developer',
|
||||||
'cn=developers,ou=Groups,dc=example,dc=com', 'cn for Developers')
|
'cn=developers,ou=Groups,dc=example,dc=com', 'cn for Developers')
|
||||||
|
|
||||||
# a user with one of these roles will be a superuser and have access to all api commands
|
# NOTE(vish): a user with one of these roles will be a superuser and
|
||||||
flags.DEFINE_list('superuser_roles', ['cloudadmin'], 'roles that ignore rbac checking completely')
|
# have access to all api commands
|
||||||
|
flags.DEFINE_list('superuser_roles', ['cloudadmin'],
|
||||||
|
'roles that ignore rbac checking completely')
|
||||||
|
|
||||||
# a user with one of these roles will have it for every project, even if he or she is not a member of the project
|
# NOTE(vish): a user with one of these roles will have it for every
|
||||||
flags.DEFINE_list('global_roles', ['cloudadmin', 'itsec'], 'roles that apply to all projects')
|
# project, even if he or she is not a member of the project
|
||||||
|
flags.DEFINE_list('global_roles', ['cloudadmin', 'itsec'],
|
||||||
|
'roles that apply to all projects')
|
||||||
|
|
||||||
flags.DEFINE_string('credentials_template',
|
flags.DEFINE_string('credentials_template',
|
||||||
utils.abspath('auth/novarc.template'),
|
utils.abspath('auth/novarc.template'),
|
||||||
@@ -90,15 +100,20 @@ flags.DEFINE_string('credential_cert_file', 'cert.pem',
|
|||||||
'Filename of certificate in credentials zip')
|
'Filename of certificate in credentials zip')
|
||||||
flags.DEFINE_string('credential_rc_file', 'novarc',
|
flags.DEFINE_string('credential_rc_file', 'novarc',
|
||||||
'Filename of rc in credentials zip')
|
'Filename of rc in credentials zip')
|
||||||
flags.DEFINE_string('vpn_ip', '127.0.0.1', 'Public IP for the cloudpipe VPN servers')
|
flags.DEFINE_string('vpn_ip', '127.0.0.1',
|
||||||
|
'Public IP for the cloudpipe VPN servers')
|
||||||
|
|
||||||
|
|
||||||
class AuthBase(object):
|
class AuthBase(object):
|
||||||
@classmethod
|
@classmethod
|
||||||
def safe_id(cls, obj):
|
def safe_id(cls, obj):
|
||||||
"""this method will return the id of the object if the object is of this class, otherwise
|
"""Safe get object id.
|
||||||
it will return the original object. This allows methods to accept objects or
|
|
||||||
ids as paramaters"""
|
This method will return the id of the object if the object
|
||||||
|
is of this class, otherwise it will return the original object.
|
||||||
|
This allows methods to accept objects or ids as paramaters.
|
||||||
|
|
||||||
|
"""
|
||||||
if isinstance(obj, cls):
|
if isinstance(obj, cls):
|
||||||
return obj.id
|
return obj.id
|
||||||
else:
|
else:
|
||||||
@@ -195,7 +210,8 @@ class User(AuthBase):
|
|||||||
return UserManager.instance().get_key_pairs(self.id)
|
return UserManager.instance().get_key_pairs(self.id)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "User('%s', '%s', '%s', '%s', %s)" % (self.id, self.name, self.access, self.secret, self.admin)
|
return "User('%s', '%s', '%s', '%s', %s)" % (
|
||||||
|
self.id, self.name, self.access, self.secret, self.admin)
|
||||||
|
|
||||||
class KeyPair(AuthBase):
|
class KeyPair(AuthBase):
|
||||||
def __init__(self, id, owner_id, public_key, fingerprint):
|
def __init__(self, id, owner_id, public_key, fingerprint):
|
||||||
@@ -209,7 +225,8 @@ class KeyPair(AuthBase):
|
|||||||
return UserManager.instance().delete_key_pair(self.owner, self.name)
|
return UserManager.instance().delete_key_pair(self.owner, self.name)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "KeyPair('%s', '%s', '%s', '%s')" % (self.id, self.owner_id, self.public_key, self.fingerprint)
|
return "KeyPair('%s', '%s', '%s', '%s')" % (
|
||||||
|
self.id, self.owner_id, self.public_key, self.fingerprint)
|
||||||
|
|
||||||
class Group(AuthBase):
|
class Group(AuthBase):
|
||||||
"""id and name are currently the same"""
|
"""id and name are currently the same"""
|
||||||
@@ -223,7 +240,8 @@ class Group(AuthBase):
|
|||||||
return User.safe_id(user) in self.member_ids
|
return User.safe_id(user) in self.member_ids
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "Group('%s', '%s', %s)" % (self.id, self.description, self.member_ids)
|
return "Group('%s', '%s', %s)" % (
|
||||||
|
self.id, self.description, self.member_ids)
|
||||||
|
|
||||||
class Project(Group):
|
class Project(Group):
|
||||||
def __init__(self, id, project_manager_id, description, member_ids):
|
def __init__(self, id, project_manager_id, description, member_ids):
|
||||||
@@ -298,7 +316,9 @@ class Project(Group):
|
|||||||
return UserManager.instance().generate_x509_cert(user, self)
|
return UserManager.instance().generate_x509_cert(user, self)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "Project('%s', '%s', '%s', %s)" % (self.id, self.project_manager_id, self.description, self.member_ids)
|
return "Project('%s', '%s', '%s', %s)" % (
|
||||||
|
self.id, self.project_manager_id,
|
||||||
|
self.description, self.member_ids)
|
||||||
|
|
||||||
class UserManager(object):
|
class UserManager(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -322,7 +342,9 @@ class UserManager(object):
|
|||||||
except: pass
|
except: pass
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def authenticate(self, access, signature, params, verb='GET', server_string='127.0.0.1:8773', path='/', verify_signature=True):
|
def authenticate(self, access, signature, params, verb='GET',
|
||||||
|
server_string='127.0.0.1:8773', path='/',
|
||||||
|
verify_signature=True):
|
||||||
# TODO: Check for valid timestamp
|
# TODO: Check for valid timestamp
|
||||||
(access_key, sep, project_name) = access.partition(':')
|
(access_key, sep, project_name) = access.partition(':')
|
||||||
|
|
||||||
@@ -334,12 +356,16 @@ class UserManager(object):
|
|||||||
|
|
||||||
project = self.get_project(project_name)
|
project = self.get_project(project_name)
|
||||||
if project == None:
|
if project == None:
|
||||||
raise exception.NotFound('No project called %s could be found' % project_name)
|
raise exception.NotFound('No project called %s could be found' %
|
||||||
|
project_name)
|
||||||
if not user.is_admin() and not project.has_member(user):
|
if not user.is_admin() and not project.has_member(user):
|
||||||
raise exception.NotFound('User %s is not a member of project %s' % (user.id, project.id))
|
raise exception.NotFound('User %s is not a member of project %s' %
|
||||||
|
(user.id, project.id))
|
||||||
if verify_signature:
|
if verify_signature:
|
||||||
# hmac can't handle unicode, so encode ensures that secret isn't unicode
|
# NOTE(vish): hmac can't handle unicode, so encode ensures that
|
||||||
expected_signature = signer.Signer(user.secret.encode()).generate(params, verb, server_string, path)
|
# secret isn't unicode
|
||||||
|
expected_signature = signer.Signer(user.secret.encode()).generate(
|
||||||
|
params, verb, server_string, path)
|
||||||
logging.debug('user.secret: %s', user.secret)
|
logging.debug('user.secret: %s', user.secret)
|
||||||
logging.debug('expected_signature: %s', expected_signature)
|
logging.debug('expected_signature: %s', expected_signature)
|
||||||
logging.debug('signature: %s', signature)
|
logging.debug('signature: %s', signature)
|
||||||
@@ -369,17 +395,21 @@ class UserManager(object):
|
|||||||
|
|
||||||
def add_role(self, user, role, project=None):
|
def add_role(self, user, role, project=None):
|
||||||
with LDAPWrapper() as conn:
|
with LDAPWrapper() as conn:
|
||||||
return conn.add_role(User.safe_id(user), role, Project.safe_id(project))
|
return conn.add_role(User.safe_id(user), role,
|
||||||
|
Project.safe_id(project))
|
||||||
|
|
||||||
def remove_role(self, user, role, project=None):
|
def remove_role(self, user, role, project=None):
|
||||||
with LDAPWrapper() as conn:
|
with LDAPWrapper() as conn:
|
||||||
return conn.remove_role(User.safe_id(user), role, Project.safe_id(project))
|
return conn.remove_role(User.safe_id(user), role,
|
||||||
|
Project.safe_id(project))
|
||||||
|
|
||||||
def create_project(self, name, manager_user, description=None, member_users=None):
|
def create_project(self, name, manager_user,
|
||||||
|
description=None, member_users=None):
|
||||||
if member_users:
|
if member_users:
|
||||||
member_users = [User.safe_id(u) for u in member_users]
|
member_users = [User.safe_id(u) for u in member_users]
|
||||||
with LDAPWrapper() as conn:
|
with LDAPWrapper() as conn:
|
||||||
return conn.create_project(name, User.safe_id(manager_user), description, member_users)
|
return conn.create_project(name, User.safe_id(manager_user),
|
||||||
|
description, member_users)
|
||||||
|
|
||||||
def get_projects(self):
|
def get_projects(self):
|
||||||
with LDAPWrapper() as conn:
|
with LDAPWrapper() as conn:
|
||||||
@@ -392,7 +422,8 @@ class UserManager(object):
|
|||||||
|
|
||||||
def add_to_project(self, user, project):
|
def add_to_project(self, user, project):
|
||||||
with LDAPWrapper() as conn:
|
with LDAPWrapper() as conn:
|
||||||
return conn.add_to_project(User.safe_id(user), Project.safe_id(project))
|
return conn.add_to_project(User.safe_id(user),
|
||||||
|
Project.safe_id(project))
|
||||||
|
|
||||||
def is_project_manager(self, user, project):
|
def is_project_manager(self, user, project):
|
||||||
if not isinstance(project, Project):
|
if not isinstance(project, Project):
|
||||||
@@ -408,7 +439,8 @@ class UserManager(object):
|
|||||||
|
|
||||||
def remove_from_project(self, user, project):
|
def remove_from_project(self, user, project):
|
||||||
with LDAPWrapper() as conn:
|
with LDAPWrapper() as conn:
|
||||||
return conn.remove_from_project(User.safe_id(user), Project.safe_id(project))
|
return conn.remove_from_project(User.safe_id(user),
|
||||||
|
Project.safe_id(project))
|
||||||
|
|
||||||
def delete_project(self, project):
|
def delete_project(self, project):
|
||||||
with LDAPWrapper() as conn:
|
with LDAPWrapper() as conn:
|
||||||
@@ -426,7 +458,8 @@ class UserManager(object):
|
|||||||
with LDAPWrapper() as conn:
|
with LDAPWrapper() as conn:
|
||||||
return conn.find_users()
|
return conn.find_users()
|
||||||
|
|
||||||
def create_user(self, user, access=None, secret=None, admin=False, create_project=True):
|
def create_user(self, user, access=None, secret=None,
|
||||||
|
admin=False, create_project=True):
|
||||||
if access == None: access = str(uuid.uuid4())
|
if access == None: access = str(uuid.uuid4())
|
||||||
if secret == None: secret = str(uuid.uuid4())
|
if secret == None: secret = str(uuid.uuid4())
|
||||||
with LDAPWrapper() as conn:
|
with LDAPWrapper() as conn:
|
||||||
@@ -503,9 +536,12 @@ class LDAPWrapper(object):
|
|||||||
def connect(self):
|
def connect(self):
|
||||||
""" connect to ldap as admin user """
|
""" connect to ldap as admin user """
|
||||||
if FLAGS.fake_users:
|
if FLAGS.fake_users:
|
||||||
|
self.NO_SUCH_OBJECT = fakeldap.NO_SUCH_OBJECT
|
||||||
|
self.OBJECT_CLASS_VIOLATION = fakeldap.OBJECT_CLASS_VIOLATION
|
||||||
self.conn = fakeldap.initialize(FLAGS.ldap_url)
|
self.conn = fakeldap.initialize(FLAGS.ldap_url)
|
||||||
else:
|
else:
|
||||||
assert(ldap.__name__ != 'fakeldap')
|
self.NO_SUCH_OBJECT = ldap.NO_SUCH_OBJECT
|
||||||
|
self.OBJECT_CLASS_VIOLATION = ldap.OBJECT_CLASS_VIOLATION
|
||||||
self.conn = ldap.initialize(FLAGS.ldap_url)
|
self.conn = ldap.initialize(FLAGS.ldap_url)
|
||||||
self.conn.simple_bind_s(self.user, self.passwd)
|
self.conn.simple_bind_s(self.user, self.passwd)
|
||||||
|
|
||||||
@@ -518,7 +554,7 @@ class LDAPWrapper(object):
|
|||||||
def find_dns(self, dn, query=None):
|
def find_dns(self, dn, query=None):
|
||||||
try:
|
try:
|
||||||
res = self.conn.search_s(dn, ldap.SCOPE_SUBTREE, query)
|
res = self.conn.search_s(dn, ldap.SCOPE_SUBTREE, query)
|
||||||
except Exception:
|
except self.NO_SUCH_OBJECT:
|
||||||
return []
|
return []
|
||||||
# just return the DNs
|
# just return the DNs
|
||||||
return [dn for dn, attributes in res]
|
return [dn for dn, attributes in res]
|
||||||
@@ -526,25 +562,29 @@ class LDAPWrapper(object):
|
|||||||
def find_objects(self, dn, query = None):
|
def find_objects(self, dn, query = None):
|
||||||
try:
|
try:
|
||||||
res = self.conn.search_s(dn, ldap.SCOPE_SUBTREE, query)
|
res = self.conn.search_s(dn, ldap.SCOPE_SUBTREE, query)
|
||||||
except Exception:
|
except self.NO_SUCH_OBJECT:
|
||||||
return []
|
return []
|
||||||
# just return the attributes
|
# just return the attributes
|
||||||
return [attributes for dn, attributes in res]
|
return [attributes for dn, attributes in res]
|
||||||
|
|
||||||
def find_users(self):
|
def find_users(self):
|
||||||
attrs = self.find_objects(FLAGS.user_ldap_subtree, '(objectclass=novaUser)')
|
attrs = self.find_objects(FLAGS.user_ldap_subtree,
|
||||||
|
'(objectclass=novaUser)')
|
||||||
return [self.__to_user(attr) for attr in attrs]
|
return [self.__to_user(attr) for attr in attrs]
|
||||||
|
|
||||||
def find_key_pairs(self, uid):
|
def find_key_pairs(self, uid):
|
||||||
attrs = self.find_objects(self.__uid_to_dn(uid), '(objectclass=novaKeyPair)')
|
attrs = self.find_objects(self.__uid_to_dn(uid),
|
||||||
|
'(objectclass=novaKeyPair)')
|
||||||
return [self.__to_key_pair(uid, attr) for attr in attrs]
|
return [self.__to_key_pair(uid, attr) for attr in attrs]
|
||||||
|
|
||||||
def find_projects(self):
|
def find_projects(self):
|
||||||
attrs = self.find_objects(FLAGS.project_ldap_subtree, '(objectclass=novaProject)')
|
attrs = self.find_objects(FLAGS.project_ldap_subtree,
|
||||||
|
'(objectclass=novaProject)')
|
||||||
return [self.__to_project(attr) for attr in attrs]
|
return [self.__to_project(attr) for attr in attrs]
|
||||||
|
|
||||||
def find_roles(self, tree):
|
def find_roles(self, tree):
|
||||||
attrs = self.find_objects(tree, '(&(objectclass=groupOfNames)(!(objectclass=NovaProject)))')
|
attrs = self.find_objects(tree,
|
||||||
|
'(&(objectclass=groupOfNames)(!(objectclass=novaProject)))')
|
||||||
return [self.__to_group(attr) for attr in attrs]
|
return [self.__to_group(attr) for attr in attrs]
|
||||||
|
|
||||||
def find_group_dns_with_member(self, tree, uid):
|
def find_group_dns_with_member(self, tree, uid):
|
||||||
@@ -554,7 +594,8 @@ class LDAPWrapper(object):
|
|||||||
return dns
|
return dns
|
||||||
|
|
||||||
def find_user(self, uid):
|
def find_user(self, uid):
|
||||||
attr = self.find_object(self.__uid_to_dn(uid), '(objectclass=novaUser)')
|
attr = self.find_object(self.__uid_to_dn(uid),
|
||||||
|
'(objectclass=novaUser)')
|
||||||
return self.__to_user(attr)
|
return self.__to_user(attr)
|
||||||
|
|
||||||
def find_key_pair(self, uid, key_name):
|
def find_key_pair(self, uid, key_name):
|
||||||
@@ -611,11 +652,14 @@ class LDAPWrapper(object):
|
|||||||
self.conn.add_s(self.__uid_to_dn(name), attr)
|
self.conn.add_s(self.__uid_to_dn(name), attr)
|
||||||
return self.__to_user(dict(attr))
|
return self.__to_user(dict(attr))
|
||||||
|
|
||||||
def create_project(self, name, manager_uid, description=None, member_uids=None):
|
def create_project(self, name, manager_uid,
|
||||||
|
description=None, member_uids=None):
|
||||||
if self.project_exists(name):
|
if self.project_exists(name):
|
||||||
raise exception.Duplicate("Project can't be created because project %s already exists" % name)
|
raise exception.Duplicate("Project can't be created because "
|
||||||
|
"project %s already exists" % name)
|
||||||
if not self.user_exists(manager_uid):
|
if not self.user_exists(manager_uid):
|
||||||
raise exception.NotFound("Project can't be created because manager %s doesn't exist" % manager_uid)
|
raise exception.NotFound("Project can't be created because "
|
||||||
|
"manager %s doesn't exist" % manager_uid)
|
||||||
manager_dn = self.__uid_to_dn(manager_uid)
|
manager_dn = self.__uid_to_dn(manager_uid)
|
||||||
# description is a required attribute
|
# description is a required attribute
|
||||||
if description is None:
|
if description is None:
|
||||||
@@ -624,7 +668,8 @@ class LDAPWrapper(object):
|
|||||||
if member_uids != None:
|
if member_uids != None:
|
||||||
for member_uid in member_uids:
|
for member_uid in member_uids:
|
||||||
if not self.user_exists(member_uid):
|
if not self.user_exists(member_uid):
|
||||||
raise exception.NotFound("Project can't be created because user %s doesn't exist" % member_uid)
|
raise exception.NotFound("Project can't be created "
|
||||||
|
"because user %s doesn't exist" % member_uid)
|
||||||
members.append(self.__uid_to_dn(member_uid))
|
members.append(self.__uid_to_dn(member_uid))
|
||||||
# always add the manager as a member because members is required
|
# always add the manager as a member because members is required
|
||||||
if not manager_dn in members:
|
if not manager_dn in members:
|
||||||
@@ -655,16 +700,21 @@ class LDAPWrapper(object):
|
|||||||
if project_id == None:
|
if project_id == None:
|
||||||
return FLAGS.__getitem__("ldap_%s" % role).value
|
return FLAGS.__getitem__("ldap_%s" % role).value
|
||||||
else:
|
else:
|
||||||
return 'cn=%s,cn=%s,%s' % (role, project_id, FLAGS.project_ldap_subtree)
|
return 'cn=%s,cn=%s,%s' % (role,
|
||||||
|
project_id,
|
||||||
|
FLAGS.project_ldap_subtree)
|
||||||
|
|
||||||
def __create_group(self, group_dn, name, uid, description, member_uids = None):
|
def __create_group(self, group_dn, name, uid,
|
||||||
|
description, member_uids = None):
|
||||||
if self.group_exists(name):
|
if self.group_exists(name):
|
||||||
raise exception.Duplicate("Group can't be created because group %s already exists" % name)
|
raise exception.Duplicate("Group can't be created because "
|
||||||
|
"group %s already exists" % name)
|
||||||
members = []
|
members = []
|
||||||
if member_uids != None:
|
if member_uids != None:
|
||||||
for member_uid in member_uids:
|
for member_uid in member_uids:
|
||||||
if not self.user_exists(member_uid):
|
if not self.user_exists(member_uid):
|
||||||
raise exception.NotFound("Group can't be created because user %s doesn't exist" % member_uid)
|
raise exception.NotFound("Group can't be created "
|
||||||
|
"because user %s doesn't exist" % member_uid)
|
||||||
members.append(self.__uid_to_dn(member_uid))
|
members.append(self.__uid_to_dn(member_uid))
|
||||||
dn = self.__uid_to_dn(uid)
|
dn = self.__uid_to_dn(uid)
|
||||||
if not dn in members:
|
if not dn in members:
|
||||||
@@ -693,15 +743,12 @@ class LDAPWrapper(object):
|
|||||||
|
|
||||||
def remove_role(self, uid, role, project_id=None):
|
def remove_role(self, uid, role, project_id=None):
|
||||||
role_dn = self.__role_to_dn(role, project_id)
|
role_dn = self.__role_to_dn(role, project_id)
|
||||||
try:
|
return self.remove_from_group(uid, role_dn)
|
||||||
return self.remove_from_group(uid, role_dn)
|
|
||||||
except Exception, ex:
|
|
||||||
print type(ex), ex
|
|
||||||
|
|
||||||
|
|
||||||
def is_in_group(self, uid, group_dn):
|
def is_in_group(self, uid, group_dn):
|
||||||
if not self.user_exists(uid):
|
if not self.user_exists(uid):
|
||||||
raise exception.NotFound("User %s can't be searched in group becuase the user doesn't exist" % (uid,))
|
raise exception.NotFound("User %s can't be searched in group "
|
||||||
|
"becuase the user doesn't exist" % (uid,))
|
||||||
if not self.group_exists(group_dn):
|
if not self.group_exists(group_dn):
|
||||||
return False
|
return False
|
||||||
res = self.find_object(group_dn,
|
res = self.find_object(group_dn,
|
||||||
@@ -710,11 +757,14 @@ class LDAPWrapper(object):
|
|||||||
|
|
||||||
def add_to_group(self, uid, group_dn):
|
def add_to_group(self, uid, group_dn):
|
||||||
if not self.user_exists(uid):
|
if not self.user_exists(uid):
|
||||||
raise exception.NotFound("User %s can't be added to the group becuase the user doesn't exist" % (uid,))
|
raise exception.NotFound("User %s can't be added to the group "
|
||||||
|
"becuase the user doesn't exist" % (uid,))
|
||||||
if not self.group_exists(group_dn):
|
if not self.group_exists(group_dn):
|
||||||
raise exception.NotFound("The group at dn %s doesn't exist" % (group_dn,))
|
raise exception.NotFound("The group at dn %s doesn't exist" %
|
||||||
|
(group_dn,))
|
||||||
if self.is_in_group(uid, group_dn):
|
if self.is_in_group(uid, group_dn):
|
||||||
raise exception.Duplicate("User %s is already a member of the group %s" % (uid, group_dn))
|
raise exception.Duplicate("User %s is already a member of "
|
||||||
|
"the group %s" % (uid, group_dn))
|
||||||
attr = [
|
attr = [
|
||||||
(ldap.MOD_ADD, 'member', self.__uid_to_dn(uid))
|
(ldap.MOD_ADD, 'member', self.__uid_to_dn(uid))
|
||||||
]
|
]
|
||||||
@@ -722,11 +772,14 @@ class LDAPWrapper(object):
|
|||||||
|
|
||||||
def remove_from_group(self, uid, group_dn):
|
def remove_from_group(self, uid, group_dn):
|
||||||
if not self.group_exists(group_dn):
|
if not self.group_exists(group_dn):
|
||||||
raise exception.NotFound("The group at dn %s doesn't exist" % (group_dn,))
|
raise exception.NotFound("The group at dn %s doesn't exist" %
|
||||||
|
(group_dn,))
|
||||||
if not self.user_exists(uid):
|
if not self.user_exists(uid):
|
||||||
raise exception.NotFound("User %s can't be removed from the group because the user doesn't exist" % (uid,))
|
raise exception.NotFound("User %s can't be removed from the "
|
||||||
|
"group because the user doesn't exist" % (uid,))
|
||||||
if not self.is_in_group(uid, group_dn):
|
if not self.is_in_group(uid, group_dn):
|
||||||
raise exception.NotFound("User %s is not a member of the group" % (uid,))
|
raise exception.NotFound("User %s is not a member of the group" %
|
||||||
|
(uid,))
|
||||||
self._safe_remove_from_group(group_dn, uid)
|
self._safe_remove_from_group(group_dn, uid)
|
||||||
|
|
||||||
def _safe_remove_from_group(self, group_dn, uid):
|
def _safe_remove_from_group(self, group_dn, uid):
|
||||||
@@ -734,14 +787,15 @@ class LDAPWrapper(object):
|
|||||||
attr = [(ldap.MOD_DELETE, 'member', self.__uid_to_dn(uid))]
|
attr = [(ldap.MOD_DELETE, 'member', self.__uid_to_dn(uid))]
|
||||||
try:
|
try:
|
||||||
self.conn.modify_s(group_dn, attr)
|
self.conn.modify_s(group_dn, attr)
|
||||||
except ldap.OBJECT_CLASS_VIOLATION:
|
except self.OBJECT_CLASS_VIOLATION:
|
||||||
logging.debug("Attempted to remove the last member of a group. "
|
logging.debug("Attempted to remove the last member of a group. "
|
||||||
"Deleting the group at %s instead." % group_dn )
|
"Deleting the group at %s instead." % group_dn )
|
||||||
self.delete_group(group_dn)
|
self.delete_group(group_dn)
|
||||||
|
|
||||||
def remove_from_all(self, uid):
|
def remove_from_all(self, uid):
|
||||||
if not self.user_exists(uid):
|
if not self.user_exists(uid):
|
||||||
raise exception.NotFound("User %s can't be removed from all because the user doesn't exist" % (uid,))
|
raise exception.NotFound("User %s can't be removed from all "
|
||||||
|
"because the user doesn't exist" % (uid,))
|
||||||
dn = self.__uid_to_dn(uid)
|
dn = self.__uid_to_dn(uid)
|
||||||
role_dns = self.find_group_dns_with_member(
|
role_dns = self.find_group_dns_with_member(
|
||||||
FLAGS.role_ldap_subtree, uid)
|
FLAGS.role_ldap_subtree, uid)
|
||||||
@@ -794,9 +848,8 @@ class LDAPWrapper(object):
|
|||||||
|
|
||||||
def delete_roles(self, project_dn):
|
def delete_roles(self, project_dn):
|
||||||
roles = self.find_roles(project_dn)
|
roles = self.find_roles(project_dn)
|
||||||
if roles != None:
|
for role in roles:
|
||||||
for role in roles:
|
self.delete_group('cn=%s,%s' % (role.id, project_dn))
|
||||||
self.delete_group('cn=%s,%s' % (role.id, project_dn))
|
|
||||||
|
|
||||||
def delete_project(self, name):
|
def delete_project(self, name):
|
||||||
project_dn = 'cn=%s,%s' % (name, FLAGS.project_ldap_subtree)
|
project_dn = 'cn=%s,%s' % (name, FLAGS.project_ldap_subtree)
|
||||||
|
|||||||
Reference in New Issue
Block a user