root / pykota / trunk / pykota / storages / ldapstorage.py @ 1029

Revision 1029, 20.6 kB (checked in by jalet, 21 years ago)

More work on LDAP storage backend.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1# PyKota
2#
3# PyKota : Print Quotas for CUPS and LPRng
4#
5# (c) 2003 Jerome Alet <alet@librelogiciel.com>
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
19#
20# $Id$
21#
22# $Log$
23# Revision 1.7  2003/06/14 22:44:21  jalet
24# More work on LDAP storage backend.
25#
26# Revision 1.6  2003/06/13 19:07:57  jalet
27# Two big bugs fixed, time to release something ;-)
28#
29# Revision 1.5  2003/06/10 16:37:54  jalet
30# Deletion of the second user which is not needed anymore.
31# Added a debug configuration field in /etc/pykota.conf
32# All queries can now be sent to the logger in debug mode, this will
33# greatly help improve performance when time for this will come.
34#
35# Revision 1.4  2003/06/10 10:45:32  jalet
36# Not implemented methods now raise an exception when called.
37#
38# Revision 1.3  2003/06/06 20:49:15  jalet
39# Very latest schema. UNTESTED.
40#
41# Revision 1.2  2003/06/06 14:21:08  jalet
42# New LDAP schema.
43# Small bug fixes.
44#
45# Revision 1.1  2003/06/05 11:19:13  jalet
46# More good work on LDAP storage.
47#
48#
49#
50
51#
52# My IANA assigned number, for
53# "Conseil Internet & Logiciels Libres, J�me Alet"
54# is 16868. Use this as a base to create the LDAP schema.
55#
56
57import fnmatch
58
59from pykota.storage import PyKotaStorageError
60
61try :
62    import ldap
63except ImportError :   
64    import sys
65    # TODO : to translate or not to translate ?
66    raise PyKotaStorageError, "This python version (%s) doesn't seem to have the python-ldap module installed correctly." % sys.version.split()[0]
67   
68class Storage :
69    def __init__(self, pykotatool, host, dbname, user, passwd) :
70        """Opens the LDAP connection."""
71        # raise PyKotaStorageError, "Sorry, the LDAP backend for PyKota is not yet implemented !"
72        self.closed = 1
73        self.tool = pykotatool
74        self.debug = pykotatool.config.getDebug()
75        self.info = pykotatool.config.getLDAPInfo()
76        try :
77            self.database = ldap.initialize(host) 
78            self.database.simple_bind_s(user, passwd)
79            self.basedn = dbname
80        except ldap.SERVER_DOWN :   
81            raise PyKotaStorageError, "LDAP backend for PyKota seems to be down !" # TODO : translate
82        else :   
83            self.closed = 0
84            if self.debug :
85                self.tool.logger.log_message("Database opened (host=%s, dbname=%s, user=%s)" % (host, dbname, user), "debug")
86           
87    def __del__(self) :       
88        """Closes the database connection."""
89        if not self.closed :
90            del self.database
91            self.closed = 1
92            if self.debug :
93                self.tool.logger.log_message("Database closed.", "debug")
94       
95    def doSearch(self, key, fields, base="", scope=ldap.SCOPE_SUBTREE) :
96        """Does an LDAP search query."""
97        try :
98            # prepends something more restrictive at the beginning of the base dn
99            if self.debug :
100                self.tool.logger.log_message("QUERY : BaseDN : %s, Scope : %s, Filter : %s, Attributes : %s" % ((base or self.basedn), scope, key, fields), "debug")
101            result = self.database.search_s(base or self.basedn, scope, key, fields)
102        except ldap.NO_SUCH_OBJECT :   
103            return
104        else :     
105            return result
106       
107    def getMatchingPrinters(self, printerpattern) :
108        """Returns the list of all printers as tuples (id, name) for printer names which match a certain pattern."""
109        result = self.doSearch("objectClass=pykotaPrinter", ["pykotaPrinterName"], base=self.info["printerbase"])
110        if result :
111            return [(printerid, printer["pykotaPrinterName"][0]) for (printerid, printer) in result if fnmatch.fnmatchcase(printer["pykotaPrinterName"][0], printerpattern)]
112           
113    def getPrinterId(self, printername) :       
114        """Returns a printerid given a printername."""
115        result = self.doSearch("(&(objectClass=pykotaPrinter)(|(pykotaPrinterName=%s)(%s=%s)))" % (printername, self.info["printerrdn"], printername), ["pykotaPrinterName"], base=self.info["printerbase"])
116        if result :
117            return result[0][0]
118           
119    def getPrinterPrices(self, printerid) :       
120        """Returns a printer prices per page and per job given a printerid."""
121        result = self.doSearch("(|(pykotaPrinterName=*)(%s=*))" % self.info["printerrdn"], ["pykotaPricePerPage", "pykotaPricePerJob"], base=printerid, scope=ldap.SCOPE_BASE)
122        if result :
123            return (float(result[0][1]["pykotaPricePerPage"][0]), float(result[0][1]["pykotaPricePerJob"][0]))
124           
125    def setPrinterPrices(self, printerid, perpage, perjob) :
126        """Sets prices per job and per page for a given printer."""
127        raise PyKotaStorageError, "Not implemented !"
128   
129    def getUserId(self, username) :
130        """Returns a userid given a username."""
131        result = self.doSearch("(&(objectClass=pykotaAccount)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["userrdn"], username), [self.info["userrdn"]], base=self.info["userbase"])
132        if result :
133            return result[0][0]
134           
135    def getGroupId(self, groupname) :
136        """Returns a groupid given a grupname."""
137        result = self.doSearch("(&(objectClass=pykotaGroup)(|(pykotaGroupName=%s)(%s=%s)))" % (groupname, self.info["grouprdn"], groupname), [self.info["grouprdn"]], base=self.info["groupbase"])
138        if result is not None :
139            (groupid, dummy) = result[0]
140            return groupid
141           
142    def getJobHistoryId(self, jobid, userid, printerid) :       
143        """Returns the history line's id given a (jobid, userid, printerid).
144       
145           TODO : delete because shouldn't be needed by the LDAP backend
146        """
147        raise PyKotaStorageError, "Not implemented !"
148           
149    def getPrinterUsers(self, printerid) :       
150        """Returns the list of userids and usernames which uses a given printer."""
151        # first get the printer's name from the id
152        result = self.doSearch("objectClass=pykotaPrinter", ["pykotaPrinterName", self.info["printerrdn"]], base=printerid, scope=ldap.SCOPE_BASE)
153        if result :
154            fields = result[0][1]
155            printername = (fields.get("pykotaPrinterName") or fields.get(self.info["printerrdn"]))[0]
156            result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s))" % printername, ["pykotaUserName"], base=self.info["userquotabase"]) 
157            if result :
158                return [(pquotauserid, fields["pykotaUserName"][0]) for (pquotauserid, fields) in result]
159       
160    def getPrinterGroups(self, printerid) :       
161        """Returns the list of groups which uses a given printer."""
162        # first get the printer's name from the id
163        result = self.doSearch("objectClass=pykotaPrinter", ["pykotaPrinterName", self.info["printerrdn"]], base=printerid, scope=ldap.SCOPE_BASE)
164        if result :
165            fields = result[0][1]
166            printername = (fields.get("pykotaPrinterName") or fields.get(self.info["printerrdn"]))[0]
167            result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaPrinterName=%s))" % printername, ["pykotaGroupName"], base=self.info["groupquotabase"]) 
168            if result :
169                return [(pquotagroupid, fields["pykotaGroupName"][0]) for (pquotagroupid, fields) in result]
170       
171    def getGroupMembersNames(self, groupname) :       
172        """Returns the list of user's names which are member of this group."""
173        result = self.doSearch("(&(objectClass=pykotaGroup)(|(pykotaGroupName=%s)(%s=%s)))" % (groupname, self.info["grouprdn"], groupname), [self.info["groupmembers"]])
174        if result :
175            fields = result[0][1]
176            return fields.get(self.info["groupmembers"])
177       
178    def getUserGroupsNames(self, userid) :       
179        """Returns the list of groups' names the user is a member of."""
180        raise PyKotaStorageError, "Not implemented !"
181       
182    def addPrinter(self, printername) :       
183        """Adds a printer to the quota storage, returns its id."""
184        raise PyKotaStorageError, "Not implemented !"
185       
186    def addUser(self, username) :       
187        """Adds a user to the quota storage, returns its id."""
188        raise PyKotaStorageError, "Not implemented !"
189       
190    def addGroup(self, groupname) :       
191        """Adds a group to the quota storage, returns its id."""
192        raise PyKotaStorageError, "Not implemented !"
193       
194    def addUserPQuota(self, username, printerid) :
195        """Initializes a user print quota on a printer, adds the user to the quota storage if needed."""
196        raise PyKotaStorageError, "Not implemented !"
197       
198    def addGroupPQuota(self, groupname, printerid) :
199        """Initializes a group print quota on a printer, adds the group to the quota storage if needed."""
200        raise PyKotaStorageError, "Not implemented !"
201       
202    def increaseUserBalance(self, userid, amount) :   
203        """Increases (or decreases) an user's account balance by a given amount."""
204        raise PyKotaStorageError, "Not implemented !"
205       
206    def getUserBalance(self, userquotaid) :   
207        """Returns the current account balance for a given user quota identifier."""
208        # first get the user's name from the user quota id
209        result = self.doSearch("objectClass=pykotaUserPQuota", ["pykotaUserName"], base=userquotaid, scope=ldap.SCOPE_BASE)
210        if result :
211            username = result[0][1]["pykotaUserName"][0]
212            result = self.doSearch("(&(objectClass=pykotaAccountBalance)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["userrdn"], username), ["pykotaBalance", "pykotaLifeTimePaid"])
213            if result :
214                fields = result[0][1]
215                return (float(fields["pykotaBalance"][0]), float(fields["pykotaLifeTimePaid"][0]))
216       
217    def getUserBalanceFromUserId(self, userid) :   
218        """Returns the current account balance for a given user id."""
219        # first get the user's name from the user id
220        result = self.doSearch("objectClass=pykotaAccount", ["pykotaUserName", self.info["userrdn"]], base=userid, scope=ldap.SCOPE_BASE)
221        if result :
222            fields = result[0][1]
223            username = (fields.get("pykotaUserName") or fields.get(self.info["userrdn"]))[0]
224            result = self.doSearch("(&(objectClass=pykotaAccountBalance)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["userrdn"], username), ["pykotaBalance", "pykotaLifeTimePaid"])
225            if result :
226                fields = result[0][1]
227                return (float(fields["pykotaBalance"][0]), float(fields["pykotaLifeTimePaid"][0]))
228       
229    def getGroupBalance(self, groupquotaid) :   
230        """Returns the current account balance for a given group, as the sum of each of its users' account balance."""
231        # first get the group's name from the group quota id
232        result = self.doSearch("objectClass=pykotaGroupPQuota", ["pykotaGroupName"], base=groupquotaid, scope=ldap.SCOPE_BASE)
233        if result :
234            groupname = result[0][1]["pykotaGroupName"][0]
235            members = self.getGroupMembersNames(groupname)
236            balance = lifetimepaid = 0.0
237            for member in members :
238                userid = self.getUserId(member)
239                if userid :
240                    userbal = self.getUserBalanceFromUserId(userid)
241                    if userbal :
242                        (bal, paid) = userbal
243                        balance += bal
244                        lifetimepaid += paid
245            return (balance, lifetimepaid)           
246       
247    def getUserLimitBy(self, userquotaid) :   
248        """Returns the way in which user printing is limited."""
249        result = self.doSearch("objectClass=pykotaUserPQuota", ["pykotaUserName"], base=userquotaid, scope=ldap.SCOPE_BASE)
250        if result :
251            username = result[0][1]["pykotaUserName"][0]
252            result = self.doSearch("(&(objectClass=pykotaAccount)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["userrdn"], username), ["pykotaLimitBy"])
253            if result :
254                return result[0][1]["pykotaLimitBy"][0]
255       
256    def getGroupLimitBy(self, groupquotaid) :   
257        """Returns the way in which group printing is limited."""
258        # first get the group's name from the group quota id
259        result = self.doSearch("objectClass=pykotaGroupPQuota", ["pykotaGroupName"], base=groupquotaid, scope=ldap.SCOPE_BASE)
260        if result :
261            groupname = result[0][1]["pykotaGroupName"][0]
262            result = self.doSearch("(&(objectClass=pykotaGroup)(|(pykotaGroupName=%s)(%s=%s)))" % (groupname, self.info["grouprdn"], groupname), ["pykotaLimitBy"])
263            if result :
264                return result[0][1]["pykotaLimitBy"][0]
265       
266    def setUserBalance(self, userid, balance) :   
267        """Sets the account balance for a given user to a fixed value."""
268        raise PyKotaStorageError, "Not implemented !"
269       
270    def limitUserBy(self, userid, limitby) :   
271        """Limits a given user based either on print quota or on account balance."""
272        raise PyKotaStorageError, "Not implemented !"
273       
274    def limitGroupBy(self, groupid, limitby) :   
275        """Limits a given group based either on print quota or on sum of its users' account balances."""
276        raise PyKotaStorageError, "Not implemented !"
277       
278    def setUserPQuota(self, userid, printerid, softlimit, hardlimit) :
279        """Sets soft and hard limits for a user quota on a specific printer given (userid, printerid)."""
280        raise PyKotaStorageError, "Not implemented !"
281       
282    def setGroupPQuota(self, groupid, printerid, softlimit, hardlimit) :
283        """Sets soft and hard limits for a group quota on a specific printer given (groupid, printerid)."""
284        raise PyKotaStorageError, "Not implemented !"
285       
286    def resetUserPQuota(self, userid, printerid) :   
287        """Resets the page counter to zero for a user on a printer. Life time page counter is kept unchanged."""
288        raise PyKotaStorageError, "Not implemented !"
289       
290    def resetGroupPQuota(self, groupid, printerid) :   
291        """Resets the page counter to zero for a group on a printer. Life time page counter is kept unchanged."""
292        raise PyKotaStorageError, "Not implemented !"
293       
294    def updateUserPQuota(self, userid, printerid, pagecount) :
295        """Updates the used user Quota information given (userid, printerid) and a job size in pages."""
296        raise PyKotaStorageError, "Not implemented !"
297       
298    def getUserPQuota(self, userquotaid, printerid) :
299        """Returns the Print Quota information for a given (userquotaid, printerid)."""
300        # first get the user's name from the id
301        result = self.doSearch("objectClass=pykotaUserPQuota", ["pykotaUserName", "pykotaPageCounter", "pykotaLifePageCounter", "pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit"], base=userquotaid, scope=ldap.SCOPE_BASE)
302        if result :
303            fields = result[0][1]
304            datelimit = fields["pykotaDateLimit"][0].strip()
305            if (not datelimit) or (datelimit.upper() == "NONE") : 
306                datelimit = None
307            return { "lifepagecounter" : int(fields["pykotaLifePageCounter"][0]), 
308                     "pagecounter" : int(fields["pykotaPageCounter"][0]),
309                     "softlimit" : int(fields["pykotaSoftLimit"][0]),
310                     "hardlimit" : int(fields["pykotaHardLimit"][0]),
311                     "datelimit" : datelimit
312                   }
313       
314    def getGroupPQuota(self, grouppquotaid, printerid) :
315        """Returns the Print Quota information for a given (grouppquotaid, printerid)."""
316        result = self.doSearch("objectClass=pykotaGroupPQuota", ["pykotaGroupName", "pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit"], base=grouppquotaid, scope=ldap.SCOPE_BASE)
317        if result :
318            fields = result[0][1]
319            groupname = fields["pykotaGroupName"][0]
320            datelimit = fields["pykotaDateLimit"][0].strip()
321            if (not datelimit) or (datelimit.upper() == "NONE") : 
322                datelimit = None
323            quota = {
324                      "softlimit" : int(fields["pykotaSoftLimit"][0]),
325                      "hardlimit" : int(fields["pykotaHardLimit"][0]),
326                      "datelimit" : datelimit
327                    }
328            members = self.getGroupMembersNames(groupname)
329            pagecounter = lifepagecounter = 0
330            printerusers = self.getPrinterUsers(printerid)
331            if printerusers :
332                for (userid, username) in printerusers :
333                    if username in members :
334                        userpquota = self.getUserPQuota(userid, printerid)
335                        if userpquota :
336                            pagecounter += userpquota["pagecounter"]
337                            lifepagecounter += userpquota["lifepagecounter"]
338            quota.update({"pagecounter": pagecounter, "lifepagecounter": lifepagecounter})               
339            return quota
340       
341    def setUserDateLimit(self, userid, printerid, datelimit) :
342        """Sets the limit date for a soft limit to become an hard one given (userid, printerid)."""
343        raise PyKotaStorageError, "Not implemented !"
344       
345    def setGroupDateLimit(self, groupid, printerid, datelimit) :
346        """Sets the limit date for a soft limit to become an hard one given (groupid, printerid)."""
347        raise PyKotaStorageError, "Not implemented !"
348       
349    def addJobToHistory(self, jobid, userid, printerid, pagecounter, action) :
350        """Adds a job to the history: (jobid, userid, printerid, last page counter taken from requester)."""
351        raise PyKotaStorageError, "Not implemented !"
352   
353    def updateJobSizeInHistory(self, historyid, jobsize) :
354        """Updates a job size in the history given the history line's id."""
355        raise PyKotaStorageError, "Not implemented !"
356   
357    def getPrinterPageCounter(self, printerid) :
358        """Returns the last page counter value for a printer given its id, also returns last username, last jobid and history line id."""
359        result = self.doSearch("objectClass=pykotaPrinter", ["pykotaPrinterName", self.info["printerrdn"]], base=printerid, scope=ldap.SCOPE_BASE)
360        if result :
361            fields = result[0][1]
362            printername = (fields.get("pykotaPrinterName") or fields.get(self.info["printerrdn"]))[0]
363            result = self.doSearch("(&(objectClass=pykotaLastjob)(|(pykotaPrinterName=%s)(%s=%s)))" % (printername, self.info["printerrdn"], printername), ["pykotaLastJobIdent"], base=self.info["lastjobbase"])
364            if result :
365                lastjobident = result[0][1]["pykotaLastJobIdent"][0]
366                result = self.doSearch("(&(objectClass=pykotaJob)(cn=%s))" % lastjobident, ["pykotaUserName", "pykotaPrinterName", "pykotaJobId", "pykotaPrinterPageCounter", "pykotaJobSize", "pykotaAction", "createTimestamp"], base=self.info["jobbase"])
367                if result :
368                    fields = result[0][1]
369                    return { "id": lastjobident, 
370                             "jobid" : fields.get("pykotaJobId")[0],
371                             "userid" : self.getUserId(fields.get("pykotaUserName")[0]),
372                             "username" : fields.get("pykotaUserName")[0], 
373                             "pagecounter" : int(fields.get("pykotaPrinterPageCounter")[0]),
374                           }
375       
376    def addUserToGroup(self, userid, groupid) :   
377        """Adds an user to a group."""
378        raise PyKotaStorageError, "Not implemented !"
379       
380    def deleteUser(self, userid) :   
381        """Completely deletes an user from the Quota Storage."""
382        raise PyKotaStorageError, "Not implemented !"
383       
384    def deleteGroup(self, groupid) :   
385        """Completely deletes an user from the Quota Storage."""
386        raise PyKotaStorageError, "Not implemented !"
387       
388    def computePrinterJobPrice(self, printerid, jobsize) :   
389        """Returns the price for a job on a given printer."""
390        # TODO : create a base class with things like this
391        prices = self.getPrinterPrices(printerid)
392        if prices is None :
393            perpage = perjob = 0.0
394        else :   
395            (perpage, perjob) = prices
396        return perjob + (perpage * jobsize)
Note: See TracBrowser for help on using the browser.