~ubuntu-branches/ubuntu/quantal/nova/quantal-proposed

« back to all changes in this revision

Viewing changes to nova/auth/ldapdriver.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2012-08-16 14:04:11 UTC
  • mto: This revision was merged to the branch mainline in revision 84.
  • Revision ID: package-import@ubuntu.com-20120816140411-0mr4n241wmk30t9l
Tags: upstream-2012.2~f3
ImportĀ upstreamĀ versionĀ 2012.2~f3

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
 
 
3
 
# Copyright 2010 United States Government as represented by the
4
 
# Administrator of the National Aeronautics and Space Administration.
5
 
# All Rights Reserved.
6
 
#
7
 
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
8
 
#    not use this file except in compliance with the License. You may obtain
9
 
#    a copy of the License at
10
 
#
11
 
#         http://www.apache.org/licenses/LICENSE-2.0
12
 
#
13
 
#    Unless required by applicable law or agreed to in writing, software
14
 
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15
 
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16
 
#    License for the specific language governing permissions and limitations
17
 
#    under the License.
18
 
 
19
 
"""
20
 
Auth driver for ldap.  Includes FakeLdapDriver.
21
 
 
22
 
It should be easy to create a replacement for this driver supporting
23
 
other backends by creating another class that exposes the same
24
 
public methods.
25
 
"""
26
 
 
27
 
import functools
28
 
import sys
29
 
 
30
 
from nova import exception
31
 
from nova import flags
32
 
from nova.openstack.common import cfg
33
 
from nova.openstack.common import log as logging
34
 
 
35
 
 
36
 
ldap_opts = [
37
 
    cfg.IntOpt('ldap_schema_version',
38
 
               default=2,
39
 
               help='Current version of the LDAP schema'),
40
 
    cfg.StrOpt('ldap_url',
41
 
               default='ldap://localhost',
42
 
               help='Point this at your ldap server'),
43
 
    cfg.StrOpt('ldap_password',
44
 
               default='changeme',
45
 
               help='LDAP password'),
46
 
    cfg.StrOpt('ldap_user_dn',
47
 
               default='cn=Manager,dc=example,dc=com',
48
 
               help='DN of admin user'),
49
 
    cfg.StrOpt('ldap_user_id_attribute',
50
 
               default='uid',
51
 
               help='Attribute to use as id'),
52
 
    cfg.StrOpt('ldap_user_name_attribute',
53
 
               default='cn',
54
 
               help='Attribute to use as name'),
55
 
    cfg.StrOpt('ldap_user_unit',
56
 
               default='Users',
57
 
               help='OID for Users'),
58
 
    cfg.StrOpt('ldap_user_subtree',
59
 
               default='ou=Users,dc=example,dc=com',
60
 
               help='OU for Users'),
61
 
    cfg.BoolOpt('ldap_user_modify_only',
62
 
                default=False,
63
 
                help='Modify user attributes instead of creating/deleting'),
64
 
    cfg.StrOpt('ldap_project_subtree',
65
 
               default='ou=Groups,dc=example,dc=com',
66
 
               help='OU for Projects'),
67
 
    cfg.StrOpt('role_project_subtree',
68
 
               default='ou=Groups,dc=example,dc=com',
69
 
               help='OU for Roles'),
70
 
 
71
 
    # NOTE(vish): mapping with these flags is necessary because we're going
72
 
    #             to tie in to an existing ldap schema
73
 
    cfg.StrOpt('ldap_cloudadmin',
74
 
               default='cn=cloudadmins,ou=Groups,dc=example,dc=com',
75
 
               help='cn for Cloud Admins'),
76
 
    cfg.StrOpt('ldap_itsec',
77
 
               default='cn=itsec,ou=Groups,dc=example,dc=com',
78
 
               help='cn for ItSec'),
79
 
    cfg.StrOpt('ldap_sysadmin',
80
 
               default='cn=sysadmins,ou=Groups,dc=example,dc=com',
81
 
               help='cn for Sysadmins'),
82
 
    cfg.StrOpt('ldap_netadmin',
83
 
               default='cn=netadmins,ou=Groups,dc=example,dc=com',
84
 
               help='cn for NetAdmins'),
85
 
    cfg.StrOpt('ldap_developer',
86
 
               default='cn=developers,ou=Groups,dc=example,dc=com',
87
 
               help='cn for Developers'),
88
 
    ]
89
 
 
90
 
FLAGS = flags.FLAGS
91
 
FLAGS.register_opts(ldap_opts)
92
 
 
93
 
LOG = logging.getLogger(__name__)
94
 
 
95
 
 
96
 
if FLAGS.memcached_servers:
97
 
    import memcache
98
 
else:
99
 
    from nova.common import memorycache as memcache
100
 
 
101
 
 
102
 
# TODO(vish): make an abstract base class with the same public methods
103
 
#             to define a set interface for AuthDrivers. I'm delaying
104
 
#             creating this now because I'm expecting an auth refactor
105
 
#             in which we may want to change the interface a bit more.
106
 
 
107
 
 
108
 
def _clean(attr):
109
 
    """Clean attr for insertion into ldap"""
110
 
    if attr is None:
111
 
        return None
112
 
    if isinstance(attr, unicode):
113
 
        return str(attr)
114
 
    return attr
115
 
 
116
 
 
117
 
def sanitize(fn):
118
 
    """Decorator to sanitize all args"""
119
 
    @functools.wraps(fn)
120
 
    def _wrapped(self, *args, **kwargs):
121
 
        args = [_clean(x) for x in args]
122
 
        kwargs = dict((k, _clean(v)) for (k, v) in kwargs)
123
 
        return fn(self, *args, **kwargs)
124
 
    _wrapped.func_name = fn.func_name
125
 
    return _wrapped
126
 
 
127
 
 
128
 
class LDAPWrapper(object):
129
 
    def __init__(self, ldap, url, user, password):
130
 
        self.ldap = ldap
131
 
        self.url = url
132
 
        self.user = user
133
 
        self.password = password
134
 
        self.conn = None
135
 
 
136
 
    def __wrap_reconnect(f):
137
 
        def inner(self, *args, **kwargs):
138
 
            if self.conn is None:
139
 
                self.connect()
140
 
                return f(self.conn)(*args, **kwargs)
141
 
            else:
142
 
                try:
143
 
                    return f(self.conn)(*args, **kwargs)
144
 
                except self.ldap.SERVER_DOWN:
145
 
                    self.connect()
146
 
                    return f(self.conn)(*args, **kwargs)
147
 
        return inner
148
 
 
149
 
    def connect(self):
150
 
        try:
151
 
            self.conn = self.ldap.initialize(self.url)
152
 
            self.conn.simple_bind_s(self.user, self.password)
153
 
        except self.ldap.SERVER_DOWN:
154
 
            self.conn = None
155
 
            raise
156
 
 
157
 
    search_s = __wrap_reconnect(lambda conn: conn.search_s)
158
 
    add_s = __wrap_reconnect(lambda conn: conn.add_s)
159
 
    delete_s = __wrap_reconnect(lambda conn: conn.delete_s)
160
 
    modify_s = __wrap_reconnect(lambda conn: conn.modify_s)
161
 
 
162
 
 
163
 
class LdapDriver(object):
164
 
    """Ldap Auth driver
165
 
 
166
 
    Defines enter and exit and therefore supports the with/as syntax.
167
 
    """
168
 
 
169
 
    project_pattern = '(owner=*)'
170
 
    isadmin_attribute = 'isNovaAdmin'
171
 
    project_attribute = 'owner'
172
 
    project_objectclass = 'groupOfNames'
173
 
    conn = None
174
 
    mc = None
175
 
 
176
 
    def __init__(self):
177
 
        """Imports the LDAP module"""
178
 
        self.ldap = __import__('ldap')
179
 
        if FLAGS.ldap_schema_version == 1:
180
 
            LdapDriver.project_pattern = '(objectclass=novaProject)'
181
 
            LdapDriver.isadmin_attribute = 'isAdmin'
182
 
            LdapDriver.project_attribute = 'projectManager'
183
 
            LdapDriver.project_objectclass = 'novaProject'
184
 
        self.__cache = None
185
 
        if LdapDriver.conn is None:
186
 
            LdapDriver.conn = LDAPWrapper(self.ldap, FLAGS.ldap_url,
187
 
                                          FLAGS.ldap_user_dn,
188
 
                                          FLAGS.ldap_password)
189
 
        if LdapDriver.mc is None:
190
 
            LdapDriver.mc = memcache.Client(FLAGS.memcached_servers, debug=0)
191
 
 
192
 
    def __enter__(self):
193
 
        # TODO(yorik-sar): Should be per-request cache, not per-driver-request
194
 
        self.__cache = {}
195
 
        return self
196
 
 
197
 
    def __exit__(self, exc_type, exc_value, traceback):
198
 
        self.__cache = None
199
 
        return False
200
 
 
201
 
    def __local_cache(key_fmt):  # pylint: disable=E0213
202
 
        """Wrap function to cache its result in self.__cache.
203
 
        Works only with functions with one fixed argument.
204
 
        """
205
 
        def do_wrap(fn):
206
 
            @functools.wraps(fn)
207
 
            def inner(self, arg, **kwargs):
208
 
                cache_key = key_fmt % (arg,)
209
 
                try:
210
 
                    res = self.__cache[cache_key]
211
 
                    LOG.debug('Local cache hit for %s by key %s' %
212
 
                              (fn.__name__, cache_key))
213
 
                    return res
214
 
                except KeyError:
215
 
                    res = fn(self, arg, **kwargs)
216
 
                    self.__cache[cache_key] = res
217
 
                    return res
218
 
            return inner
219
 
        return do_wrap
220
 
 
221
 
    @sanitize
222
 
    @__local_cache('uid_user-%s')
223
 
    def get_user(self, uid):
224
 
        """Retrieve user by id"""
225
 
        attr = self.__get_ldap_user(uid)
226
 
        if attr is None:
227
 
            raise exception.LDAPUserNotFound(user_id=uid)
228
 
        return self.__to_user(attr)
229
 
 
230
 
    @sanitize
231
 
    def get_user_from_access_key(self, access):
232
 
        """Retrieve user by access key"""
233
 
        cache_key = 'uak_dn_%s' % (access,)
234
 
        user_dn = self.mc.get(cache_key)
235
 
        if user_dn:
236
 
            user = self.__to_user(
237
 
                self.__find_object(user_dn, scope=self.ldap.SCOPE_BASE))
238
 
            if user:
239
 
                if user['access'] == access:
240
 
                    return user
241
 
                else:
242
 
                    self.mc.set(cache_key, None)
243
 
        query = '(accessKey=%s)' % access
244
 
        dn = FLAGS.ldap_user_subtree
245
 
        user_obj = self.__find_object(dn, query)
246
 
        user = self.__to_user(user_obj)
247
 
        if user:
248
 
            self.mc.set(cache_key, user_obj['dn'][0])
249
 
        return user
250
 
 
251
 
    @sanitize
252
 
    @__local_cache('pid_project-%s')
253
 
    def get_project(self, pid):
254
 
        """Retrieve project by id"""
255
 
        dn = self.__project_to_dn(pid, search=False)
256
 
        attr = self.__find_object(dn, LdapDriver.project_pattern,
257
 
                                  scope=self.ldap.SCOPE_BASE)
258
 
        return self.__to_project(attr)
259
 
 
260
 
    @sanitize
261
 
    def get_users(self):
262
 
        """Retrieve list of users"""
263
 
        attrs = self.__find_objects(FLAGS.ldap_user_subtree,
264
 
                                    '(objectclass=novaUser)')
265
 
        users = []
266
 
        for attr in attrs:
267
 
            user = self.__to_user(attr)
268
 
            if user is not None:
269
 
                users.append(user)
270
 
        return users
271
 
 
272
 
    @sanitize
273
 
    def get_projects(self, uid=None):
274
 
        """Retrieve list of projects"""
275
 
        pattern = LdapDriver.project_pattern
276
 
        if uid:
277
 
            pattern = "(&%s(member=%s))" % (pattern, self.__uid_to_dn(uid))
278
 
        attrs = self.__find_objects(FLAGS.ldap_project_subtree,
279
 
                                    pattern)
280
 
        return [self.__to_project(attr) for attr in attrs]
281
 
 
282
 
    @sanitize
283
 
    def create_user(self, name, access_key, secret_key, is_admin):
284
 
        """Create a user"""
285
 
        if self.__user_exists(name):
286
 
            raise exception.LDAPUserExists(user=name)
287
 
        if FLAGS.ldap_user_modify_only:
288
 
            if self.__ldap_user_exists(name):
289
 
                # Retrieve user by name
290
 
                user = self.__get_ldap_user(name)
291
 
                # Entry could be malformed, test for missing attrs.
292
 
                # Malformed entries are useless, replace attributes found.
293
 
                attr = []
294
 
                if 'secretKey' in user.keys():
295
 
                    attr.append((self.ldap.MOD_REPLACE, 'secretKey',
296
 
                                 [secret_key]))
297
 
                else:
298
 
                    attr.append((self.ldap.MOD_ADD, 'secretKey',
299
 
                                 [secret_key]))
300
 
                if 'accessKey' in user.keys():
301
 
                    attr.append((self.ldap.MOD_REPLACE, 'accessKey',
302
 
                                 [access_key]))
303
 
                else:
304
 
                    attr.append((self.ldap.MOD_ADD, 'accessKey',
305
 
                                 [access_key]))
306
 
                if LdapDriver.isadmin_attribute in user.keys():
307
 
                    attr.append((self.ldap.MOD_REPLACE,
308
 
                                 LdapDriver.isadmin_attribute,
309
 
                                 [str(is_admin).upper()]))
310
 
                else:
311
 
                    attr.append((self.ldap.MOD_ADD,
312
 
                                 LdapDriver.isadmin_attribute,
313
 
                                 [str(is_admin).upper()]))
314
 
                self.conn.modify_s(self.__uid_to_dn(name), attr)
315
 
                return self.get_user(name)
316
 
            else:
317
 
                raise exception.LDAPUserNotFound(user_id=name)
318
 
        else:
319
 
            attr = [
320
 
                ('objectclass', ['person',
321
 
                                 'organizationalPerson',
322
 
                                 'inetOrgPerson',
323
 
                                 'novaUser']),
324
 
                ('ou', [FLAGS.ldap_user_unit]),
325
 
                (FLAGS.ldap_user_id_attribute, [name]),
326
 
                ('sn', [name]),
327
 
                (FLAGS.ldap_user_name_attribute, [name]),
328
 
                ('secretKey', [secret_key]),
329
 
                ('accessKey', [access_key]),
330
 
                (LdapDriver.isadmin_attribute, [str(is_admin).upper()]),
331
 
            ]
332
 
            self.conn.add_s(self.__uid_to_dn(name), attr)
333
 
            return self.__to_user(dict(attr))
334
 
 
335
 
    @sanitize
336
 
    def create_project(self, name, manager_uid,
337
 
                       description=None, member_uids=None):
338
 
        """Create a project"""
339
 
        if self.__project_exists(name):
340
 
            raise exception.ProjectExists(project=name)
341
 
        if not self.__user_exists(manager_uid):
342
 
            raise exception.LDAPUserNotFound(user_id=manager_uid)
343
 
        manager_dn = self.__uid_to_dn(manager_uid)
344
 
        # description is a required attribute
345
 
        if description is None:
346
 
            description = name
347
 
        members = []
348
 
        if member_uids is not None:
349
 
            for member_uid in member_uids:
350
 
                if not self.__user_exists(member_uid):
351
 
                    raise exception.LDAPUserNotFound(user_id=member_uid)
352
 
                members.append(self.__uid_to_dn(member_uid))
353
 
        # always add the manager as a member because members is required
354
 
        if not manager_dn in members:
355
 
            members.append(manager_dn)
356
 
        attr = [
357
 
            ('objectclass', [LdapDriver.project_objectclass]),
358
 
            ('cn', [name]),
359
 
            ('description', [description]),
360
 
            (LdapDriver.project_attribute, [manager_dn]),
361
 
            ('member', members)]
362
 
        dn = self.__project_to_dn(name, search=False)
363
 
        self.conn.add_s(dn, attr)
364
 
        return self.__to_project(dict(attr))
365
 
 
366
 
    @sanitize
367
 
    def modify_project(self, project_id, manager_uid=None, description=None):
368
 
        """Modify an existing project"""
369
 
        if not manager_uid and not description:
370
 
            return
371
 
        attr = []
372
 
        if manager_uid:
373
 
            if not self.__user_exists(manager_uid):
374
 
                raise exception.LDAPUserNotFound(user_id=manager_uid)
375
 
            manager_dn = self.__uid_to_dn(manager_uid)
376
 
            attr.append((self.ldap.MOD_REPLACE, LdapDriver.project_attribute,
377
 
                         manager_dn))
378
 
        if description:
379
 
            attr.append((self.ldap.MOD_REPLACE, 'description', description))
380
 
        dn = self.__project_to_dn(project_id)
381
 
        self.conn.modify_s(dn, attr)
382
 
        if not self.is_in_project(manager_uid, project_id):
383
 
            self.add_to_project(manager_uid, project_id)
384
 
 
385
 
    @sanitize
386
 
    def add_to_project(self, uid, project_id):
387
 
        """Add user to project"""
388
 
        dn = self.__project_to_dn(project_id)
389
 
        return self.__add_to_group(uid, dn)
390
 
 
391
 
    @sanitize
392
 
    def remove_from_project(self, uid, project_id):
393
 
        """Remove user from project"""
394
 
        dn = self.__project_to_dn(project_id)
395
 
        return self.__remove_from_group(uid, dn)
396
 
 
397
 
    @sanitize
398
 
    def is_in_project(self, uid, project_id):
399
 
        """Check if user is in project"""
400
 
        dn = self.__project_to_dn(project_id)
401
 
        return self.__is_in_group(uid, dn)
402
 
 
403
 
    @sanitize
404
 
    def has_role(self, uid, role, project_id=None):
405
 
        """Check if user has role
406
 
 
407
 
        If project is specified, it checks for local role, otherwise it
408
 
        checks for global role
409
 
        """
410
 
        role_dn = self.__role_to_dn(role, project_id)
411
 
        return self.__is_in_group(uid, role_dn)
412
 
 
413
 
    @sanitize
414
 
    def add_role(self, uid, role, project_id=None):
415
 
        """Add role for user (or user and project)"""
416
 
        role_dn = self.__role_to_dn(role, project_id)
417
 
        if not self.__group_exists(role_dn):
418
 
            # create the role if it doesn't exist
419
 
            description = '%s role for %s' % (role, project_id)
420
 
            self.__create_group(role_dn, role, uid, description)
421
 
        else:
422
 
            return self.__add_to_group(uid, role_dn)
423
 
 
424
 
    @sanitize
425
 
    def remove_role(self, uid, role, project_id=None):
426
 
        """Remove role for user (or user and project)"""
427
 
        role_dn = self.__role_to_dn(role, project_id)
428
 
        return self.__remove_from_group(uid, role_dn)
429
 
 
430
 
    @sanitize
431
 
    def get_user_roles(self, uid, project_id=None):
432
 
        """Retrieve list of roles for user (or user and project)"""
433
 
        if project_id is None:
434
 
            # NOTE(vish): This is unneccesarily slow, but since we can't
435
 
            #             guarantee that the global roles are located
436
 
            #             together in the ldap tree, we're doing this version.
437
 
            roles = []
438
 
            for role in FLAGS.allowed_roles:
439
 
                role_dn = self.__role_to_dn(role)
440
 
                if self.__is_in_group(uid, role_dn):
441
 
                    roles.append(role)
442
 
            return roles
443
 
        else:
444
 
            project_dn = self.__project_to_dn(project_id)
445
 
            query = ('(&(&(objectclass=groupOfNames)(!%s))(member=%s))' %
446
 
                     (LdapDriver.project_pattern, self.__uid_to_dn(uid)))
447
 
            roles = self.__find_objects(project_dn, query)
448
 
            return [role['cn'][0] for role in roles]
449
 
 
450
 
    @sanitize
451
 
    def delete_user(self, uid):
452
 
        """Delete a user"""
453
 
        if not self.__user_exists(uid):
454
 
            raise exception.LDAPUserNotFound(user_id=uid)
455
 
        self.__remove_from_all(uid)
456
 
        if FLAGS.ldap_user_modify_only:
457
 
            # Delete attributes
458
 
            attr = []
459
 
            # Retrieve user by name
460
 
            user = self.__get_ldap_user(uid)
461
 
            if 'secretKey' in user.keys():
462
 
                attr.append((self.ldap.MOD_DELETE, 'secretKey',
463
 
                             user['secretKey']))
464
 
            if 'accessKey' in user.keys():
465
 
                attr.append((self.ldap.MOD_DELETE, 'accessKey',
466
 
                             user['accessKey']))
467
 
            if LdapDriver.isadmin_attribute in user.keys():
468
 
                attr.append((self.ldap.MOD_DELETE,
469
 
                             LdapDriver.isadmin_attribute,
470
 
                             user[LdapDriver.isadmin_attribute]))
471
 
            self.conn.modify_s(self.__uid_to_dn(uid), attr)
472
 
        else:
473
 
            # Delete entry
474
 
            self.conn.delete_s(self.__uid_to_dn(uid))
475
 
 
476
 
    @sanitize
477
 
    def delete_project(self, project_id):
478
 
        """Delete a project"""
479
 
        project_dn = self.__project_to_dn(project_id)
480
 
        self.__delete_roles(project_dn)
481
 
        self.__delete_group(project_dn)
482
 
 
483
 
    @sanitize
484
 
    def modify_user(self, uid, access_key=None, secret_key=None, admin=None):
485
 
        """Modify an existing user"""
486
 
        if not access_key and not secret_key and admin is None:
487
 
            return
488
 
        attr = []
489
 
        if access_key:
490
 
            attr.append((self.ldap.MOD_REPLACE, 'accessKey', access_key))
491
 
        if secret_key:
492
 
            attr.append((self.ldap.MOD_REPLACE, 'secretKey', secret_key))
493
 
        if admin is not None:
494
 
            attr.append((self.ldap.MOD_REPLACE, LdapDriver.isadmin_attribute,
495
 
                         str(admin).upper()))
496
 
        self.conn.modify_s(self.__uid_to_dn(uid), attr)
497
 
 
498
 
    def __user_exists(self, uid):
499
 
        """Check if user exists"""
500
 
        try:
501
 
            return self.get_user(uid) is not None
502
 
        except exception.LDAPUserNotFound:
503
 
            return False
504
 
 
505
 
    def __ldap_user_exists(self, uid):
506
 
        """Check if the user exists in ldap"""
507
 
        return self.__get_ldap_user(uid) is not None
508
 
 
509
 
    def __project_exists(self, project_id):
510
 
        """Check if project exists"""
511
 
        return self.get_project(project_id) is not None
512
 
 
513
 
    @__local_cache('uid_attrs-%s')
514
 
    def __get_ldap_user(self, uid):
515
 
        """Retrieve LDAP user entry by id"""
516
 
        dn = FLAGS.ldap_user_subtree
517
 
        query = ('(&(%s=%s)(objectclass=novaUser))' %
518
 
                 (FLAGS.ldap_user_id_attribute, uid))
519
 
        return self.__find_object(dn, query)
520
 
 
521
 
    def __find_object(self, dn, query=None, scope=None):
522
 
        """Find an object by dn and query"""
523
 
        objects = self.__find_objects(dn, query, scope)
524
 
        if len(objects) == 0:
525
 
            return None
526
 
        return objects[0]
527
 
 
528
 
    def __find_dns(self, dn, query=None, scope=None):
529
 
        """Find dns by query"""
530
 
        if scope is None:
531
 
            # One of the flags is 0!
532
 
            scope = self.ldap.SCOPE_SUBTREE
533
 
        try:
534
 
            res = self.conn.search_s(dn, scope, query)
535
 
        except self.ldap.NO_SUCH_OBJECT:
536
 
            return []
537
 
        # Just return the DNs
538
 
        return [dn for dn, _attributes in res]
539
 
 
540
 
    def __find_objects(self, dn, query=None, scope=None):
541
 
        """Find objects by query"""
542
 
        if scope is None:
543
 
            # One of the flags is 0!
544
 
            scope = self.ldap.SCOPE_SUBTREE
545
 
        if query is None:
546
 
            query = "(objectClass=*)"
547
 
        try:
548
 
            res = self.conn.search_s(dn, scope, query)
549
 
        except self.ldap.NO_SUCH_OBJECT:
550
 
            return []
551
 
        # Just return the attributes
552
 
        # FIXME(yorik-sar): Whole driver should be refactored to
553
 
        #                   prevent this hack
554
 
        res1 = []
555
 
        for dn, attrs in res:
556
 
            attrs['dn'] = [dn]
557
 
            res1.append(attrs)
558
 
        return res1
559
 
 
560
 
    def __find_role_dns(self, tree):
561
 
        """Find dns of role objects in given tree"""
562
 
        query = ('(&(objectclass=groupOfNames)(!%s))' %
563
 
                 LdapDriver.project_pattern)
564
 
        return self.__find_dns(tree, query)
565
 
 
566
 
    def __find_group_dns_with_member(self, tree, uid):
567
 
        """Find dns of group objects in a given tree that contain member"""
568
 
        query = ('(&(objectclass=groupOfNames)(member=%s))' %
569
 
                 self.__uid_to_dn(uid))
570
 
        dns = self.__find_dns(tree, query)
571
 
        return dns
572
 
 
573
 
    def __group_exists(self, dn):
574
 
        """Check if group exists"""
575
 
        query = '(objectclass=groupOfNames)'
576
 
        return self.__find_object(dn, query) is not None
577
 
 
578
 
    def __role_to_dn(self, role, project_id=None):
579
 
        """Convert role to corresponding dn"""
580
 
        if project_id is None:
581
 
            return FLAGS["ldap_%s" % role]
582
 
        else:
583
 
            project_dn = self.__project_to_dn(project_id)
584
 
            return 'cn=%s,%s' % (role, project_dn)
585
 
 
586
 
    def __create_group(self, group_dn, name, uid,
587
 
                       description, member_uids=None):
588
 
        """Create a group"""
589
 
        if self.__group_exists(group_dn):
590
 
            raise exception.LDAPGroupExists(group=name)
591
 
        members = []
592
 
        if member_uids is not None:
593
 
            for member_uid in member_uids:
594
 
                if not self.__user_exists(member_uid):
595
 
                    raise exception.LDAPUserNotFound(user_id=member_uid)
596
 
                members.append(self.__uid_to_dn(member_uid))
597
 
        dn = self.__uid_to_dn(uid)
598
 
        if not dn in members:
599
 
            members.append(dn)
600
 
        attr = [
601
 
            ('objectclass', ['groupOfNames']),
602
 
            ('cn', [name]),
603
 
            ('description', [description]),
604
 
            ('member', members)]
605
 
        self.conn.add_s(group_dn, attr)
606
 
 
607
 
    def __is_in_group(self, uid, group_dn):
608
 
        """Check if user is in group"""
609
 
        if not self.__user_exists(uid):
610
 
            raise exception.LDAPUserNotFound(user_id=uid)
611
 
        if not self.__group_exists(group_dn):
612
 
            return False
613
 
        res = self.__find_object(group_dn,
614
 
                                 '(member=%s)' % self.__uid_to_dn(uid),
615
 
                                 self.ldap.SCOPE_BASE)
616
 
        return res is not None
617
 
 
618
 
    def __add_to_group(self, uid, group_dn):
619
 
        """Add user to group"""
620
 
        if not self.__user_exists(uid):
621
 
            raise exception.LDAPUserNotFound(user_id=uid)
622
 
        if not self.__group_exists(group_dn):
623
 
            raise exception.LDAPGroupNotFound(group_id=group_dn)
624
 
        if self.__is_in_group(uid, group_dn):
625
 
            raise exception.LDAPMembershipExists(uid=uid, group_dn=group_dn)
626
 
        attr = [(self.ldap.MOD_ADD, 'member', self.__uid_to_dn(uid))]
627
 
        self.conn.modify_s(group_dn, attr)
628
 
 
629
 
    def __remove_from_group(self, uid, group_dn):
630
 
        """Remove user from group"""
631
 
        if not self.__group_exists(group_dn):
632
 
            raise exception.LDAPGroupNotFound(group_id=group_dn)
633
 
        if not self.__user_exists(uid):
634
 
            raise exception.LDAPUserNotFound(user_id=uid)
635
 
        if not self.__is_in_group(uid, group_dn):
636
 
            raise exception.LDAPGroupMembershipNotFound(user_id=uid,
637
 
                                                        group_id=group_dn)
638
 
        # NOTE(vish): remove user from group and any sub_groups
639
 
        sub_dns = self.__find_group_dns_with_member(group_dn, uid)
640
 
        for sub_dn in sub_dns:
641
 
            self.__safe_remove_from_group(uid, sub_dn)
642
 
 
643
 
    def __safe_remove_from_group(self, uid, group_dn):
644
 
        """Remove user from group, deleting group if user is last member"""
645
 
        # FIXME(vish): what if deleted user is a project manager?
646
 
        attr = [(self.ldap.MOD_DELETE, 'member', self.__uid_to_dn(uid))]
647
 
        try:
648
 
            self.conn.modify_s(group_dn, attr)
649
 
        except self.ldap.OBJECT_CLASS_VIOLATION:
650
 
            LOG.debug(_("Attempted to remove the last member of a group. "
651
 
                        "Deleting the group at %s instead."), group_dn)
652
 
            self.__delete_group(group_dn)
653
 
 
654
 
    def __remove_from_all(self, uid):
655
 
        """Remove user from all roles and projects"""
656
 
        if not self.__user_exists(uid):
657
 
            raise exception.LDAPUserNotFound(user_id=uid)
658
 
        role_dns = self.__find_group_dns_with_member(
659
 
                FLAGS.role_project_subtree, uid)
660
 
        for role_dn in role_dns:
661
 
            self.__safe_remove_from_group(uid, role_dn)
662
 
        project_dns = self.__find_group_dns_with_member(
663
 
                FLAGS.ldap_project_subtree, uid)
664
 
        for project_dn in project_dns:
665
 
            self.__safe_remove_from_group(uid, project_dn)
666
 
 
667
 
    def __delete_group(self, group_dn):
668
 
        """Delete Group"""
669
 
        if not self.__group_exists(group_dn):
670
 
            raise exception.LDAPGroupNotFound(group_id=group_dn)
671
 
        self.conn.delete_s(group_dn)
672
 
 
673
 
    def __delete_roles(self, project_dn):
674
 
        """Delete all roles for project"""
675
 
        for role_dn in self.__find_role_dns(project_dn):
676
 
            self.__delete_group(role_dn)
677
 
 
678
 
    def __to_project(self, attr):
679
 
        """Convert ldap attributes to Project object"""
680
 
        if attr is None:
681
 
            return None
682
 
        member_dns = attr.get('member', [])
683
 
        return {
684
 
            'id': attr['cn'][0],
685
 
            'name': attr['cn'][0],
686
 
            'project_manager_id':
687
 
                self.__dn_to_uid(attr[LdapDriver.project_attribute][0]),
688
 
            'description': attr.get('description', [None])[0],
689
 
            'member_ids': [self.__dn_to_uid(x) for x in member_dns]}
690
 
 
691
 
    @__local_cache('uid_dn-%s')
692
 
    def __uid_to_dn(self, uid, search=True):
693
 
        """Convert uid to dn"""
694
 
        # By default return a generated DN
695
 
        userdn = (FLAGS.ldap_user_id_attribute + '=%s,%s'
696
 
                  % (uid, FLAGS.ldap_user_subtree))
697
 
        if search:
698
 
            query = ('%s=%s' % (FLAGS.ldap_user_id_attribute, uid))
699
 
            user = self.__find_dns(FLAGS.ldap_user_subtree, query)
700
 
            if len(user) > 0:
701
 
                userdn = user[0]
702
 
        return userdn
703
 
 
704
 
    @__local_cache('pid_dn-%s')
705
 
    def __project_to_dn(self, pid, search=True):
706
 
        """Convert pid to dn"""
707
 
        # By default return a generated DN
708
 
        projectdn = ('cn=%s,%s' % (pid, FLAGS.ldap_project_subtree))
709
 
        if search:
710
 
            query = ('(&(cn=%s)%s)' % (pid, LdapDriver.project_pattern))
711
 
            project = self.__find_dns(FLAGS.ldap_project_subtree, query)
712
 
            if len(project) > 0:
713
 
                projectdn = project[0]
714
 
        return projectdn
715
 
 
716
 
    @staticmethod
717
 
    def __to_user(attr):
718
 
        """Convert ldap attributes to User object"""
719
 
        if attr is None:
720
 
            return None
721
 
        if ('accessKey' in attr.keys() and 'secretKey' in attr.keys() and
722
 
            LdapDriver.isadmin_attribute in attr.keys()):
723
 
            return {
724
 
                'id': attr[FLAGS.ldap_user_id_attribute][0],
725
 
                'name': attr[FLAGS.ldap_user_name_attribute][0],
726
 
                'access': attr['accessKey'][0],
727
 
                'secret': attr['secretKey'][0],
728
 
                'admin': (attr[LdapDriver.isadmin_attribute][0] == 'TRUE')}
729
 
        else:
730
 
            return None
731
 
 
732
 
    @__local_cache('dn_uid-%s')
733
 
    def __dn_to_uid(self, dn):
734
 
        """Convert user dn to uid"""
735
 
        query = '(objectclass=novaUser)'
736
 
        user = self.__find_object(dn, query, scope=self.ldap.SCOPE_BASE)
737
 
        return user[FLAGS.ldap_user_id_attribute][0]
738
 
 
739
 
 
740
 
class FakeLdapDriver(LdapDriver):
741
 
    """Fake Ldap Auth driver"""
742
 
 
743
 
    def __init__(self):
744
 
        import nova.auth.fakeldap
745
 
        sys.modules['ldap'] = nova.auth.fakeldap
746
 
        super(FakeLdapDriver, self).__init__()