Merge lp:~blr/rutabaga/trunk into lp:~wgrant/rutabaga/empty

Proposed by William Grant
Status: Needs review
Proposed branch: lp:~blr/rutabaga/trunk
Merge into: lp:~wgrant/rutabaga/empty
Diff against target: 2202 lines (+2066/-0)
25 files modified
.bzrignore (+16/-0)
LICENSE (+662/-0)
Makefile (+23/-0)
README.rst (+22/-0)
config.yaml (+2/-0)
db/rutabaga.schema (+10/-0)
dev-requirements.txt (+5/-0)
docs/Makefile (+177/-0)
docs/conf.py (+260/-0)
docs/index.rst (+23/-0)
docs/make.bat (+242/-0)
requirements.txt (+4/-0)
rutabaga.ini (+45/-0)
rutabaga/__init__.py (+13/-0)
rutabaga/auth.py (+80/-0)
rutabaga/config.py (+19/-0)
rutabaga/helpers.py (+12/-0)
rutabaga/scripts/purge.py (+17/-0)
rutabaga/scripts/rutabaga_auth_helper.py (+56/-0)
rutabaga/tests/test_auth.py (+106/-0)
rutabaga/tests/test_views.py (+104/-0)
rutabaga/views.py (+83/-0)
setup.py (+33/-0)
test-requirements.txt (+5/-0)
tests/test_integration.py (+47/-0)
To merge this branch: bzr merge lp:~blr/rutabaga/trunk
Reviewer Review Type Date Requested Status
William Grant Pending
Review via email: mp+273357@code.launchpad.net
To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
lp:~blr/rutabaga/trunk updated
38. By Kit Randel on 2015-10-07

* Add iso8601 0.1.10 and pytz 2015.6.
* Use iso8601 for token timestamps.
* Check token validity in sql.
* Rename /token endpoint to pular/tokens.
* Remove reference to Snap in logger.
* Use empty list for bindings kwarg and pass to cursor in fetchall case.
* Only call delete_invalid_tokens in purge script (delete fn calls invalidate_expired_tokens).

39. By Kit Randel on 2015-10-08

* Remove client_ip (may be reintroduced later once tokens can be restricted by source IP).
* Remove check for existing token.
* Remove /tokens/ GET for validation.
* Add '/validate' POST endpoint, requiring both build_id and token_id, and refactor related tests.
* Add max_retries to http session in squid proxy auth helper.
* Validate presence of token_id in response from /validate in auth helper.

Revision history for this message
William Grant (wgrant) :
lp:~blr/rutabaga/trunk updated
40. By Kit Randel on 2015-10-08

* Rename build_id and token_id to username and secret.
* Remove unneeded deps.
* Remove token_invalidation logic and check token ttl deltas in SQL.

41. By Kit Randel on 2015-10-08

Remove fetchall() case.

Unmerged revisions

41. By Kit Randel on 2015-10-08

Remove fetchall() case.

40. By Kit Randel on 2015-10-08

* Rename build_id and token_id to username and secret.
* Remove unneeded deps.
* Remove token_invalidation logic and check token ttl deltas in SQL.

39. By Kit Randel on 2015-10-08

* Remove client_ip (may be reintroduced later once tokens can be restricted by source IP).
* Remove check for existing token.
* Remove /tokens/ GET for validation.
* Add '/validate' POST endpoint, requiring both build_id and token_id, and refactor related tests.
* Add max_retries to http session in squid proxy auth helper.
* Validate presence of token_id in response from /validate in auth helper.

38. By Kit Randel on 2015-10-07

* Add iso8601 0.1.10 and pytz 2015.6.
* Use iso8601 for token timestamps.
* Check token validity in sql.
* Rename /token endpoint to pular/tokens.
* Remove reference to Snap in logger.
* Use empty list for bindings kwarg and pass to cursor in fetchall case.
* Only call delete_invalid_tokens in purge script (delete fn calls invalidate_expired_tokens).

37. By Kit Randel on 2015-09-30

Add logging for purge cron job.

36. By Kit Randel on 2015-09-29

Call len once.

35. By Kit Randel on 2015-09-29

Delete invalidated_tokens.

34. By Kit Randel on 2015-09-29

Correct sqlite bindings in the case of multiple tokens to invalidate.

33. By Kit Randel on 2015-09-29

Add fixtures and WebTest to test requirements.

32. By Kit Randel on 2015-09-29

Provide comment on useful squid debug options.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file '.bzrignore'
2--- .bzrignore 1970-01-01 00:00:00 +0000
3+++ .bzrignore 2015-10-08 21:20:21 +0000
4@@ -0,0 +1,16 @@
5+bin
6+build
7+db/*.db
8+develop-eggs
9+dist
10+docs/_build
11+download-cache
12+eggs
13+*.egg*
14+*.egg-info
15+.installed.cfg
16+*.log
17+parts
18+.python-version
19+tags
20+TAGS
21\ No newline at end of file
22
23=== added file 'LICENSE'
24--- LICENSE 1970-01-01 00:00:00 +0000
25+++ LICENSE 2015-10-08 21:20:21 +0000
26@@ -0,0 +1,662 @@
27+ GNU AFFERO GENERAL PUBLIC LICENSE
28+ Version 3, 19 November 2007
29+ (http://www.gnu.org/licenses/agpl.html)
30+
31+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
32+ Everyone is permitted to copy and distribute verbatim copies
33+ of this license document, but changing it is not allowed.
34+
35+ Preamble
36+
37+ The GNU Affero General Public License is a free, copyleft license for
38+software and other kinds of works, specifically designed to ensure
39+cooperation with the community in the case of network server software.
40+
41+ The licenses for most software and other practical works are designed
42+to take away your freedom to share and change the works. By contrast,
43+our General Public Licenses are intended to guarantee your freedom to
44+share and change all versions of a program--to make sure it remains free
45+software for all its users.
46+
47+ When we speak of free software, we are referring to freedom, not
48+price. Our General Public Licenses are designed to make sure that you
49+have the freedom to distribute copies of free software (and charge for
50+them if you wish), that you receive source code or can get it if you
51+want it, that you can change the software or use pieces of it in new
52+free programs, and that you know you can do these things.
53+
54+ Developers that use our General Public Licenses protect your rights
55+with two steps: (1) assert copyright on the software, and (2) offer
56+you this License which gives you legal permission to copy, distribute
57+and/or modify the software.
58+
59+ A secondary benefit of defending all users' freedom is that
60+improvements made in alternate versions of the program, if they
61+receive widespread use, become available for other developers to
62+incorporate. Many developers of free software are heartened and
63+encouraged by the resulting cooperation. However, in the case of
64+software used on network servers, this result may fail to come about.
65+The GNU General Public License permits making a modified version and
66+letting the public access it on a server without ever releasing its
67+source code to the public.
68+
69+ The GNU Affero General Public License is designed specifically to
70+ensure that, in such cases, the modified source code becomes available
71+to the community. It requires the operator of a network server to
72+provide the source code of the modified version running there to the
73+users of that server. Therefore, public use of a modified version, on
74+a publicly accessible server, gives the public access to the source
75+code of the modified version.
76+
77+ An older license, called the Affero General Public License and
78+published by Affero, was designed to accomplish similar goals. This is
79+a different license, not a version of the Affero GPL, but Affero has
80+released a new version of the Affero GPL which permits relicensing under
81+this license.
82+
83+ The precise terms and conditions for copying, distribution and
84+modification follow.
85+
86+ TERMS AND CONDITIONS
87+
88+ 0. Definitions.
89+
90+ "This License" refers to version 3 of the GNU Affero General Public License.
91+
92+ "Copyright" also means copyright-like laws that apply to other kinds of
93+works, such as semiconductor masks.
94+
95+ "The Program" refers to any copyrightable work licensed under this
96+License. Each licensee is addressed as "you". "Licensees" and
97+"recipients" may be individuals or organizations.
98+
99+ To "modify" a work means to copy from or adapt all or part of the work
100+in a fashion requiring copyright permission, other than the making of an
101+exact copy. The resulting work is called a "modified version" of the
102+earlier work or a work "based on" the earlier work.
103+
104+ A "covered work" means either the unmodified Program or a work based
105+on the Program.
106+
107+ To "propagate" a work means to do anything with it that, without
108+permission, would make you directly or secondarily liable for
109+infringement under applicable copyright law, except executing it on a
110+computer or modifying a private copy. Propagation includes copying,
111+distribution (with or without modification), making available to the
112+public, and in some countries other activities as well.
113+
114+ To "convey" a work means any kind of propagation that enables other
115+parties to make or receive copies. Mere interaction with a user through
116+a computer network, with no transfer of a copy, is not conveying.
117+
118+ An interactive user interface displays "Appropriate Legal Notices"
119+to the extent that it includes a convenient and prominently visible
120+feature that (1) displays an appropriate copyright notice, and (2)
121+tells the user that there is no warranty for the work (except to the
122+extent that warranties are provided), that licensees may convey the
123+work under this License, and how to view a copy of this License. If
124+the interface presents a list of user commands or options, such as a
125+menu, a prominent item in the list meets this criterion.
126+
127+ 1. Source Code.
128+
129+ The "source code" for a work means the preferred form of the work
130+for making modifications to it. "Object code" means any non-source
131+form of a work.
132+
133+ A "Standard Interface" means an interface that either is an official
134+standard defined by a recognized standards body, or, in the case of
135+interfaces specified for a particular programming language, one that
136+is widely used among developers working in that language.
137+
138+ The "System Libraries" of an executable work include anything, other
139+than the work as a whole, that (a) is included in the normal form of
140+packaging a Major Component, but which is not part of that Major
141+Component, and (b) serves only to enable use of the work with that
142+Major Component, or to implement a Standard Interface for which an
143+implementation is available to the public in source code form. A
144+"Major Component", in this context, means a major essential component
145+(kernel, window system, and so on) of the specific operating system
146+(if any) on which the executable work runs, or a compiler used to
147+produce the work, or an object code interpreter used to run it.
148+
149+ The "Corresponding Source" for a work in object code form means all
150+the source code needed to generate, install, and (for an executable
151+work) run the object code and to modify the work, including scripts to
152+control those activities. However, it does not include the work's
153+System Libraries, or general-purpose tools or generally available free
154+programs which are used unmodified in performing those activities but
155+which are not part of the work. For example, Corresponding Source
156+includes interface definition files associated with source files for
157+the work, and the source code for shared libraries and dynamically
158+linked subprograms that the work is specifically designed to require,
159+such as by intimate data communication or control flow between those
160+subprograms and other parts of the work.
161+
162+ The Corresponding Source need not include anything that users
163+can regenerate automatically from other parts of the Corresponding
164+Source.
165+
166+ The Corresponding Source for a work in source code form is that
167+same work.
168+
169+ 2. Basic Permissions.
170+
171+ All rights granted under this License are granted for the term of
172+copyright on the Program, and are irrevocable provided the stated
173+conditions are met. This License explicitly affirms your unlimited
174+permission to run the unmodified Program. The output from running a
175+covered work is covered by this License only if the output, given its
176+content, constitutes a covered work. This License acknowledges your
177+rights of fair use or other equivalent, as provided by copyright law.
178+
179+ You may make, run and propagate covered works that you do not
180+convey, without conditions so long as your license otherwise remains
181+in force. You may convey covered works to others for the sole purpose
182+of having them make modifications exclusively for you, or provide you
183+with facilities for running those works, provided that you comply with
184+the terms of this License in conveying all material for which you do
185+not control copyright. Those thus making or running the covered works
186+for you must do so exclusively on your behalf, under your direction
187+and control, on terms that prohibit them from making any copies of
188+your copyrighted material outside their relationship with you.
189+
190+ Conveying under any other circumstances is permitted solely under
191+the conditions stated below. Sublicensing is not allowed; section 10
192+makes it unnecessary.
193+
194+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
195+
196+ No covered work shall be deemed part of an effective technological
197+measure under any applicable law fulfilling obligations under article
198+11 of the WIPO copyright treaty adopted on 20 December 1996, or
199+similar laws prohibiting or restricting circumvention of such
200+measures.
201+
202+ When you convey a covered work, you waive any legal power to forbid
203+circumvention of technological measures to the extent such circumvention
204+is effected by exercising rights under this License with respect to
205+the covered work, and you disclaim any intention to limit operation or
206+modification of the work as a means of enforcing, against the work's
207+users, your or third parties' legal rights to forbid circumvention of
208+technological measures.
209+
210+ 4. Conveying Verbatim Copies.
211+
212+ You may convey verbatim copies of the Program's source code as you
213+receive it, in any medium, provided that you conspicuously and
214+appropriately publish on each copy an appropriate copyright notice;
215+keep intact all notices stating that this License and any
216+non-permissive terms added in accord with section 7 apply to the code;
217+keep intact all notices of the absence of any warranty; and give all
218+recipients a copy of this License along with the Program.
219+
220+ You may charge any price or no price for each copy that you convey,
221+and you may offer support or warranty protection for a fee.
222+
223+ 5. Conveying Modified Source Versions.
224+
225+ You may convey a work based on the Program, or the modifications to
226+produce it from the Program, in the form of source code under the
227+terms of section 4, provided that you also meet all of these conditions:
228+
229+ a) The work must carry prominent notices stating that you modified
230+ it, and giving a relevant date.
231+
232+ b) The work must carry prominent notices stating that it is
233+ released under this License and any conditions added under section
234+ 7. This requirement modifies the requirement in section 4 to
235+ "keep intact all notices".
236+
237+ c) You must license the entire work, as a whole, under this
238+ License to anyone who comes into possession of a copy. This
239+ License will therefore apply, along with any applicable section 7
240+ additional terms, to the whole of the work, and all its parts,
241+ regardless of how they are packaged. This License gives no
242+ permission to license the work in any other way, but it does not
243+ invalidate such permission if you have separately received it.
244+
245+ d) If the work has interactive user interfaces, each must display
246+ Appropriate Legal Notices; however, if the Program has interactive
247+ interfaces that do not display Appropriate Legal Notices, your
248+ work need not make them do so.
249+
250+ A compilation of a covered work with other separate and independent
251+works, which are not by their nature extensions of the covered work,
252+and which are not combined with it such as to form a larger program,
253+in or on a volume of a storage or distribution medium, is called an
254+"aggregate" if the compilation and its resulting copyright are not
255+used to limit the access or legal rights of the compilation's users
256+beyond what the individual works permit. Inclusion of a covered work
257+in an aggregate does not cause this License to apply to the other
258+parts of the aggregate.
259+
260+ 6. Conveying Non-Source Forms.
261+
262+ You may convey a covered work in object code form under the terms
263+of sections 4 and 5, provided that you also convey the
264+machine-readable Corresponding Source under the terms of this License,
265+in one of these ways:
266+
267+ a) Convey the object code in, or embodied in, a physical product
268+ (including a physical distribution medium), accompanied by the
269+ Corresponding Source fixed on a durable physical medium
270+ customarily used for software interchange.
271+
272+ b) Convey the object code in, or embodied in, a physical product
273+ (including a physical distribution medium), accompanied by a
274+ written offer, valid for at least three years and valid for as
275+ long as you offer spare parts or customer support for that product
276+ model, to give anyone who possesses the object code either (1) a
277+ copy of the Corresponding Source for all the software in the
278+ product that is covered by this License, on a durable physical
279+ medium customarily used for software interchange, for a price no
280+ more than your reasonable cost of physically performing this
281+ conveying of source, or (2) access to copy the
282+ Corresponding Source from a network server at no charge.
283+
284+ c) Convey individual copies of the object code with a copy of the
285+ written offer to provide the Corresponding Source. This
286+ alternative is allowed only occasionally and noncommercially, and
287+ only if you received the object code with such an offer, in accord
288+ with subsection 6b.
289+
290+ d) Convey the object code by offering access from a designated
291+ place (gratis or for a charge), and offer equivalent access to the
292+ Corresponding Source in the same way through the same place at no
293+ further charge. You need not require recipients to copy the
294+ Corresponding Source along with the object code. If the place to
295+ copy the object code is a network server, the Corresponding Source
296+ may be on a different server (operated by you or a third party)
297+ that supports equivalent copying facilities, provided you maintain
298+ clear directions next to the object code saying where to find the
299+ Corresponding Source. Regardless of what server hosts the
300+ Corresponding Source, you remain obligated to ensure that it is
301+ available for as long as needed to satisfy these requirements.
302+
303+ e) Convey the object code using peer-to-peer transmission, provided
304+ you inform other peers where the object code and Corresponding
305+ Source of the work are being offered to the general public at no
306+ charge under subsection 6d.
307+
308+ A separable portion of the object code, whose source code is excluded
309+from the Corresponding Source as a System Library, need not be
310+included in conveying the object code work.
311+
312+ A "User Product" is either (1) a "consumer product", which means any
313+tangible personal property which is normally used for personal, family,
314+or household purposes, or (2) anything designed or sold for incorporation
315+into a dwelling. In determining whether a product is a consumer product,
316+doubtful cases shall be resolved in favor of coverage. For a particular
317+product received by a particular user, "normally used" refers to a
318+typical or common use of that class of product, regardless of the status
319+of the particular user or of the way in which the particular user
320+actually uses, or expects or is expected to use, the product. A product
321+is a consumer product regardless of whether the product has substantial
322+commercial, industrial or non-consumer uses, unless such uses represent
323+the only significant mode of use of the product.
324+
325+ "Installation Information" for a User Product means any methods,
326+procedures, authorization keys, or other information required to install
327+and execute modified versions of a covered work in that User Product from
328+a modified version of its Corresponding Source. The information must
329+suffice to ensure that the continued functioning of the modified object
330+code is in no case prevented or interfered with solely because
331+modification has been made.
332+
333+ If you convey an object code work under this section in, or with, or
334+specifically for use in, a User Product, and the conveying occurs as
335+part of a transaction in which the right of possession and use of the
336+User Product is transferred to the recipient in perpetuity or for a
337+fixed term (regardless of how the transaction is characterized), the
338+Corresponding Source conveyed under this section must be accompanied
339+by the Installation Information. But this requirement does not apply
340+if neither you nor any third party retains the ability to install
341+modified object code on the User Product (for example, the work has
342+been installed in ROM).
343+
344+ The requirement to provide Installation Information does not include a
345+requirement to continue to provide support service, warranty, or updates
346+for a work that has been modified or installed by the recipient, or for
347+the User Product in which it has been modified or installed. Access to a
348+network may be denied when the modification itself materially and
349+adversely affects the operation of the network or violates the rules and
350+protocols for communication across the network.
351+
352+ Corresponding Source conveyed, and Installation Information provided,
353+in accord with this section must be in a format that is publicly
354+documented (and with an implementation available to the public in
355+source code form), and must require no special password or key for
356+unpacking, reading or copying.
357+
358+ 7. Additional Terms.
359+
360+ "Additional permissions" are terms that supplement the terms of this
361+License by making exceptions from one or more of its conditions.
362+Additional permissions that are applicable to the entire Program shall
363+be treated as though they were included in this License, to the extent
364+that they are valid under applicable law. If additional permissions
365+apply only to part of the Program, that part may be used separately
366+under those permissions, but the entire Program remains governed by
367+this License without regard to the additional permissions.
368+
369+ When you convey a copy of a covered work, you may at your option
370+remove any additional permissions from that copy, or from any part of
371+it. (Additional permissions may be written to require their own
372+removal in certain cases when you modify the work.) You may place
373+additional permissions on material, added by you to a covered work,
374+for which you have or can give appropriate copyright permission.
375+
376+ Notwithstanding any other provision of this License, for material you
377+add to a covered work, you may (if authorized by the copyright holders of
378+that material) supplement the terms of this License with terms:
379+
380+ a) Disclaiming warranty or limiting liability differently from the
381+ terms of sections 15 and 16 of this License; or
382+
383+ b) Requiring preservation of specified reasonable legal notices or
384+ author attributions in that material or in the Appropriate Legal
385+ Notices displayed by works containing it; or
386+
387+ c) Prohibiting misrepresentation of the origin of that material, or
388+ requiring that modified versions of such material be marked in
389+ reasonable ways as different from the original version; or
390+
391+ d) Limiting the use for publicity purposes of names of licensors or
392+ authors of the material; or
393+
394+ e) Declining to grant rights under trademark law for use of some
395+ trade names, trademarks, or service marks; or
396+
397+ f) Requiring indemnification of licensors and authors of that
398+ material by anyone who conveys the material (or modified versions of
399+ it) with contractual assumptions of liability to the recipient, for
400+ any liability that these contractual assumptions directly impose on
401+ those licensors and authors.
402+
403+ All other non-permissive additional terms are considered "further
404+restrictions" within the meaning of section 10. If the Program as you
405+received it, or any part of it, contains a notice stating that it is
406+governed by this License along with a term that is a further
407+restriction, you may remove that term. If a license document contains
408+a further restriction but permits relicensing or conveying under this
409+License, you may add to a covered work material governed by the terms
410+of that license document, provided that the further restriction does
411+not survive such relicensing or conveying.
412+
413+ If you add terms to a covered work in accord with this section, you
414+must place, in the relevant source files, a statement of the
415+additional terms that apply to those files, or a notice indicating
416+where to find the applicable terms.
417+
418+ Additional terms, permissive or non-permissive, may be stated in the
419+form of a separately written license, or stated as exceptions;
420+the above requirements apply either way.
421+
422+ 8. Termination.
423+
424+ You may not propagate or modify a covered work except as expressly
425+provided under this License. Any attempt otherwise to propagate or
426+modify it is void, and will automatically terminate your rights under
427+this License (including any patent licenses granted under the third
428+paragraph of section 11).
429+
430+ However, if you cease all violation of this License, then your
431+license from a particular copyright holder is reinstated (a)
432+provisionally, unless and until the copyright holder explicitly and
433+finally terminates your license, and (b) permanently, if the copyright
434+holder fails to notify you of the violation by some reasonable means
435+prior to 60 days after the cessation.
436+
437+ Moreover, your license from a particular copyright holder is
438+reinstated permanently if the copyright holder notifies you of the
439+violation by some reasonable means, this is the first time you have
440+received notice of violation of this License (for any work) from that
441+copyright holder, and you cure the violation prior to 30 days after
442+your receipt of the notice.
443+
444+ Termination of your rights under this section does not terminate the
445+licenses of parties who have received copies or rights from you under
446+this License. If your rights have been terminated and not permanently
447+reinstated, you do not qualify to receive new licenses for the same
448+material under section 10.
449+
450+ 9. Acceptance Not Required for Having Copies.
451+
452+ You are not required to accept this License in order to receive or
453+run a copy of the Program. Ancillary propagation of a covered work
454+occurring solely as a consequence of using peer-to-peer transmission
455+to receive a copy likewise does not require acceptance. However,
456+nothing other than this License grants you permission to propagate or
457+modify any covered work. These actions infringe copyright if you do
458+not accept this License. Therefore, by modifying or propagating a
459+covered work, you indicate your acceptance of this License to do so.
460+
461+ 10. Automatic Licensing of Downstream Recipients.
462+
463+ Each time you convey a covered work, the recipient automatically
464+receives a license from the original licensors, to run, modify and
465+propagate that work, subject to this License. You are not responsible
466+for enforcing compliance by third parties with this License.
467+
468+ An "entity transaction" is a transaction transferring control of an
469+organization, or substantially all assets of one, or subdividing an
470+organization, or merging organizations. If propagation of a covered
471+work results from an entity transaction, each party to that
472+transaction who receives a copy of the work also receives whatever
473+licenses to the work the party's predecessor in interest had or could
474+give under the previous paragraph, plus a right to possession of the
475+Corresponding Source of the work from the predecessor in interest, if
476+the predecessor has it or can get it with reasonable efforts.
477+
478+ You may not impose any further restrictions on the exercise of the
479+rights granted or affirmed under this License. For example, you may
480+not impose a license fee, royalty, or other charge for exercise of
481+rights granted under this License, and you may not initiate litigation
482+(including a cross-claim or counterclaim in a lawsuit) alleging that
483+any patent claim is infringed by making, using, selling, offering for
484+sale, or importing the Program or any portion of it.
485+
486+ 11. Patents.
487+
488+ A "contributor" is a copyright holder who authorizes use under this
489+License of the Program or a work on which the Program is based. The
490+work thus licensed is called the contributor's "contributor version".
491+
492+ A contributor's "essential patent claims" are all patent claims
493+owned or controlled by the contributor, whether already acquired or
494+hereafter acquired, that would be infringed by some manner, permitted
495+by this License, of making, using, or selling its contributor version,
496+but do not include claims that would be infringed only as a
497+consequence of further modification of the contributor version. For
498+purposes of this definition, "control" includes the right to grant
499+patent sublicenses in a manner consistent with the requirements of
500+this License.
501+
502+ Each contributor grants you a non-exclusive, worldwide, royalty-free
503+patent license under the contributor's essential patent claims, to
504+make, use, sell, offer for sale, import and otherwise run, modify and
505+propagate the contents of its contributor version.
506+
507+ In the following three paragraphs, a "patent license" is any express
508+agreement or commitment, however denominated, not to enforce a patent
509+(such as an express permission to practice a patent or covenant not to
510+sue for patent infringement). To "grant" such a patent license to a
511+party means to make such an agreement or commitment not to enforce a
512+patent against the party.
513+
514+ If you convey a covered work, knowingly relying on a patent license,
515+and the Corresponding Source of the work is not available for anyone
516+to copy, free of charge and under the terms of this License, through a
517+publicly available network server or other readily accessible means,
518+then you must either (1) cause the Corresponding Source to be so
519+available, or (2) arrange to deprive yourself of the benefit of the
520+patent license for this particular work, or (3) arrange, in a manner
521+consistent with the requirements of this License, to extend the patent
522+license to downstream recipients. "Knowingly relying" means you have
523+actual knowledge that, but for the patent license, your conveying the
524+covered work in a country, or your recipient's use of the covered work
525+in a country, would infringe one or more identifiable patents in that
526+country that you have reason to believe are valid.
527+
528+ If, pursuant to or in connection with a single transaction or
529+arrangement, you convey, or propagate by procuring conveyance of, a
530+covered work, and grant a patent license to some of the parties
531+receiving the covered work authorizing them to use, propagate, modify
532+or convey a specific copy of the covered work, then the patent license
533+you grant is automatically extended to all recipients of the covered
534+work and works based on it.
535+
536+ A patent license is "discriminatory" if it does not include within
537+the scope of its coverage, prohibits the exercise of, or is
538+conditioned on the non-exercise of one or more of the rights that are
539+specifically granted under this License. You may not convey a covered
540+work if you are a party to an arrangement with a third party that is
541+in the business of distributing software, under which you make payment
542+to the third party based on the extent of your activity of conveying
543+the work, and under which the third party grants, to any of the
544+parties who would receive the covered work from you, a discriminatory
545+patent license (a) in connection with copies of the covered work
546+conveyed by you (or copies made from those copies), or (b) primarily
547+for and in connection with specific products or compilations that
548+contain the covered work, unless you entered into that arrangement,
549+or that patent license was granted, prior to 28 March 2007.
550+
551+ Nothing in this License shall be construed as excluding or limiting
552+any implied license or other defenses to infringement that may
553+otherwise be available to you under applicable patent law.
554+
555+ 12. No Surrender of Others' Freedom.
556+
557+ If conditions are imposed on you (whether by court order, agreement or
558+otherwise) that contradict the conditions of this License, they do not
559+excuse you from the conditions of this License. If you cannot convey a
560+covered work so as to satisfy simultaneously your obligations under this
561+License and any other pertinent obligations, then as a consequence you may
562+not convey it at all. For example, if you agree to terms that obligate you
563+to collect a royalty for further conveying from those to whom you convey
564+the Program, the only way you could satisfy both those terms and this
565+License would be to refrain entirely from conveying the Program.
566+
567+ 13. Remote Network Interaction; Use with the GNU General Public License.
568+
569+ Notwithstanding any other provision of this License, if you modify the
570+Program, your modified version must prominently offer all users
571+interacting with it remotely through a computer network (if your version
572+supports such interaction) an opportunity to receive the Corresponding
573+Source of your version by providing access to the Corresponding Source
574+from a network server at no charge, through some standard or customary
575+means of facilitating copying of software. This Corresponding Source
576+shall include the Corresponding Source for any work covered by version 3
577+of the GNU General Public License that is incorporated pursuant to the
578+following paragraph.
579+
580+ Notwithstanding any other provision of this License, you have
581+permission to link or combine any covered work with a work licensed
582+under version 3 of the GNU General Public License into a single
583+combined work, and to convey the resulting work. The terms of this
584+License will continue to apply to the part which is the covered work,
585+but the work with which it is combined will remain governed by version
586+3 of the GNU General Public License.
587+
588+ 14. Revised Versions of this License.
589+
590+ The Free Software Foundation may publish revised and/or new versions of
591+the GNU Affero General Public License from time to time. Such new versions
592+will be similar in spirit to the present version, but may differ in detail to
593+address new problems or concerns.
594+
595+ Each version is given a distinguishing version number. If the
596+Program specifies that a certain numbered version of the GNU Affero General
597+Public License "or any later version" applies to it, you have the
598+option of following the terms and conditions either of that numbered
599+version or of any later version published by the Free Software
600+Foundation. If the Program does not specify a version number of the
601+GNU Affero General Public License, you may choose any version ever published
602+by the Free Software Foundation.
603+
604+ If the Program specifies that a proxy can decide which future
605+versions of the GNU Affero General Public License can be used, that proxy's
606+public statement of acceptance of a version permanently authorizes you
607+to choose that version for the Program.
608+
609+ Later license versions may give you additional or different
610+permissions. However, no additional obligations are imposed on any
611+author or copyright holder as a result of your choosing to follow a
612+later version.
613+
614+ 15. Disclaimer of Warranty.
615+
616+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
617+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
618+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
619+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
620+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
621+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
622+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
623+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
624+
625+ 16. Limitation of Liability.
626+
627+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
628+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
629+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
630+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
631+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
632+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
633+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
634+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
635+SUCH DAMAGES.
636+
637+ 17. Interpretation of Sections 15 and 16.
638+
639+ If the disclaimer of warranty and limitation of liability provided
640+above cannot be given local legal effect according to their terms,
641+reviewing courts shall apply local law that most closely approximates
642+an absolute waiver of all civil liability in connection with the
643+Program, unless a warranty or assumption of liability accompanies a
644+copy of the Program in return for a fee.
645+
646+ END OF TERMS AND CONDITIONS
647+
648+ How to Apply These Terms to Your New Programs
649+
650+ If you develop a new program, and you want it to be of the greatest
651+possible use to the public, the best way to achieve this is to make it
652+free software which everyone can redistribute and change under these terms.
653+
654+ To do so, attach the following notices to the program. It is safest
655+to attach them to the start of each source file to most effectively
656+state the exclusion of warranty; and each file should have at least
657+the "copyright" line and a pointer to where the full notice is found.
658+
659+ <one line to give the program's name and a brief idea of what it does.>
660+ Copyright (C) <year> <name of author>
661+
662+ This program is free software: you can redistribute it and/or modify
663+ it under the terms of the GNU Affero General Public License as published by
664+ the Free Software Foundation, either version 3 of the License, or
665+ (at your option) any later version.
666+
667+ This program is distributed in the hope that it will be useful,
668+ but WITHOUT ANY WARRANTY; without even the implied warranty of
669+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
670+ GNU Affero General Public License for more details.
671+
672+ You should have received a copy of the GNU Affero General Public License
673+ along with this program. If not, see <http://www.gnu.org/licenses/>.
674+
675+Also add information on how to contact you by electronic and paper mail.
676+
677+ If your software can interact with users remotely through a computer
678+network, you should also make sure that it provides a way for users to
679+get its source. For example, if your program is a web application, its
680+interface could display a "Source" link that leads users to an archive
681+of the code. There are many ways you could offer source, and different
682+solutions will be better for different programs; see section 13 for the
683+specific requirements.
684+
685+ You should also get your employer (if you work as a programmer) or school,
686+if any, to sign a "copyright disclaimer" for the program, if necessary.
687+For more information on this, and how to apply and follow the GNU AGPL, see
688+<http://www.gnu.org/licenses/>.
689
690=== added file 'Makefile'
691--- Makefile 1970-01-01 00:00:00 +0000
692+++ Makefile 2015-10-08 21:20:21 +0000
693@@ -0,0 +1,23 @@
694+# Copyright 2005-2015 Canonical Ltd. This software is licensed under the
695+# GNU Affero General Public License version 3 (see the file LICENSE).
696+
697+PYTHON=python3
698+PSERVE=pserve
699+FLAKE8=flake8
700+
701+check:
702+ $(PYTHON) -m unittest discover rutabaga
703+
704+clean:
705+ @find db -name '*.db' -exec rm '{}' \;
706+
707+lint:
708+ @$(FLAKE8) rutabaga
709+
710+schema:
711+ sqlite3 db/rutabaga.db < db/rutabaga.schema
712+
713+start-api:
714+ $(PSERVE) rutabaga.ini --reload
715+
716+.PHONY: clean check lint start-api
717
718=== added file 'README.rst'
719--- README.rst 1970-01-01 00:00:00 +0000
720+++ README.rst 2015-10-08 21:20:21 +0000
721@@ -0,0 +1,22 @@
722+Rutabaga
723+========
724+
725+Rutabaga is a microservice for creating and revoking time limited authentication tokens in Squid.
726+
727+Development
728+-----------
729+
730+Install::
731+
732+ pip install -r requirements.txt
733+ pip install -r dev-requirements.txt
734+ python ./setup.py develop
735+
736+Run::
737+
738+ make start-api
739+
740+Deployment
741+----------
742+
743+TODO
744
745=== added file 'config.yaml'
746--- config.yaml 1970-01-01 00:00:00 +0000
747+++ config.yaml 2015-10-08 21:20:21 +0000
748@@ -0,0 +1,2 @@
749+database: 'db/rutabaga.db'
750+token_ttl_minutes: 5
751
752=== added directory 'db'
753=== added file 'db/rutabaga.schema'
754--- db/rutabaga.schema 1970-01-01 00:00:00 +0000
755+++ db/rutabaga.schema 2015-10-08 21:20:21 +0000
756@@ -0,0 +1,10 @@
757+BEGIN TRANSACTION;
758+CREATE TABLE Token
759+(
760+id INTEGER PRIMARY KEY AUTOINCREMENT,
761+secret VARCHAR(255),
762+username VARCHAR(255),
763+timestamp TEXT,
764+valid INTEGER DEFAULT 1
765+);
766+COMMIT;
767\ No newline at end of file
768
769=== added file 'dev-requirements.txt'
770--- dev-requirements.txt 1970-01-01 00:00:00 +0000
771+++ dev-requirements.txt 2015-10-08 21:20:21 +0000
772@@ -0,0 +1,5 @@
773+fixtures==1.3.1
774+flake8==2.4.1
775+Sphinx==1.3.1
776+testtools==1.8.0
777+webtest==2.0.18
778
779=== added directory 'docs'
780=== added file 'docs/Makefile'
781--- docs/Makefile 1970-01-01 00:00:00 +0000
782+++ docs/Makefile 2015-10-08 21:20:21 +0000
783@@ -0,0 +1,177 @@
784+# Makefile for Sphinx documentation
785+#
786+
787+# You can set these variables from the command line.
788+SPHINXOPTS =
789+SPHINXBUILD = sphinx-build
790+PAPER =
791+BUILDDIR = _build
792+
793+# User-friendly check for sphinx-build
794+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
795+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
796+endif
797+
798+# Internal variables.
799+PAPEROPT_a4 = -D latex_paper_size=a4
800+PAPEROPT_letter = -D latex_paper_size=letter
801+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
802+# the i18n builder cannot share the environment and doctrees with the others
803+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
804+
805+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
806+
807+help:
808+ @echo "Please use \`make <target>' where <target> is one of"
809+ @echo " html to make standalone HTML files"
810+ @echo " dirhtml to make HTML files named index.html in directories"
811+ @echo " singlehtml to make a single large HTML file"
812+ @echo " pickle to make pickle files"
813+ @echo " json to make JSON files"
814+ @echo " htmlhelp to make HTML files and a HTML help project"
815+ @echo " qthelp to make HTML files and a qthelp project"
816+ @echo " devhelp to make HTML files and a Devhelp project"
817+ @echo " epub to make an epub"
818+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
819+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
820+ @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
821+ @echo " text to make text files"
822+ @echo " man to make manual pages"
823+ @echo " texinfo to make Texinfo files"
824+ @echo " info to make Texinfo files and run them through makeinfo"
825+ @echo " gettext to make PO message catalogs"
826+ @echo " changes to make an overview of all changed/added/deprecated items"
827+ @echo " xml to make Docutils-native XML files"
828+ @echo " pseudoxml to make pseudoxml-XML files for display purposes"
829+ @echo " linkcheck to check all external links for integrity"
830+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
831+
832+clean:
833+ rm -rf $(BUILDDIR)/*
834+
835+html:
836+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
837+ @echo
838+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
839+
840+dirhtml:
841+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
842+ @echo
843+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
844+
845+singlehtml:
846+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
847+ @echo
848+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
849+
850+pickle:
851+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
852+ @echo
853+ @echo "Build finished; now you can process the pickle files."
854+
855+json:
856+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
857+ @echo
858+ @echo "Build finished; now you can process the JSON files."
859+
860+htmlhelp:
861+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
862+ @echo
863+ @echo "Build finished; now you can run HTML Help Workshop with the" \
864+ ".hhp project file in $(BUILDDIR)/htmlhelp."
865+
866+qthelp:
867+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
868+ @echo
869+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
870+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
871+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Rutabaga.qhcp"
872+ @echo "To view the help file:"
873+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Rutabaga.qhc"
874+
875+devhelp:
876+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
877+ @echo
878+ @echo "Build finished."
879+ @echo "To view the help file:"
880+ @echo "# mkdir -p $$HOME/.local/share/devhelp/Rutabaga"
881+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Rutabaga"
882+ @echo "# devhelp"
883+
884+epub:
885+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
886+ @echo
887+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
888+
889+latex:
890+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
891+ @echo
892+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
893+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
894+ "(use \`make latexpdf' here to do that automatically)."
895+
896+latexpdf:
897+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
898+ @echo "Running LaTeX files through pdflatex..."
899+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
900+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
901+
902+latexpdfja:
903+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
904+ @echo "Running LaTeX files through platex and dvipdfmx..."
905+ $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
906+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
907+
908+text:
909+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
910+ @echo
911+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
912+
913+man:
914+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
915+ @echo
916+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
917+
918+texinfo:
919+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
920+ @echo
921+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
922+ @echo "Run \`make' in that directory to run these through makeinfo" \
923+ "(use \`make info' here to do that automatically)."
924+
925+info:
926+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
927+ @echo "Running Texinfo files through makeinfo..."
928+ make -C $(BUILDDIR)/texinfo info
929+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
930+
931+gettext:
932+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
933+ @echo
934+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
935+
936+changes:
937+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
938+ @echo
939+ @echo "The overview file is in $(BUILDDIR)/changes."
940+
941+linkcheck:
942+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
943+ @echo
944+ @echo "Link check complete; look for any errors in the above output " \
945+ "or in $(BUILDDIR)/linkcheck/output.txt."
946+
947+doctest:
948+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
949+ @echo "Testing of doctests in the sources finished, look at the " \
950+ "results in $(BUILDDIR)/doctest/output.txt."
951+
952+xml:
953+ $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
954+ @echo
955+ @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
956+
957+pseudoxml:
958+ $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
959+ @echo
960+ @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
961
962=== added directory 'docs/_static'
963=== added directory 'docs/_templates'
964=== added file 'docs/conf.py'
965--- docs/conf.py 1970-01-01 00:00:00 +0000
966+++ docs/conf.py 2015-10-08 21:20:21 +0000
967@@ -0,0 +1,260 @@
968+# -*- coding: utf-8 -*-
969+#
970+# Rutabaga documentation build configuration file, created by
971+# sphinx-quickstart on Thu Jul 16 11:11:38 2015.
972+#
973+# This file is execfile()d with the current directory set to its
974+# containing dir.
975+#
976+# Note that not all possible configuration values are present in this
977+# autogenerated file.
978+#
979+# All configuration values have a default; values that are commented out
980+# serve to show the default.
981+
982+import sys
983+import os
984+
985+import cornice
986+
987+# If extensions (or modules to document with autodoc) are in another directory,
988+# add these directories to sys.path here. If the directory is relative to the
989+# documentation root, use os.path.abspath to make it absolute, like shown here.
990+sys.path.insert(0, os.path.abspath('../'))
991+
992+# -- General configuration ------------------------------------------------
993+
994+# If your documentation needs a minimal Sphinx version, state it here.
995+#needs_sphinx = '1.0'
996+
997+# Add any Sphinx extension module names here, as strings. They can be
998+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
999+# ones.
1000+extensions = ['sphinx.ext.autodoc', 'cornice.ext.sphinxext']
1001+
1002+# Add any paths that contain templates here, relative to this directory.
1003+templates_path = ['_templates']
1004+
1005+# The suffix of source filenames.
1006+source_suffix = '.rst'
1007+
1008+# The encoding of source files.
1009+#source_encoding = 'utf-8-sig'
1010+
1011+# The master toctree document.
1012+master_doc = 'index'
1013+
1014+# General information about the project.
1015+project = u'Rutabaga'
1016+copyright = u'2015, Kit Randel, Canonical Launchpad Engineering'
1017+
1018+# The version info for the project you're documenting, acts as replacement for
1019+# |version| and |release|, also used in various other places throughout the
1020+# built documents.
1021+#
1022+# The short X.Y version.
1023+version = '0.1'
1024+# The full version, including alpha/beta/rc tags.
1025+release = '0.1'
1026+
1027+# The language for content autogenerated by Sphinx. Refer to documentation
1028+# for a list of supported languages.
1029+#language = None
1030+
1031+# There are two options for replacing |today|: either, you set today to some
1032+# non-false value, then it is used:
1033+#today = ''
1034+# Else, today_fmt is used as the format for a strftime call.
1035+#today_fmt = '%B %d, %Y'
1036+
1037+# List of patterns, relative to source directory, that match files and
1038+# directories to ignore when looking for source files.
1039+exclude_patterns = ['_build']
1040+
1041+# The reST default role (used for this markup: `text`) to use for all
1042+# documents.
1043+#default_role = None
1044+
1045+# If true, '()' will be appended to :func: etc. cross-reference text.
1046+#add_function_parentheses = True
1047+
1048+# If true, the current module name will be prepended to all description
1049+# unit titles (such as .. function::).
1050+#add_module_names = True
1051+
1052+# If true, sectionauthor and moduleauthor directives will be shown in the
1053+# output. They are ignored by default.
1054+#show_authors = False
1055+
1056+# The name of the Pygments (syntax highlighting) style to use.
1057+pygments_style = 'sphinx'
1058+
1059+# A list of ignored prefixes for module index sorting.
1060+#modindex_common_prefix = []
1061+
1062+# If true, keep warnings as "system message" paragraphs in the built documents.
1063+#keep_warnings = False
1064+
1065+
1066+# -- Options for HTML output ----------------------------------------------
1067+
1068+# The theme to use for HTML and HTML Help pages. See the documentation for
1069+# a list of builtin themes.
1070+html_theme = 'default'
1071+
1072+# Theme options are theme-specific and customize the look and feel of a theme
1073+# further. For a list of options available for each theme, see the
1074+# documentation.
1075+#html_theme_options = {}
1076+
1077+# Add any paths that contain custom themes here, relative to this directory.
1078+#html_theme_path = []
1079+
1080+# The name for this set of Sphinx documents. If None, it defaults to
1081+# "<project> v<release> documentation".
1082+#html_title = None
1083+
1084+# A shorter title for the navigation bar. Default is the same as html_title.
1085+#html_short_title = None
1086+
1087+# The name of an image file (relative to this directory) to place at the top
1088+# of the sidebar.
1089+#html_logo = None
1090+
1091+# The name of an image file (within the static path) to use as favicon of the
1092+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
1093+# pixels large.
1094+#html_favicon = None
1095+
1096+# Add any paths that contain custom static files (such as style sheets) here,
1097+# relative to this directory. They are copied after the builtin static files,
1098+# so a file named "default.css" will overwrite the builtin "default.css".
1099+html_static_path = ['_static']
1100+
1101+# Add any extra paths that contain custom files (such as robots.txt or
1102+# .htaccess) here, relative to this directory. These files are copied
1103+# directly to the root of the documentation.
1104+#html_extra_path = []
1105+
1106+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
1107+# using the given strftime format.
1108+#html_last_updated_fmt = '%b %d, %Y'
1109+
1110+# If true, SmartyPants will be used to convert quotes and dashes to
1111+# typographically correct entities.
1112+#html_use_smartypants = True
1113+
1114+# Custom sidebar templates, maps document names to template names.
1115+#html_sidebars = {}
1116+
1117+# Additional templates that should be rendered to pages, maps page names to
1118+# template names.
1119+#html_additional_pages = {}
1120+
1121+# If false, no module index is generated.
1122+#html_domain_indices = True
1123+
1124+# If false, no index is generated.
1125+#html_use_index = True
1126+
1127+# If true, the index is split into individual pages for each letter.
1128+#html_split_index = False
1129+
1130+# If true, links to the reST sources are added to the pages.
1131+#html_show_sourcelink = True
1132+
1133+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
1134+#html_show_sphinx = True
1135+
1136+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
1137+#html_show_copyright = True
1138+
1139+# If true, an OpenSearch description file will be output, and all pages will
1140+# contain a <link> tag referring to it. The value of this option must be the
1141+# base URL from which the finished HTML is served.
1142+#html_use_opensearch = ''
1143+
1144+# This is the file name suffix for HTML files (e.g. ".xhtml").
1145+#html_file_suffix = None
1146+
1147+# Output file base name for HTML help builder.
1148+htmlhelp_basename = 'Rutabagadoc'
1149+
1150+
1151+# -- Options for LaTeX output ---------------------------------------------
1152+
1153+latex_elements = {
1154+# The paper size ('letterpaper' or 'a4paper').
1155+#'papersize': 'letterpaper',
1156+
1157+# The font size ('10pt', '11pt' or '12pt').
1158+#'pointsize': '10pt',
1159+
1160+# Additional stuff for the LaTeX preamble.
1161+#'preamble': '',
1162+}
1163+
1164+# Grouping the document tree into LaTeX files. List of tuples
1165+# (source start file, target name, title,
1166+# author, documentclass [howto, manual, or own class]).
1167+latex_documents = [
1168+ ('index', 'Rutabaga.tex', u'Rutabaga Documentation',
1169+ u'Kit Randel, Canonical Launchpad Engineering', 'manual'),
1170+]
1171+
1172+# The name of an image file (relative to this directory) to place at the top of
1173+# the title page.
1174+#latex_logo = None
1175+
1176+# For "manual" documents, if this is true, then toplevel headings are parts,
1177+# not chapters.
1178+#latex_use_parts = False
1179+
1180+# If true, show page references after internal links.
1181+#latex_show_pagerefs = False
1182+
1183+# If true, show URL addresses after external links.
1184+#latex_show_urls = False
1185+
1186+# Documents to append as an appendix to all manuals.
1187+#latex_appendices = []
1188+
1189+# If false, no module index is generated.
1190+#latex_domain_indices = True
1191+
1192+
1193+# -- Options for manual page output ---------------------------------------
1194+
1195+# One entry per manual page. List of tuples
1196+# (source start file, name, description, authors, manual section).
1197+man_pages = [
1198+ ('index', 'rutabaga', u'Rutabaga Documentation',
1199+ [u'Kit Randel, Canonical Launchpad Engineering'], 1)
1200+]
1201+
1202+# If true, show URL addresses after external links.
1203+#man_show_urls = False
1204+
1205+
1206+# -- Options for Texinfo output -------------------------------------------
1207+
1208+# Grouping the document tree into Texinfo files. List of tuples
1209+# (source start file, target name, title, author,
1210+# dir menu entry, description, category)
1211+texinfo_documents = [
1212+ ('index', 'Rutabaga', u'Rutabaga Documentation',
1213+ u'Kit Randel, Canonical Launchpad Engineering', 'Rutabaga', 'One line description of project.',
1214+ 'Miscellaneous'),
1215+]
1216+
1217+# Documents to append as an appendix to all manuals.
1218+#texinfo_appendices = []
1219+
1220+# If false, no module index is generated.
1221+#texinfo_domain_indices = True
1222+
1223+# How to display URL addresses: 'footnote', 'no', or 'inline'.
1224+#texinfo_show_urls = 'footnote'
1225+
1226+# If true, do not generate a @detailmenu in the "Top" node's menu.
1227+#texinfo_no_detailmenu = False
1228
1229=== added file 'docs/index.rst'
1230--- docs/index.rst 1970-01-01 00:00:00 +0000
1231+++ docs/index.rst 2015-10-08 21:20:21 +0000
1232@@ -0,0 +1,23 @@
1233+.. Rutabaga documentation master file, created by
1234+ sphinx-quickstart on Thu Jul 16 11:11:38 2015.
1235+ You can adapt this file completely to your liking, but it should at least
1236+ contain the root `toctree` directive.
1237+
1238+Welcome to Rutabaga's documentation!
1239+====================================
1240+
1241+Contents:
1242+
1243+.. toctree::
1244+ :maxdepth: 2
1245+
1246+.. services::
1247+ :modules: rutabaga.views
1248+
1249+Indices and tables
1250+==================
1251+
1252+* :ref:`genindex`
1253+* :ref:`modindex`
1254+* :ref:`search`
1255+
1256
1257=== added file 'docs/make.bat'
1258--- docs/make.bat 1970-01-01 00:00:00 +0000
1259+++ docs/make.bat 2015-10-08 21:20:21 +0000
1260@@ -0,0 +1,242 @@
1261+@ECHO OFF
1262+
1263+REM Command file for Sphinx documentation
1264+
1265+if "%SPHINXBUILD%" == "" (
1266+ set SPHINXBUILD=sphinx-build
1267+)
1268+set BUILDDIR=_build
1269+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
1270+set I18NSPHINXOPTS=%SPHINXOPTS% .
1271+if NOT "%PAPER%" == "" (
1272+ set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
1273+ set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
1274+)
1275+
1276+if "%1" == "" goto help
1277+
1278+if "%1" == "help" (
1279+ :help
1280+ echo.Please use `make ^<target^>` where ^<target^> is one of
1281+ echo. html to make standalone HTML files
1282+ echo. dirhtml to make HTML files named index.html in directories
1283+ echo. singlehtml to make a single large HTML file
1284+ echo. pickle to make pickle files
1285+ echo. json to make JSON files
1286+ echo. htmlhelp to make HTML files and a HTML help project
1287+ echo. qthelp to make HTML files and a qthelp project
1288+ echo. devhelp to make HTML files and a Devhelp project
1289+ echo. epub to make an epub
1290+ echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
1291+ echo. text to make text files
1292+ echo. man to make manual pages
1293+ echo. texinfo to make Texinfo files
1294+ echo. gettext to make PO message catalogs
1295+ echo. changes to make an overview over all changed/added/deprecated items
1296+ echo. xml to make Docutils-native XML files
1297+ echo. pseudoxml to make pseudoxml-XML files for display purposes
1298+ echo. linkcheck to check all external links for integrity
1299+ echo. doctest to run all doctests embedded in the documentation if enabled
1300+ goto end
1301+)
1302+
1303+if "%1" == "clean" (
1304+ for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
1305+ del /q /s %BUILDDIR%\*
1306+ goto end
1307+)
1308+
1309+
1310+%SPHINXBUILD% 2> nul
1311+if errorlevel 9009 (
1312+ echo.
1313+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
1314+ echo.installed, then set the SPHINXBUILD environment variable to point
1315+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
1316+ echo.may add the Sphinx directory to PATH.
1317+ echo.
1318+ echo.If you don't have Sphinx installed, grab it from
1319+ echo.http://sphinx-doc.org/
1320+ exit /b 1
1321+)
1322+
1323+if "%1" == "html" (
1324+ %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
1325+ if errorlevel 1 exit /b 1
1326+ echo.
1327+ echo.Build finished. The HTML pages are in %BUILDDIR%/html.
1328+ goto end
1329+)
1330+
1331+if "%1" == "dirhtml" (
1332+ %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
1333+ if errorlevel 1 exit /b 1
1334+ echo.
1335+ echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
1336+ goto end
1337+)
1338+
1339+if "%1" == "singlehtml" (
1340+ %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
1341+ if errorlevel 1 exit /b 1
1342+ echo.
1343+ echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
1344+ goto end
1345+)
1346+
1347+if "%1" == "pickle" (
1348+ %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
1349+ if errorlevel 1 exit /b 1
1350+ echo.
1351+ echo.Build finished; now you can process the pickle files.
1352+ goto end
1353+)
1354+
1355+if "%1" == "json" (
1356+ %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
1357+ if errorlevel 1 exit /b 1
1358+ echo.
1359+ echo.Build finished; now you can process the JSON files.
1360+ goto end
1361+)
1362+
1363+if "%1" == "htmlhelp" (
1364+ %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
1365+ if errorlevel 1 exit /b 1
1366+ echo.
1367+ echo.Build finished; now you can run HTML Help Workshop with the ^
1368+.hhp project file in %BUILDDIR%/htmlhelp.
1369+ goto end
1370+)
1371+
1372+if "%1" == "qthelp" (
1373+ %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
1374+ if errorlevel 1 exit /b 1
1375+ echo.
1376+ echo.Build finished; now you can run "qcollectiongenerator" with the ^
1377+.qhcp project file in %BUILDDIR%/qthelp, like this:
1378+ echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Rutabaga.qhcp
1379+ echo.To view the help file:
1380+ echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Rutabaga.ghc
1381+ goto end
1382+)
1383+
1384+if "%1" == "devhelp" (
1385+ %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
1386+ if errorlevel 1 exit /b 1
1387+ echo.
1388+ echo.Build finished.
1389+ goto end
1390+)
1391+
1392+if "%1" == "epub" (
1393+ %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
1394+ if errorlevel 1 exit /b 1
1395+ echo.
1396+ echo.Build finished. The epub file is in %BUILDDIR%/epub.
1397+ goto end
1398+)
1399+
1400+if "%1" == "latex" (
1401+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
1402+ if errorlevel 1 exit /b 1
1403+ echo.
1404+ echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
1405+ goto end
1406+)
1407+
1408+if "%1" == "latexpdf" (
1409+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
1410+ cd %BUILDDIR%/latex
1411+ make all-pdf
1412+ cd %BUILDDIR%/..
1413+ echo.
1414+ echo.Build finished; the PDF files are in %BUILDDIR%/latex.
1415+ goto end
1416+)
1417+
1418+if "%1" == "latexpdfja" (
1419+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
1420+ cd %BUILDDIR%/latex
1421+ make all-pdf-ja
1422+ cd %BUILDDIR%/..
1423+ echo.
1424+ echo.Build finished; the PDF files are in %BUILDDIR%/latex.
1425+ goto end
1426+)
1427+
1428+if "%1" == "text" (
1429+ %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
1430+ if errorlevel 1 exit /b 1
1431+ echo.
1432+ echo.Build finished. The text files are in %BUILDDIR%/text.
1433+ goto end
1434+)
1435+
1436+if "%1" == "man" (
1437+ %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
1438+ if errorlevel 1 exit /b 1
1439+ echo.
1440+ echo.Build finished. The manual pages are in %BUILDDIR%/man.
1441+ goto end
1442+)
1443+
1444+if "%1" == "texinfo" (
1445+ %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
1446+ if errorlevel 1 exit /b 1
1447+ echo.
1448+ echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
1449+ goto end
1450+)
1451+
1452+if "%1" == "gettext" (
1453+ %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
1454+ if errorlevel 1 exit /b 1
1455+ echo.
1456+ echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
1457+ goto end
1458+)
1459+
1460+if "%1" == "changes" (
1461+ %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
1462+ if errorlevel 1 exit /b 1
1463+ echo.
1464+ echo.The overview file is in %BUILDDIR%/changes.
1465+ goto end
1466+)
1467+
1468+if "%1" == "linkcheck" (
1469+ %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
1470+ if errorlevel 1 exit /b 1
1471+ echo.
1472+ echo.Link check complete; look for any errors in the above output ^
1473+or in %BUILDDIR%/linkcheck/output.txt.
1474+ goto end
1475+)
1476+
1477+if "%1" == "doctest" (
1478+ %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
1479+ if errorlevel 1 exit /b 1
1480+ echo.
1481+ echo.Testing of doctests in the sources finished, look at the ^
1482+results in %BUILDDIR%/doctest/output.txt.
1483+ goto end
1484+)
1485+
1486+if "%1" == "xml" (
1487+ %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
1488+ if errorlevel 1 exit /b 1
1489+ echo.
1490+ echo.Build finished. The XML files are in %BUILDDIR%/xml.
1491+ goto end
1492+)
1493+
1494+if "%1" == "pseudoxml" (
1495+ %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
1496+ if errorlevel 1 exit /b 1
1497+ echo.
1498+ echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
1499+ goto end
1500+)
1501+
1502+:end
1503
1504=== added file 'requirements.txt'
1505--- requirements.txt 1970-01-01 00:00:00 +0000
1506+++ requirements.txt 2015-10-08 21:20:21 +0000
1507@@ -0,0 +1,4 @@
1508+cornice==1.0.0
1509+PyYAML==3.11
1510+requests==2.7.0
1511+waitress==0.8.9
1512
1513=== added directory 'rutabaga'
1514=== added file 'rutabaga.ini'
1515--- rutabaga.ini 1970-01-01 00:00:00 +0000
1516+++ rutabaga.ini 2015-10-08 21:20:21 +0000
1517@@ -0,0 +1,45 @@
1518+[app:main]
1519+use = egg:rutabaga
1520+
1521+pyramid.reload_templates = true
1522+pyramid.debug_authorization = false
1523+pyramid.debug_notfound = false
1524+pyramid.debug_routematch = false
1525+pyramid.debug_templates = true
1526+pyramid.default_locale_name = en
1527+
1528+[server:main]
1529+use = egg:waitress#main
1530+host = 0.0.0.0
1531+port = 6543
1532+
1533+# Begin logging configuration
1534+
1535+[loggers]
1536+keys = root, rutabaga
1537+
1538+[handlers]
1539+keys = console
1540+
1541+[formatters]
1542+keys = generic
1543+
1544+[logger_root]
1545+level = INFO
1546+handlers = console
1547+
1548+[logger_rutabaga]
1549+level = DEBUG
1550+handlers =
1551+qualname = rutabaga
1552+
1553+[handler_console]
1554+class = StreamHandler
1555+args = (sys.stderr,)
1556+level = NOTSET
1557+formatter = generic
1558+
1559+[formatter_generic]
1560+format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
1561+
1562+# End logging configuration
1563
1564=== added file 'rutabaga/__init__.py'
1565--- rutabaga/__init__.py 1970-01-01 00:00:00 +0000
1566+++ rutabaga/__init__.py 2015-10-08 21:20:21 +0000
1567@@ -0,0 +1,13 @@
1568+# Copyright 2015 Canonical Ltd. This software is licensed under the
1569+# GNU Affero General Public License version 3 (see the file LICENSE).
1570+
1571+"""Main entry point
1572+"""
1573+from pyramid.config import Configurator
1574+
1575+
1576+def main(global_config, **settings):
1577+ config = Configurator(settings=settings)
1578+ config.include("cornice")
1579+ config.scan("rutabaga.views")
1580+ return config.make_wsgi_app()
1581
1582=== added file 'rutabaga/auth.py'
1583--- rutabaga/auth.py 1970-01-01 00:00:00 +0000
1584+++ rutabaga/auth.py 2015-10-08 21:20:21 +0000
1585@@ -0,0 +1,80 @@
1586+# Copyright 2015 Canonical Ltd. This software is licensed under the
1587+# GNU Affero General Public License version 3 (see the file LICENSE).
1588+
1589+from datetime import datetime
1590+
1591+import sqlite3
1592+import uuid
1593+
1594+from rutabaga.config import RutabagaConfig
1595+
1596+
1597+CONFIG = RutabagaConfig()
1598+
1599+
1600+def _db_conn():
1601+ return sqlite3.connect(CONFIG.get('database'))
1602+
1603+
1604+def _execute(query, bindings=[], conn=None, rows=None):
1605+ if not conn:
1606+ conn = _db_conn()
1607+ with conn:
1608+ conn.row_factory = sqlite3.Row
1609+ cursor = conn.cursor()
1610+ return (cursor.execute(query, bindings).fetchone(), cursor.rowcount)
1611+
1612+
1613+def create_token(username, conn=None):
1614+ """Create a unique token, or return existing valid token.
1615+
1616+ 'username secret' are effectively 'username password' for
1617+ proxy basic auth.
1618+ """
1619+ unique_id = uuid.uuid4().hex
1620+ timestamp = datetime.utcnow().isoformat()
1621+ return {'username': username,
1622+ 'secret': unique_id,
1623+ 'timestamp': timestamp}
1624+
1625+
1626+def store_token(token, conn=None):
1627+ """Persist token."""
1628+ if retrieve_token(token['username'], token['secret'], conn=conn):
1629+ return # token exists
1630+ query = ("INSERT INTO Token (secret, username, timestamp) "
1631+ "values (:secret, :username, :timestamp);")
1632+ _execute(query, token, conn=conn)
1633+
1634+
1635+def retrieve_token(username, secret, conn=None):
1636+ """Retrieve a valid token from database by secret."""
1637+ query = ("SELECT secret, username, timestamp, valid "
1638+ "FROM Token WHERE secret=:secret AND username=:username "
1639+ "AND valid=1 AND datetime(timestamp, '+{ttl} minute') "
1640+ ">= datetime('now');").format(ttl=CONFIG.get('token_ttl_minutes'))
1641+
1642+ (token, _) = _execute(query, [secret, username], conn=conn)
1643+ if token:
1644+ return dict(token)
1645+ else:
1646+ return None
1647+
1648+
1649+def invalidate_token(secret, conn=None):
1650+ """Invalidate a token."""
1651+ query = "UPDATE Token SET valid=0 WHERE secret=:secret;"
1652+ (_, count) = _execute(query, [secret], conn=conn)
1653+ return bool(count)
1654+
1655+
1656+def delete_invalid_tokens(conn=None):
1657+ """Delete invalid tokens.
1658+
1659+ Called from sweeper.
1660+ """
1661+ query = ("DELETE FROM Token WHERE valid=0 OR "
1662+ "datetime(timestamp, '+{ttl} minute') "
1663+ "<= datetime('now');").format(ttl=CONFIG.get('token_ttl_minutes'))
1664+ (_, count) = _execute(query, conn=conn)
1665+ return count
1666
1667=== added file 'rutabaga/config.py'
1668--- rutabaga/config.py 1970-01-01 00:00:00 +0000
1669+++ rutabaga/config.py 2015-10-08 21:20:21 +0000
1670@@ -0,0 +1,19 @@
1671+# Copyright 2015 Canonical Ltd. This software is licensed under the
1672+# GNU Affero General Public License version 3 (see the file LICENSE).
1673+
1674+from __future__ import unicode_literals
1675+
1676+import os
1677+
1678+import yaml
1679+
1680+
1681+class RutabagaConfig(object):
1682+ """Return configuration from environment or defaults."""
1683+
1684+ def get(self, key):
1685+ config = os.path.join(os.path.dirname(os.path.dirname(__file__)),
1686+ 'config.yaml')
1687+ with open(config) as config_file:
1688+ defaults = yaml.load(config_file)
1689+ return (os.getenv(key.upper()) or defaults.get(key.lower()) or '')
1690
1691=== added file 'rutabaga/helpers.py'
1692--- rutabaga/helpers.py 1970-01-01 00:00:00 +0000
1693+++ rutabaga/helpers.py 2015-10-08 21:20:21 +0000
1694@@ -0,0 +1,12 @@
1695+# Copyright 2015 Canonical Ltd. This software is licensed under the
1696+# GNU Affero General Public License version 3 (see the file LICENSE).
1697+
1698+import uuid
1699+
1700+
1701+def validate_uuid4(uuid_string):
1702+ try:
1703+ val = uuid.UUID(uuid_string, version=4)
1704+ except ValueError:
1705+ return False
1706+ return val.hex == uuid_string
1707
1708=== added directory 'rutabaga/scripts'
1709=== added file 'rutabaga/scripts/__init__.py'
1710=== added file 'rutabaga/scripts/purge.py'
1711--- rutabaga/scripts/purge.py 1970-01-01 00:00:00 +0000
1712+++ rutabaga/scripts/purge.py 2015-10-08 21:20:21 +0000
1713@@ -0,0 +1,17 @@
1714+#!/usr/bin/env python3
1715+
1716+import logging
1717+
1718+from rutabaga.auth import delete_invalid_tokens
1719+
1720+logging.basicConfig(
1721+ filemode='a',
1722+ filename='/srv/rutabaga/logs/rutabaga-purge.log',
1723+ format='%(asctime)s, %(name)s %(levelname)s %(message)s',
1724+ level=logging.INFO
1725+ )
1726+logger = logging.getLogger('BuilderProxyApiService')
1727+
1728+purged_tokens = delete_invalid_tokens()
1729+if purged_tokens:
1730+ logger.info('%d tokens purged.' % purged_tokens)
1731
1732=== added file 'rutabaga/scripts/rutabaga_auth_helper.py'
1733--- rutabaga/scripts/rutabaga_auth_helper.py 1970-01-01 00:00:00 +0000
1734+++ rutabaga/scripts/rutabaga_auth_helper.py 2015-10-08 21:20:21 +0000
1735@@ -0,0 +1,56 @@
1736+#!/srv/rutabaga/venv/bin/python3
1737+
1738+import sys
1739+
1740+from requests import (
1741+ codes,
1742+ Session,
1743+ )
1744+from requests.adapters import HTTPAdapter
1745+from requests.exceptions import (
1746+ ConnectionError,
1747+ Timeout,
1748+ )
1749+
1750+
1751+AUTH_WS_URL = 'http://localhost:8080/validate'
1752+
1753+# squid 'debug_options ALL,1 82,9 84,9' can be potentially
1754+# useful for debugging this helper.
1755+
1756+
1757+def read_stdin():
1758+ line = sys.stdin.readline()
1759+ validate(line)
1760+
1761+
1762+def validate(squid_stdin):
1763+ data = squid_stdin.split()
1764+ if len(data) != 2:
1765+ # squid can potentially return channel_id in position 0
1766+ # if concurrency is enabled.
1767+ raise IOError
1768+
1769+ username = data[0] # username
1770+ secret = data[1] # password
1771+
1772+ s = Session()
1773+ s.mount('http://', HTTPAdapter(max_retries=5))
1774+
1775+ try:
1776+ r = s.post(AUTH_WS_URL, json={'secret': secret,
1777+ 'username': username})
1778+ except (ConnectionError, Timeout):
1779+ # squid 3.4 implements a more informative helper failure code
1780+ # 'BH'. If we upgrade, consider using.
1781+ # http://wiki.squid-cache.org/Features/AddonHelpers#Basic_Scheme
1782+ sys.exit(1)
1783+
1784+ if r.status_code == codes.ok and r.json()['username'] == username:
1785+ sys.stdout.write('OK\n')
1786+ else:
1787+ sys.stderr.write('ERR\n')
1788+
1789+
1790+if __name__ == '__main__':
1791+ read_stdin()
1792
1793=== added directory 'rutabaga/tests'
1794=== added file 'rutabaga/tests/__init__.py'
1795=== added file 'rutabaga/tests/test_auth.py'
1796--- rutabaga/tests/test_auth.py 1970-01-01 00:00:00 +0000
1797+++ rutabaga/tests/test_auth.py 2015-10-08 21:20:21 +0000
1798@@ -0,0 +1,106 @@
1799+# Copyright 2015 Canonical Ltd. This software is licensed under the
1800+# GNU Affero General Public License version 3 (see the file LICENSE).
1801+
1802+from __future__ import print_function
1803+
1804+from datetime import timedelta
1805+import sqlite3
1806+import unittest
1807+import uuid
1808+
1809+import iso8601
1810+from testtools import TestCase
1811+
1812+from rutabaga import auth
1813+from rutabaga.config import RutabagaConfig
1814+from rutabaga.helpers import validate_uuid4
1815+
1816+
1817+class AuthTestCase(TestCase):
1818+
1819+ def _db_conn(self):
1820+ conn = sqlite3.connect(':memory:')
1821+ with open('db/rutabaga.schema', 'r') as schema:
1822+ conn.executescript(schema.read())
1823+ conn.commit()
1824+ return conn
1825+
1826+ def _username(self):
1827+ return 'BUILD-%s' % uuid.uuid4().hex
1828+
1829+ def _create_expired_token(self):
1830+ # Create a token with an expired timestamp.
1831+ token = self._create_valid_token()
1832+ minutes = 1 + self.config.get('token_ttl_minutes')
1833+ expired_delta = timedelta(minutes=-minutes)
1834+ timestamp = iso8601.parse_date(token['timestamp'])
1835+ token['timestamp'] = str(timestamp + expired_delta)
1836+ return token
1837+
1838+ def _create_valid_token(self):
1839+ return auth.create_token(
1840+ username=self._username(),
1841+ conn=self.db)
1842+
1843+ def _row_count(self, conn):
1844+ cursor = conn.cursor()
1845+ cursor.execute("SELECT Count(*) FROM Token")
1846+ return cursor.fetchone()[0]
1847+
1848+ def setUp(self):
1849+ super(AuthTestCase, self).setUp()
1850+ self.config = RutabagaConfig()
1851+ self.db = self._db_conn()
1852+ self.token = self._create_valid_token()
1853+ self.expired_token = self._create_expired_token()
1854+
1855+ def tearDown(self):
1856+ super(AuthTestCase, self).tearDown()
1857+ self.db.close()
1858+
1859+ def test_create_token(self):
1860+ self.assertTrue(validate_uuid4(self.token['secret']))
1861+ self.assertTrue(iso8601.parse_date(self.token['timestamp']))
1862+
1863+ def test_store_token(self):
1864+ with self.db as conn:
1865+ auth.store_token(self.token, conn=conn)
1866+ cursor = conn.cursor()
1867+ cursor.execute("SELECT secret FROM Token")
1868+ self.assertEqual(self.token['secret'], cursor.fetchone()[0])
1869+
1870+ def test_retrieve_token(self):
1871+ with self.db as conn:
1872+ auth.store_token(self.token, conn=conn)
1873+ token = auth.retrieve_token(
1874+ self.token['username'], self.token['secret'], conn=conn)
1875+ self.assertEqual(self.token['username'], token['username'])
1876+
1877+ def test_invalidate_invalid_token(self):
1878+ with self.db as conn:
1879+ self.assertFalse(auth.invalidate_token('junk', conn=conn))
1880+
1881+ def test_invalidate_token(self):
1882+ with self.db as conn:
1883+ auth.store_token(self.token, conn=conn)
1884+ self.assertTrue(
1885+ auth.invalidate_token(self.token['secret'], conn=conn))
1886+ self.assertIsNone(auth.retrieve_token(
1887+ self.token['username'], self.token['secret'], conn=conn))
1888+
1889+ def test_delete_expired_tokens(self):
1890+ with self.db as conn:
1891+ auth.store_token(self.token, conn=conn) # valid
1892+ for _ in range(3):
1893+ auth.store_token(self.expired_token, conn=conn)
1894+ auth.delete_invalid_tokens(conn=conn)
1895+ self.assertIsNot(None, auth.retrieve_token(
1896+ self.token['username'], self.token['secret'], conn=conn))
1897+ self.assertIsNone(auth.retrieve_token(
1898+ self.expired_token['username'],
1899+ self.expired_token['secret'],
1900+ conn=conn))
1901+
1902+
1903+if __name__ == '__main__':
1904+ unittest.main()
1905
1906=== added file 'rutabaga/tests/test_views.py'
1907--- rutabaga/tests/test_views.py 1970-01-01 00:00:00 +0000
1908+++ rutabaga/tests/test_views.py 2015-10-08 21:20:21 +0000
1909@@ -0,0 +1,104 @@
1910+# Copyright 2015 Canonical Ltd. This software is licensed under the
1911+# GNU Affero General Public License version 3 (see the file LICENSE).
1912+
1913+import os
1914+import sqlite3
1915+import unittest
1916+
1917+from fixtures import EnvironmentVariable
1918+from testtools import TestCase
1919+from webtest import TestApp
1920+
1921+import rutabaga
1922+
1923+
1924+class ApiBaseTestCase(TestCase):
1925+
1926+ tempdb = 'tempdb'
1927+
1928+ def _db_conn(self):
1929+ conn = sqlite3.connect(self.tempdb)
1930+ with open('db/rutabaga.schema', 'r') as schema:
1931+ conn.executescript(schema.read())
1932+ conn.commit()
1933+ return conn
1934+
1935+ def setUp(self):
1936+ super(ApiBaseTestCase, self).setUp()
1937+ self.useFixture(EnvironmentVariable("DATABASE", self.tempdb))
1938+ self.app = TestApp(rutabaga.main({}))
1939+ self.db = self._db_conn()
1940+
1941+ def tearDown(self):
1942+ super(ApiBaseTestCase, self).tearDown()
1943+ self.db.close()
1944+ os.remove(self.tempdb)
1945+
1946+
1947+class ApiTokenTestCase(ApiBaseTestCase):
1948+
1949+ def test_create_token(self):
1950+ username = 'BUILD-1234'
1951+ resp = self.app.post_json('/tokens', {'username': username})
1952+ self.assertIn(username, resp.json['username'])
1953+ self.assertEqual(200, resp.status_code)
1954+
1955+ def test_invalid_post(self):
1956+ resp = self.app.post_json(
1957+ '/tokens', {'junk': 'junk'}, expect_errors=True)
1958+ self.assertIn('username is missing',
1959+ resp.json['errors'][0]['description'])
1960+ self.assertEqual(400, resp.status_code)
1961+
1962+ def test_invalid_delete(self):
1963+ resp = self.app.delete('/tokens/', expect_errors=True)
1964+ self.assertEqual(404, resp.status_code)
1965+
1966+ def test_invalid_delete_token(self):
1967+ resp = self.app.delete('/tokens/junk', expect_errors=True)
1968+ self.assertIn('invalid secret',
1969+ resp.json['errors'][0]['description'])
1970+ self.assertEqual(400, resp.status_code)
1971+
1972+ def test_invalidate_token(self):
1973+ username = 'BUILD-9876'
1974+ token = self.app.post_json('/tokens', {'username': username})
1975+ resp = self.app.delete(
1976+ '/tokens/{id}'.format(id=token.json['secret']),
1977+ params={'secret': token.json['secret']})
1978+ self.assertEqual(200, resp.status_code)
1979+
1980+
1981+class ApiTokenValidationTestCase(ApiBaseTestCase):
1982+
1983+ def test_invalid_validate(self):
1984+ resp = self.app.post_json(
1985+ '/validate', {'junk': 'junk'}, expect_errors=True)
1986+ self.assertEqual(400, resp.status_code)
1987+
1988+ def test_validate_token(self):
1989+ token = self.app.post_json('/tokens', {'username': 'BUILD-1234'})
1990+ resp = self.app.post_json(
1991+ '/validate', {'username': token.json['username'],
1992+ 'secret': token.json['secret']})
1993+ self.assertEqual(200, resp.status_code)
1994+
1995+ def test_invalid_username(self):
1996+ token = self.app.post_json('/tokens', {'username': 'BUILD-1234'})
1997+ resp = self.app.post_json(
1998+ '/validate', {'username': 'BUILD-INVALID',
1999+ 'secret': token.json['secret']},
2000+ expect_errors=True)
2001+ self.assertEqual(404, resp.status_code)
2002+
2003+ def test_invalid_secret(self):
2004+ token = self.app.post_json('/tokens', {'username': 'BUILD-1234'})
2005+ resp = self.app.post_json(
2006+ '/validate', {'username': token.json['username'],
2007+ 'secret': 'junk'},
2008+ expect_errors=True)
2009+ self.assertEqual(404, resp.status_code)
2010+
2011+
2012+if __name__ == '__main__':
2013+ unittest.main()
2014
2015=== added file 'rutabaga/views.py'
2016--- rutabaga/views.py 1970-01-01 00:00:00 +0000
2017+++ rutabaga/views.py 2015-10-08 21:20:21 +0000
2018@@ -0,0 +1,83 @@
2019+# Copyright 2015 Canonical Ltd. This software is licensed under the
2020+# GNU Affero General Public License version 3 (see the file LICENSE).
2021+
2022+from cornice.resource import resource
2023+from cornice.util import extract_json_data
2024+import pyramid.httpexceptions as exc
2025+
2026+from rutabaga import auth
2027+from rutabaga.helpers import validate_uuid4
2028+
2029+
2030+def validate_token_req(request):
2031+ if request.method == 'POST':
2032+ if 'username' not in request.json_body:
2033+ request.errors.add('body', 'username', 'username is missing.')
2034+
2035+ if request.method == 'DELETE':
2036+ if 'secret' not in request.matchdict:
2037+ request.errors.add(
2038+ 'querystring', 'secret', 'secret is missing.')
2039+ if not validate_uuid4(request.matchdict['secret']):
2040+ request.errors.add(
2041+ 'resource', 'secret', 'invalid secret.')
2042+ return request
2043+
2044+
2045+@resource(collection_path='/tokens', path='/tokens/{secret}',
2046+ validators=validate_token_req)
2047+class TokenAPI(object):
2048+
2049+ def __init__(self, request):
2050+ super(TokenAPI, self).__init__()
2051+ self.request = request
2052+
2053+ def collection_post(self):
2054+ """Get a new time limited auth token.
2055+
2056+ :param username: build id provided to buildd from builder.
2057+ """
2058+ username = extract_json_data(self.request).get('username')
2059+ token = auth.create_token(username=username)
2060+ auth.store_token(token)
2061+ return token
2062+
2063+ def delete(self):
2064+ """Invalidate an existing token.
2065+ :param secret: auth token id.
2066+ """
2067+ secret = self.request.matchdict['secret']
2068+ token = auth.invalidate_token(secret)
2069+ if not token:
2070+ return exc.HTTPNotFound()
2071+ return exc.HTTPOk()
2072+
2073+
2074+def validate_validation_req(request):
2075+ if 'username' not in request.json_body:
2076+ request.errors.add('body', 'username', 'username is missing.')
2077+
2078+ if 'secret' not in request.json_body:
2079+ request.errors.add('body', 'secret', 'secret is missing.')
2080+ return request
2081+
2082+
2083+@resource(path='/validate', validators=validate_validation_req)
2084+class TokenValidationAPI(object):
2085+
2086+ def __init__(self, request):
2087+ super(TokenValidationAPI, self).__init__()
2088+ self.request = request
2089+
2090+ def post(self):
2091+ """Validate a token (username and secret pair).
2092+
2093+ :param username: username for existing token.
2094+ :param secret: secret of existing token.
2095+ """
2096+ username = extract_json_data(self.request).get('username')
2097+ secret = extract_json_data(self.request).get('secret')
2098+ token = auth.retrieve_token(username, secret)
2099+ if not token:
2100+ return exc.HTTPNotFound()
2101+ return {'username': token['username']}
2102
2103=== added file 'setup.py'
2104--- setup.py 1970-01-01 00:00:00 +0000
2105+++ setup.py 2015-10-08 21:20:21 +0000
2106@@ -0,0 +1,33 @@
2107+import os
2108+from setuptools import setup, find_packages
2109+
2110+here = os.path.abspath(os.path.dirname(__file__))
2111+
2112+with open(os.path.join(here, 'README.rst')) as f:
2113+ README = f.read()
2114+
2115+
2116+setup(name='rutabaga',
2117+ version=0.1,
2118+ description='rutabaga',
2119+ long_description=README,
2120+ classifiers=[
2121+ "Programming Language :: Python",
2122+ "Framework :: Pylons",
2123+ "Topic :: Internet :: WWW/HTTP",
2124+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application"
2125+ ],
2126+ data_files=[('.', ['config.yaml'])],
2127+ keywords="web services",
2128+ author='',
2129+ author_email='',
2130+ url='',
2131+ packages=find_packages(),
2132+ include_package_data=True,
2133+ zip_safe=False,
2134+ install_requires=['cornice', 'waitress'],
2135+ entry_points = """\
2136+ [paste.app_factory]
2137+ main = rutabaga:main
2138+ """,
2139+ paster_plugins=['pyramid'])
2140
2141=== added file 'test-requirements.txt'
2142--- test-requirements.txt 1970-01-01 00:00:00 +0000
2143+++ test-requirements.txt 2015-10-08 21:20:21 +0000
2144@@ -0,0 +1,5 @@
2145+fixtures==1.3.1
2146+ipdb==0.8.1
2147+requests==2.7.0
2148+testtools==1.8.0
2149+WebTest==2.0.18
2150
2151=== added directory 'tests'
2152=== added file 'tests/test_integration.py'
2153--- tests/test_integration.py 1970-01-01 00:00:00 +0000
2154+++ tests/test_integration.py 2015-10-08 21:20:21 +0000
2155@@ -0,0 +1,47 @@
2156+#!/usr/bin/env python3
2157+
2158+from random import randint
2159+import unittest
2160+
2161+import requests
2162+from testtools import TestCase
2163+
2164+
2165+SNAP_PROXY = 'snapproxy.launchpad.dev'
2166+SNAP_PROXY_PORT = 3128
2167+
2168+SNAP_PROXY_API = 'builderproxyapi.launchpad.dev'
2169+SNAP_PROXY_API_PORT = 8080
2170+
2171+TEST_URL = 'http://www.canonical.com'
2172+
2173+
2174+class TestProxyApiIntegration(TestCase):
2175+
2176+ def createToken(self, username):
2177+ token_endpoint = 'http://{url}:{port}/token'.format(
2178+ url=SNAP_PROXY_API, port=SNAP_PROXY_API_PORT)
2179+ payload = {'username': username}
2180+ r = requests.post(token_endpoint, json=payload)
2181+ assert r.status_code == 200
2182+ return r.json()['secret']
2183+
2184+ def setUp(self):
2185+ super(TestProxyApiIntegration, self).setUp()
2186+ self.username = 'BUILD-{}'.format(randint(100, 999))
2187+ self.token = self.createToken(self.username)
2188+ self.proxies = {
2189+ 'http': 'http://{username}:{password}@{url}:{port}'.format(
2190+ username=self.username,
2191+ password=self.token,
2192+ url=SNAP_PROXY,
2193+ port=SNAP_PROXY_PORT)
2194+ }
2195+
2196+ def test_proxy_basic_auth(self):
2197+ r = requests.get(TEST_URL, proxies=self.proxies)
2198+ self.assertEqual(200, r.status_code)
2199+
2200+
2201+if __name__ == '__main__':
2202+ unittest.main(warnings='ignore')

Subscribers

People subscribed via source and target branches

to all changes: