root / pykota / trunk / bin / pkusers @ 3426

Revision 3426, 21.0 kB (checked in by jerome, 16 years ago)

pkusers now supports new style command line handling code. Needs more
serious testing...

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2# -*- coding: utf-8 -*-*-
3#
4# PyKota : Print Quotas for CUPS
5#
6# (c) 2003, 2004, 2005, 2006, 2007, 2008 Jerome Alet <alet@librelogiciel.com>
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19#
20# $Id$
21#
22#
23
24"""A users and groups manager for PyKota."""
25
26import sys
27import pwd
28import grp
29
30import pykota.appinit
31from pykota.utils import run
32from pykota.commandline import PyKotaOptionParser
33from pykota.errors import PyKotaCommandLineError
34from pykota.tool import Percent, PyKotaTool
35from pykota.storage import StorageUser, StorageGroup
36
37
38
39class PKUsers(PyKotaTool) :
40    """A class for a users and users groups manager."""
41    def modifyEntry(self, entry, groups, limitby, description, overcharge=None, balance=None, balancevalue=None, comment=None, email=None) :
42        """Modifies an entry."""
43        if description is not None : # NB : "" is allowed !
44            entry.setDescription(description)
45        if limitby :
46            entry.setLimitBy(limitby)
47        if not groups :
48            if email is not None :      # we allow "" to empty the field
49                if email.startswith("@") :
50                    email = "%s%s" % (entry.Name, email)
51                if email and email.count('@') != 1 :
52                    raise PyKotaCommandLineError, _("Invalid email address %s") % email
53                entry.setEmail(email)
54            if overcharge is not None : # NB : 0 is allowed !
55                entry.setOverChargeFactor(overcharge)
56            if balance :
57                if balance.startswith("+") or balance.startswith("-") :
58                    newbalance = float(entry.AccountBalance or 0.0) + balancevalue
59                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + balancevalue
60                    entry.setAccountBalance(newbalance, newlifetimepaid, comment)
61                else :
62                    diff = balancevalue - float(entry.AccountBalance or 0.0)
63                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff
64                    entry.setAccountBalance(balancevalue, newlifetimepaid, comment)
65
66    def manageUsersGroups(self, ugroups, user, remove) :
67        """Manage user group membership."""
68        for ugroup in ugroups :
69            if remove :
70                ugroup.delUserFromGroup(user)
71            else :
72                ugroup.addUserToGroup(user)
73
74    def sanitizeNames(self, names, isgroups) :
75        """Sanitize users and groups names if needed."""
76        if not self.config.isAdmin :
77            username = pwd.getpwuid(os.geteuid())[0]
78            if isgroups :
79                user = self.storage.getUser(username)
80                if user.Exists :
81                    return [ g.Name for g in self.storage.getUserGroups(user) ]
82            return [ username ]
83        return names
84
85    def main(self, names, options) :
86        """Manage users or groups."""
87        islist = (options.action == "list")
88        isadd = (options.action == "add")
89        isdelete = (options.action == "delete")
90
91        if not islist :
92            self.adminOnly()
93
94        names = self.sanitizeNames(names, options.groups)
95        if not names :
96            if isdelete or isadd :
97                raise PyKotaCommandLineError, _("You must specify users or groups names on the command line.")
98            names = [u"*"]
99
100        if options.remove and not options.ingroups :
101            raise PyKotaCommandLineError, _("You must specify users groups names on the command line.")
102        elif (((islist or isdelete) and (options.limitby  \
103                                      or options.balance \
104                                      or options.email \
105                                      or options.remove \
106                                      or options.overcharge \
107                                      or options.ingroups \
108                                      or options.description \
109                                      or options.skipexisting \
110                                      or options.comment))) \
111             or (options.groups and (options.ingroups \
112                                  or options.balance \
113                                  or options.email \
114                                  or options.remove \
115                                  or options.overcharge \
116                                  or options.comment)) :
117            raise PyKotaCommandLineError, _("Incompatible command line options. Please look at the online help or manual page.")
118
119        suffix = (options.groups and "Group") or "User"
120
121        if not islist :
122            percent = Percent(self)
123
124        if not isadd :
125            if not islist :
126                percent.display("%s..." % _("Extracting datas"))
127            entries = getattr(self.storage, "getMatching%ss" % suffix)(",".join(names))
128            if not entries :
129                if not islist :
130                    percent.display("\n")
131                raise PyKotaCommandLineError, _("There's no %s matching %s") \
132                    % (_(suffix.lower()), " ".join(names))
133            if not islist :
134                percent.setSize(len(entries))
135
136        if islist :
137            if suffix == "User" :
138                maildomain = self.config.getMailDomain()
139                smtpserver = self.config.getSMTPServer()
140                for entry in entries :
141                    email = entry.Email
142                    if not email :
143                        if maildomain :
144                            email = "%s@%s" % (entry.Name, maildomain)
145                        elif smtpserver :
146                            email = "%s@%s" % (entry.Name, smtpserver)
147                        else :
148                            email = "%s@%s" % (entry.Name, "localhost")
149                    msg = "%s - <%s>" % (entry.Name, email)
150                    if entry.Description :
151                        msg += " - %s" % entry.Description
152                    self.display("%s\n" % msg)
153                    self.display("    %s\n" % (_("Limited by : %s") % entry.LimitBy))
154                    self.display("    %s\n" % (_("Account balance : %.2f") % (entry.AccountBalance or 0)))
155                    self.display("    %s\n" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0)))
156                    self.display("    %s\n" % (_("Overcharging factor : %.2f") % entry.OverCharge))
157                    self.display("\n")
158            else :
159                for entry in entries :
160                    msg = "%s" % entry.Name
161                    if entry.Description :
162                        msg += " - %s" % entry.Description
163                    self.display("%s\n" % msg)
164                    self.display("    %s\n" % (_("Limited by : %s") % entry.LimitBy))
165                    self.display("    %s\n" % (_("Group balance : %.2f") % (entry.AccountBalance or 0)))
166                    self.display("    %s\n" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0)))
167                    self.display("\n")
168        elif isdelete :
169            percent.display("\n%s..." % _("Deletion"))
170            getattr(self.storage, "deleteMany%ss" % suffix)(entries)
171            percent.display("\n")
172        else :
173            limitby = options.limitby
174            if limitby :
175                limitby = limitby.strip().lower()
176            if limitby :
177                if limitby not in ('quota',
178                                   'balance',
179                                   'noquota',
180                                   'noprint',
181                                   'nochange') :
182                    raise PyKotaCommandLineError, _("Invalid limitby value %s") \
183                        % options.limitby
184                if (limitby in ('nochange', 'noprint')) and options.groups :
185                    raise PyKotaCommandLineError, _("Invalid limitby value %s") \
186                        % options.limitby
187
188            balance = options.balance
189            if balance :
190                balance = balance.strip()
191                try :
192                    balancevalue = float(balance)
193                except ValueError :
194                    raise PyKotaCommandLineError, _("Invalid balance value %s") \
195                        % options.balance
196            else :
197                balancevalue = None
198
199            if options.ingroups :
200                usersgroups = self.storage.getMatchingGroups(options.ingroups)
201                if not usersgroups :
202                    raise PyKotaCommandLineError, _("There's no users group matching %s") \
203                        % " ".join(options.ingroups.split(','))
204            else :
205                usersgroups = []
206
207            if options.description :
208                options.description = options.description.strip()
209
210            if options.comment :
211                options.comment = options.comment.strip()
212
213            if options.email :
214                options.email = options.email.strip()
215
216            self.storage.beginTransaction()
217            try :
218                if isadd :
219                    rejectunknown = self.config.getRejectUnknown()
220                    percent.display("%s...\n" % _("Creation"))
221                    percent.setSize(len(names))
222                    for ename in names :
223                        useremail = None
224                        if not options.groups :
225                            splitname = ename.split('/', 1)     # username/email
226                            if len(splitname) == 1 :
227                                splitname.append("")
228                            (ename, useremail) = splitname
229                        if self.isValidName(ename) :
230                            reject = 0
231                            if rejectunknown :
232                                if options.groups :
233                                    try :
234                                        grp.getgrnam(ename)
235                                    except KeyError :
236                                        self.printInfo(_("Unknown group %s") % ename, "error")
237                                        reject = 1
238                                else :
239                                    try :
240                                        pwd.getpwnam(ename)
241                                    except KeyError :
242                                        self.printInfo(_("Unknown user %s") % ename, "error")
243                                        reject = 1
244                            if not reject :
245                                entry = globals()["Storage%s" % suffix](self.storage, ename)
246                                if options.groups :
247                                    self.modifyEntry(entry,
248                                                     options.groups,
249                                                     limitby,
250                                                     options.description)
251                                else :
252                                    self.modifyEntry(entry,
253                                                     options.groups,
254                                                     limitby,
255                                                     options.description,
256                                                     options.overcharge,
257                                                     balance,
258                                                     balancevalue,
259                                                     options.comment,
260                                                     useremail or options.email)
261                                oldentry = getattr(self.storage, "add%s" % suffix)(entry)
262                                if oldentry is not None :
263                                    if options.skipexisting :
264                                        self.logdebug(_("%s %s already exists, skipping.") \
265                                                          % (_(suffix), ename))
266                                    else :
267                                        self.logdebug(_("%s %s already exists, will be modified.") \
268                                                          % (_(suffix), ename))
269                                        if options.groups :
270                                            self.modifyEntry(oldentry,
271                                                             options.groups,
272                                                             limitby,
273                                                             options.description)
274                                        else :
275                                            self.modifyEntry(oldentry,
276                                                             options.groups,
277                                                             limitby,
278                                                             options.description,
279                                                             options.overcharge,
280                                                             balance,
281                                                             balancevalue,
282                                                             options.comment,
283                                                             useremail or options.email)
284                                        oldentry.save()
285                                        if not options.groups :
286                                            self.manageUsersGroups(usersgroups,
287                                                                   oldentry,
288                                                                   options.remove)
289                                elif usersgroups and not options.groups :
290                                    self.manageUsersGroups(usersgroups, \
291                                                           self.storage.getUser(ename), \
292                                                           options.remove)
293                        else :
294                            raise PyKotaCommandLineError, _("Invalid name %s") % ename
295                        percent.oneMore()
296                else :
297                    percent.display("\n%s...\n" % _("Modification"))
298                    for entry in entries :
299                        if options.groups :
300                            self.modifyEntry(entry,
301                                             options.groups,
302                                             limitby,
303                                             options.description)
304                        else :
305                            self.modifyEntry(entry,
306                                             options.groups,
307                                             limitby,
308                                             options.description,
309                                             options.overcharge,
310                                             balance,
311                                             balancevalue,
312                                             options.comment,
313                                             options.email)
314                            self.manageUsersGroups(usersgroups,
315                                                   entry,
316                                                   options.remove)
317                        entry.save()
318                        percent.oneMore()
319            except :
320                self.storage.rollbackTransaction()
321                raise
322            else :
323                self.storage.commitTransaction()
324
325        if not islist :
326            percent.done()
327
328if __name__ == "__main__" :
329    parser = PyKotaOptionParser(description=_("Manages PyKota users or users groups."),
330                                usage="pkusers [options] name1 name2 ... nameN")
331    parser.add_option("-a", "--add",
332                            action="store_const",
333                            const="add",
334                            dest="action",
335                            help=_("Add new, or modify existing, users or groups."))
336    parser.add_option("-b", "--balance",
337                            dest="balance",
338                            help=_("Set an user's account balance. The value can also be increased or decreased when the value is prefixed with '+' or '-'. Users groups don't have a real account balance, instead the sum of their members' account balances is used."))
339    parser.add_option("-C", "--comment",
340                            dest="comment",
341                            default="",
342                            help=_("Associate a textual comment with a change in an user's account balance. Only meaningful when --balance is also used."))
343    parser.add_option("-d", "--delete",
344                            action="store_const",
345                            const="delete",
346                            dest="action",
347                            help=_("Delete the specified users or groups. Also purge the print quota entries and printing history matching the specified users or groups."))
348    parser.add_option("-D", "--description",
349                            dest="description",
350                            help=_("Set a textual description for the specified users or groups."))
351    parser.add_option("-e", "--email",
352                            dest="email",
353                            help=_("Set an user's email address. If this parameter begins with '@' then the username is prepended to this parameter to form a valid email address."))
354    parser.add_option("-g", "--groups",
355                            action="store_true",
356                            dest="groups",
357                            help=_("Manage users groups instead of users."))
358    parser.add_option("-i", "--ingroups",
359                            dest="ingroups",
360                            help=_("Put the specified users into the specified groups. When combined with the --remove option, users are removed from the specified groups instead."))
361    parser.add_option("-l", "--limitby",
362                            dest="limitby",
363                            help=_("Set the limiting factor for the specified users or groups. Can be any of 'quota' (limit by number of pages per printer), 'balance' (limit by number of credits), 'noquota' (no limit but accounting done), 'nochange' (no limit and not accounting), or 'noprint' (printing is denied). The two latter ones are not supported for groups."))
364    parser.add_option("-L", "--list",
365                            action="store_const",
366                            const="list",
367                            dest="action",
368                            help=_("Display detailed informations about the specified users or groups."))
369    parser.add_option("-o", "--overcharge",
370                            type="float",
371                            dest="overcharge",
372                            help=_("Set the overcharging factor applied to the specified users when computing the cost of a print job. Any floating point value can be used, allowing you to express your creativity..."))
373    parser.add_option("-r", "--remove",
374                            action="store_true",
375                            dest="remove",
376                            help=_("When combined with the --ingroups option, remove users from the specified users groups."))
377    parser.add_option("-s", "--skipexisting",
378                            action="store_true",
379                            dest="skipexisting",
380                            help=_("If --add is used, ensure that existing users or groups won't be modified."))
381
382    parser.add_example("--add john paul george ringo/ringo@example.com",
383                       _("Would make users 'john', 'paul', 'george' and 'ringo' be known to PyKota. User 'ringo''s email address would be set to 'ringo@example.com'."))
384    parser.add_example("--add --groups coders it",
385                       _("Would create two users groups named 'coders' and 'it'."))
386    parser.add_example("--add --ingroups coders,it jerome",
387                       _("Would add user 'jerome' and put him into the 'coders' and 'it' groups. Both groups would have to be existing."))
388    parser.add_example("--limitby balance --balance 10.0 john",
389                       _("Would give 10.0 credits to 'john' and make his printing be limited by his account balance."))
390    parser.add_example('--balance +10.0 --comment "He paid with his blood." jerome',
391                       _("Would add 10.0 credits to 'jerome''s account and register a comment associated with his payment."))
392    parser.add_example('--delete "jer*" "rach*"',
393                       _("Would delete all user accounts whose names begin with either 'jer' or 'rach'."))
394    parser.add_example("--overcharge -1.50 theboss",
395                       _("Would make the boss earn money whenever he prints."))
396    parser.add_example("--email @example.com",
397                       _("Would set the email address for each existing user to username@example.com"))
398    parser.add_example("--list",
399                       _("Would list all users."))
400    run(parser, PKUsers)
Note: See TracBrowser for help on using the browser.