Commit | Line | Data |
---|---|---|
a2916c06 MW |
1 | ### -*-python-*- |
2 | ### | |
3 | ### Administrative commands | |
4 | ### | |
5 | ### (c) 2013 Mark Wooding | |
6 | ### | |
7 | ||
8 | ###----- Licensing notice --------------------------------------------------- | |
9 | ### | |
10 | ### This file is part of Chopwood: a password-changing service. | |
11 | ### | |
12 | ### Chopwood is free software; you can redistribute it and/or modify | |
13 | ### it under the terms of the GNU Affero General Public License as | |
14 | ### published by the Free Software Foundation; either version 3 of the | |
15 | ### License, or (at your option) any later version. | |
16 | ### | |
17 | ### Chopwood is distributed in the hope that it will be useful, | |
18 | ### but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | ### GNU Affero General Public License for more details. | |
21 | ### | |
22 | ### You should have received a copy of the GNU Affero General Public | |
23 | ### License along with Chopwood; if not, see | |
24 | ### <http://www.gnu.org/licenses/>. | |
25 | ||
26 | from __future__ import with_statement | |
27 | ||
28 | import agpl as AGPL | |
82d4f64b | 29 | import backend as BE |
a2916c06 | 30 | import cmdutil as CU |
82d4f64b | 31 | import config as CONF; CFG = CONF.CFG |
a2916c06 | 32 | import dbmaint as D |
82d4f64b | 33 | import operation as OP |
a2916c06 | 34 | from output import OUT, PRINT |
82d4f64b | 35 | import service as S |
a2916c06 MW |
36 | import subcommand as SC |
37 | import util as U | |
38 | ||
39 | @SC.subcommand( | |
40 | 'listusers', ['admin'], 'List the existing users.', | |
41 | opts = [SC.Opt('service', '-s', '--service', | |
42 | 'list users with SERVICE accounts', | |
43 | argname = 'SERVICE')], | |
44 | oparams = [SC.Arg('pat')]) | |
45 | def cmd_listuser(service = None, pat = None): | |
46 | CU.format_list(CU.list_users(service, pat), | |
47 | [CU.column('USER', "~={0.user}A", 3), | |
48 | CU.column('EMAIL', "~={0.email}:[---~;~={0.email}A~]")]) | |
49 | ||
50 | @SC.subcommand( | |
51 | 'adduser', ['admin'], 'Add a user to the master database.', | |
52 | opts = [SC.Opt('email', '-e', '--email', | |
53 | "email address for the new user", | |
54 | argname = 'EMAIL')], | |
55 | params = [SC.Arg('user')]) | |
56 | def cmd_adduser(user, email = None): | |
57 | with D.DB: | |
58 | CU.check_user(user, False) | |
59 | D.DB.execute("INSERT INTO users (user, email) VALUES ($user, $email)", | |
60 | user = user, email = email) | |
61 | D.DB.execute("""INSERT INTO services (user, service) | |
62 | VALUES ($user, $service)""", | |
63 | user = user, service = 'master') | |
64 | ||
65 | @SC.subcommand( | |
66 | 'deluser', ['admin'], 'Remove USER from the master database.', | |
67 | params = [SC.Arg('user')]) | |
68 | def cmd_deluser(user, email = None): | |
69 | with D.DB: | |
70 | CU.check_user(user) | |
82d4f64b MW |
71 | for service, alias in D.DB.execute( |
72 | "SELECT service, alias FROM services WHERE user = $user", | |
73 | user = user): | |
74 | if service == 'master': continue | |
75 | try: | |
76 | svc = S.SERVICES[service] | |
77 | except KeyError: | |
78 | OUT.warn("User `%s' has account for unknown service `%s'" % | |
79 | (user, service)) | |
80 | else: | |
81 | if svc.manage_pwent_p: | |
82 | if alias is None: alias = user | |
83 | svc.rmpwent(alias) | |
a2916c06 MW |
84 | D.DB.execute("DELETE FROM users WHERE user = $user", user = user) |
85 | ||
86 | @SC.subcommand( | |
87 | 'edituser', ['admin'], 'Modify a user record.', | |
88 | opts = [SC.Opt('email', '-e', '--email', | |
89 | "change USER's email address", | |
90 | argname = 'EMAIL'), | |
91 | SC.Opt('noemail', '-E', '--no-email', | |
92 | "forget USER's email address"), | |
93 | SC.Opt('rename', '-r', '--rename', | |
94 | "rename USER", | |
95 | argname = 'NEW-NAME')], | |
96 | params = [SC.Arg('user')]) | |
97 | def cmd_edituser(user, email = None, noemail = False, rename = None): | |
98 | with D.DB: | |
99 | CU.check_user(user) | |
100 | if rename is not None: check_user(rename, False) | |
101 | CU.edit_records('users', 'user = $user', | |
102 | [('email', email, noemail), | |
103 | ('user', rename, False)], | |
104 | user = user) | |
105 | ||
106 | @SC.subcommand( | |
107 | 'delsvc', ['admin'], "Remove all records for SERVICE.", | |
108 | params = [SC.Arg('service')]) | |
109 | def cmd_delsvc(service): | |
110 | with D.DB: | |
111 | CU.check_service(service, must_config_p = False, must_records_p = True) | |
112 | D.DB.execute("DELETE FROM services WHERE service = $service", | |
113 | service = service) | |
114 | ||
115 | @SC.subcommand( | |
116 | 'editsvc', ['admin'], "Edit a given SERVICE.", | |
117 | opts = [SC.Opt('rename', '-r', '--rename', "rename the SERVICE", | |
118 | argname = 'NEW-NAME')], | |
119 | params = [SC.Arg('service')]) | |
120 | def cmd_editsvc(service, rename = None): | |
121 | with D.DB: | |
122 | if service == 'master': | |
123 | raise U.ExpectedError, (400, "Can't edit the master service") | |
124 | if rename is None: | |
125 | CU.check_service(service, must_config_p = True, must_records_p = True) | |
126 | else: | |
127 | CU.check_service(service, must_config_p = False, must_records_p = True) | |
128 | CU.check_service(rename, must_config_p = True, must_records_p = False) | |
129 | CU.edit_records('services', 'service = $service', | |
130 | [('service', rename, False)], | |
131 | service = service) | |
132 | ||
133 | @SC.subcommand( | |
134 | 'addacct', ['admin'], 'Add an account for a user.', | |
135 | opts = [SC.Opt('alias', '-a', '--alias', | |
136 | "alias by which USER is known to SERVICE", | |
137 | argname = 'ALIAS')], | |
82d4f64b MW |
138 | params = [SC.Arg('user'), SC.Arg('service')], |
139 | rparam = SC.Arg('fields')) | |
140 | def cmd_addacct(user, service, fields, alias = None): | |
a2916c06 MW |
141 | with D.DB: |
142 | CU.check_user(user) | |
82d4f64b | 143 | svc = CU.check_service(service) |
a2916c06 MW |
144 | D.DB.execute("""SELECT 1 FROM services |
145 | WHERE user = $user AND service = $service""", | |
146 | user = user, service = service) | |
147 | if D.DB.fetchone() is not None: | |
148 | raise U.ExpectedError, ( | |
74b87214 | 149 | 400, "User `%s' already has a `%s' account" % (user, service)) |
a2916c06 MW |
150 | D.DB.execute("""INSERT INTO services (service, user, alias) |
151 | VALUES ($service, $user, $alias)""", | |
152 | service = service, user = user, alias = alias) | |
153 | ||
82d4f64b MW |
154 | if svc.manage_pwent_p: |
155 | if alias is None: alias = user | |
156 | passwd = CFG.RQCLASS.reset([OP.acct(svc, alias)]).pwgen() | |
157 | svc.mkpwent(alias, passwd, fields) | |
158 | elif fields: | |
159 | raise U.ExpectedError, ( | |
160 | 400, "Password entry fields supplied, " | |
161 | "but `%s' entries must be created manually" % service) | |
162 | ||
a2916c06 MW |
163 | @SC.subcommand( |
164 | 'delacct', ['admin'], "Remove USER's SERVICE account.", | |
165 | params = [SC.Arg('user'), SC.Arg('service')]) | |
166 | def cmd_delacct(user, service): | |
167 | with D.DB: | |
82d4f64b | 168 | svc, alias = CU.resolve_account(service, user) |
a2916c06 MW |
169 | if service == 'master': |
170 | raise U.ExpectedError, \ | |
171 | (400, "Can't delete master accounts: use `deluser'") | |
172 | D.DB.execute("""DELETE FROM services | |
173 | WHERE service = $service AND user = $user""", | |
174 | service = service, user = user) | |
82d4f64b MW |
175 | if svc.manage_pwent_p: |
176 | svc.rmpwent(alias) | |
177 | else: | |
178 | OUT.warn("You must remove the `%s' password entry for `%s' by hand" % | |
179 | (service, user)) | |
a2916c06 MW |
180 | |
181 | @SC.subcommand( | |
182 | 'editacct', ['admin'], "Modify USER's SERVICE account record.", | |
183 | opts = [SC.Opt('alias', '-a', '--alias', | |
184 | "change USER's login name for SERVICE", | |
185 | argname = 'ALIAS'), | |
186 | SC.Opt('noalias', '-A', '--no-alias', | |
187 | "use USER's master login name")], | |
188 | params = [SC.Arg('user'), SC.Arg('service')]) | |
189 | def cmd_editacct(user, service, alias = None, noalias = False): | |
190 | with D.DB: | |
191 | CU.resolve_account(service, user) | |
192 | if service == 'master': | |
193 | raise U.ExpectedError, (400, "Can't edit master accounts") | |
194 | CU.edit_records('services', 'user = $user AND service = $service', | |
195 | [('alias', alias, noalias)], | |
196 | user = user, service = service) | |
197 | ||
198 | @SC.subcommand( | |
4f09f456 | 199 | 'source', ['admin', 'userv', 'remote'], """\ |
a2916c06 MW |
200 | Write source code (in `.tar.gz' format) to standard output.""") |
201 | def cmd_source_admin(): | |
202 | AGPL.source(OUT) | |
203 | ||
204 | ###----- That's all, folks -------------------------------------------------- |