| 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 -------------------------------------------------- |