From 4e603409f76c14ba7b11c437eac6116a2afce603 Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Sat, 9 Mar 2013 23:02:29 +0100 Subject: move Search class to search module --- pynslcd/Makefile.am | 4 +- pynslcd/alias.py | 5 +- pynslcd/common.py | 128 ++-------------------------------------------- pynslcd/ether.py | 3 +- pynslcd/group.py | 5 +- pynslcd/host.py | 5 +- pynslcd/netgroup.py | 3 +- pynslcd/network.py | 5 +- pynslcd/pam.py | 5 +- pynslcd/passwd.py | 5 +- pynslcd/protocol.py | 5 +- pynslcd/rpc.py | 5 +- pynslcd/search.py | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++ pynslcd/service.py | 5 +- pynslcd/shadow.py | 5 +- 15 files changed, 184 insertions(+), 148 deletions(-) create mode 100644 pynslcd/search.py (limited to 'pynslcd') diff --git a/pynslcd/Makefile.am b/pynslcd/Makefile.am index 1381b6b..cff5629 100644 --- a/pynslcd/Makefile.am +++ b/pynslcd/Makefile.am @@ -1,6 +1,6 @@ # Makefile.am - use automake to generate Makefile.in # -# Copyright (C) 2010, 2011, 2012 Arthur de Jong +# Copyright (C) 2010, 2011, 2012, 2013 Arthur de Jong # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -20,7 +20,7 @@ pynslcddir = $(datadir)/pynslcd pynslcd_PYTHON = pynslcd.py attmap.py cache.py cfg.py common.py expr.py \ - mypidfile.py tio.py \ + mypidfile.py search.py tio.py \ alias.py ether.py group.py host.py netgroup.py network.py \ pam.py passwd.py protocol.py rpc.py service.py shadow.py nodist_pynslcd_PYTHON = constants.py diff --git a/pynslcd/alias.py b/pynslcd/alias.py index 06b1e44..46c4d6b 100644 --- a/pynslcd/alias.py +++ b/pynslcd/alias.py @@ -1,7 +1,7 @@ # alias.py - lookup functions for email aliases # -# Copyright (C) 2010, 2011, 2012 Arthur de Jong +# Copyright (C) 2010, 2011, 2012, 2013 Arthur de Jong # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -21,13 +21,14 @@ import cache import common import constants +import search attmap = common.Attributes(cn='cn', rfc822MailMember='rfc822MailMember') filter = '(objectClass=nisMailAlias)' -class Search(common.Search): +class Search(search.LDAPSearch): case_insensitive = ('cn', ) limit_attributes = ('cn', ) diff --git a/pynslcd/common.py b/pynslcd/common.py index 208e321..bbffef4 100644 --- a/pynslcd/common.py +++ b/pynslcd/common.py @@ -1,7 +1,7 @@ # common.py - functions that are used by different modules # -# Copyright (C) 2010, 2011, 2012 Arthur de Jong +# Copyright (C) 2010, 2011, 2012, 2013 Arthur de Jong # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -22,7 +22,6 @@ import logging import sys import ldap -import ldap.dn from attmap import Attributes #import cache @@ -53,125 +52,6 @@ def validate_name(name): raise ValueError('%r: denied by validnames option' % name) -class Search(object): - """ - Class that performs a search. Subclasses are expected to define the actual - searches and should implement the following members: - - case_sensitive - check that these attributes are present in the response - if they were in the request - case_insensitive - check that these attributes are present in the - response if they were in the request - limit_attributes - override response attributes with request attributes - (ensure that only one copy of the value is returned) - required - attributes that are required - canonical_first - search the DN for these attributes and ensure that - they are listed first in the attribute values - mk_filter() (optional) - function that returns the LDAP search filter - - The module that contains the Request class can also contain the following - definitions: - - attmap - an attribute mapping definition (using he Attributes class) - filter - an LDAP search filter - bases - search bases to be used, falls back to cfg.bases - scope - search scope, falls back to cfg.scope - - """ - - canonical_first = [] - required = [] - case_sensitive = [] - case_insensitive = [] - limit_attributes = [] - -# FIXME: figure out which of these arguments are actually needed - - def __init__(self, conn, base=None, scope=None, filter=None, attributes=None, - parameters=None): - # load information from module that defines the class - self.conn = conn - module = sys.modules[self.__module__] - self.attmap = getattr(module, 'attmap', None) - self.filter = filter or getattr(module, 'filter', None) - self.parameters = parameters or {} - if base: - self.bases = [base] - else: - self.bases = getattr(module, 'bases', cfg.bases) - self.scope = scope or getattr(module, 'scope', cfg.scope) - self.attributes = attributes or self.attmap.attributes() - - def __iter__(self): - return self.items() - - def items(self): - """Return the results from the search.""" - filter = self.mk_filter() - for base in self.bases: - logging.debug('SEARCHING %s %s', base, filter) - try: - for entry in self.conn.search_s(base, self.scope, filter, self.attributes): - if entry[0]: - entry = self.handle_entry(entry[0], entry[1]) - if entry: - yield entry - except ldap.NO_SUCH_OBJECT: - # FIXME: log message - pass - - def escape(self, value): - """Escape the provided value so it may be used in a search filter.""" - return ldap.filter.escape_filter_chars(str(value)) - - def mk_filter(self): - """Return the active search filter (based on the read parameters).""" - if self.parameters: - return '(&%s%s)' % ( - self.filter, - ''.join(self.attmap.mk_filter(attribute, value) - for attribute, value in self.parameters.items())) - return self.filter - - def handle_entry(self, dn, attributes): - """Handle an entry with the specified attributes, filtering it with - the request parameters where needed.""" - # translate the attributes using the attribute mapping - if self.attmap: - attributes = self.attmap.translate(attributes) - # make sure value from DN is first value - for attr in self.canonical_first: - primary_value = self.attmap.get_rdn_value(dn, attr) - if primary_value: - values = attributes[attr] - if primary_value in values: - values.remove(primary_value) - attributes[attr] = [primary_value] + values - # check that these attributes have at least one value - for attr in self.required: - if not attributes.get(attr, None): - logging.warning('%s: %s: missing', dn, self.attmap[attr]) - return - # check that requested attribute is present (case sensitive) - for attr in self.case_sensitive: - value = self.parameters.get(attr, None) - if value and str(value) not in attributes[attr]: - logging.debug('%s: %s: does not contain %r value', dn, self.attmap[attr], value) - return # not found, skip entry - # check that requested attribute is present (case insensitive) - for attr in self.case_insensitive: - value = self.parameters.get(attr, None) - if value and str(value).lower() not in (x.lower() for x in attributes[attr]): - logging.debug('%s: %s: does not contain %r value', dn, self.attmap[attr], value) - return # not found, skip entry - # limit attribute values to requested value - for attr in self.limit_attributes: - if attr in self.parameters: - attributes[attr] = [self.parameters[attr]] - # return the entry - return dn, attributes - - class Request(object): """ Request handler class. Subclasses are expected to handle actual requests @@ -198,8 +78,8 @@ class Request(object): self.cache = None def read_parameters(self, fp): - """This method should read the parameters from ths stream and - store them in self.""" + """This method should read and return the parameters from the + stream.""" pass def handle_request(self, parameters): @@ -208,7 +88,7 @@ class Request(object): try: #with cache.con: if True: - for dn, attributes in self.search(conn=self.conn, parameters=parameters): + for dn, attributes in self.search(self.conn, parameters=parameters): for values in self.convert(dn, attributes, parameters): self.fp.write_int32(constants.NSLCD_RESULT_BEGIN) self.write(*values) diff --git a/pynslcd/ether.py b/pynslcd/ether.py index e6975d5..d5d8c06 100644 --- a/pynslcd/ether.py +++ b/pynslcd/ether.py @@ -23,6 +23,7 @@ import struct import cache import common import constants +import search def ether_aton(ether): @@ -40,7 +41,7 @@ attmap = common.Attributes(cn='cn', macAddress='macAddress') filter = '(objectClass=ieee802Device)' -class Search(common.Search): +class Search(search.LDAPSearch): case_insensitive = ('cn', ) limit_attributes = ('cn', 'macAddress') diff --git a/pynslcd/group.py b/pynslcd/group.py index ab07adc..a9626a3 100644 --- a/pynslcd/group.py +++ b/pynslcd/group.py @@ -1,7 +1,7 @@ # group.py - group entry lookup routines # -# Copyright (C) 2010, 2011, 2012 Arthur de Jong +# Copyright (C) 2010, 2011, 2012, 2013 Arthur de Jong # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -25,6 +25,7 @@ from passwd import dn2uid, uid2dn import cache import common import constants +import search def clean(lst): @@ -41,7 +42,7 @@ attmap = common.Attributes(cn='cn', filter = '(objectClass=posixGroup)' -class Search(common.Search): +class Search(search.LDAPSearch): case_sensitive = ('cn', ) limit_attributes = ('cn', 'gidNumber') diff --git a/pynslcd/host.py b/pynslcd/host.py index 49de45b..ffd9588 100644 --- a/pynslcd/host.py +++ b/pynslcd/host.py @@ -1,7 +1,7 @@ # host.py - lookup functions for host names and addresses # -# Copyright (C) 2011, 2012 Arthur de Jong +# Copyright (C) 2011, 2012, 2013 Arthur de Jong # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -21,13 +21,14 @@ import cache import common import constants +import search attmap = common.Attributes(cn='cn', ipHostNumber='ipHostNumber') filter = '(objectClass=ipHost)' -class Search(common.Search): +class Search(search.LDAPSearch): canonical_first = ('cn', ) required = ('cn', ) diff --git a/pynslcd/netgroup.py b/pynslcd/netgroup.py index aa6d79a..ca7fb13 100644 --- a/pynslcd/netgroup.py +++ b/pynslcd/netgroup.py @@ -24,6 +24,7 @@ import re import cache import common import constants +import search _netgroup_triple_re = re.compile(r'^\s*\(\s*(?P.*)\s*,\s*(?P.*)\s*,\s*(?P.*)\s*\)\s*$') @@ -35,7 +36,7 @@ attmap = common.Attributes(cn='cn', filter = '(objectClass=nisNetgroup)' -class Search(common.Search): +class Search(search.LDAPSearch): case_sensitive = ('cn', ) required = ('cn', ) diff --git a/pynslcd/network.py b/pynslcd/network.py index 88778d7..dc91d68 100644 --- a/pynslcd/network.py +++ b/pynslcd/network.py @@ -1,7 +1,7 @@ # network.py - lookup functions for network names and addresses # -# Copyright (C) 2011, 2012 Arthur de Jong +# Copyright (C) 2011, 2012, 2013 Arthur de Jong # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -21,6 +21,7 @@ import cache import common import constants +import search attmap = common.Attributes(cn='cn', @@ -28,7 +29,7 @@ attmap = common.Attributes(cn='cn', filter = '(objectClass=ipNetwork)' -class Search(common.Search): +class Search(search.LDAPSearch): canonical_first = ('cn', ) required = ('cn', ) diff --git a/pynslcd/pam.py b/pynslcd/pam.py index abde4de..f2493ec 100644 --- a/pynslcd/pam.py +++ b/pynslcd/pam.py @@ -29,6 +29,7 @@ import cfg import common import constants import passwd +import search def try_bind(userdn, password): @@ -188,9 +189,9 @@ class PAMAuthorisationRequest(PAMRequest): for x in cfg.pam_authz_searches: filter = x.value(variables) logging.debug('trying pam_authz_search "%s"', filter) - search = common.Search(self.conn, filter=filter, attributes=('dn', )) + srch = search.LDAPSearch(self.conn, filter=filter, attributes=('dn', )) try: - dn, values = search.items().next() + dn, values = srch.items().next() except StopIteration: logging.error('pam_authz_search "%s" found no matches', filter) raise diff --git a/pynslcd/passwd.py b/pynslcd/passwd.py index 222d3c6..3be7885 100644 --- a/pynslcd/passwd.py +++ b/pynslcd/passwd.py @@ -1,7 +1,7 @@ # passwd.py - lookup functions for user account information # -# Copyright (C) 2010, 2011, 2012 Arthur de Jong +# Copyright (C) 2010, 2011, 2012, 2013 Arthur de Jong # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -23,6 +23,7 @@ import logging import cache import common import constants +import search attmap = common.Attributes(uid='uid', @@ -36,7 +37,7 @@ attmap = common.Attributes(uid='uid', filter = '(objectClass=posixAccount)' -class Search(common.Search): +class Search(search.LDAPSearch): case_sensitive = ('uid', 'uidNumber', ) limit_attributes = ('uid', 'uidNumber', ) diff --git a/pynslcd/protocol.py b/pynslcd/protocol.py index 91feaea..cafda9d 100644 --- a/pynslcd/protocol.py +++ b/pynslcd/protocol.py @@ -1,7 +1,7 @@ # protocol.py - protocol name and number lookup routines # -# Copyright (C) 2011, 2012 Arthur de Jong +# Copyright (C) 2011, 2012, 2013 Arthur de Jong # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -21,13 +21,14 @@ import cache import common import constants +import search attmap = common.Attributes(cn='cn', ipProtocolNumber='ipProtocolNumber') filter = '(objectClass=ipProtocol)' -class Search(common.Search): +class Search(search.LDAPSearch): case_sensitive = ('cn', ) canonical_first = ('cn', ) diff --git a/pynslcd/rpc.py b/pynslcd/rpc.py index c667ffa..f20960e 100644 --- a/pynslcd/rpc.py +++ b/pynslcd/rpc.py @@ -1,7 +1,7 @@ # rpc.py - rpc name lookup routines # -# Copyright (C) 2011, 2012 Arthur de Jong +# Copyright (C) 2011, 2012, 2013 Arthur de Jong # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -21,13 +21,14 @@ import cache import common import constants +import search attmap = common.Attributes(cn='cn', oncRpcNumber='oncRpcNumber') filter = '(objectClass=oncRpc)' -class Search(common.Search): +class Search(search.LDAPSearch): case_sensitive = ('cn', ) canonical_first = ('cn', ) diff --git a/pynslcd/search.py b/pynslcd/search.py new file mode 100644 index 0000000..60d36ff --- /dev/null +++ b/pynslcd/search.py @@ -0,0 +1,144 @@ + +# search.py - functions for searching the LDAP database +# +# Copyright (C) 2010, 2011, 2012, 2013 Arthur de Jong +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +import logging +import sys + +import ldap + +import cfg + + +class LDAPSearch(object): + """ + Class that performs an LDAP search. Subclasses are expected to define the + actual searches and should implement the following members: + + case_sensitive - check that these attributes are present in the response + if they were in the request + case_insensitive - check that these attributes are present in the + response if they were in the request + limit_attributes - override response attributes with request attributes + (ensure that only one copy of the value is returned) + required - attributes that are required + canonical_first - search the DN for these attributes and ensure that + they are listed first in the attribute values + mk_filter() (optional) - function that returns the LDAP search filter + + The module that contains the Search class can also contain the following + definitions: + + bases - list of search bases to be used, if absent or empty falls back + to cfg.bases + scope - search scope, falls back to cfg.scope if absent or empty + filter - an LDAP search filter + attmap - an attribute mapping definition (using he Attributes class) + + """ + + canonical_first = [] + required = [] + case_sensitive = [] + case_insensitive = [] + limit_attributes = [] + + def __init__(self, conn, base=None, scope=None, filter=None, + attributes=None, parameters=None): + self.conn = conn + # load information from module that defines the class + module = sys.modules[self.__module__] + if base: + self.bases = [base] + else: + self.bases = getattr(module, 'bases', cfg.bases) + self.scope = scope or getattr(module, 'scope', cfg.scope) + self.filter = filter or getattr(module, 'filter', None) + self.attmap = getattr(module, 'attmap', None) + self.attributes = attributes or self.attmap.attributes() + self.parameters = parameters or {} + + def __iter__(self): + return self.items() + + def items(self): + """Return the results from the search.""" + filter = self.mk_filter() + for base in self.bases: + logging.debug('LDAPSearch(base=%r, filter=%r)', base, filter) + try: + for entry in self.conn.search_s(base, self.scope, filter, self.attributes): + if entry[0]: + entry = self._transform(entry[0], entry[1]) + if entry: + yield entry + except ldap.NO_SUCH_OBJECT: + # FIXME: log message + pass + + def escape(self, value): + """Escape the provided value so it may be used in a search filter.""" + return ldap.filter.escape_filter_chars(str(value)) + + def mk_filter(self): + """Return the active search filter (based on the read parameters).""" + if self.parameters: + return '(&%s%s)' % ( + self.filter, + ''.join(self.attmap.mk_filter(attribute, value) + for attribute, value in self.parameters.items())) + return self.filter + + def _transform(self, dn, attributes): + """Handle a single search result entry filtering it with the request + parameters, search options and attribute mapping.""" + # translate the attributes using the attribute mapping + if self.attmap: + attributes = self.attmap.translate(attributes) + # make sure value from DN is first value + for attr in self.canonical_first: + primary_value = self.attmap.get_rdn_value(dn, attr) + if primary_value: + values = attributes[attr] + if primary_value in values: + values.remove(primary_value) + attributes[attr] = [primary_value] + values + # check that these attributes have at least one value + for attr in self.required: + if not attributes.get(attr, None): + logging.warning('%s: %s: missing', dn, self.attmap[attr]) + return + # check that requested attribute is present (case sensitive) + for attr in self.case_sensitive: + value = self.parameters.get(attr, None) + if value and str(value) not in attributes[attr]: + logging.debug('%s: %s: does not contain %r value', dn, self.attmap[attr], value) + return # not found, skip entry + # check that requested attribute is present (case insensitive) + for attr in self.case_insensitive: + value = self.parameters.get(attr, None) + if value and str(value).lower() not in (x.lower() for x in attributes[attr]): + logging.debug('%s: %s: does not contain %r value', dn, self.attmap[attr], value) + return # not found, skip entry + # limit attribute values to requested value + for attr in self.limit_attributes: + if attr in self.parameters: + attributes[attr] = [self.parameters[attr]] + # return the entry + return dn, attributes diff --git a/pynslcd/service.py b/pynslcd/service.py index a3c00f1..f3082e6 100644 --- a/pynslcd/service.py +++ b/pynslcd/service.py @@ -1,7 +1,7 @@ # service.py - service entry lookup routines # -# Copyright (C) 2011, 2012 Arthur de Jong +# Copyright (C) 2011, 2012, 2013 Arthur de Jong # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -25,6 +25,7 @@ import logging import cache import common import constants +import search attmap = common.Attributes(cn='cn', @@ -33,7 +34,7 @@ attmap = common.Attributes(cn='cn', filter = '(objectClass=ipService)' -class Search(common.Search): +class Search(search.LDAPSearch): case_sensitive = ('cn', 'ipServiceProtocol') limit_attributes = ('ipServiceProtocol', ) diff --git a/pynslcd/shadow.py b/pynslcd/shadow.py index a8d3382..e0170e2 100644 --- a/pynslcd/shadow.py +++ b/pynslcd/shadow.py @@ -1,7 +1,7 @@ # shadow.py - lookup functions for shadow information # -# Copyright (C) 2010, 2011, 2012 Arthur de Jong +# Copyright (C) 2010, 2011, 2012, 2013 Arthur de Jong # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -23,6 +23,7 @@ import logging import cache import common import constants +import search attmap = common.Attributes(uid='uid', @@ -37,7 +38,7 @@ attmap = common.Attributes(uid='uid', filter = '(objectClass=shadowAccount)' -class Search(common.Search): +class Search(search.LDAPSearch): case_sensitive = ('uid', ) limit_attributes = ('uid', ) -- cgit v1.2.3