root / pykota / trunk / bin / pkusers @ 3428

Revision 3428, 21.1 kB (checked in by jerome, 16 years ago)

Fixed missing import and usage info.

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