root / pykota / trunk / bin / pkusers @ 2792

Revision 2792, 20.7 kB (checked in by jerome, 18 years ago)

Changed severity for some messages.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3
4# PyKota Users Manager
5#
6# PyKota - Print Quotas for CUPS and LPRng
7#
8# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22#
23# $Id$
24#
25#
26
27import os
28import sys
29import pwd
30import grp
31
32from pykota.tool import Percent, PyKotaTool, PyKotaToolError, PyKotaCommandLineError, crashed, N_
33from pykota.storage import StorageUser, StorageGroup
34
35__doc__ = N_("""pkusers v%(__version__)s (c) %(__years__)s %(__author__)s
36
37An Users and Groups Manager for PyKota.
38
39command line usage :
40
41  pkusers [options] user1 user2 user3 ... userN
42 
43or : 
44
45  pkusers --groups [options] group1 group2 group3 ... groupN
46
47options :
48
49  -v | --version       Prints pkusers's version number then exits.
50  -h | --help          Prints this message then exits.
51 
52  -a | --add           Adds users if they don't exist on the database.
53                       If they exist, they are modified unless
54                       -s|--skipexisting is also used.
55                       
56  -d | --delete        Deletes users from the quota storage.
57 
58  -D | --description d Adds a textual description to users or groups.
59                       
60  -g | --groups        Edit users groups instead of users.
61                         
62  -o | --overcharge f  Sets the overcharging factor applied to the user
63                       when computing the cost of a print job. Positive or
64                       negative floating point values are allowed,
65                       this allows you to do some really creative
66                       things like giving money to an user whenever
67                       he prints. The number of pages in a print job
68                       is not modified by this coefficient, only the
69                       cost of the job for a particular user.
70                       Only users have such a coefficient.
71 
72  -i | --ingroups g1[,g2...]  Puts the users into each of the groups
73                              listed, separated by commas. The groups
74                              must already exist in the Quota Storage.
75                       
76  -L | --list          Lists users or groups.
77 
78  -l | --limitby l     Choose if the user/group is limited in printing                     
79                       by its account balance or by its page quota.
80                       The default value is 'quota'. Allowed values
81                       are 'quota' 'balance' 'noquota' 'noprint' 
82                       and 'nochange' :
83                       
84                         - quota : limit by number of pages per printer.
85                         - balance : limit by number of credits in account.
86                         - noquota : no limit, accounting still done.
87                         - nochange : no limit, accounting not done.
88                         - noprint : printing is denied.
89                       NB : nochange and noprint are not supported for groups.
90                       
91  -b | --balance b     Sets the user's account balance to b.                     
92                       Account balance may be increase or decreased
93                       if b is prefixed with + or -.
94                       WARNING : when decreasing account balance,
95                       the total paid so far by the user is decreased
96                       too.
97                       Groups don't have a real balance, but the
98                       sum of their users' account balance.
99                       
100  -C | --comment txt   Defines some informational text to be associated
101                       with a change to an user's account balance.
102                       Only meaningful if -b | --balance is also used.
103                       
104                       
105  -r | --remove        In combination with the --ingroups option above,                       
106                       remove users from the specified users groups.
107                       
108  -s | --skipexisting  In combination with the --add option above, tells
109                       pkusers to not modify existing users.
110                       
111  user1 through userN and group1 through groupN can use wildcards
112  if the --add option is not set.
113 
114examples :                             
115
116  $ pkusers --add john paul george ringo/ringo@example.com
117 
118  This will add users john, paul, george and ringo to the quota
119  database. User ringo's email address will also be set to
120  'ringo@example.com'
121 
122  $ pkusers --ingroups coders,it jerome
123 
124  User jerome is put into the groups "coders" and "it" which must
125  already exist in the quota database.
126           
127  $ pkusers --limitby balance jerome
128 
129  This will tell PyKota to limit jerome by his account's balance
130  when printing.
131 
132  $ pkusers --balance +10.0 --comment "He paid with his blood !" jerome
133 
134  This will increase jerome's account balance by 10.0 (in your
135  own currency). You can decrease the account balance with a
136  dash prefix, and set it to a fixed amount with no prefix.
137  A comment will be stored for this balance change.
138 
139  $ pkusers --delete jerome rachel
140 
141  This will completely delete jerome and rachel from the quota
142  database. All their quotas and jobs will be deleted too.
143 
144  $ pkusers --overcharge 2.5 poorstudent
145 
146  This will overcharge the poorstudent user by a factor of 2.5.
147 
148  $ pkusers --overcharge -1 jerome
149 
150  User jerome will actually earn money whenever he prints.
151 
152  $ pkusers --overcharge 0 boss
153 
154  User boss can print at will, it won't cost him anything because the
155  cost of each print job will be multiplied by zero before charging
156  his account.
157""")
158       
159class PKUsers(PyKotaTool) :       
160    """A class for a users and users groups manager."""
161    def modifyEntry(self, entry, groups, limitby, description, overcharge=None, balance=None, balancevalue=None, comment=None, email=None) :
162        """Modifies an entry."""
163        if description is not None : # NB : "" is allowed !
164            entry.setDescription(description)
165        if limitby :   
166            entry.setLimitBy(limitby)
167        if not groups :
168            if email :
169                entry.Email = email
170            if overcharge is not None : # NB : 0 is allowed !     
171                entry.setOverChargeFactor(overcharge)
172            if balance :
173                if balance.startswith("+") or balance.startswith("-") :
174                    newbalance = float(entry.AccountBalance or 0.0) + balancevalue
175                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + balancevalue
176                    entry.setAccountBalance(newbalance, newlifetimepaid, comment)
177                else :
178                    diff = balancevalue - float(entry.AccountBalance or 0.0)
179                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff
180                    entry.setAccountBalance(balancevalue, newlifetimepaid, comment)
181                   
182    def manageUsersGroups(self, ugroups, user, remove) :       
183        """Manage user group membership."""
184        for ugroup in ugroups :
185            if remove :
186                ugroup.delUserFromGroup(user)
187            else :
188                ugroup.addUserToGroup(user)
189               
190    def main(self, names, options) :
191        """Manage users or groups."""
192        names = self.sanitizeNames(options, names)
193        suffix = (options["groups"] and "Group") or "User"       
194       
195        if not options["list"] :
196            percent = Percent(self)
197           
198        if not options["add"] :
199            if not options["list"] :
200                percent.display("%s..." % _("Extracting datas"))
201            if not names :      # NB : can't happen for --delete because it's catched earlier
202                names = ["*"]
203            entries = getattr(self.storage, "getMatching%ss" % suffix)(",".join(names))
204            if not entries :
205                raise PyKotaCommandLineError, _("There's no %s matching %s") % (_(suffix.lower()), " ".join(names))
206            if not options["list"] :   
207                percent.setSize(len(entries))
208               
209        if options["list"] :
210            if suffix == "User" :
211                maildomain = self.config.getMailDomain()
212                smtpserver = self.config.getSMTPServer()
213                for entry in entries :
214                    email = entry.Email
215                    if not email :
216                        if maildomain :     
217                            email = "%s@%s" % (entry.Name, maildomain)
218                        elif smtpserver :   
219                            email = "%s@%s" % (entry.Name, smtpserver)
220                        else :   
221                            email = "%s@%s" % (entry.Name, "localhost")
222                    msg = "%s - <%s>" % (entry.Name, email)
223                    if entry.Description :
224                        msg += " - %s" % entry.Description
225                    print msg   
226                    print "    %s" % (_("Limited by : %s") % entry.LimitBy)
227                    print "    %s" % (_("Account balance : %.2f") % (entry.AccountBalance or 0.0))
228                    print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
229                    print "    %s" % (_("Overcharging factor : %.2f") % entry.OverCharge)
230                    print
231            else :   
232                for entry in entries :
233                    msg = "%s" % entry.Name
234                    if entry.Description :
235                        msg += " - %s" % entry.Description
236                    print msg   
237                    print "    %s" % (_("Limited by : %s") % entry.LimitBy)
238                    print "    %s" % (_("Group balance : %.2f") % (entry.AccountBalance or 0.0))
239                    print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
240                    print
241        elif options["delete"] :   
242            percent.display("\n%s..." % _("Deletion"))
243            getattr(self.storage, "deleteMany%ss" % suffix)(entries)
244            percent.display("\n")
245        else :
246            limitby = options["limitby"]
247            if limitby :
248                limitby = limitby.strip().lower()
249            if limitby :
250                if limitby not in ('quota', 'balance', 'noquota', \
251                                            'noprint', 'nochange') :
252                    raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
253                if (limitby in ('nochange', 'noprint')) and options["groups"] :   
254                    raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
255               
256            overcharge = options["overcharge"]
257            if overcharge :
258                try :
259                    overcharge = float(overcharge.strip())
260                except (ValueError, AttributeError) :   
261                    raise PyKotaCommandLineError, _("Invalid overcharge value %s") % options["overcharge"]
262                   
263            balance = options["balance"]
264            if balance :
265                balance = balance.strip()
266                try :
267                    balancevalue = float(balance)
268                except ValueError :   
269                    raise PyKotaCommandLineError, _("Invalid balance value %s") % options["balance"]
270            else :   
271                balancevalue = None
272               
273            if options["ingroups"] :
274                usersgroups = self.storage.getMatchingGroups(options["ingroups"])
275                if not usersgroups :
276                    raise PyKotaCommandLineError, _("There's no users group matching %s") % " ".join(options["ingroups"].split(','))
277            else :         
278                usersgroups = []
279                   
280            description = options["description"]
281            if description :
282                description = options["description"].strip()
283               
284            comment = options["comment"]
285            if comment :
286                comment = options["comment"].strip()
287            skipexisting = options["skipexisting"]   
288            groups = options["groups"]
289            remove = options["remove"]
290            self.storage.beginTransaction()
291            try :   
292                if options["add"] :   
293                    rejectunknown = self.config.getRejectUnknown()   
294                    percent.display("%s...\n" % _("Creation"))
295                    percent.setSize(len(names))
296                    for ename in names :
297                        email = None
298                        if not groups :
299                            splitname = ename.split('/', 1)     # username/email
300                            if len(splitname) == 1 :
301                                splitname.append("")
302                            (ename, email) = splitname
303                            if email and (email.count('@') != 1) :
304                                raise PyKotaCommandLineError, _("Invalid email address %s") % email
305                        if self.isValidName(ename) :
306                            reject = 0
307                            if rejectunknown :
308                                if groups :
309                                    try :
310                                        grp.getgrnam(ename)
311                                    except KeyError :   
312                                        self.printInfo(_("Unknown group %s") % ename, "error")
313                                        reject = 1
314                                else :   
315                                    try :
316                                        pwd.getpwnam(ename)
317                                    except KeyError :   
318                                        self.printInfo(_("Unknown user %s") % ename, "error")
319                                        reject = 1
320                            if not reject :       
321                                entry = globals()["Storage%s" % suffix](self.storage, ename)
322                                if groups :
323                                    self.modifyEntry(entry, groups, limitby, \
324                                                     description)
325                                else :   
326                                    self.modifyEntry(entry, groups, limitby, \
327                                                     description, overcharge,\
328                                                     balance, balancevalue, \
329                                                     comment, email)
330                                oldentry = getattr(self.storage, "add%s" % suffix)(entry)
331                                if oldentry is not None :
332                                    if skipexisting :
333                                        self.logdebug(_("%s %s already exists, skipping.") % (_(suffix), ename))
334                                    else :   
335                                        self.logdebug(_("%s %s already exists, will be modified.") % (_(suffix), ename))
336                                        if groups :
337                                            self.modifyEntry(oldentry, groups, \
338                                                     limitby, description)
339                                        else :
340                                            self.modifyEntry(oldentry, groups, limitby, \
341                                                     description, overcharge,\
342                                                     balance, balancevalue, \
343                                                     comment, email)
344                                        oldentry.save()
345                                        if not groups :
346                                            self.manageUsersGroups(usersgroups, oldentry, remove)
347                                elif usersgroups and not groups :
348                                    self.manageUsersGroups(usersgroups, \
349                                                           self.storage.getUser(ename), \
350                                                           remove)
351                        else :
352                            raise PyKotaCommandLineError, _("Invalid name %s") % ename
353                        percent.oneMore()
354                else :
355                    percent.display("\n%s...\n" % _("Modification"))
356                    for entry in entries :
357                        if groups :
358                            self.modifyEntry(entry, groups, limitby, description)
359                        else :   
360                            self.modifyEntry(entry, groups, limitby, description, \
361                                             overcharge, balance, balancevalue, \
362                                             comment)
363                            self.manageUsersGroups(usersgroups, entry, remove)               
364                        entry.save()   
365                        percent.oneMore()
366            except :                   
367                self.storage.rollbackTransaction()
368                raise
369            else :   
370                self.storage.commitTransaction()
371               
372        if not options["list"] :
373            percent.done()
374                     
375if __name__ == "__main__" : 
376    retcode = 0
377    try :
378        defaults = { \
379                     "comment" : "", \
380                   }
381        short_options = "hvaD:dgl:rso:i:b:C:L"
382        long_options = ["help", "version", "add", "description=", \
383                        "delete", "groups", "list", "remove", \
384                        "skipexisting", "overcharge=", \
385                        "ingroups=", "limitby=", "balance=", "comment=", \
386                       ]
387                       
388       
389        # Initializes the command line tool
390        manager = PKUsers(doc=__doc__)
391        manager.deferredInit()
392       
393        # parse and checks the command line
394        (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options)
395       
396        # sets long options
397        options["help"] = options["h"] or options["help"]
398        options["version"] = options["v"] or options["version"]
399        options["add"] = options["a"] or options["add"]
400        options["description"] = options["D"] or options["description"]
401        options["delete"] = options["d"] or options["delete"] 
402        options["groups"] = options["g"] or options["groups"]
403        options["list"] = options["L"] or options["list"]
404        options["remove"] = options["r"] or options["remove"]
405        options["skipexisting"] = options["s"] or options["skipexisting"]
406        options["limitby"] = options["l"] or options["limitby"]
407        options["balance"] = options["b"] or options["balance"] 
408        options["ingroups"] = options["i"] or options["ingroups"]
409        options["overcharge"] = options["o"] or options["overcharge"]
410        options["comment"] = options["C"] or options["comment"] or defaults["comment"]
411       
412        if options["help"] :
413            manager.display_usage_and_quit()
414        elif options["version"] :
415            manager.display_version_and_quit()
416        elif (options["delete"] and (options["add"] or options["remove"] or options["description"])) \
417           or (options["skipexisting"] and not options["add"]) \
418           or (options["list"] and (options["add"] or options["delete"] or options["remove"] or options["description"])) \
419           or (options["groups"] and (options["balance"] or options["ingroups"] or options["overcharge"])) :
420            raise PyKotaCommandLineError, _("incompatible options, see help.")
421        elif options["remove"] and not options["ingroups"] :   
422            raise PyKotaCommandLineError, _("You have to pass user groups names on the command line")
423        elif (not args) and (options["add"] or options["delete"]) :
424            raise PyKotaCommandLineError, _("You have to pass user or group names on the command line")
425        else :
426            retcode = manager.main(args, options)
427    except KeyboardInterrupt :       
428        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
429        retcode = -3
430    except PyKotaCommandLineError, msg :   
431        sys.stderr.write("%s : %s\n" % (sys.argv[0], msg))
432        retcode = -2
433    except SystemExit :       
434        pass
435    except :
436        try :
437            manager.crashed("pkusers failed")
438        except :   
439            crashed("pkusers failed")
440        retcode = -1
441
442    try :
443        manager.storage.close()
444    except (TypeError, NameError, AttributeError) :   
445        pass
446       
447    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.