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

# Authors: 

#     Sumit Bose <sbose@redhat.com> 

# 

# Copyright (C) 2012  Red Hat 

# see file 'COPYING' for use and warranty information 

# 

# This program is free software; you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 3 of the License, or 

# (at your option) any later version. 

# 

# This program is distributed in the hope that it will be useful, 

# but WITHOUT ANY WARRANTY; without even the implied warranty of 

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 

# GNU General Public License for more details. 

# 

# You should have received a copy of the GNU General Public License 

# along with this program.  If not, see <http://www.gnu.org/licenses/>. 

 

from ipalib.plugins.baseldap import * 

from ipalib import api, Str, Password, DefaultFrom, _, ngettext, Object 

from ipalib.parameters import Enum 

from ipalib import Command 

from ipalib import errors 

from ipapython import ipautil 

from ipalib import util 

from ipapython.dn import DN 

 

if api.env.in_server and api.env.context in ['lite', 'server']: 

    try: 

        import ipaserver.dcerpc 

        _dcerpc_bindings_installed = True 

    except ImportError: 

        _dcerpc_bindings_installed = False 

 

__doc__ = _(""" 

ID ranges 

 

Manage ID ranges  used to map Posix IDs to SIDs and back. 

 

There are two type of ID ranges which are both handled by this utility: 

 

- the ID ranges of the local domain 

- the ID ranges of trusted remote domains 

 

Both types have the following attributes in common: 

 

- base-id: the first ID of the Posix ID range 

- range-size: the size of the range 

 

With those two attributes a range object can reserve the Posix IDs starting 

with base-id up to but not including base-id+range-size exclusively. 

 

Additionally an ID range of the local domain may set 

- rid-base: the first RID(*) of the corresponding RID range 

- secondary-rid-base: first RID of the secondary RID range 

 

and an ID range of a trusted domain must set 

- rid-base: the first RID of the corresponding RID range 

- sid: domain SID of the trusted domain 

 

 

 

EXAMPLE: Add a new ID range for a trusted domain 

 

Since there might be more than one trusted domain the domain SID must be given 

while creating the ID range. 

 

  ipa idrange-add --base-id=1200000 --range-size=200000 --rid-base=0 \\ 

                  --dom-sid=S-1-5-21-123-456-789 trusted_dom_range 

 

This ID range is then used by the IPA server and the SSSD IPA provider to 

assign Posix UIDs to users from the trusted domain. 

 

If e.g a range for a trusted domain is configured with the following values: 

base-id = 1200000 

range-size = 200000 

rid-base = 0 

the RIDs 0 to 199999 are mapped to the Posix ID from 1200000 to 13999999. So 

RID 1000 <-> Posix ID 1201000 

 

 

 

EXAMPLE: Add a new ID range for the local domain 

 

To create an ID range for the local domain it is not necessary to specify a 

domain SID. But since it is possible that a user and a group can have the same 

value as Posix ID a second RID interval is needed to handle conflicts. 

 

  ipa idrange-add --base-id=1200000 --range-size=200000 --rid-base=1000 \\ 

                  --secondary-rid-base=1000000 local_range 

 

The data from the ID ranges of the local domain are used by the IPA server 

internally to assign SIDs to IPA users and groups. The SID will then be stored 

in the user or group objects. 

 

If e.g. the ID range for the local domain is configured with the values from 

the example above then a new user with the UID 1200007 will get the RID 1007. 

If this RID is already used by a group the RID will be 1000007. This can only 

happen if a user or a group object was created with a fixed ID because the 

automatic assignment will not assign the same ID twice. Since there are only 

users and groups sharing the same ID namespace it is sufficient to have only 

one fallback range to handle conflicts. 

 

To find the Posix ID for a given RID from the local domain it has to be 

checked first if the RID falls in the primary or secondary RID range and 

the rid-base or the secondary-rid-base has to be subtracted, respectively, 

and the base-id has to be added to get the Posix ID. 

 

Typically the creation of ID ranges happens behind the scenes and this CLI 

must not be used at all. The ID range for the local domain will be created 

during installation or upgrade from an older version. The ID range for a 

trusted domain will be created together with the trust by 'ipa trust-add ...'. 

 

USE CASES: 

 

  Add an ID range from a transitively trusted domain 

 

    If the trusted domain (A) trusts another domain (B) as well and this trust 

    is transitive 'ipa trust-add domain-A' will only create a range for 

    domain A.  The ID range for domain B must be added manually. 

 

  Add an additional ID range for the local domain 

 

    If the ID range of the local domain is exhausted, i.e. no new IDs can be 

    assigned to Posix users or groups by the DNA plugin, a new range has to be 

    created to allow new users and groups to be added. (Currently there is no 

    connection between this range CLI and the DNA plugin, but a future version 

    might be able to modify the configuration of the DNS plugin as well) 

 

In general it is not necessary to modify or delete ID ranges. If there is no 

other way to achieve a certain configuration than to modify or delete an ID 

range it should be done with great care. Because UIDs are stored in the file 

system and are used for access control it might be possible that users are 

allowed to access files of other users if an ID range got deleted and reused 

for a different domain. 

 

(*) The RID is typically the last integer of a user or group SID which follows 

the domain SID. E.g. if the domain SID is S-1-5-21-123-456-789 and a user from 

this domain has the SID S-1-5-21-123-456-789-1010 then 1010 id the RID of the 

user. RIDs are unique in a domain, 32bit values and are used for users and 

groups. 

 

WARNING: 

 

DNA plugin in 389-ds will allocate IDs based on the ranges configured for the 

local domain. Currently the DNA plugin *cannot* be reconfigured itself based 

on the local ranges set via this family of commands. 

 

Manual configuration change has to be done in the DNA plugin configuration for 

the new local range. Specifically, The dnaNextRange attribute of 'cn=Posix 

IDs,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config' has to be 

modified to match the new range. 

""") 

 

class idrange(LDAPObject): 

    """ 

    Range object. 

    """ 

 

    range_type = ('domain', 'ad', 'ipa') 

    container_dn = api.env.container_ranges 

    object_name = ('range') 

    object_name_plural = ('ranges') 

    object_class = ['ipaIDrange'] 

    possible_objectclasses = ['ipadomainidrange', 'ipatrustedaddomainrange'] 

    default_attributes = ['cn', 'ipabaseid', 'ipaidrangesize', 'ipabaserid', 

                          'ipasecondarybaserid', 'ipanttrusteddomainsid', 

                          'iparangetype'] 

 

    label = _('ID Ranges') 

    label_singular = _('ID Range') 

 

    takes_params = ( 

        Str('cn', 

            cli_name='name', 

            label=_('Range name'), 

            primary_key=True, 

        ), 

        Int('ipabaseid', 

            cli_name='base_id', 

            label=_("First Posix ID of the range"), 

        ), 

        Int('ipaidrangesize', 

            cli_name='range_size', 

            label=_("Number of IDs in the range"), 

        ), 

        Int('ipabaserid?', 

            cli_name='rid_base', 

            label=_('First RID of the corresponding RID range'), 

        ), 

        Int('ipasecondarybaserid?', 

            cli_name='secondary_rid_base', 

            label=_('First RID of the secondary RID range'), 

        ), 

        Str('ipanttrusteddomainsid?', 

            cli_name='dom_sid', 

            label=_('Domain SID of the trusted domain'), 

        ), 

        Str('ipanttrusteddomainname?', 

            cli_name='dom_name', 

            flags=('no_search', 'virtual_attribute'), 

            label=_('Name of the trusted domain'), 

        ), 

        Str('iparangetype?', 

            label=_('Range type'), 

            flags=['no_option'], 

        ) 

    ) 

 

    def handle_iparangetype(self, entry_attrs, options, keep_objectclass=False): 

        if not options.get('pkey_only', False): 

            if 'ipatrustedaddomainrange' in entry_attrs.get('objectclass', []): 

                entry_attrs['iparangetype'] = [unicode(_('Active Directory domain range'))] 

            else: 

                entry_attrs['iparangetype'] = [unicode(_(u'local domain range'))] 

        if not keep_objectclass: 

            if not options.get('all', False) or options.get('pkey_only', False): 

                entry_attrs.pop('objectclass', None) 

 

    def check_ids_in_modified_range(self, old_base, old_size, new_base, new_size): 

        if new_base is None and new_size is None: 

            # nothing to check 

            return 

        if new_base is None: 

            new_base = old_base 

        if new_size is None: 

            new_size = old_size 

        old_interval = (old_base, old_base + old_size - 1) 

        new_interval = (new_base, new_base + new_size - 1) 

        checked_intervals = [] 

        low_diff = new_interval[0] - old_interval[0] 

        if low_diff > 0: 

            checked_intervals.append( 

                    (old_interval[0], min(old_interval[1], new_interval[0] - 1))) 

        high_diff = old_interval[1] - new_interval[1] 

        if high_diff > 0: 

            checked_intervals.append( 

                    (max(old_interval[0], new_interval[1] + 1), old_interval[1])) 

 

        if not checked_intervals: 

            # range is equal or covers the entire old range, nothing to check 

            return 

 

        ldap = self.backend 

        id_filter_base = ["(objectclass=posixAccount)", 

                          "(objectclass=posixGroup)", 

                          "(objectclass=ipaIDObject)"] 

        id_filter_ids = [] 

 

        for id_low, id_high in checked_intervals: 

            id_filter_ids.append("(&(uidNumber>=%(low)d)(uidNumber<=%(high)d))" 

                                 % dict(low=id_low, high=id_high)) 

            id_filter_ids.append("(&(gidNumber>=%(low)d)(gidNumber<=%(high)d))" 

                                 % dict(low=id_low, high=id_high)) 

        id_filter = ldap.combine_filters( 

                        [ldap.combine_filters(id_filter_base, "|"), 

                          ldap.combine_filters(id_filter_ids, "|")], 

                        "&") 

 

        try: 

            (objects, truncated) = ldap.find_entries(filter=id_filter, 

                    attrs_list=['uid', 'cn'], 

                    base_dn=DN(api.env.container_accounts, api.env.basedn)) 

        except errors.NotFound: 

            # no objects in this range found, allow the command 

            pass 

        else: 

            raise errors.ValidationError(name="ipabaseid,ipaidrangesize", 

                    error=_('range modification leaving objects with ID out ' 

                            'of the defined range is not allowed')) 

 

    def get_domain_validator(self): 

        if not _dcerpc_bindings_installed: 

            raise errors.NotFound(reason=_('Cannot perform SID validation ' 

                'without Samba 4 support installed. Make sure you have ' 

                'installed server-trust-ad sub-package of IPA on the server')) 

 

        domain_validator = ipaserver.dcerpc.DomainValidator(self.api) 

 

        if not domain_validator.is_configured(): 

            raise errors.NotFound(reason=_('Cross-realm trusts are not ' 

                'configured. Make sure you have run ipa-adtrust-install ' 

                'on the IPA server first')) 

 

        return domain_validator 

 

    def validate_trusted_domain_sid(self, sid): 

 

        domain_validator = self.get_domain_validator() 

 

        if not domain_validator.is_trusted_domain_sid_valid(sid): 

            raise errors.ValidationError(name='domain SID', 

                  error=_('SID is not recognized as a valid SID for a ' 

                          'trusted domain')) 

 

    def get_trusted_domain_sid_from_name(self, name): 

        """ Returns unicode string representation for given trusted domain name 

        or None if SID forthe given trusted domain name could not be found.""" 

 

        domain_validator = self.get_domain_validator() 

 

        sid = domain_validator.get_sid_from_domain_name(name) 

 

        if sid is not None: 

            sid = unicode(sid) 

 

        return sid 

 

    # checks that primary and secondary rid ranges do not overlap 

    def are_rid_ranges_overlapping(self, rid_base, secondary_rid_base, size): 

 

        # if any of these is None, the check does not apply 

        if any(attr is None for attr in (rid_base, secondary_rid_base, size)): 

            return False 

 

        # sort the bases 

        if rid_base > secondary_rid_base: 

            rid_base, secondary_rid_base = secondary_rid_base, rid_base 

 

        # rid_base is now <= secondary_rid_base, 

        # so the following check is sufficient 

        if rid_base + size <= secondary_rid_base: 

            return False 

        else: 

            return True 

 

 

class idrange_add(LDAPCreate): 

    __doc__ = _(""" 

    Add new ID range. 

 

    To add a new ID range you always have to specify 

 

        --base-id 

        --range-size 

 

    Additionally 

 

        --rid-base 

        --secondary-rid-base 

 

    may be given for a new ID range for the local domain while 

 

        --rid-bas 

        --dom-sid 

 

    must be given to add a new range for a trusted AD domain. 

 

    WARNING: 

 

    DNA plugin in 389-ds will allocate IDs based on the ranges configured for the 

    local domain. Currently the DNA plugin *cannot* be reconfigured itself based 

    on the local ranges set via this family of commands. 

 

    Manual configuration change has to be done in the DNA plugin configuration for 

    the new local range. Specifically, The dnaNextRange attribute of 'cn=Posix 

    IDs,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config' has to be 

    modified to match the new range. 

    """) 

 

    msg_summary = _('Added ID range "%(value)s"') 

 

    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): 

        assert isinstance(dn, DN) 

 

        is_set = lambda x: (x in entry_attrs) and (entry_attrs[x] is not None) 

 

        # This needs to stay in options since there is no 

        # ipanttrusteddomainname attribute in LDAP 

        if 'ipanttrusteddomainname' in options: 

            if is_set('ipanttrusteddomainsid'): 

                raise errors.ValidationError(name='ID Range setup', 

                    error=_('Options dom-sid and dom-name ' 

                            'cannot be used together')) 

 

            sid = self.obj.get_trusted_domain_sid_from_name( 

                options['ipanttrusteddomainname']) 

 

            if sid is not None: 

                entry_attrs['ipanttrusteddomainsid'] = sid 

            else: 

                raise errors.ValidationError(name='ID Range setup', 

                    error=_('SID for the specified trusted domain name could ' 

                            'not be found. Please specify the SID directly ' 

                            'using dom-sid option.')) 

 

        if is_set('ipanttrusteddomainsid'): 

            if is_set('ipasecondarybaserid'): 

                raise errors.ValidationError(name='ID Range setup', 

                    error=_('Options dom-sid/dom-name and secondary-rid-base ' 

                            'cannot be used together')) 

 

            if not is_set('ipabaserid'): 

                raise errors.ValidationError(name='ID Range setup', 

                    error=_('Options dom-sid/dom-name and rid-base must ' 

                            'be used together')) 

 

            # Validate SID as the one of trusted domains 

            self.obj.validate_trusted_domain_sid(entry_attrs['ipanttrusteddomainsid']) 

            # Finally, add trusted AD domain range object class 

            entry_attrs['objectclass'].append('ipatrustedaddomainrange') 

 

        else: 

             # secondary base rid must be set if and only if base rid is set 

            if is_set('ipasecondarybaserid') != is_set('ipabaserid'): 

                raise errors.ValidationError(name='ID Range setup', 

                    error=_('Options secondary-rid-base and rid-base must ' 

                            'be used together')) 

 

            # and they must not overlap 

            if is_set('ipabaserid') and is_set('ipasecondarybaserid'): 

                if self.obj.are_rid_ranges_overlapping( 

                    entry_attrs['ipabaserid'], 

                    entry_attrs['ipasecondarybaserid'], 

                    entry_attrs['ipaidrangesize']): 

                       raise errors.ValidationError(name='ID Range setup', 

                           error=_("Primary RID range and secondary RID range" 

                               " cannot overlap")) 

 

            entry_attrs['objectclass'].append('ipadomainidrange') 

 

        return dn 

 

    def post_callback(self, ldap, dn, entry_attrs, *keys, **options): 

        assert isinstance(dn, DN) 

        self.obj.handle_iparangetype(entry_attrs, options, keep_objectclass=True) 

        return dn 

 

class idrange_del(LDAPDelete): 

    __doc__ = _('Delete an ID range.') 

 

    msg_summary = _('Deleted ID range "%(value)s"') 

 

    def pre_callback(self, ldap, dn, *keys, **options): 

        try: 

            (old_dn, old_attrs) = ldap.get_entry(dn, ['ipabaseid', 'ipaidrangesize']) 

        except errors.NotFound: 

            self.obj.handle_not_found(*keys) 

 

        old_base_id = int(old_attrs.get('ipabaseid', [0])[0]) 

        old_range_size = int(old_attrs.get('ipaidrangesize', [0])[0]) 

        self.obj.check_ids_in_modified_range( 

                old_base_id, old_range_size, 0, 0) 

        return dn 

 

class idrange_find(LDAPSearch): 

    __doc__ = _('Search for ranges.') 

 

    msg_summary = ngettext( 

        '%(count)d range matched', '%(count)d ranges matched', 0 

    ) 

 

    # Since all range types are stored within separate containers under 

    # 'cn=ranges,cn=etc' search can be done on a one-level scope 

    def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options): 

        assert isinstance(base_dn, DN) 

        attrs_list.append('objectclass') 

        return (filters, base_dn, ldap.SCOPE_ONELEVEL) 

 

    def post_callback(self, ldap, entries, truncated, *args, **options): 

        for dn, entry in entries: 

            self.obj.handle_iparangetype(entry, options) 

        return truncated 

 

class idrange_show(LDAPRetrieve): 

    __doc__ = _('Display information about a range.') 

 

    def pre_callback(self, ldap, dn, attrs_list, *keys, **options): 

        assert isinstance(dn, DN) 

        attrs_list.append('objectclass') 

        return dn 

 

    def post_callback(self, ldap, dn, entry_attrs, *keys, **options): 

        assert isinstance(dn, DN) 

        self.obj.handle_iparangetype(entry_attrs, options) 

        return dn 

 

class idrange_mod(LDAPUpdate): 

    __doc__ = _('Modify ID range.') 

 

    msg_summary = _('Modified ID range "%(value)s"') 

 

    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): 

        assert isinstance(dn, DN) 

        attrs_list.append('objectclass') 

 

        try: 

            (old_dn, old_attrs) = ldap.get_entry(dn, ['*']) 

        except errors.NotFound: 

            self.obj.handle_not_found(*keys) 

 

        is_set = lambda x: (x in entry_attrs) and (entry_attrs[x] is not None) 

        in_updated_attrs = lambda x:\ 

            (x in entry_attrs and entry_attrs[x] is not None) or\ 

            (x not in entry_attrs and x in old_attrs 

                and old_attrs[x] is not None) 

 

        # This needs to stay in options since there is no 

        # ipanttrusteddomainname attribute in LDAP 

        if 'ipanttrusteddomainname' in options: 

            if is_set('ipanttrusteddomainsid'): 

                raise errors.ValidationError(name='ID Range setup', 

                    error=_('Options dom-sid and dom-name ' 

                            'cannot be used together')) 

 

            sid = self.obj.get_trusted_domain_sid_from_name( 

                options['ipanttrusteddomainname']) 

 

            # we translate the name into sid so further validation can rely 

            # on ipanttrusteddomainsid attribute only 

            if sid is not None: 

                entry_attrs['ipanttrusteddomainsid'] = sid 

            else: 

                raise errors.ValidationError(name='ID Range setup', 

                    error=_('SID for the specified trusted domain name could ' 

                            'not be found. Please specify the SID directly ' 

                            'using dom-sid option.')) 

 

        if in_updated_attrs('ipanttrusteddomainsid'): 

            if in_updated_attrs('ipasecondarybaserid'): 

                raise errors.ValidationError(name='ID Range setup', 

                    error=_('Options dom-sid and secondary-rid-base cannot ' 

                            'be used together')) 

 

            if not in_updated_attrs('ipabaserid'): 

                raise errors.ValidationError(name='ID Range setup', 

                    error=_('Options dom-sid and rid-base must ' 

                            'be used together')) 

 

            if is_set('ipanttrusteddomainsid'): 

                # Validate SID as the one of trusted domains 

                # perform this check only if the attribute was changed 

                self.obj.validate_trusted_domain_sid( 

                    entry_attrs['ipanttrusteddomainsid']) 

 

           # Add trusted AD domain range object class, if it wasn't there 

            if not 'ipatrustedaddomainrange' in old_attrs['objectclass']: 

                entry_attrs['objectclass'].append('ipatrustedaddomainrange') 

 

        else: 

            # secondary base rid must be set if and only if base rid is set 

            if in_updated_attrs('ipasecondarybaserid') !=\ 

                in_updated_attrs('ipabaserid'): 

                raise errors.ValidationError(name='ID Range setup', 

                    error=_('Options secondary-rid-base and rid-base must ' 

                            'be used together')) 

 

        # ensure that primary and secondary rid ranges do not overlap 

        if all(in_updated_attrs(base) 

               for base in ('ipabaserid', 'ipasecondarybaserid')): 

 

            # make sure we are working with updated attributes 

            rid_range_attributes = ('ipabaserid', 'ipasecondarybaserid', 

                                    'ipaidrangesize') 

            updated_values = dict() 

 

            for attr in rid_range_attributes: 

                if is_set(attr): 

                    updated_values[attr] = entry_attrs[attr] 

                else: 

                    updated_values[attr] = int(old_attrs[attr][0]) 

 

            if self.obj.are_rid_ranges_overlapping( 

                updated_values['ipabaserid'], 

                updated_values['ipasecondarybaserid'], 

                updated_values['ipaidrangesize']): 

                    raise errors.ValidationError(name='ID Range setup', 

                            error=_("Primary RID range and secondary RID range" 

                                 " cannot overlap")) 

 

        # check whether ids are in modified range 

        old_base_id = int(old_attrs.get('ipabaseid', [0])[0]) 

        old_range_size = int(old_attrs.get('ipaidrangesize', [0])[0]) 

        new_base_id = entry_attrs.get('ipabaseid') 

 

        if new_base_id is not None: 

            new_base_id = int(new_base_id) 

 

        new_range_size = entry_attrs.get('ipaidrangesize') 

 

        if new_range_size is not None: 

            new_range_size = int(new_range_size) 

 

        self.obj.check_ids_in_modified_range(old_base_id, old_range_size, 

                                             new_base_id, new_range_size) 

 

        return dn 

 

    def post_callback(self, ldap, dn, entry_attrs, *keys, **options): 

        assert isinstance(dn, DN) 

        self.obj.handle_iparangetype(entry_attrs, options) 

        return dn 

 

api.register(idrange) 

api.register(idrange_add) 

api.register(idrange_mod) 

api.register(idrange_del) 

api.register(idrange_find) 

api.register(idrange_show)