Commit | Line | Data |
---|---|---|
a2916c06 MW |
1 | ### -*-python-*- |
2 | ### | |
3 | ### CGI 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 errno as E | |
29 | import os as OS | |
30 | ||
31 | from auto import PACKAGE, VERSION | |
32 | import agpl as AGPL | |
33 | import cgi as CGI | |
34 | import cmdutil as CU | |
35 | import dbmaint as D | |
36 | import httpauth as HA | |
37 | import operation as OP | |
38 | import output as O; OUT = O.OUT; PRINT = O.PRINT | |
39 | import service as S | |
40 | import subcommand as SC | |
41 | import util as U | |
42 | ||
43 | ###-------------------------------------------------------------------------- | |
44 | ### Utilities. | |
45 | ||
46 | def operate(what, op, services, *args, **kw): | |
47 | accts = CU.resolve_accounts(CU.USER, services) | |
48 | o, ii, rq, ops = OP.operate(op, accts, *args, **kw) | |
49 | CGI.page('operate.fhtml', | |
50 | header = dict(pragma = 'no-cache', cache_control = 'no-cache'), | |
51 | title = 'Chopwood: %s' % what, | |
52 | what = what, | |
53 | outcome = o, info = ii, results = ops) | |
54 | ||
55 | ###-------------------------------------------------------------------------- | |
56 | ### Commands. | |
57 | ||
58 | @CGI.subcommand('list', ['cgi-query'], 'List available accounts') | |
59 | def cmd_list_cgi(): | |
60 | CGI.page('list.fhtml', | |
6a749ccf | 61 | header = dict(pragma = 'no-cache', cache_control = 'no-cache'), |
a2916c06 MW |
62 | title = 'Chopwood: accounts list', |
63 | accts = CU.list_accounts(CU.USER), | |
64 | nonce = HA.NONCE) | |
65 | ||
66 | @CGI.subcommand( | |
67 | 'set', ['cgi'], 'Set password for a collection of services.', | |
9e574017 | 68 | methods = ['POST'], |
a2916c06 MW |
69 | params = [SC.Arg('first'), SC.Arg('second')], |
70 | rparam = SC.Arg('services')) | |
71 | def cmd_set_cgi(first, second, services = []): | |
72 | if first != second: raise U.ExpectedError, (400, "Passwords don't match") | |
73 | operate('set passwords', 'set', services, first) | |
74 | ||
75 | @CGI.subcommand( | |
76 | 'reset', ['cgi'], | |
77 | 'Reset passwords for a collection of services.', | |
9e574017 | 78 | methods = ['POST'], |
a2916c06 MW |
79 | rparam = SC.Arg('services')) |
80 | def cmd_reset_cgi(services = []): | |
81 | operate('reset passwords', 'reset', services) | |
82 | ||
83 | @CGI.subcommand( | |
84 | 'clear', ['cgi'], | |
85 | 'Clear passwords for a collection of services.', | |
9e574017 | 86 | methods = ['POST'], |
a2916c06 MW |
87 | rparam = SC.Arg('services')) |
88 | def cmd_clear_cgi(services = []): | |
89 | operate('clear passwords', 'clear', services) | |
90 | ||
170f1769 MW |
91 | @CGI.subcommand( |
92 | 'logout', ['cgi'], | |
9e574017 MW |
93 | 'Log out of the web interface.', |
94 | methods = ['POST']) | |
170f1769 MW |
95 | def cmd_logout_cgi(): |
96 | CGI.redirect(CGI.action('login', why = 'LOGOUT'), | |
97 | set_cookie = HA.bake_cookie('logged-out')) | |
98 | ||
a2916c06 MW |
99 | @CGI.subcommand( |
100 | 'fail', ['cgi-noauth'], | |
101 | 'Raise an exception, to test the error reporting machinery.', | |
102 | opts = [SC.Opt('partial', '-p', '--partial', | |
103 | 'Raise exception after producing partial output.')]) | |
104 | def cmd_fail_cgi(partial = False): | |
105 | if partial: | |
106 | OUT.header(content_type = 'text/html') | |
107 | PRINT("""\ | |
108 | <html> | |
109 | <head><title>Chopwood: filler text</title></head> | |
110 | <body> | |
111 | <h1>Failure expected soon | |
112 | <p>This is some normal output which will be rudely interrupted.""") | |
113 | raise Exception, 'You asked for this.' | |
114 | ||
5b7c6334 MW |
115 | @CGI.subcommand( |
116 | 'warn', ['cgi-noauth'], | |
117 | 'Raise an exception, to test the error reporting machinery.') | |
118 | def cmd_warn_cgi(): | |
119 | OUT.header(content_type = 'text/html') | |
120 | OUT.warn("Here's a very important warning.") | |
121 | CGI.format_tmpl(CGI.TMPL['wrapper.fhtml'], | |
122 | title = "Warning test", warnings = OUT.warnings, | |
123 | payload = "<h1>Chopwood: warning test</h1>\n" | |
124 | "<p>There ought to be a warning below.\n") | |
125 | ||
a2916c06 MW |
126 | ###-------------------------------------------------------------------------- |
127 | ### Static content. | |
128 | ||
129 | ## A map of file names to content objects. See below. | |
130 | CONTENT = {} | |
131 | ||
132 | class PlainOutput (O.FileOutput): | |
133 | def header(me, **kw): | |
134 | pass | |
135 | ||
136 | class StaticContent (object): | |
137 | def __init__(me, type): | |
138 | me._type = type | |
139 | def emit(me): | |
140 | OUT.header(content_type = me._type) | |
141 | me._emit() | |
142 | def _write(me, dest): | |
143 | with open(dest, 'w') as f: | |
144 | with OUT.redirect_to(PlainOutput(f)): | |
145 | me.emit() | |
146 | def write(me, dest): | |
147 | new = dest + '.new' | |
148 | try: OS.unlink(new) | |
149 | except OSError, e: | |
150 | if e.errno != E.ENOENT: raise | |
151 | me._write(new) | |
152 | OS.rename(new, dest) | |
153 | ||
154 | class TemplateContent (StaticContent): | |
155 | def __init__(me, template, *args, **kw): | |
156 | super(TemplateContent, me).__init__(*args, **kw) | |
157 | me._template = template | |
158 | def _emit(me): | |
159 | CGI.format_tmpl(CGI.TMPL[me._template]) | |
160 | ||
161 | class HTMLContent (StaticContent): | |
162 | def __init__(me, title, template, type = 'text/html', *args, **kw): | |
163 | super(HTMLContent, me).__init__(type = type, *args, **kw) | |
164 | me._template = template | |
165 | me._title = title | |
166 | def emit(me): | |
167 | CGI.page(me._template, title = me._title) | |
168 | ||
169 | CONTENT.update({ | |
170 | 'chpwd.css': TemplateContent(template = 'chpwd.css', | |
171 | type = 'text/css'), | |
172 | 'chpwd.js': TemplateContent(template = 'chpwd.js', | |
173 | type = 'text/javascript'), | |
174 | 'about.html': HTMLContent('Chopwood: about this program', | |
175 | template = 'about.fhtml'), | |
176 | 'cookies.html': HTMLContent('Chopwood: use of cookies', | |
177 | template = 'cookies.fhtml') | |
178 | }) | |
179 | ||
180 | @CGI.subcommand( | |
181 | 'static', ['cgi-noauth'], 'Output a static file.', | |
182 | rparam = SC.Arg('path')) | |
183 | def cmd_static_cgi(path): | |
184 | name = '/'.join(path) | |
185 | try: content = CONTENT[name] | |
186 | except KeyError: raise U.ExpectedError, (404, "Unknown file `%s'" % name) | |
187 | content.emit() | |
188 | ||
189 | @SC.subcommand( | |
190 | 'static', ['admin'], 'Write the static files to DIR.', | |
191 | params = [SC.Arg('dir')]) | |
192 | def cmd_static_admin(dir): | |
193 | try: OS.makedirs(dir, 0777) | |
194 | except OSError, e: | |
195 | if e.errno != E.EEXIST: raise | |
196 | for f, c in CONTENT.iteritems(): | |
197 | c.write(OS.path.join(dir, f)) | |
198 | ||
199 | TARBALL = '%s-%s.tar.gz' % (PACKAGE, VERSION) | |
200 | @CGI.subcommand(TARBALL, ['cgi-noauth'], """\ | |
201 | Download source code (in `.tar.gz' format).""") | |
202 | def cmd_source_cgi(): | |
203 | OUT.header(content_type = 'application/octet-stream') | |
204 | AGPL.source(OUT) | |
205 | ||
206 | @CGI.subcommand('source', ['cgi-noauth'], """\ | |
d674bfda | 207 | Redirect to the source code tarball (so that it's correctly named).""") |
a2916c06 MW |
208 | def cmd_sourceredirect_cgi(): |
209 | CGI.redirect(CGI.action(TARBALL)) | |
210 | ||
211 | ###----- That's all, folks -------------------------------------------------- |