Ticket #815: Ldap.py

File Ldap.py, 6.5 KB (added by https://www.google.com/accounts/o8/id?id=AItOawmAKncTPlNx89GjuQyu9LtzyN37kpWeSCw, 13 years ago)

plugin to add data from an LDAP directory to the metadata object

Line 
1import imp
2import ldap
3import Bcfg2.Options
4import Bcfg2.Server.Plugin
5
6SCOPE_MAP = {
7    "base" : ldap.SCOPE_BASE,
8    "one" : ldap.SCOPE_ONELEVEL,
9    "sub" : ldap.SCOPE_SUBTREE,
10}
11
12LDAP_QUERIES = []
13
14def register_query(query):
15    LDAP_QUERIES.append(query)
16
17class ConfigFile(Bcfg2.Server.Plugin.FileBacked):
18    """
19    Config file for the Ldap plugin
20
21    The config file cannot be 'parsed' in the traditional sense as we would
22    need some serious type checking ugliness to just get the LdapQuery
23    subclasses. The alternative would be to have the user create a list with
24    a predefined name that contains all queries.
25    The approach implemented here is having the user call a registering
26    decorator that updates a global variable in this module.
27    """
28    def __init__(self, filename, fam):
29        self.filename = filename
30        Bcfg2.Server.Plugin.FileBacked.__init__(self, self.filename)
31        fam.AddMonitor(self.filename, self)
32
33    def Index(self):
34        """
35        Reregisters the queries in the config file
36       
37        The config will take care of actually registering the queries,
38        so we just load it once and don't keep it.
39        """
40        global LDAP_QUERIES
41        LDAP_QUERIES = []
42        imp.load_source("ldap_cfg", self.filename)
43
44class Ldap(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Connector):
45    """
46    The Ldap plugin allows adding data from an LDAP server to your metadata.
47    """
48    name = "Ldap"
49    version = "$Revision: $"
50    experimental = True
51    debug_flag = False
52   
53    def __init__(self, core, datastore):
54        Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
55        Bcfg2.Server.Plugin.Connector.__init__(self)
56        self.config = ConfigFile(self.data + "/config.py", core.fam)
57   
58    def get_additional_data(self, metadata):
59        try:
60            data = {}
61            self.debug_log("LdapPlugin debug: found queries " + 
62                                              str(LDAP_QUERIES))
63            for QueryClass in LDAP_QUERIES:
64                query = QueryClass(metadata)
65                if query.is_applicable(metadata):
66                    self.debug_log("LdapPlugin debug: processing query '" +
67                                                           query.name + "'")
68                    data[query.name] = query.get_result(metadata)
69                else:
70                    self.debug_log("LdapPlugin debug: query '" + query.name +
71                        "' not applicable to host '" + metadata.hostname + "'")
72            return data
73        except Exception, error_msg:
74            if self.debug_flag:
75                raise
76            else:
77                Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " + 
78                                                         str(error_msg))
79                return {}
80
81class LdapConnection(object):
82    """
83    Connection to an LDAP server.
84    """
85    def __init__(self, host = "localhost", port = 389, 
86                       binddn = None, bindpw = None):
87        self.host = host
88        self.port = port
89        self.binddn = binddn
90        self.bindpw = bindpw
91        self.conn = None
92       
93    def __del__(self):
94        if self.conn:
95            self.conn.unbind()
96   
97    def init_conn(self):
98        self.conn = ldap.initialize(self.url)
99        if self.binddn is not None and self.bindpw is not None:
100            self.conn.simple_bind_s(self.binddn, self.bindpw)
101   
102    def run_query(self, query):
103        if not self.conn:
104            self.init_conn()
105        return self.conn.search_s(
106            query.base,
107            SCOPE_MAP[query.scope],
108            query.filter,
109            query.attrs,
110        )
111   
112    @property
113    def url(self):
114        return "ldap://" + self.host + ":" + str(self.port)
115   
116class LdapQuery(object):
117    """
118    Query referencing an LdapConnection and providing several
119    methods for query manipulation.
120    """
121   
122    name = "unknown"
123    base = ""
124    scope = "sub"
125    filter = "(objectClass=*)"
126    attrs = None
127    connection = None
128    result = None
129   
130    def __init__(self, metadata):
131        """
132        Override this to alter the query before execution.
133       
134        In most cases, you will do something like
135       
136        self.filter = "(cn=" + metadata.hostname + ")"
137       
138        here.
139        """
140        pass
141   
142    def __unicode__(self):
143        return "LdapQuery:" + self.name
144   
145    def is_applicable(self, metadata):
146        """
147        Overrideable method to determine if the query is to be executed for
148        the given metadata object.
149        Defaults to true.
150        """
151        return True
152   
153    def prepare_query(self, metadata):
154        """
155        Overrideable method to alter the query based on metadata.
156        Defaults to doing nothing.
157        """
158        pass
159   
160    def process_result(self, metadata):
161        """
162        Overrideable method to post-process the query result.
163        Defaults to returning the unaltered result.
164        """
165        return self.result
166   
167    def get_result(self, metadata):
168        """
169        Method to handle preparing, executing and processing the query.
170        """
171        if isinstance(self.connection, LdapConnection):
172            self.prepare_query(metadata)
173            self.result = self.connection.run_query(self)
174            self.result = self.process_result(metadata)
175            return self.result
176        else:
177            Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " + 
178              "No valid connection defined for query " + str(self))
179            return None
180
181class LdapSubQuery(LdapQuery):
182    """
183    SubQueries are meant for internal use only and are not added
184    to the metadata object. They are useful for situations where
185    you need to run more than one query to obtain some data.
186    """
187    def prepare_query(self, metadata, subquery_data = None):
188        """
189        Overrideable method to alter the query based on metadata.
190        Defaults to doing nothing.
191        """
192        pass
193   
194    def get_result(self, metadata, subquery_data = None):
195        """
196        Method to handle preparing, executing and processing the query.
197        """
198        if isinstance(self.connection, LdapConnection):
199            self.prepare_query(metadata, subquery_data)
200            self.result = self.connection.run_query(self)
201            self.process_result(metadata, subquery_data)
202            return self.result
203        else:
204            Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " + 
205              "No valid connection defined for query " + str(self))
206            return None