2019-04-12 15:23:26 +03:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008-2011
#
# 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/>.
import optparse
import sys
import os
from itertools import permutations
import traceback
sys . path . insert ( 0 , " bin/python " )
import samba
from samba . tests . subunitrun import SubunitOptions , TestProgram
import samba . getopt as options
from samba . auth import system_session
from ldb import SCOPE_BASE , LdbError
from ldb import Message , MessageElement , Dn
from ldb import FLAG_MOD_ADD , FLAG_MOD_REPLACE , FLAG_MOD_DELETE
from samba . samdb import SamDB
from samba . tests import delete_force
TEST_DATA_DIR = os . path . join (
os . path . dirname ( __file__ ) ,
' testdata ' )
LDB_STRERR = { }
def _build_ldb_strerr ( ) :
import ldb
for k , v in vars ( ldb ) . items ( ) :
if k . startswith ( ' ERR_ ' ) and isinstance ( v , int ) :
LDB_STRERR [ v ] = k
_build_ldb_strerr ( )
class ModifyOrderTests ( samba . tests . TestCase ) :
def setUp ( self ) :
super ( ) . setUp ( )
2019-04-18 04:39:58 +03:00
self . admin_dsdb = get_dsdb ( admin_creds )
self . base_dn = self . admin_dsdb . domain_dn ( )
2019-04-12 15:23:26 +03:00
def delete_object ( self , dn ) :
2019-04-18 04:39:58 +03:00
delete_force ( self . admin_dsdb , dn )
def get_user_dn ( self , name ) :
return " CN= %s ,CN=Users, %s " % ( name , self . base_dn )
2019-04-12 15:23:26 +03:00
def _test_modify_order ( self ,
start_attrs ,
mod_attrs ,
extra_search_attrs = ( ) ,
name = None ) :
if name is None :
name = traceback . extract_stack ( ) [ - 2 ] [ 2 ] [ 5 : ]
2019-04-18 04:39:58 +03:00
if opts . normal_user :
name + = ' -non-admin '
username = " user123 "
password = " pass123@#$@# "
self . admin_dsdb . newuser ( username , password )
self . addCleanup ( self . delete_object , self . get_user_dn ( username ) )
mod_creds = self . insta_creds ( template = admin_creds ,
username = username ,
userpass = password )
else :
mod_creds = admin_creds
mod_dsdb = get_dsdb ( mod_creds )
2019-04-12 15:23:26 +03:00
sig = [ ]
op_lut = [ ' ' , ' add ' , ' replace ' , ' delete ' ]
search_attrs = set ( extra_search_attrs )
lines = [ name , " initial attrs: " ]
for k , v in start_attrs :
lines . append ( " %20s : %r " % ( k , v ) )
search_attrs . add ( k )
for k , v , op in mod_attrs :
search_attrs . add ( k )
search_attrs = sorted ( search_attrs )
header = " \n " . join ( lines )
sig . append ( header )
clusters = { }
for i , attrs in enumerate ( permutations ( mod_attrs ) ) :
2023-08-02 11:44:32 +03:00
# for each permutation we construct a string describing the
2019-04-12 15:23:26 +03:00
# requested operations, and a string describing the result
# (which may be an exception). The we cluster the
# attribute strings by their results.
dn = " cn=ldaptest_ %s _ %d ,cn=users, %s " % ( name , i , self . base_dn )
m = Message ( )
2019-04-18 04:39:58 +03:00
m . dn = Dn ( self . admin_dsdb , dn )
# We are using Message objects here for add (rather than the
# more convenient dict) because we maybe care about the order
# in which attributes are added.
2019-04-12 15:23:26 +03:00
for k , v in start_attrs :
m [ k ] = MessageElement ( v , 0 , k )
2019-04-18 04:39:58 +03:00
self . admin_dsdb . add ( m )
2019-04-12 15:23:26 +03:00
self . addCleanup ( self . delete_object , dn )
m = Message ( )
2019-04-18 04:39:58 +03:00
m . dn = Dn ( mod_dsdb , dn )
2019-04-12 15:23:26 +03:00
attr_lines = [ ]
for k , v , op in attrs :
if v is None :
v = dn
m [ k ] = MessageElement ( v , op , k )
attr_lines . append ( " %16s %-8s %s " % ( k , op_lut [ op ] , v ) )
attr_str = ' \n ' . join ( attr_lines )
try :
2019-04-18 04:39:58 +03:00
mod_dsdb . modify ( m )
2019-04-12 15:23:26 +03:00
except LdbError as e :
err , _ = e . args
s = LDB_STRERR . get ( err , " unknown error " )
result_str = " %s ( %d ) " % ( s , err )
else :
2019-04-18 04:39:58 +03:00
res = self . admin_dsdb . search ( base = dn , scope = SCOPE_BASE ,
attrs = search_attrs )
2019-04-12 15:23:26 +03:00
lines = [ ]
for k , v in sorted ( dict ( res [ 0 ] ) . items ( ) ) :
if k != " dn " or k in extra_search_attrs :
lines . append ( " %20s : %r " % ( k , sorted ( v ) ) )
result_str = ' \n ' . join ( lines )
clusters . setdefault ( result_str , [ ] ) . append ( attr_str )
for s , attrs in sorted ( clusters . items ( ) ) :
sig . extend ( [
" == result ===[ %3d ]======================= " % len ( attrs ) ,
s ,
" -- operations --------------------------- " ] )
for a in attrs :
sig . append ( a )
sig . append ( " - " * 34 )
sig = ' \n ' . join ( sig ) . replace ( self . base_dn , " { base dn} " )
if opts . verbose :
print ( sig )
if opts . rewrite_ground_truth :
f = open ( os . path . join ( TEST_DATA_DIR , name + ' .expected ' ) , ' w ' )
f . write ( sig )
f . close ( )
f = open ( os . path . join ( TEST_DATA_DIR , name + ' .expected ' ) )
expected = f . read ( )
f . close ( )
self . assertStringsEqual ( sig , expected )
def test_modify_order_mixed ( self ) :
start_attrs = [ ( " objectclass " , " user " ) ,
( " carLicense " , [ " 1 " , " 2 " , " 3 " ] ) ,
( " otherTelephone " , " 123 " ) ]
mod_attrs = [ ( " carLicense " , " 3 " , FLAG_MOD_DELETE ) ,
( " carLicense " , " 4 " , FLAG_MOD_ADD ) ,
( " otherTelephone " , " 4 " , FLAG_MOD_REPLACE ) ,
( " otherTelephone " , " 123 " , FLAG_MOD_DELETE ) ]
self . _test_modify_order ( start_attrs , mod_attrs )
def test_modify_order_mixed2 ( self ) :
start_attrs = [ ( " objectclass " , " user " ) ,
( " carLicense " , [ " 1 " , " 2 " , " 3 " ] ) ,
( " ipPhone " , " 123 " ) ]
mod_attrs = [ ( " carLicense " , " 3 " , FLAG_MOD_DELETE ) ,
( " carLicense " , " 4 " , FLAG_MOD_ADD ) ,
( " ipPhone " , " 4 " , FLAG_MOD_REPLACE ) ,
( " ipPhone " , " 123 " , FLAG_MOD_DELETE ) ]
self . _test_modify_order ( start_attrs , mod_attrs )
def test_modify_order_telephone ( self ) :
start_attrs = [ ( " objectclass " , " user " ) ,
( " otherTelephone " , " 123 " ) ]
mod_attrs = [ ( " carLicense " , " 3 " , FLAG_MOD_REPLACE ) ,
( " carLicense " , " 4 " , FLAG_MOD_ADD ) ,
( " otherTelephone " , " 4 " , FLAG_MOD_REPLACE ) ,
( " otherTelephone " , " 4 " , FLAG_MOD_ADD ) ,
( " otherTelephone " , " 123 " , FLAG_MOD_DELETE ) ]
self . _test_modify_order ( start_attrs , mod_attrs )
def test_modify_order_telephone_delete_delete ( self ) :
start_attrs = [ ( " objectclass " , " user " ) ,
( " otherTelephone " , " 123 " ) ]
mod_attrs = [ ( " carLicense " , " 3 " , FLAG_MOD_REPLACE ) ,
( " carLicense " , " 4 " , FLAG_MOD_DELETE ) ,
( " otherTelephone " , " 4 " , FLAG_MOD_REPLACE ) ,
( " otherTelephone " , " 4 " , FLAG_MOD_DELETE ) ,
( " otherTelephone " , " 123 " , FLAG_MOD_DELETE ) ]
self . _test_modify_order ( start_attrs , mod_attrs )
def test_modify_order_objectclass ( self ) :
start_attrs = [ ( " objectclass " , " user " ) ,
( " otherTelephone " , " 123 " ) ]
mod_attrs = [ ( " objectclass " , " computer " , FLAG_MOD_REPLACE ) ,
( " objectclass " , " user " , FLAG_MOD_DELETE ) ,
( " objectclass " , " person " , FLAG_MOD_DELETE ) ]
self . _test_modify_order ( start_attrs , mod_attrs )
def test_modify_order_objectclass2 ( self ) :
start_attrs = [ ( " objectclass " , " user " ) ]
mod_attrs = [ ( " objectclass " , " computer " , FLAG_MOD_REPLACE ) ,
( " objectclass " , " user " , FLAG_MOD_ADD ) ,
( " objectclass " , " attributeSchema " , FLAG_MOD_REPLACE ) ,
( " objectclass " , " inetOrgPerson " , FLAG_MOD_ADD ) ,
( " objectclass " , " person " , FLAG_MOD_DELETE ) ]
self . _test_modify_order ( start_attrs , mod_attrs )
def test_modify_order_singlevalue ( self ) :
start_attrs = [ ( " objectclass " , " user " ) ,
( " givenName " , " a " ) ]
mod_attrs = [ ( " givenName " , " a " , FLAG_MOD_REPLACE ) ,
( " givenName " , [ " b " , " a " ] , FLAG_MOD_REPLACE ) ,
( " givenName " , " b " , FLAG_MOD_DELETE ) ,
( " givenName " , " a " , FLAG_MOD_DELETE ) ,
( " givenName " , " c " , FLAG_MOD_ADD ) ]
self . _test_modify_order ( start_attrs , mod_attrs )
def test_modify_order_inapplicable ( self ) :
2023-08-02 11:44:32 +03:00
#attributes that don't go on a user
2019-04-12 15:23:26 +03:00
start_attrs = [ ( " objectclass " , " user " ) ,
( " givenName " , " a " ) ]
mod_attrs = [ ( " dhcpSites " , " b " , FLAG_MOD_REPLACE ) ,
( " dhcpSites " , " b " , FLAG_MOD_DELETE ) ,
( " dhcpSites " , " c " , FLAG_MOD_ADD ) ]
self . _test_modify_order ( start_attrs , mod_attrs )
def test_modify_order_sometimes_inapplicable ( self ) :
# attributes that don't go on a user, but do on a computer,
# which we sometimes change into.
start_attrs = [ ( " objectclass " , " user " ) ,
( " givenName " , " a " ) ]
mod_attrs = [ ( " objectclass " , " computer " , FLAG_MOD_REPLACE ) ,
( " objectclass " , " person " , FLAG_MOD_DELETE ) ,
( " dnsHostName " , " b " , FLAG_MOD_ADD ) ,
( " dnsHostName " , " c " , FLAG_MOD_REPLACE ) ]
self . _test_modify_order ( start_attrs , mod_attrs )
def test_modify_order_account_locality_device ( self ) :
# account, locality, and device all take l (locality name) but
# only device takes owner. We shouldn't be able to change
# objectclass at all.
start_attrs = [ ( " objectclass " , " account " ) ,
( " l " , " a " ) ]
mod_attrs = [ ( " objectclass " , [ " device " , " top " ] , FLAG_MOD_REPLACE ) ,
( " l " , " a " , FLAG_MOD_DELETE ) ,
( " owner " , " c " , FLAG_MOD_ADD )
]
self . _test_modify_order ( start_attrs , mod_attrs )
def test_modify_order_container_flags_multivalue ( self ) :
# account, locality, and device all take l (locality name)
# but only device takes owner
start_attrs = [ ( " objectclass " , " container " ) ,
( " wWWHomePage " , " a " ) ]
mod_attrs = [ ( " flags " , [ " 0 " , " 1 " ] , FLAG_MOD_ADD ) ,
( " flags " , " 65355 " , FLAG_MOD_ADD ) ,
( " flags " , " 65355 " , FLAG_MOD_DELETE ) ,
( " flags " , [ " 2 " , " 101 " ] , FLAG_MOD_REPLACE ) ,
]
self . _test_modify_order ( start_attrs , mod_attrs )
def test_modify_order_container_flags ( self ) :
#flags should be an integer
start_attrs = [ ( " objectclass " , " container " ) ]
mod_attrs = [ ( " flags " , " 0x6 " , FLAG_MOD_ADD ) ,
( " flags " , " 5 " , FLAG_MOD_ADD ) ,
( " flags " , " 101 " , FLAG_MOD_REPLACE ) ,
( " flags " , " c " , FLAG_MOD_DELETE ) ,
]
self . _test_modify_order ( start_attrs , mod_attrs )
def test_modify_order_member ( self ) :
name = " modify_order_member_other_group "
dn2 = " cn= %s , %s " % ( name , self . base_dn )
m = Message ( )
2019-04-18 04:39:58 +03:00
m . dn = Dn ( self . admin_dsdb , dn2 )
self . admin_dsdb . add ( { " dn " : dn2 , " objectclass " : " group " } )
2019-04-12 15:23:26 +03:00
self . addCleanup ( self . delete_object , dn2 )
start_attrs = [ ( " objectclass " , " group " ) ,
( " member " , dn2 ) ]
mod_attrs = [ ( " member " , None , FLAG_MOD_DELETE ) ,
( " member " , None , FLAG_MOD_REPLACE ) ,
( " member " , dn2 , FLAG_MOD_DELETE ) ,
( " member " , None , FLAG_MOD_ADD ) ,
]
self . _test_modify_order ( start_attrs , mod_attrs , [ " memberOf " ] )
2019-04-18 04:39:58 +03:00
def get_dsdb ( creds = None ) :
if creds is None :
creds = admin_creds
2019-04-12 15:23:26 +03:00
dsdb = SamDB ( host ,
credentials = creds ,
session_info = system_session ( lp ) ,
lp = lp )
return dsdb
parser = optparse . OptionParser ( " ldap_modify_order.py [options] <host> " )
sambaopts = options . SambaOptions ( parser )
parser . add_option_group ( sambaopts )
parser . add_option_group ( options . VersionOptions ( parser ) )
credopts = options . CredentialsOptions ( parser )
parser . add_option_group ( credopts )
subunitopts = SubunitOptions ( parser )
parser . add_option_group ( subunitopts )
parser . add_option ( " --rewrite-ground-truth " , action = " store_true " ,
help = " write expected values " )
parser . add_option ( " -v " , " --verbose " , action = " store_true " )
2019-04-18 04:39:58 +03:00
parser . add_option ( " --normal-user " , action = " store_true " )
2019-04-12 15:23:26 +03:00
opts , args = parser . parse_args ( )
if len ( args ) < 1 :
parser . print_usage ( )
sys . exit ( 1 )
host = args [ 0 ]
lp = sambaopts . get_loadparm ( )
2019-04-18 04:39:58 +03:00
admin_creds = credopts . get_credentials ( lp )
2019-04-12 15:23:26 +03:00
if " :// " not in host :
if os . path . isfile ( host ) :
host = " tdb:// %s " % host
else :
host = " ldap:// %s " % host
TestProgram ( module = __name__ , opts = subunitopts )