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.', | |
68 | params = [SC.Arg('first'), SC.Arg('second')], | |
69 | rparam = SC.Arg('services')) | |
70 | def cmd_set_cgi(first, second, services = []): | |
71 | if first != second: raise U.ExpectedError, (400, "Passwords don't match") | |
72 | operate('set passwords', 'set', services, first) | |
73 | ||
74 | @CGI.subcommand( | |
75 | 'reset', ['cgi'], | |
76 | 'Reset passwords for a collection of services.', | |
77 | rparam = SC.Arg('services')) | |
78 | def cmd_reset_cgi(services = []): | |
79 | operate('reset passwords', 'reset', services) | |
80 | ||
81 | @CGI.subcommand( | |
82 | 'clear', ['cgi'], | |
83 | 'Clear passwords for a collection of services.', | |
84 | rparam = SC.Arg('services')) | |
85 | def cmd_clear_cgi(services = []): | |
86 | operate('clear passwords', 'clear', services) | |
87 | ||
170f1769 MW |
88 | @CGI.subcommand( |
89 | 'logout', ['cgi'], | |
90 | 'Log out of the web interface.') | |
91 | def cmd_logout_cgi(): | |
92 | CGI.redirect(CGI.action('login', why = 'LOGOUT'), | |
93 | set_cookie = HA.bake_cookie('logged-out')) | |
94 | ||
a2916c06 MW |
95 | @CGI.subcommand( |
96 | 'fail', ['cgi-noauth'], | |
97 | 'Raise an exception, to test the error reporting machinery.', | |
98 | opts = [SC.Opt('partial', '-p', '--partial', | |
99 | 'Raise exception after producing partial output.')]) | |
100 | def cmd_fail_cgi(partial = False): | |
101 | if partial: | |
102 | OUT.header(content_type = 'text/html') | |
103 | PRINT("""\ | |
104 | <html> | |
105 | <head><title>Chopwood: filler text</title></head> | |
106 | <body> | |
107 | <h1>Failure expected soon | |
108 | <p>This is some normal output which will be rudely interrupted.""") | |
109 | raise Exception, 'You asked for this.' | |
110 | ||
111 | ###-------------------------------------------------------------------------- | |
112 | ### Static content. | |
113 | ||
114 | ## A map of file names to content objects. See below. | |
115 | CONTENT = {} | |
116 | ||
117 | class PlainOutput (O.FileOutput): | |
118 | def header(me, **kw): | |
119 | pass | |
120 | ||
121 | class StaticContent (object): | |
122 | def __init__(me, type): | |
123 | me._type = type | |
124 | def emit(me): | |
125 | OUT.header(content_type = me._type) | |
126 | me._emit() | |
127 | def _write(me, dest): | |
128 | with open(dest, 'w') as f: | |
129 | with OUT.redirect_to(PlainOutput(f)): | |
130 | me.emit() | |
131 | def write(me, dest): | |
132 | new = dest + '.new' | |
133 | try: OS.unlink(new) | |
134 | except OSError, e: | |
135 | if e.errno != E.ENOENT: raise | |
136 | me._write(new) | |
137 | OS.rename(new, dest) | |
138 | ||
139 | class TemplateContent (StaticContent): | |
140 | def __init__(me, template, *args, **kw): | |
141 | super(TemplateContent, me).__init__(*args, **kw) | |
142 | me._template = template | |
143 | def _emit(me): | |
144 | CGI.format_tmpl(CGI.TMPL[me._template]) | |
145 | ||
146 | class HTMLContent (StaticContent): | |
147 | def __init__(me, title, template, type = 'text/html', *args, **kw): | |
148 | super(HTMLContent, me).__init__(type = type, *args, **kw) | |
149 | me._template = template | |
150 | me._title = title | |
151 | def emit(me): | |
152 | CGI.page(me._template, title = me._title) | |
153 | ||
154 | CONTENT.update({ | |
155 | 'chpwd.css': TemplateContent(template = 'chpwd.css', | |
156 | type = 'text/css'), | |
157 | 'chpwd.js': TemplateContent(template = 'chpwd.js', | |
158 | type = 'text/javascript'), | |
159 | 'about.html': HTMLContent('Chopwood: about this program', | |
160 | template = 'about.fhtml'), | |
161 | 'cookies.html': HTMLContent('Chopwood: use of cookies', | |
162 | template = 'cookies.fhtml') | |
163 | }) | |
164 | ||
165 | @CGI.subcommand( | |
166 | 'static', ['cgi-noauth'], 'Output a static file.', | |
167 | rparam = SC.Arg('path')) | |
168 | def cmd_static_cgi(path): | |
169 | name = '/'.join(path) | |
170 | try: content = CONTENT[name] | |
171 | except KeyError: raise U.ExpectedError, (404, "Unknown file `%s'" % name) | |
172 | content.emit() | |
173 | ||
174 | @SC.subcommand( | |
175 | 'static', ['admin'], 'Write the static files to DIR.', | |
176 | params = [SC.Arg('dir')]) | |
177 | def cmd_static_admin(dir): | |
178 | try: OS.makedirs(dir, 0777) | |
179 | except OSError, e: | |
180 | if e.errno != E.EEXIST: raise | |
181 | for f, c in CONTENT.iteritems(): | |
182 | c.write(OS.path.join(dir, f)) | |
183 | ||
184 | TARBALL = '%s-%s.tar.gz' % (PACKAGE, VERSION) | |
185 | @CGI.subcommand(TARBALL, ['cgi-noauth'], """\ | |
186 | Download source code (in `.tar.gz' format).""") | |
187 | def cmd_source_cgi(): | |
188 | OUT.header(content_type = 'application/octet-stream') | |
189 | AGPL.source(OUT) | |
190 | ||
191 | @CGI.subcommand('source', ['cgi-noauth'], """\ | |
d674bfda | 192 | Redirect to the source code tarball (so that it's correctly named).""") |
a2916c06 MW |
193 | def cmd_sourceredirect_cgi(): |
194 | CGI.redirect(CGI.action(TARBALL)) | |
195 | ||
196 | ###----- That's all, folks -------------------------------------------------- |