Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

558

559

560

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

584

585

586

587

588

589

590

591

592

593

594

595

596

597

598

599

600

601

602

603

604

605

606

607

608

609

610

611

612

613

614

615

616

617

618

619

620

621

622

623

624

625

626

627

628

629

630

631

632

633

634

635

636

637

638

639

640

641

642

643

644

645

646

647

648

649

650

651

652

653

654

655

656

657

658

659

660

661

662

663

664

665

666

667

668

669

670

671

672

673

674

675

676

677

678

679

680

681

682

683

684

685

686

687

688

689

690

691

692

693

694

695

696

697

698

699

700

701

702

703

704

705

706

707

708

709

710

711

712

713

714

715

716

717

718

# vim: tabstop=4 shiftwidth=4 softtabstop=4 

 

# Copyright 2012 OpenStack LLC 

# 

# Licensed under the Apache License, Version 2.0 (the "License"); you may 

# not use this file except in compliance with the License. You may obtain 

# a copy of the License at 

# 

#      http://www.apache.org/licenses/LICENSE-2.0 

# 

# Unless required by applicable law or agreed to in writing, software 

# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

# License for the specific language governing permissions and limitations 

# under the License. 

 

import os.path 

 

import ldap 

from ldap import filter as ldap_filter 

 

from keystone.common.ldap import fakeldap 

from keystone.common import logging 

from keystone import exception 

 

 

LOG = logging.getLogger(__name__) 

 

 

LDAP_VALUES = {'TRUE': True, 'FALSE': False} 

CONTROL_TREEDELETE = '1.2.840.113556.1.4.805' 

LDAP_SCOPES = {'one': ldap.SCOPE_ONELEVEL, 

               'sub': ldap.SCOPE_SUBTREE} 

LDAP_DEREF = {'always': ldap.DEREF_ALWAYS, 

              'default': None, 

              'finding': ldap.DEREF_FINDING, 

              'never': ldap.DEREF_NEVER, 

              'searching': ldap.DEREF_SEARCHING} 

LDAP_TLS_CERTS = {'never': ldap.OPT_X_TLS_NEVER, 

                  'demand': ldap.OPT_X_TLS_DEMAND, 

                  'allow': ldap.OPT_X_TLS_ALLOW} 

 

 

def py2ldap(val): 

    if isinstance(val, str): 

        return val 

    elif isinstance(val, bool): 

        return 'TRUE' if val else 'FALSE' 

    else: 

        return str(val) 

 

 

def ldap2py(val): 

    try: 

        return LDAP_VALUES[val] 

    except KeyError: 

        pass 

    try: 

        return int(val) 

    except ValueError: 

        pass 

    return val 

 

 

def safe_iter(attrs): 

    if attrs is None: 

        return 

    elif isinstance(attrs, list): 

        for e in attrs: 

            yield e 

    else: 

        yield attrs 

 

 

def parse_deref(opt): 

    try: 

        return LDAP_DEREF[opt] 

    except KeyError: 

        raise ValueError((_('Invalid LDAP deref option: %s. Choose one of: ') % 

                         opt) + ', '.join(LDAP_DEREF.keys())) 

 

 

def parse_tls_cert(opt): 

    try: 

        return LDAP_TLS_CERTS[opt] 

    except KeyError: 

        raise ValueError(_( 

            'Invalid LDAP TLS certs option: %(option). ' 

            'Choose one of: %(options)s') % { 

                'option': opt, 

                'options': ', '.join(LDAP_TLS_CERTS.keys())}) 

 

 

def ldap_scope(scope): 

    try: 

        return LDAP_SCOPES[scope] 

    except KeyError: 

        raise ValueError( 

            _('Invalid LDAP scope: %(scope)s. Choose one of: %(options)s') % { 

                'scope': scope, 

                'options': ', '.join(LDAP_SCOPES.keys())}) 

 

 

class BaseLdap(object): 

    DEFAULT_SUFFIX = "dc=example,dc=com" 

    DEFAULT_OU = None 

    DEFAULT_STRUCTURAL_CLASSES = None 

    DEFAULT_ID_ATTR = 'cn' 

    DEFAULT_OBJECTCLASS = None 

    DEFAULT_FILTER = None 

    DEFAULT_EXTRA_ATTR_MAPPING = [] 

    DUMB_MEMBER_DN = 'cn=dumb,dc=nonexistent' 

    NotFound = None 

    notfound_arg = None 

    options_name = None 

    model = None 

    attribute_mapping = {} 

    attribute_ignore = [] 

    tree_dn = None 

 

    def __init__(self, conf): 

        self.LDAP_URL = conf.ldap.url 

        self.LDAP_USER = conf.ldap.user 

        self.LDAP_PASSWORD = conf.ldap.password 

        self.LDAP_SCOPE = ldap_scope(conf.ldap.query_scope) 

        self.alias_dereferencing = parse_deref(conf.ldap.alias_dereferencing) 

        self.page_size = conf.ldap.page_size 

        self.use_tls = conf.ldap.use_tls 

        self.tls_cacertfile = conf.ldap.tls_cacertfile 

        self.tls_cacertdir = conf.ldap.tls_cacertdir 

        self.tls_req_cert = parse_tls_cert(conf.ldap.tls_req_cert) 

 

170        if self.options_name is not None: 

            self.suffix = conf.ldap.suffix 

136            if self.suffix is None: 

                self.suffix = self.DEFAULT_SUFFIX 

            dn = '%s_tree_dn' % self.options_name 

            self.tree_dn = (getattr(conf.ldap, dn) 

                            or '%s,%s' % (self.DEFAULT_OU, self.suffix)) 

 

            idatt = '%s_id_attribute' % self.options_name 

            self.id_attr = getattr(conf.ldap, idatt) or self.DEFAULT_ID_ATTR 

 

            objclass = '%s_objectclass' % self.options_name 

            self.object_class = (getattr(conf.ldap, objclass) 

                                 or self.DEFAULT_OBJECTCLASS) 

 

            attr_mapping_opt = ('%s_additional_attribute_mapping' % 

                                self.options_name) 

            attr_mapping = (getattr(conf.ldap, attr_mapping_opt) 

                            or self.DEFAULT_EXTRA_ATTR_MAPPING) 

            self.extra_attr_mapping = self._parse_extra_attrs(attr_mapping) 

 

            filter = '%s_filter' % self.options_name 

            self.filter = getattr(conf.ldap, filter) or self.DEFAULT_FILTER 

 

            allow_create = '%s_allow_create' % self.options_name 

            self.allow_create = getattr(conf.ldap, allow_create) 

 

            allow_update = '%s_allow_update' % self.options_name 

            self.allow_update = getattr(conf.ldap, allow_update) 

 

            allow_delete = '%s_allow_delete' % self.options_name 

            self.allow_delete = getattr(conf.ldap, allow_delete) 

 

            self.structural_classes = self.DEFAULT_STRUCTURAL_CLASSES 

 

            if self.notfound_arg is None: 

                self.notfound_arg = self.options_name + '_id' 

        self.use_dumb_member = getattr(conf.ldap, 'use_dumb_member') 

        self.dumb_member = (getattr(conf.ldap, 'dumb_member') or 

                            self.DUMB_MEMBER_DN) 

 

        self.subtree_delete_enabled = getattr(conf.ldap, 

                                              'allow_subtree_delete') 

 

    def _not_found(self, object_id): 

179        if self.NotFound is None: 

            return exception.NotFound(target=object_id) 

        else: 

            return self.NotFound(**{self.notfound_arg: object_id}) 

 

    def _parse_extra_attrs(self, option_list): 

        mapping = {} 

        for item in option_list: 

            try: 

                ldap_attr, attr_map = item.split(':') 

            except Exception: 

                LOG.warn(_( 

                    'Invalid additional attribute mapping: "%s". ' 

                    'Format must be <ldap_attribute>:<keystone_attribute>') 

                    % item) 

                continue 

            if attr_map not in self.attribute_mapping: 

                LOG.warn(_('Invalid additional attribute mapping: "%(item)s". ' 

                           'Value "%(attr_map)s" must use one of %(keys)s.') % 

                         {'item': item, 'attr_map': attr_map, 

                          'keys': ', '.join(self.attribute_mapping.keys())}) 

                continue 

            mapping[ldap_attr] = attr_map 

        return mapping 

 

    def get_connection(self, user=None, password=None): 

207        if self.LDAP_URL.startswith('fake://'): 

            conn = fakeldap.FakeLdap(self.LDAP_URL) 

        else: 

            conn = LdapWrapper(self.LDAP_URL, 

                               self.page_size, 

                               alias_dereferencing=self.alias_dereferencing, 

                               use_tls=self.use_tls, 

                               tls_cacertfile=self.tls_cacertfile, 

                               tls_cacertdir=self.tls_cacertdir, 

                               tls_req_cert=self.tls_req_cert) 

 

        if user is None: 

            user = self.LDAP_USER 

 

        if password is None: 

            password = self.LDAP_PASSWORD 

 

        # not all LDAP servers require authentication, so we don't bind 

        # if we don't have any user/pass 

        if user and password: 

            conn.simple_bind_s(user, password) 

 

        return conn 

 

    def _id_to_dn_string(self, id): 

        return '%s=%s,%s' % (self.id_attr, 

                             ldap.dn.escape_dn_chars(str(id)), 

                             self.tree_dn) 

 

    def _id_to_dn(self, id): 

236        if self.LDAP_SCOPE == ldap.SCOPE_ONELEVEL: 

            return self._id_to_dn_string(id) 

        conn = self.get_connection() 

        search_result = conn.search_s( 

            self.tree_dn, self.LDAP_SCOPE, 

            '(&(%(id_attr)s=%(id)s)(objectclass=%(objclass)s))' % 

            {'id_attr': self.id_attr, 

             'id': ldap.filter.escape_filter_chars(str(id)), 

             'objclass': self.object_class}) 

        if search_result: 

            dn, attrs = search_result[0] 

            return dn 

        else: 

            return self._id_to_dn_string(id) 

 

    @staticmethod 

    def _dn_to_id(dn): 

        return ldap.dn.str2dn(dn)[0][0][1] 

 

    def _ldap_res_to_model(self, res): 

        obj = self.model(id=self._dn_to_id(res[0])) 

        for k in obj.known_keys: 

            if k in self.attribute_ignore: 

                continue 

 

            try: 

                v = res[1][self.attribute_mapping.get(k, k)] 

            except KeyError: 

                pass 

            else: 

                try: 

                    obj[k] = v[0] 

                except IndexError: 

                    obj[k] = None 

 

        return obj 

 

    def affirm_unique(self, values): 

282        if values.get('name') is not None: 

            try: 

                self.get_by_name(values['name']) 

            except exception.NotFound: 

                pass 

            else: 

                raise exception.Conflict(type=self.options_name, 

                                         details=_('Duplicate name, %s.') % 

                                         values['name']) 

 

        if values.get('id') is not None: 

            try: 

                self.get(values['id']) 

            except exception.NotFound: 

                pass 

            else: 

                raise exception.Conflict(type=self.options_name, 

                                         details=_('Duplicate ID, %s.') % 

                                         values['id']) 

 

    def create(self, values): 

        if not self.allow_create: 

            action = _('LDAP %s create') % self.options_name 

            raise exception.ForbiddenAction(action=action) 

 

        conn = self.get_connection() 

        object_classes = self.structural_classes + [self.object_class] 

        attrs = [('objectClass', object_classes)] 

        for k, v in values.iteritems(): 

            if k == 'id' or k in self.attribute_ignore: 

                continue 

300            if v is not None: 

                attr_type = self.attribute_mapping.get(k, k) 

                attrs.append((attr_type, [v])) 

                extra_attrs = [attr for attr, name 

                               in self.extra_attr_mapping.iteritems() 

                               if name == k] 

                for attr in extra_attrs: 

                    attrs.append((attr, [v])) 

 

        if 'groupOfNames' in object_classes and self.use_dumb_member: 

            attrs.append(('member', [self.dumb_member])) 

 

        conn.add_s(self._id_to_dn(values['id']), attrs) 

        return values 

 

    def _ldap_get(self, id, filter=None): 

        conn = self.get_connection() 

        query = ('(&(%(id_attr)s=%(id)s)' 

                 '%(filter)s' 

                 '(objectClass=%(object_class)s))' 

                 % {'id_attr': self.id_attr, 

                    'id': ldap.filter.escape_filter_chars(str(id)), 

                    'filter': (filter or self.filter or ''), 

                    'object_class': self.object_class}) 

        try: 

            attrs = list(set((self.attribute_mapping.values() + 

                              self.extra_attr_mapping.keys()))) 

            res = conn.search_s(self.tree_dn, self.LDAP_SCOPE, query, attrs) 

        except ldap.NO_SUCH_OBJECT: 

            return None 

        try: 

            return res[0] 

        except IndexError: 

            return None 

 

    def _ldap_get_all(self, filter=None): 

        conn = self.get_connection() 

        query = '(&%s(objectClass=%s))' % (filter or self.filter or '', 

                                           self.object_class) 

        try: 

            return conn.search_s(self.tree_dn, 

                                 self.LDAP_SCOPE, 

                                 query, 

                                 self.attribute_mapping.values()) 

        except ldap.NO_SUCH_OBJECT: 

            return [] 

 

    def get(self, id, filter=None): 

        res = self._ldap_get(id, filter) 

        if res is None: 

            raise self._not_found(id) 

        else: 

            return self._ldap_res_to_model(res) 

 

    def get_by_name(self, name, filter=None): 

        query = ('(%s=%s)' % (self.attribute_mapping['name'], 

                              ldap_filter.escape_filter_chars(name))) 

        res = self.get_all(query) 

        try: 

            return res[0] 

        except IndexError: 

            raise self._not_found(name) 

 

    def get_all(self, filter=None): 

        return [self._ldap_res_to_model(x) 

                for x in self._ldap_get_all(filter)] 

 

    def update(self, id, values, old_obj=None): 

        if not self.allow_update: 

            action = _('LDAP %s update') % self.options_name 

            raise exception.ForbiddenAction(action=action) 

 

        if old_obj is None: 

            old_obj = self.get(id) 

 

        modlist = [] 

        for k, v in values.iteritems(): 

            if k == 'id' or k in self.attribute_ignore: 

                continue 

383            if v is None: 

                if old_obj[k] is not None: 

                    modlist.append((ldap.MOD_DELETE, 

                                    self.attribute_mapping.get(k, k), 

                                    None)) 

            elif old_obj[k] != v: 

389                if old_obj[k] is None: 

                    op = ldap.MOD_ADD 

                else: 

                    op = ldap.MOD_REPLACE 

                modlist.append((op, self.attribute_mapping.get(k, k), [v])) 

 

        if modlist: 

            conn = self.get_connection() 

            try: 

                conn.modify_s(self._id_to_dn(id), modlist) 

            except ldap.NO_SUCH_OBJECT: 

                raise self._not_found(id) 

 

        return self.get(id) 

 

    def delete(self, id): 

        if not self.allow_delete: 

            action = _('LDAP %s delete') % self.options_name 

            raise exception.ForbiddenAction(action=action) 

 

        conn = self.get_connection() 

        try: 

            conn.delete_s(self._id_to_dn(id)) 

        except ldap.NO_SUCH_OBJECT: 

            raise self._not_found(id) 

 

    def deleteTree(self, id): 

        conn = self.get_connection() 

        tree_delete_control = ldap.controls.LDAPControl(CONTROL_TREEDELETE, 

                                                        0, 

                                                        None) 

        try: 

            conn.delete_ext_s(self._id_to_dn(id), 

                              serverctrls=[tree_delete_control]) 

        except ldap.NO_SUCH_OBJECT: 

            raise self._not_found(id) 

 

 

class LdapWrapper(object): 

    def __init__(self, url, page_size, alias_dereferencing=None, 

                 use_tls=False, tls_cacertfile=None, tls_cacertdir=None, 

                 tls_req_cert='demand'): 

        LOG.debug(_("LDAP init: url=%s"), url) 

        LOG.debug(_('LDAP init: use_tls=%(use_tls)s\n' 

                  'tls_cacertfile=%(tls_cacertfile)s\n' 

                  'tls_cacertdir=%(tls_cacertdir)s\n' 

                  'tls_req_cert=%(tls_req_cert)s\n' 

                  'tls_avail=%(tls_avail)s\n') % 

                  {'use_tls': use_tls, 

                   'tls_cacertfile': tls_cacertfile, 

                   'tls_cacertdir': tls_cacertdir, 

                   'tls_req_cert': tls_req_cert, 

                   'tls_avail': ldap.TLS_AVAIL 

                   }) 

 

        #NOTE(topol) 

        #for extra debugging uncomment the following line 

        #ldap.set_option(ldap.OPT_DEBUG_LEVEL, 4095) 

 

        using_ldaps = url.lower().startswith("ldaps") 

 

        if use_tls and using_ldaps: 

            raise AssertionError(_('Invalid TLS / LDAPS combination')) 

 

        if use_tls: 

            if not ldap.TLS_AVAIL: 

                raise ValueError(_('Invalid LDAP TLS_AVAIL option: %s. TLS ' 

                                   'not available') % ldap.TLS_AVAIL) 

            if tls_cacertfile: 

                #NOTE(topol) 

                #python ldap TLS does not verify CACERTFILE or CACERTDIR 

                #so we add some extra simple sanity check verification 

                #Also, setting these values globally (i.e. on the ldap object) 

                #works but these values are ignored when setting them on the 

                #connection 

                if not os.path.isfile(tls_cacertfile): 

                    raise IOError(_("tls_cacertfile %s not found " 

                                    "or is not a file") % 

                                  tls_cacertfile) 

                ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, tls_cacertfile) 

            elif tls_cacertdir: 

                #NOTE(topol) 

                #python ldap TLS does not verify CACERTFILE or CACERTDIR 

                #so we add some extra simple sanity check verification 

                #Also, setting these values globally (i.e. on the ldap object) 

                #works but these values are ignored when setting them on the 

                #connection 

                if not os.path.isdir(tls_cacertdir): 

                    raise IOError(_("tls_cacertdir %s not found " 

                                    "or is not a directory") % 

                                  tls_cacertdir) 

                ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, tls_cacertdir) 

            if tls_req_cert in LDAP_TLS_CERTS.values(): 

                ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, tls_req_cert) 

            else: 

                LOG.debug(_("LDAP TLS: invalid TLS_REQUIRE_CERT Option=%s"), 

                          tls_req_cert) 

 

        self.conn = ldap.initialize(url) 

        self.conn.protocol_version = ldap.VERSION3 

 

        if alias_dereferencing is not None: 

            self.conn.set_option(ldap.OPT_DEREF, alias_dereferencing) 

        self.page_size = page_size 

 

        if use_tls: 

            self.conn.start_tls_s() 

 

    def simple_bind_s(self, user, password): 

        LOG.debug(_("LDAP bind: dn=%s"), user) 

        return self.conn.simple_bind_s(user, password) 

 

    def add_s(self, dn, attrs): 

        ldap_attrs = [(kind, [py2ldap(x) for x in safe_iter(values)]) 

                      for kind, values in attrs] 

        if LOG.isEnabledFor(logging.DEBUG): 

            sane_attrs = [(kind, values 

                           if kind != 'userPassword' 

                           else ['****']) 

                          for kind, values in ldap_attrs] 

            LOG.debug(_('LDAP add: dn=%(dn)s, attrs=%(attrs)s') % { 

                'dn': dn, 'attrs': sane_attrs}) 

        return self.conn.add_s(dn, ldap_attrs) 

 

    def search_s(self, dn, scope, query, attrlist=None): 

        if LOG.isEnabledFor(logging.DEBUG): 

            LOG.debug(_( 

                'LDAP search: dn=%(dn)s, scope=%(scope)s, query=%(query)s, ' 

                'attrs=%(attrlist)s') % { 

                    'dn': dn, 

                    'scope': scope, 

                    'query': query, 

                    'attrlist': attrlist}) 

        if self.page_size: 

            res = self.paged_search_s(dn, scope, query, attrlist) 

        else: 

            res = self.conn.search_s(dn, scope, query, attrlist) 

 

        o = [] 

        for dn, attrs in res: 

            o.append((dn, dict((kind, [ldap2py(x) for x in values]) 

                               for kind, values in attrs.iteritems()))) 

        return o 

 

    def paged_search_s(self, dn, scope, query, attrlist=None): 

        res = [] 

        lc = ldap.controls.SimplePagedResultsControl( 

            controlType=ldap.LDAP_CONTROL_PAGE_OID, 

            criticality=True, 

            controlValue=(self.page_size, '')) 

        msgid = self.conn.search_ext(dn, 

                                     scope, 

                                     query, 

                                     attrlist, 

                                     serverctrls=[lc]) 

        # Endless loop request pages on ldap server until it has no data 

        while True: 

            # Request to the ldap server a page with 'page_size' entries 

            rtype, rdata, rmsgid, serverctrls = self.conn.result3(msgid) 

            # Receive the data 

            res.extend(rdata) 

            pctrls = [c for c in serverctrls 

                      if c.controlType == ldap.LDAP_CONTROL_PAGE_OID] 

            if pctrls: 

                # LDAP server supports pagination 

                est, cookie = pctrls[0].controlValue 

                if cookie: 

                    # There is more data still on the server 

                    # so we request another page 

                    lc.controlValue = (self.page_size, cookie) 

                    msgid = self.conn.search_ext(dn, 

                                                 scope, 

                                                 query, 

                                                 attrlist, 

                                                 serverctrls=[lc]) 

                else: 

                    # Exit condition no more data on server 

                    break 

            else: 

                LOG.warning(_('LDAP Server does not support paging. ' 

                              'Disable paging in keystone.conf to ' 

                              'avoid this message.')) 

                self._disable_paging() 

                break 

        return res 

 

    def modify_s(self, dn, modlist): 

        ldap_modlist = [ 

            (op, kind, (None if values is None 

                        else [py2ldap(x) for x in safe_iter(values)])) 

            for op, kind, values in modlist] 

 

        if LOG.isEnabledFor(logging.DEBUG): 

            sane_modlist = [(op, kind, (values if kind != 'userPassword' 

                                        else ['****'])) 

                            for op, kind, values in ldap_modlist] 

            LOG.debug(_('LDAP modify: dn=%(dn)s, modlist=%(modlist)s') % { 

                'dn': dn, 'modlist': sane_modlist}) 

 

        return self.conn.modify_s(dn, ldap_modlist) 

 

    def delete_s(self, dn): 

        LOG.debug(_("LDAP delete: dn=%s"), dn) 

        return self.conn.delete_s(dn) 

 

    def delete_ext_s(self, dn, serverctrls): 

        LOG.debug( 

            _('LDAP delete_ext: dn=%(dn)s, serverctrls=%(serverctrls)s') % { 

                'dn': dn, 'serverctrls': serverctrls}) 

        return self.conn.delete_ext_s(dn, serverctrls) 

 

    def _disable_paging(self): 

        # Disable the pagination from now on 

        self.page_size = 0 

 

 

class EnabledEmuMixIn(BaseLdap): 

    """Emulates boolean 'enabled' attribute if turned on. 

 

    Creates groupOfNames holding all enabled objects of this class, all missing 

    objects are considered disabled. 

 

    Options: 

 

    * $name_enabled_emulation - boolean, on/off 

    * $name_enabled_emulation_dn - DN of that groupOfNames, default is 

      cn=enabled_$name,$tree_dn 

 

    Where $name is self.options_name ('user' or 'tenant'), $tree_dn is 

    self.tree_dn. 

    """ 

 

    def __init__(self, conf): 

        super(EnabledEmuMixIn, self).__init__(conf) 

        enabled_emulation = '%s_enabled_emulation' % self.options_name 

        self.enabled_emulation = getattr(conf.ldap, enabled_emulation) 

 

        enabled_emulation_dn = '%s_enabled_emulation_dn' % self.options_name 

        self.enabled_emulation_dn = getattr(conf.ldap, enabled_emulation_dn) 

exit        if not self.enabled_emulation_dn: 

            self.enabled_emulation_dn = ('cn=enabled_%ss,%s' % 

                                         (self.options_name, self.tree_dn)) 

 

    def _get_enabled(self, object_id): 

        conn = self.get_connection() 

        dn = self._id_to_dn(object_id) 

        query = '(member=%s)' % dn 

        try: 

            enabled_value = conn.search_s(self.enabled_emulation_dn, 

                                          ldap.SCOPE_BASE, 

                                          query) 

        except ldap.NO_SUCH_OBJECT: 

            return False 

        else: 

            return bool(enabled_value) 

 

    def _add_enabled(self, object_id): 

        if not self._get_enabled(object_id): 

            conn = self.get_connection() 

            modlist = [(ldap.MOD_ADD, 

                        'member', 

                        [self._id_to_dn(object_id)])] 

            try: 

                conn.modify_s(self.enabled_emulation_dn, modlist) 

            except ldap.NO_SUCH_OBJECT: 

                attr_list = [('objectClass', ['groupOfNames']), 

                             ('member', 

                             [self._id_to_dn(object_id)])] 

                if self.use_dumb_member: 

                    attr_list[1][1].append(self.dumb_member) 

                conn.add_s(self.enabled_emulation_dn, attr_list) 

 

    def _remove_enabled(self, object_id): 

        conn = self.get_connection() 

        modlist = [(ldap.MOD_DELETE, 

                    'member', 

                    [self._id_to_dn(object_id)])] 

        try: 

            conn.modify_s(self.enabled_emulation_dn, modlist) 

        except (ldap.NO_SUCH_OBJECT, ldap.NO_SUCH_ATTRIBUTE): 

            pass 

 

    def create(self, values): 

        if self.enabled_emulation: 

            enabled_value = values.pop('enabled', True) 

            ref = super(EnabledEmuMixIn, self).create(values) 

            if 'enabled' not in self.attribute_ignore: 

                if enabled_value: 

                    self._add_enabled(ref['id']) 

                ref['enabled'] = enabled_value 

            return ref 

        else: 

            return super(EnabledEmuMixIn, self).create(values) 

 

    def get(self, object_id, filter=None): 

        ref = super(EnabledEmuMixIn, self).get(object_id, filter) 

        if 'enabled' not in self.attribute_ignore and self.enabled_emulation: 

            ref['enabled'] = self._get_enabled(object_id) 

        return ref 

 

    def get_all(self, filter=None): 

        if 'enabled' not in self.attribute_ignore and self.enabled_emulation: 

            # had to copy BaseLdap.get_all here to filter by DN 

            tenant_list = [self._ldap_res_to_model(x) 

                           for x in self._ldap_get_all(filter) 

                           if x[0] != self.enabled_emulation_dn] 

            for tenant_ref in tenant_list: 

                tenant_ref['enabled'] = self._get_enabled(tenant_ref['id']) 

            return tenant_list 

        else: 

            return super(EnabledEmuMixIn, self).get_all(filter) 

 

    def update(self, object_id, values, old_obj=None): 

        if 'enabled' not in self.attribute_ignore and self.enabled_emulation: 

            data = values.copy() 

            enabled_value = data.pop('enabled', None) 

            ref = super(EnabledEmuMixIn, self).update(object_id, data, old_obj) 

            if enabled_value is not None: 

                if enabled_value: 

                    self._add_enabled(object_id) 

                else: 

                    self._remove_enabled(object_id) 

            return ref 

        else: 

            return super(EnabledEmuMixIn, self).update( 

                object_id, values, old_obj) 

 

    def delete(self, object_id): 

        if self.enabled_emulation: 

            self._remove_enabled(object_id) 

        super(EnabledEmuMixIn, self).delete(object_id)