root / pykota / trunk / bin / pkusers @ 3431

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

Removed blank lines.

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