chiark / gitweb /
wrapper.fhtml: Add `license' relationship to the AGPL link.
[chopwood] / cmd-admin.py
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
29 import backend as BE
30 import cmdutil as CU
31 import config as CONF; CFG = CONF.CFG
32 import dbmaint as D
33 import operation as OP
34 from output import OUT, PRINT
35 import service as S
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)
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)
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')],
138   params = [SC.Arg('user'), SC.Arg('service')],
139   rparam = SC.Arg('fields'))
140 def cmd_addacct(user, service, fields, alias = None):
141   with D.DB:
142     CU.check_user(user)
143     svc = CU.check_service(service)
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, (
149         400, "User `%s' already has a `%s' account" % (user, service))
150     D.DB.execute("""INSERT INTO services (service, user, alias)
151                     VALUES ($service, $user, $alias)""",
152                  service = service, user = user, alias = alias)
153
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
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:
168     svc, alias = CU.resolve_account(service, user)
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)
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))
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(
199   'source', ['admin', 'userv', 'remote'], """\
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 --------------------------------------------------