From 03c1e860a58c07b6c24767178aa3048ea7771c30 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: May 23 2016 12:08:04 +0000 Subject: Ticket 48363 - CI test - add test suite Description: Refactor and improve test suite for the requesting all attributes with '+' feature. Please see: https://tools.ietf.org/html/rfc3673 https://fedorahosted.org/389/ticket/48363 Review by: wibrown (Thanks!) --- diff --git a/dirsrvtests/tests/suites/filter/rfc3673_all_oper_attrs_test.py b/dirsrvtests/tests/suites/filter/rfc3673_all_oper_attrs_test.py new file mode 100644 index 0000000..efea5a5 --- /dev/null +++ b/dirsrvtests/tests/suites/filter/rfc3673_all_oper_attrs_test.py @@ -0,0 +1,209 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2016 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +import os +import ldap +import logging +import pytest +from lib389 import DirSrv, Entry, tools, tasks +from lib389.tools import DirSrvTools +from lib389._constants import * +from lib389.properties import * +from lib389.tasks import * +from lib389.utils import * + +logging.getLogger(__name__).setLevel(logging.DEBUG) +log = logging.getLogger(__name__) + +DN_PEOPLE = 'ou=people,%s' % DEFAULT_SUFFIX +DN_ROOT = '' +TEST_USER_NAME = 'all_attrs_test' +TEST_USER_DN = 'uid=%s,%s' % (TEST_USER_NAME, DN_PEOPLE) +TEST_USER_PWD = 'all_attrs_test' + +# Suffix for search, Regular user boolean, List of expected attrs +TEST_PARAMS = [(DN_ROOT, False, [ + 'aci', 'createTimestamp', 'creatorsName', + 'modifiersName', 'modifyTimestamp', 'namingContexts', + 'nsBackendSuffix', 'nsUniqueId', 'subschemaSubentry', + 'supportedControl', 'supportedExtension', + 'supportedFeatures', 'supportedLDAPVersion', + 'supportedSASLMechanisms', 'vendorName', 'vendorVersion' + ]), + (DN_ROOT, True, [ + 'createTimestamp', 'creatorsName', + 'modifiersName', 'modifyTimestamp', 'namingContexts', + 'nsBackendSuffix', 'nsUniqueId', 'subschemaSubentry', + 'supportedControl', 'supportedExtension', + 'supportedFeatures', 'supportedLDAPVersion', + 'supportedSASLMechanisms', 'vendorName', 'vendorVersion' + ]), + (DN_PEOPLE, False, [ + 'aci', 'createTimestamp', 'creatorsName', 'entrydn', + 'entryid', 'modifiersName', 'modifyTimestamp', + 'nsUniqueId', 'numSubordinates', 'parentid' + ]), + (DN_PEOPLE, True, [ + 'aci', 'createTimestamp', 'creatorsName', 'entrydn', + 'entryid', 'modifyTimestamp', 'nsUniqueId', + 'numSubordinates', 'parentid' + ]), + (TEST_USER_DN, False, [ + 'createTimestamp', 'creatorsName', 'entrydn', + 'entryid', 'modifiersName', 'modifyTimestamp', + 'nsUniqueId', 'parentid' + ]), + (TEST_USER_DN, True, [ + 'createTimestamp', 'creatorsName', 'entrydn', + 'entryid', 'modifyTimestamp', 'nsUniqueId', 'parentid' + ]), + (DN_CONFIG, False, ['numSubordinates', 'passwordHistory'])] + + +class TopologyStandalone(object): + def __init__(self, standalone): + standalone.open() + self.standalone = standalone + + +@pytest.fixture(scope="module") +def topology(request): + # Creating standalone instance ... + standalone = DirSrv(verbose=False) + args_instance[SER_HOST] = HOST_STANDALONE + args_instance[SER_PORT] = PORT_STANDALONE + args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE + args_instance[SER_CREATION_SUFFIX] = DEFAULT_SUFFIX + args_standalone = args_instance.copy() + standalone.allocate(args_standalone) + instance_standalone = standalone.exists() + if instance_standalone: + standalone.delete() + standalone.create() + standalone.open() + + # Delete each instance in the end + def fin(): + standalone.delete() + request.addfinalizer(fin) + + # Clear out the tmp dir + standalone.clearTmpDir(__file__) + + return TopologyStandalone(standalone) + + +@pytest.fixture(scope="module") +def test_user(topology): + """User for binding operation""" + + try: + topology.standalone.add_s(Entry((TEST_USER_DN, { + 'objectclass': 'top person'.split(), + 'objectclass': 'organizationalPerson', + 'objectclass': 'inetorgperson', + 'cn': TEST_USER_NAME, + 'sn': TEST_USER_NAME, + 'userpassword': TEST_USER_PWD, + 'mail': '%s@redhat.com' % TEST_USER_NAME, + 'uid': TEST_USER_NAME + }))) + except ldap.LDAPError as e: + log.error('Failed to add user (%s): error (%s)' % (TEST_USER_DN, + e.message['desc'])) + raise e + + +@pytest.fixture(scope="module") +def user_aci(topology): + """Deny modifiersName attribute for the test user + under whole suffix + """ + + ACI_TARGET = '(targetattr= "modifiersName")' + ACI_ALLOW = '(version 3.0; acl "Deny modifiersName for user"; deny (read)' + ACI_SUBJECT = ' userdn = "ldap:///%s";)' % TEST_USER_DN + ACI_BODY = ACI_TARGET + ACI_ALLOW + ACI_SUBJECT + topology.standalone.modify_s(DEFAULT_SUFFIX, [(ldap.MOD_ADD, + 'aci', + ACI_BODY)]) + + +def test_supported_features(topology): + """Verify that OID 1.3.6.1.4.1.4203.1.5.1 is published + in the supportedFeatures [RFC3674] attribute in the rootDSE. + + :Feature: Filter + + :Setup: Standalone instance + + :Steps: 1. Search for 'supportedFeatures' at rootDSE + + :Assert: Value 1.3.6.1.4.1.4203.1.5.1 is presented + """ + + entries = topology.standalone.search_s('', ldap.SCOPE_BASE, + '(objectClass=*)', + ['supportedFeatures']) + supported_value = entries[0].data['supportedfeatures'] + + assert supported_value == ['1.3.6.1.4.1.4203.1.5.1'] + + +@pytest.mark.parametrize('add_attr', ['', '*', 'objectClass']) +@pytest.mark.parametrize('search_suffix,regular_user,oper_attr_list', + TEST_PARAMS) +def test_search_basic(topology, test_user, user_aci, add_attr, + search_suffix, regular_user, oper_attr_list): + """Verify that you can get all expected operational attributes + by a Search Request [RFC2251] with '+' (ASCII 43) filter. + Please see: https://tools.ietf.org/html/rfc3673 + + :Feature: Filter + + :Setup: Standalone instance, test user for binding, + deny one attribute aci for that user + + :Steps: 1. Bind as regular user or Directory Manager + 2. Search with '+' filter and with additionaly + 'objectClass' and '*' attrs too + + :Assert: All expected values were returned, not more + """ + + if regular_user: + topology.standalone.simple_bind_s(TEST_USER_DN, TEST_USER_PWD) + else: + topology.standalone.simple_bind_s(DN_DM, PASSWORD) + + search_filter = ['+'] + if add_attr: + search_filter.append(add_attr) + expected_attrs = sorted(oper_attr_list + ['objectClass']) + else: + expected_attrs = sorted(oper_attr_list) + + entries = topology.standalone.search_s(search_suffix, ldap.SCOPE_BASE, + '(objectclass=*)', + search_filter) + found_attrs = sorted(entries[0].data.keys()) + + if add_attr == '*': + # Check that found attrs contain both operational + # and non-operational attributes + assert all(attr in found_attrs + for attr in ['objectClass', expected_attrs[0]]) + else: + assert cmp(found_attrs, expected_attrs) == 0 + + +if __name__ == '__main__': + # Run isolated + # -s for DEBUG mode + CURRENT_FILE = os.path.realpath(__file__) + pytest.main("-s %s" % CURRENT_FILE) diff --git a/dirsrvtests/tests/tickets/ticket48363_test.py b/dirsrvtests/tests/tickets/ticket48363_test.py deleted file mode 100644 index 33245a1..0000000 --- a/dirsrvtests/tests/tickets/ticket48363_test.py +++ /dev/null @@ -1,206 +0,0 @@ -import os -import sys -import time -import ldap -import logging -import pytest -from lib389 import DirSrv, Entry, tools, tasks -from lib389.tools import DirSrvTools -from lib389._constants import * -from lib389.properties import * -from lib389.tasks import * -from lib389.utils import * - -logging.getLogger(__name__).setLevel(logging.DEBUG) -log = logging.getLogger(__name__) - -TEST_USER = 'uid=test,%s' % DEFAULT_SUFFIX -# Well, it's better than "password" or "password1" -TEST_PASS = 'banana cream pie' - -class TopologyStandalone(object): - def __init__(self, standalone): - standalone.open() - self.standalone = standalone - - -@pytest.fixture(scope="module") -def topology(request): - - # Creating standalone instance ... - standalone = DirSrv(verbose=False) - args_instance[SER_HOST] = HOST_STANDALONE - args_instance[SER_PORT] = PORT_STANDALONE - args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE - args_instance[SER_CREATION_SUFFIX] = DEFAULT_SUFFIX - args_standalone = args_instance.copy() - standalone.allocate(args_standalone) - instance_standalone = standalone.exists() - if instance_standalone: - standalone.delete() - standalone.create() - standalone.open() - - # Delete each instance in the end - def fin(): - standalone.delete() - request.addfinalizer(fin) - - # Clear out the tmp dir - standalone.clearTmpDir(__file__) - - return TopologyStandalone(standalone) - - -def test_ticket48363(topology): - """ - Test the implementation of rfc3673, '+' for all operational attributes. - - Please see: https://tools.ietf.org/html/rfc3673 - - """ - - # Test the implementation of the supportFeatures - - # Section 2: - # Servers supporting this feature SHOULD publish the Object Identifier - # 1.3.6.1.4.1.4203.1.5.1 as a value of the 'supportedFeatures' - # [RFC3674] attribute in the root DSE. - - results = topology.standalone.search_s('', ldap.SCOPE_BASE, 'objectClass=*', ['supportedFeatures'] )[0] - if results.hasAttr('supportedfeatures') is False: - assert False - if results.hasValue('supportedfeatures', '1.3.6.1.4.1.4203.1.5.1') is False: - assert False - - # Section 2: - # The presence of the attribute description "+" (ASCII 43) in the list - # of attributes in a Search Request [RFC2251] SHALL signify a request - # for the return of all operational attributes. - - # Test the two backends, rootdse, and a real ldbm backend - - # Root DSE - results = topology.standalone.search_s('', ldap.SCOPE_BASE, 'objectClass=*', ['+'] )[0] - # There are a number of obvious ones in rootdse. These are: - - rootdse_op_attrs = [ - 'supportedExtension', - 'supportedControl', - 'supportedFeatures', - 'supportedSASLMechanisms', - 'supportedLDAPVersion', - 'vendorName', - 'vendorVersion', - ] - - for opattr in rootdse_op_attrs: - if results.hasAttr(opattr) is False: - assert False - - # LDBM backend - # We are going to examine the root of the suffix, as it's a good easy target - dc_op_attrs = [ - 'nsuniqueid', - 'entrydn', - 'entryid', - 'aci', - ] - dc_user_attrs = [ - 'objectClass', - 'dc', - ] - - # We should show that the following work: - - # '+' - results = topology.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_BASE, 'objectClass=*', ['+'] )[0] - for opattr in dc_op_attrs: - if results.hasAttr(opattr) is False: - assert False - for userattr in dc_user_attrs: - if results.hasAttr(userattr) is False: - assert True - - # '+' '*' - results = topology.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_BASE, 'objectClass=*', ['+', '*'] )[0] - for opattr in dc_op_attrs: - if results.hasAttr(opattr) is False: - assert False - for userattr in dc_user_attrs: - if results.hasAttr(userattr) is False: - assert False - - # Section 2: - # Client implementors should also note - # that certain operational attributes may be returned only if requested - # by name even when "+" is present. - - # We do not currently have any types that are excluded. - # However, we should ensure that a search for "+ namedType" returns - # both all operational and the namedType - - # '+' dc - results = topology.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_BASE, 'objectClass=*', ['+', 'dc'] )[0] - for opattr in dc_op_attrs: - if results.hasAttr(opattr) is False: - assert False - if results.hasAttr('dc') is False: - assert False - if results.hasAttr('objectclass') is False: - assert True - - # '*' nsUniqueId - results = topology.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_BASE, 'objectClass=*', ['*', 'nsuniqueid'] )[0] - for userattr in dc_user_attrs: - if results.hasAttr(userattr) is False: - assert False - if results.hasAttr('nsuniqueid') is False: - assert False - if results.hasAttr('entrydn') is False: - assert True - - # Section 2: - # As with all search requests, client implementors should note that - # results may not include all requested attributes due to access - # controls or other restrictions. - - # Test that with a user with limit read aci, that these are enforced on - # the + request. - - # Create the user - uentry = Entry(TEST_USER) - uentry.setValues('objectclass', 'top', 'extensibleobject') - uentry.setValues('uid', 'test') - uentry.setValues('userPassword', TEST_PASS) - topology.standalone.add_s(uentry) - - # Give them a limited read aci: We may need to purge other acis - anonaci = '(targetattr!="userPassword")(version 3.0; acl "Enable anonymous access"; allow (read, search, compare) userdn="ldap:///anyone";)' - topology.standalone.modify_s(DEFAULT_SUFFIX, [(ldap.MOD_DELETE, 'aci', anonaci)]) - - # Now we need to create an aci that allows anon/all read to only a few attrs - # Lets make one real, and one operational. - - anonaci = '(targetattr="objectclass || dc || nsuniqueid")(version 3.0; acl "Enable anonymous access"; allow (read, search, compare) userdn="ldap:///anyone";)' - topology.standalone.modify_s(DEFAULT_SUFFIX, [(ldap.MOD_ADD, 'aci', anonaci)]) - - # bind as them, and test. - topology.standalone.simple_bind_s(TEST_USER, TEST_PASS) - results = topology.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_BASE, 'objectClass=*', ['*', '+'] )[0] - - if results.hasAttr('dc') is False: - assert False - if results.hasAttr('nsuniqueid') is False: - assert False - if results.hasAttr('entrydn') is False: - assert True - - log.info('Test complete') - - -if __name__ == '__main__': - # Run isolated - # -s for DEBUG mode - CURRENT_FILE = os.path.realpath(__file__) - pytest.main("-s %s" % CURRENT_FILE)