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