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 | |
---|
26 | import sys |
---|
27 | import pwd |
---|
28 | import grp |
---|
29 | |
---|
30 | import pykota.appinit |
---|
31 | from pykota.utils import run |
---|
32 | from pykota.commandline import PyKotaOptionParser |
---|
33 | from pykota.errors import PyKotaCommandLineError |
---|
34 | from pykota.tool import Percent, PyKotaTool |
---|
35 | from pykota.storage import StorageUser, StorageGroup |
---|
36 | |
---|
37 | |
---|
38 | |
---|
39 | class 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 | |
---|
328 | if __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) |
---|