From 0d6c010cce9e63c2bfc1719e179f5579f7d62065 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 1 Apr 2022 01:02:09 +0200 Subject: [PATCH] feat: init [skip ci] --- .gitignore | 12 + LICENSE | 621 ++++++++++++++++++++ Pipfile | 13 + Pipfile.lock | 485 +++++++++++++++ bitbucket-pipelines.yml | 104 ++++ docs/.gitignore | 2 + docs/ARCHITECTURE.rst | 17 + docs/CONTRIBUTING.rst | 21 + docs/README.rst | 61 ++ docs/_assets/images/Data Flow.png | Bin 0 -> 100833 bytes docs/_assets/images/dog-gf2faaf3f6_1920.png | Bin 0 -> 24938 bytes docs/_templates/autosummary/module.rst | 42 ++ docs/architecture.png | Bin 0 -> 8394 bytes docs/architecture.uml | 7 + docs/conf.py | 61 ++ docs/guide/advanced-usage.rst | 17 + docs/guide/backend.rst | 17 + docs/guide/cli.rstas | 2 + docs/guide/get-started.rst | 70 +++ docs/index.rst | 16 + pyproject.toml | 12 + setup.cfg | 35 ++ setup.py | 4 + src/httpaste/__init__.py | 173 ++++++ src/httpaste/__main__.py | 139 +++++ src/httpaste/backend/__init__.py | 64 ++ src/httpaste/backend/file/__init__.py | 87 +++ src/httpaste/backend/file/paste.py | 85 +++ src/httpaste/backend/file/user.py | 79 +++ src/httpaste/backend/sqlite/__init__.py | 86 +++ src/httpaste/backend/sqlite/paste.py | 71 +++ src/httpaste/backend/sqlite/paste.sql | 10 + src/httpaste/backend/sqlite/user.py | 55 ++ src/httpaste/backend/sqlite/user.sql | 6 + src/httpaste/cgi.py | 11 + src/httpaste/controller/__init__.py | 90 +++ src/httpaste/controller/index.html | 14 + src/httpaste/controller/paste/__init__.py | 0 src/httpaste/controller/paste/private.py | 154 +++++ src/httpaste/controller/paste/public.py | 125 ++++ src/httpaste/fcgi.py | 13 + src/httpaste/helper/common.py | 23 + src/httpaste/helper/crypto.py | 80 +++ src/httpaste/helper/exception.py | 65 ++ src/httpaste/helper/syntax.py | 16 + src/httpaste/model/__init__.py | 135 +++++ src/httpaste/model/paste.py | 258 ++++++++ src/httpaste/model/user.py | 178 ++++++ src/httpaste/schema/httpaste.openapi.json | 322 ++++++++++ src/httpaste/wsgi.py | 8 + tests/__init__.py | 0 tests/httpaste/__init__.py | 0 tests/httpaste/model/__init__.py | 0 tests/httpaste/model/test_user.py | 99 ++++ tox.ini | 151 +++++ 55 files changed, 4216 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 bitbucket-pipelines.yml create mode 100644 docs/.gitignore create mode 100644 docs/ARCHITECTURE.rst create mode 100644 docs/CONTRIBUTING.rst create mode 100644 docs/README.rst create mode 100644 docs/_assets/images/Data Flow.png create mode 100644 docs/_assets/images/dog-gf2faaf3f6_1920.png create mode 100644 docs/_templates/autosummary/module.rst create mode 100644 docs/architecture.png create mode 100644 docs/architecture.uml create mode 100644 docs/conf.py create mode 100644 docs/guide/advanced-usage.rst create mode 100644 docs/guide/backend.rst create mode 100644 docs/guide/cli.rstas create mode 100644 docs/guide/get-started.rst create mode 100644 docs/index.rst create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100755 setup.py create mode 100755 src/httpaste/__init__.py create mode 100644 src/httpaste/__main__.py create mode 100644 src/httpaste/backend/__init__.py create mode 100644 src/httpaste/backend/file/__init__.py create mode 100644 src/httpaste/backend/file/paste.py create mode 100644 src/httpaste/backend/file/user.py create mode 100644 src/httpaste/backend/sqlite/__init__.py create mode 100644 src/httpaste/backend/sqlite/paste.py create mode 100644 src/httpaste/backend/sqlite/paste.sql create mode 100644 src/httpaste/backend/sqlite/user.py create mode 100644 src/httpaste/backend/sqlite/user.sql create mode 100755 src/httpaste/cgi.py create mode 100644 src/httpaste/controller/__init__.py create mode 100644 src/httpaste/controller/index.html create mode 100644 src/httpaste/controller/paste/__init__.py create mode 100644 src/httpaste/controller/paste/private.py create mode 100644 src/httpaste/controller/paste/public.py create mode 100755 src/httpaste/fcgi.py create mode 100644 src/httpaste/helper/common.py create mode 100755 src/httpaste/helper/crypto.py create mode 100644 src/httpaste/helper/exception.py create mode 100644 src/httpaste/helper/syntax.py create mode 100644 src/httpaste/model/__init__.py create mode 100755 src/httpaste/model/paste.py create mode 100755 src/httpaste/model/user.py create mode 100644 src/httpaste/schema/httpaste.openapi.json create mode 100755 src/httpaste/wsgi.py create mode 100644 tests/__init__.py create mode 100644 tests/httpaste/__init__.py create mode 100644 tests/httpaste/model/__init__.py create mode 100755 tests/httpaste/model/test_user.py create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ec6dc9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/.pytest_cache/ +/.tox/ +/build/ +/dist/ +/docs/_stubs/ +/docs/_tree/ +/test-reports/ +/src/*.egg-info/ +**/__pycache__/ +.DS_Store +.coverage +/*.md \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ea55861 --- /dev/null +++ b/LICENSE @@ -0,0 +1,621 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..01b679f --- /dev/null +++ b/Pipfile @@ -0,0 +1,13 @@ +[[source]] +url = 'https://pypi.python.org/simple' +verify_ssl = true +name = 'pypi' + +[requires] +python_version = '3' + +[packages] +httpaste = {editable = true, path = "."} + +[dev-packages] +tox = '==3.23.0' \ No newline at end of file diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..4223497 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,485 @@ +{ + "_meta": { + "hash": { + "sha256": "6fc8f1480cab514207ed13c95c3533fd240e04aa466d8fe781b969aa42b6313d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "attrs": { + "hashes": [ + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.4.0" + }, + "certifi": { + "hashes": [ + "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", + "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" + ], + "version": "==2021.10.8" + }, + "cffi": { + "hashes": [ + "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3", + "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2", + "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636", + "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20", + "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728", + "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27", + "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66", + "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443", + "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0", + "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7", + "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39", + "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605", + "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a", + "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37", + "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029", + "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139", + "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc", + "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df", + "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14", + "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880", + "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2", + "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a", + "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e", + "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474", + "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024", + "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8", + "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0", + "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e", + "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a", + "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e", + "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032", + "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6", + "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e", + "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b", + "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e", + "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954", + "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962", + "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c", + "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4", + "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55", + "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962", + "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023", + "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c", + "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6", + "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8", + "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382", + "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7", + "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc", + "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997", + "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796" + ], + "version": "==1.15.0" + }, + "charset-normalizer": { + "hashes": [ + "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", + "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" + ], + "markers": "python_version >= '3'", + "version": "==2.0.12" + }, + "click": { + "hashes": [ + "sha256:5e0d195c2067da3136efb897449ec1e9e6c98282fbf30d7f9e164af9be901a6b", + "sha256:7ab900e38149c9872376e8f9b5986ddcaf68c0f413cf73678a0bca5547e6f976" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.1" + }, + "clickclick": { + "hashes": [ + "sha256:4efb13e62353e34c5eef7ed6582c4920b418d7dedc86d819e22ee089ba01802c", + "sha256:c8f33e6d9ec83f68416dd2136a7950125bd256ec39ccc9a85c6e280a16be2bb5" + ], + "version": "==20.10.2" + }, + "connexion": { + "extras": [ + "swagger-ui" + ], + "hashes": [ + "sha256:0ba5c163d34cb3cb3bf597d5b95fc14bad5d3596bf10ec86e32cdb63f68d0c8a", + "sha256:26a570a0283bbe4cdaf5d90dfb3441aaf8e18cb9de10f3f96bbc128a8a3d8b47" + ], + "markers": "python_version >= '3.6'", + "version": "==2.13.0" + }, + "cryptography": { + "hashes": [ + "sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b", + "sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51", + "sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7", + "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d", + "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6", + "sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29", + "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9", + "sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf", + "sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815", + "sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf", + "sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85", + "sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77", + "sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86", + "sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb", + "sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e", + "sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0", + "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3", + "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84", + "sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2", + "sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6" + ], + "markers": "python_version >= '3.6'", + "version": "==36.0.2" + }, + "flask": { + "hashes": [ + "sha256:8a4cf32d904cf5621db9f0c9fbcd7efabf3003f22a04e4d0ce790c7137ec5264", + "sha256:a8c9bd3e558ec99646d177a9739c41df1ded0629480b4c8d2975412f3c9519c8" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.1" + }, + "httpaste": { + "editable": true, + "path": "." + }, + "httpaste-victorykit": { + "editable": true, + "path": "." + }, + "idna": { + "hashes": [ + "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", + "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + ], + "markers": "python_version >= '3'", + "version": "==3.3" + }, + "importlib-metadata": { + "hashes": [ + "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6", + "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539" + ], + "markers": "python_version < '3.10'", + "version": "==4.11.3" + }, + "inflection": { + "hashes": [ + "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", + "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2" + ], + "markers": "python_version >= '3.5'", + "version": "==0.5.1" + }, + "itsdangerous": { + "hashes": [ + "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", + "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.2" + }, + "jinja2": { + "hashes": [ + "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119", + "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.1" + }, + "jsonschema": { + "hashes": [ + "sha256:636694eb41b3535ed608fe04129f26542b59ed99808b4f688aa32dcf55317a83", + "sha256:77281a1f71684953ee8b3d488371b162419767973789272434bbc3f29d9c8823" + ], + "markers": "python_version >= '3.7'", + "version": "==4.4.0" + }, + "markupsafe": { + "hashes": [ + "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", + "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", + "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", + "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", + "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", + "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", + "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", + "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", + "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", + "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", + "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", + "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", + "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", + "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", + "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", + "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", + "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", + "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", + "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", + "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", + "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", + "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", + "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", + "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", + "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", + "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", + "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", + "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", + "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", + "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", + "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", + "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", + "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", + "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", + "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", + "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", + "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", + "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", + "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", + "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.1" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "markers": "python_version >= '3.6'", + "version": "==21.3" + }, + "pycparser": { + "hashes": [ + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + ], + "version": "==2.21" + }, + "pygments": { + "hashes": [ + "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65", + "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a" + ], + "markers": "python_version >= '3.5'", + "version": "==2.11.2" + }, + "pyparsing": { + "hashes": [ + "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea", + "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484" + ], + "markers": "python_version >= '3.6'", + "version": "==3.0.7" + }, + "pyrsistent": { + "hashes": [ + "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c", + "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc", + "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e", + "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26", + "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec", + "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286", + "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045", + "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec", + "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8", + "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c", + "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca", + "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22", + "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a", + "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96", + "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc", + "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1", + "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07", + "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6", + "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b", + "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5", + "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6" + ], + "markers": "python_version >= '3.7'", + "version": "==0.18.1" + }, + "pyyaml": { + "hashes": [ + "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", + "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", + "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", + "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", + "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", + "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", + "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", + "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", + "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", + "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", + "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", + "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", + "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", + "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", + "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", + "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", + "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", + "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", + "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", + "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", + "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", + "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", + "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", + "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", + "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", + "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", + "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", + "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", + "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", + "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", + "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", + "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", + "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + ], + "markers": "python_version >= '3.6'", + "version": "==6.0" + }, + "requests": { + "hashes": [ + "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", + "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==2.27.1" + }, + "swagger-ui-bundle": { + "hashes": [ + "sha256:b462aa1460261796ab78fd4663961a7f6f347ce01760f1303bbbdf630f11f516", + "sha256:cea116ed81147c345001027325c1ddc9ca78c1ee7319935c3c75d3669279d575" + ], + "version": "==0.0.9" + }, + "urllib3": { + "hashes": [ + "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", + "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.9" + }, + "werkzeug": { + "hashes": [ + "sha256:094ecfc981948f228b30ee09dbfe250e474823b69b9b1292658301b5894bbf08", + "sha256:9b55466a3e99e13b1f0686a66117d39bda85a992166e0a79aedfcf3586328f7a" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.0" + }, + "zipp": { + "hashes": [ + "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d", + "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375" + ], + "markers": "python_version >= '3.7'", + "version": "==3.7.0" + } + }, + "develop": { + "distlib": { + "hashes": [ + "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b", + "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579" + ], + "version": "==0.3.4" + }, + "filelock": { + "hashes": [ + "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85", + "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0" + ], + "markers": "python_version >= '3.7'", + "version": "==3.6.0" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "markers": "python_version >= '3.6'", + "version": "==21.3" + }, + "platformdirs": { + "hashes": [ + "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d", + "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227" + ], + "markers": "python_version >= '3.7'", + "version": "==2.5.1" + }, + "pluggy": { + "hashes": [ + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" + ], + "markers": "python_version >= '3.6'", + "version": "==1.0.0" + }, + "py": { + "hashes": [ + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.11.0" + }, + "pyparsing": { + "hashes": [ + "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea", + "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484" + ], + "markers": "python_version >= '3.6'", + "version": "==3.0.7" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.10.2" + }, + "tox": { + "hashes": [ + "sha256:05a4dbd5e4d3d8269b72b55600f0b0303e2eb47ad5c6fe76d3576f4c58d93661", + "sha256:e007673f3595cede9b17a7c4962389e4305d4a3682a6c5a4159a1453b4f326aa" + ], + "index": "pypi", + "version": "==3.23.0" + }, + "virtualenv": { + "hashes": [ + "sha256:1e8588f35e8b42c6ec6841a13c5e88239de1e6e4e4cedfd3916b306dc826ec66", + "sha256:8e5b402037287126e81ccde9432b95a8be5b19d36584f64957060a3488c11ca8" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==20.14.0" + } + } +} diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml new file mode 100644 index 0000000..07cd6ba --- /dev/null +++ b/bitbucket-pipelines.yml @@ -0,0 +1,104 @@ +image: 'python:3.9-alpine' +definitions: + caches: + tox-test: '.tox/test/' + tox-build: '.tox/build/' + tox-build-docs: '.tox/build-docs/' + tox-publish-docs: '.tox/publish-docs/' + venv: '.venv/' +pipelines: + branches: + master: + - step: + name: 'Init' + caches: + - 'venv' + - 'pip' + script: + - 'python3 -m pip install pipenv' + - 'mkdir -p .venv' + - 'python3 -m pipenv install -d' + condition: + changesets: + includePaths: + - "src/**" + - "docs/**" + - parallel: + - step: + name: 'Test' + caches: + - 'tox-test' + - 'venv' + - 'pip' + script: + - 'python3 -m pip install pipenv' + - 'python3 -m pipenv run python3 -m tox -e test' + artifacts: + - 'test-reports/**' + condition: + changesets: + includePaths: + - "src/**" + - step: + name: 'Build' + caches: + - 'tox-build' + - 'venv' + - 'pip' + script: + - 'python3 -m pip install pipenv' + - 'python3 -m pipenv run tox -e build' + artifacts: + - 'build/**' + condition: + changesets: + includePaths: + - "src/**" + - step: + name: 'Build Docs' + caches: + - 'tox-build-docs' + - 'venv' + - 'pip' + script: + - 'python3 -m pip install pipenv' + - 'python3 -m pipenv run tox -e build-docs' + - + artifacts: + - 'dist/docs/**' + - '*.md' + condition: + changesets: + includePaths: + - "src/**" + - "docs/**" + - step: + name: 'Publish Docs' + caches: + - 'tox-publish-docs' + - 'venv' + - 'pip' + artifacts: + - '*.md' + script: + - 'python3 -m pip install pipenv' + - 'apk update && apk add git openssh-client' + - 'python3 -m pipenv run tox -e publish-docs' + condition: + changesets: + includePaths: + - "src/**" + - "docs/**" + - step: + name: 'Self-Mutate Docs' + script: + - 'apk update && apk add git openssh-client' + - 'git add --force *.md' + - 'git commit -m "docs(repository): self-mutation"' + - 'git push' + condition: + changesets: + includePaths: + - "docs/ARCHITECTURE.rst" + - "docs/README.rst" + - "docs/CONTRIBUTING.rst" \ No newline at end of file diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..407df4e --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +_stubs/ +_tree/ \ No newline at end of file diff --git a/docs/ARCHITECTURE.rst b/docs/ARCHITECTURE.rst new file mode 100644 index 0000000..dcd450c --- /dev/null +++ b/docs/ARCHITECTURE.rst @@ -0,0 +1,17 @@ +Architecture +============ + +.. only:: not readme + + .. uml:: architecture.uml + + + +Programming Paradigm +-------------------- + +This python package is mainly authored according to a functional programming +paradigm, however, strictly adhering to said seemed ignorant, since not all +objects in Python are immutable anyway. Modification of mutable input objects +are therefore not regarded as side-effects per se, due to the contractual nature +of a function's parameter signature. \ No newline at end of file diff --git a/docs/CONTRIBUTING.rst b/docs/CONTRIBUTING.rst new file mode 100644 index 0000000..dd31711 --- /dev/null +++ b/docs/CONTRIBUTING.rst @@ -0,0 +1,21 @@ +Contribution Guidelines +======================= + +TO-DO test + +Prerequisites +------------- + +You need the following tools to be installed: + +* Python (> ver. 3.7) +* Python *pip* module + +.. code-block:: shell + + $ git clone git@bitbucket.org:victorykit/ezcfn.git + $ cd ezcfn + $ python3 -m pipenv install -d + $ python3 -m pipenv run tox -e test + $ python3 -m pipenv run tox -e build + $ python3 -m pipenv run tox -e build-docs \ No newline at end of file diff --git a/docs/README.rst b/docs/README.rst new file mode 100644 index 0000000..d2af101 --- /dev/null +++ b/docs/README.rst @@ -0,0 +1,61 @@ +httpaste +======== + +.. only:: readme + + ![](docs/_assets/images/dog-gf2faaf3f6_1920.png) + +.. only:: not readme + + .. image:: _assets/images/dog-gf2faaf3f6_1920.png + +Simple and safe pasting over HTTP. + +httpaste is a pastebin application for easily pasting and retrieving data over HTTP from shell environments and web browsers. It is inspired by sprunge.us and ix.io, but focuses on extendability and advanced security. + +public and private pastes are supported. In any case, httpaste provides full anonymity, with each paste being stored encrypted. + +Why +--- + +As a user: Sometimes we say and write stupid things. Things so stupid, that +others may even be offended. Others being so offended, that they wish to take +legal actions against you. Legal actions which infringe upon your rights for + privacy. Who is the others? Well, it's the + +Public pastes: Only people you share the paste id with are able to view the paste. Public pastes aren't indexed publicly, so Public pastes are encrypted with their paste id and the paste id is only +stored as a hash. Therefore the httpaste application hoster is unable to view any pastes, unless he maliciously logs any of the HTTP requests. + + +.. include:: guide/get-started.rst + +Documentation +------------- + +The documentation can be found under ``_. + + +Licensing +--------- + +Copyright (C) 2021 Tiara Rodney (victoryk.it) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +.. toctree:: + :maxdepth: 1 + :caption: More Information + + ARCHITECTURE + CONTRIBUTING \ No newline at end of file diff --git a/docs/_assets/images/Data Flow.png b/docs/_assets/images/Data Flow.png new file mode 100644 index 0000000000000000000000000000000000000000..37fea7f3153332aaade103491f0d58e23393e48d GIT binary patch literal 100833 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 literal 0 HcmV?d00001 diff --git a/docs/_assets/images/dog-gf2faaf3f6_1920.png b/docs/_assets/images/dog-gf2faaf3f6_1920.png new file mode 100644 index 0000000000000000000000000000000000000000..51779a8b573513a143de4c1c9290ed72b0cbd7e9 GIT binary patch literal 24938 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 M07*qoM6N<$g1`{;&j0`b literal 0 HcmV?d00001 diff --git a/docs/_templates/autosummary/module.rst b/docs/_templates/autosummary/module.rst new file mode 100644 index 0000000..edbc3c3 --- /dev/null +++ b/docs/_templates/autosummary/module.rst @@ -0,0 +1,42 @@ +{{ fullname | escape | underline }} + +.. rubric:: Description + +.. automodule:: {{ fullname }} + :special-members: + +.. currentmodule:: {{ fullname }} + +{% if modules %} +.. rubric:: Modules + +.. autosummary:: + :toctree: . + :recursive: + {% for module in modules %} + {{ module }} + {% endfor %} + +{% endif %} + +{% if classes %} +.. rubric:: Classes + +.. autosummary:: + :toctree: . + {% for class in classes %} + {{ class }} + {% endfor %} + +{% endif %} + +{% if functions %} +.. rubric:: Functions + +.. autosummary:: + :toctree: . + {% for function in functions %} + {{ function }} + {% endfor %} + +{% endif %} \ No newline at end of file diff --git a/docs/architecture.png b/docs/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..081951a4b420fe8246b14e245baf701250c371b7 GIT binary patch literal 8394 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 L<58)CdC-3W=I&UL literal 0 HcmV?d00001 diff --git a/docs/architecture.uml b/docs/architecture.uml new file mode 100644 index 0000000..1a53daf --- /dev/null +++ b/docs/architecture.uml @@ -0,0 +1,7 @@ +@startuml +Alice -> Bob: Authentication Request +Bob --> Alice: Authentication Response + +Alice -> Bob: Another authentication Request +Alice <-- Bob: Another authentication Response +@enduml \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..1ad01bd --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,61 @@ +import os +import sys + + +# -- Project information ------------------------------------------------------- + +project = 'httpaste' +copyright = '2022 - Tiara Rodney (victoryk.it)' +author = 'Tiara Rodney =2.13.0,<3 + cryptography>=36.0.2,<37 + pygments>=2.11.2,<3 +zip_safe = true +package_dir = + =src +python_requires = >=3.7 +test_suite = test +packages = find: + +[options.entry_points] +console_scripts = + httpaste = httpaste.__main__:main + +[options.packages.find] +where = src diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..f187f2c --- /dev/null +++ b/setup.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +import setuptools + +setuptools.setup() diff --git a/src/httpaste/__init__.py b/src/httpaste/__init__.py new file mode 100755 index 0000000..3e147a5 --- /dev/null +++ b/src/httpaste/__init__.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +"""httpaste - secure pasting over http +""" +from typing import NamedTuple, Tuple, Any +from string import ascii_uppercase, digits, ascii_letters, punctuation +from inspect import isclass +from configparser import ConfigParser +from ast import literal_eval +from io import StringIO + +from connexion import FlaskApp +from connexion.resolver import RestyResolver + +from httpaste.model import Backend +from httpaste.backend import get_backend_map +from httpaste.helper.exception import BadRequestHttpException, ForbiddenHttpException, GoneHttpException, NotFoundHttpException, UnauthorizedHttpException +from httpaste.helper.common import generate_random_string + + +CONFIGPATH_ENVIRON = 'HTTPASTE_CONFIG' + + +def get_sanitized_config_charset(charset:str): + + for x in [ "$", "%"]: + + charset = charset.replace(x, f'{x}{x}') + + return charset + + +class ConfigException(BaseException): + """Config Exception + """ + + +class Config: + """httpaste global config + """ + salt: bytes = get_sanitized_config_charset(generate_random_string(32, ascii_letters + digits + punctuation)).encode('utf-8') + paste_id_size: int = 8 + paste_id_charset: str = ascii_letters + digits + paste_key_size: int = 32 + paste_key_charset: str = get_sanitized_config_charset(ascii_letters + digits + punctuation) + paste_lifetime: int = 5 + backend: Backend = None + + +class ServerConfig: + """connexion config + """ + swagger_ui: bool = True + bind_address = None + + +def get_config_path(environ:str=CONFIGPATH_ENVIRON): + + try: + + return os.environ[environ] + except KeyError as e: + + raise ConfigException('environment variable \'{environ}\' not set.') from e + + +def load_config(path: str) -> Tuple[Config, ServerConfig]: + """get config objects from file + """ + + _config = ConfigParser() + _config.read(path) + + backends = get_backend_map() + bconf = dict(_config.items('backend')) + btype = bconf.pop('type') + + try: + bcl, bparamcl = backends[btype] + except KeyError as e: + bids = ', '.join(backends.keys()) + msg = f'invalid backend \'{btype}\' in \'{path}\'. must be any of [{bids}]' + raise ConfigException(msg) from e + + config = dict(_config.items('general')) + server_config = dict(_config.items('server')) + + c = Config() + sc = ServerConfig() + + # typecast model_backend section items + bconf = {k: literal_eval(v) for k, v in bconf.items()} + # initialize model backend + c.backend = bcl(bparamcl(**bconf)) + + # typecast general section items + for k, v in config.items(): + setattr(c, k, literal_eval(v)) + # typecast server section items + for k, v in server_config.items(): + setattr(sc, k, literal_eval(v)) + + c.salt = c.salt.encode('utf-8') + + return c, sc + + +def default_config() -> str: + + config = ConfigParser() + + config['general'] = { + 'salt': Config.salt.decode('utf-8'), + 'paste_key_charset': Config.paste_key_charset, + 'paste_id_charset': Config.paste_id_charset + } + + for literal in [ + 'paste_id_size', + 'paste_key_size', + 'paste_lifetime' + ]: + config['general'][literal] = str(getattr(Config, literal)) + + config['backend'] = { + 'type': 'sqlite', + 'path': 'file::memory:?cache=shared' + } + + config['server'] = {} + for literal in [ + 'swagger_ui', + 'bind_address' + ]: + config['server'][literal] = str(getattr(ServerConfig, literal)) + + stream = StringIO() + config.write(stream) + stream.seek(0) + + return stream.read() + + +def get_flask_app( + config: Config, + server_config: ServerConfig = ServerConfig) -> FlaskApp: + """get a flask app object + """ + + options = {"swagger_ui": server_config.swagger_ui} + + app = FlaskApp( + __name__, + specification_dir='schema/') + + app.add_api( + 'httpaste.openapi.json', + options=options, + resolver=RestyResolver('httpaste.controller') + ) + + app.add_error_handler( + BadRequestHttpException, + BadRequestHttpException.render) + app.add_error_handler(ForbiddenHttpException, ForbiddenHttpException.render) + app.add_error_handler(GoneHttpException, GoneHttpException.render) + app.add_error_handler(NotFoundHttpException, NotFoundHttpException.render) + app.add_error_handler(UnauthorizedHttpException, UnauthorizedHttpException.render) + + with app.app.app_context(): + + app.app.httpaste = config + + return app diff --git a/src/httpaste/__main__.py b/src/httpaste/__main__.py new file mode 100644 index 0000000..c0cfe8c --- /dev/null +++ b/src/httpaste/__main__.py @@ -0,0 +1,139 @@ +"""Main +""" +import argparse +import os + + +def _this_dir(basename:str)-> str: + """build path with script directory name and provided basename + """ + + return os.path.join(os.path.dirname(__file__), basename) + + +def _path_output(path, echo:bool=False) -> str: + """print path content or path + """ + + if not echo: + + return path + else: + + with open(path, 'r') as fh: + + return fh.read() + + +def command_standalone(**kwargs): + """run standalone evaluation server + """ + + from httpaste import load_config, get_flask_app + + config, server_config = load_config(kwargs.get('config_path')) + + application = get_flask_app(config, server_config) + + application.run(port=8080) + + +def command_wsgi(**kwargs): + """get WSGI script + """ + + print(_path_output(_this_dir('wsgi.py'), kwargs.get('echo'))) + + +def command_cgi(**kwargs): + """get CGI script + """ + + print(_path_output(_this_dir('cgi.py'), kwargs.get('echo'))) + + +def command_fcgi(**kwargs): + """get FastCGI script + """ + + print(_path_output(_this_dir('fcgi.py'), kwargs.get('echo'))) + + +def command_default_config(**kwargs): + """get default config + """ + + from httpaste import default_config + + dconfig = default_config() + + if kwargs.get('config'): + + with open(kwargs.get('config'), 'w') as fh: + + fh.write(dconfig) + + return + + print(dconfig) + + +def command_init_backend(**kwargs): + """initialize the backend + """ + + from httpaste import load_config + + config, _ = load_config(kwargs.get('config')) + + config.backend.user.init() + config.backend.paste.init() + + +def parser(): + + p = argparse.ArgumentParser(description='Process some integers.') + + sp = p.add_subparsers(dest='command') + + p_standalone = sp.add_parser('standalone', help=command_standalone.__doc__) + p_standalone.add_argument('--config-path', '-c', required=True) + p_standalone.add_argument('--port', '-p', default=8080) + + p_wsgi = sp.add_parser('wsgi', help=command_wsgi.__doc__) + p_wsgi.add_argument('--echo', '-e', action='store_true') + + p_cgi = sp.add_parser('cgi', help=command_cgi.__doc__) + p_cgi.add_argument('--echo', '-e', action='store_true') + + p_fcgi = sp.add_parser('fcgi', help=command_fcgi.__doc__) + p_fcgi.add_argument('--echo', '-e', action='store_true') + + p_default_config = sp.add_parser('default-config', help=command_default_config.__doc__) + p_default_config.add_argument('--dump', '-d') + + p_init_backend = sp.add_parser('init-backend', help=command_init_backend.__doc__) + p_init_backend.add_argument('--config', '-c', required=True) + + return p + + +def main(): + + p = parser() + + kwargs = vars(p.parse_args()) + + { + 'standalone': command_standalone, + 'wsgi': command_wsgi, + 'cgi': command_cgi, + 'fcgi': command_fcgi, + 'default-config': command_default_config, + 'init-backend': command_init_backend + }[kwargs.pop('command')](**kwargs) + + +if __name__ == '__main__': + + main() diff --git a/src/httpaste/backend/__init__.py b/src/httpaste/backend/__init__.py new file mode 100644 index 0000000..debd8c5 --- /dev/null +++ b/src/httpaste/backend/__init__.py @@ -0,0 +1,64 @@ +"""Backend Interfaces + +implements backend of model +""" +import sys +from inspect import isclass +from typing import Dict, Tuple + +from httpaste.model import Backend, UserDataSchema, PasteDataSchema, User, Paste +from .sqlite import Parameters as SqliteParameters +from .sqlite import User as SqliteUser +from .sqlite import Paste as SqlitePaste +from .sqlite import get_connection as get_sqlite_connection +from .file import Parameters as FileParameters +from .file import User as FileUser +from .file import Paste as FilePaste + + +class SQLite(Backend): + """SQLite backend interface + """ + + parameter_class = SqliteParameters + user: SqliteUser + paste: SqlitePaste + + def __init__(self, parameters: SqliteParameters): + + parameters['connection'] = get_sqlite_connection(parameters) + + self.user = SqliteUser(parameters, User) + self.paste = SqlitePaste(parameters, Paste) + + +class File(Backend): + """File backend interface + """ + + parameter_class = FileParameters + user: FileUser + paste: FilePaste + + def __init__(self, parameters: FileParameters): + + self.user = FileUser(parameters, User, UserDataSchema) + self.paste = FilePaste(parameters, Paste, PasteDataSchema) + + +def get_backend_map() -> Dict[str, Tuple[type, type]]: + """get a map of backend ids and their classes + """ + + mod = sys.modules[__name__] + out = {} + + for i in dir(mod): + + obj = getattr(mod, i) + + if isclass(obj) and obj.__module__ == __name__: + + out[i.lower()] = (obj, obj.parameter_class) + + return out diff --git a/src/httpaste/backend/file/__init__.py b/src/httpaste/backend/file/__init__.py new file mode 100644 index 0000000..9b4ef56 --- /dev/null +++ b/src/httpaste/backend/file/__init__.py @@ -0,0 +1,87 @@ +"""Filesystem backend +""" +import os +from pathlib import Path +from typing import NamedTuple, Optional + +from . import user +from . import paste + + +class Parameters(NamedTuple): + """Filesystem backend parameters + """ + + base_dirname: str + user_dirname:str = 'users' + paste_dirname:str = 'pastes' + + +class User(object): + """Filesystem user model backend + """ + + dirname: Path + path: Path + + def __init__(self, parameters: Parameters, model_class:type, model_schema:type): + + self.model_class = model_class + + self.model_schema = model_schema + + self.dirname = os.path.join(parameters.base_dirname, parameters.user_dirname) + + self.path = Path(self.dirname) + + + def load(self, proto: object): + + return user.load(proto, self.path, self.model_class, self.model_schema) + + + def dump(self, model: object): + + return user.dump(model, self.path, self.model_schema) + + def delete(self, proto: object): + + return user.delete(proto, self.path) + + def init(self): + + return user.init(self.path) + + +class Paste(object): + """Filesystem paste model backend + """ + + dirname: str + path: Path + + def __init__(self, parameters: Parameters, model_class:type, model_schema:type): + + self.model_class = model_class + + self.model_schema = model_schema + + self.dirname = os.path.join(parameters.base_dirname, parameters.paste_dirname) + + self.path = Path(self.dirname) + + def load(self, proto: object): + + return paste.load(proto, self.path, self.model_class, self.model_schema) + + def dump(self, model: object): + + return paste.dump(model, self.path, self.model_schema) + + def delete(self, proto: object): + + return paste.delete(proto, self.path) + + def init(self): + + return paste.init(self.path) \ No newline at end of file diff --git a/src/httpaste/backend/file/paste.py b/src/httpaste/backend/file/paste.py new file mode 100644 index 0000000..01415cf --- /dev/null +++ b/src/httpaste/backend/file/paste.py @@ -0,0 +1,85 @@ +"""Filesystem backend paste model interface + +emulates a table database with base directory acting as the row, and files +acting as cells. +""" +from pathlib import Path +from ast import literal_eval + + +def load(proto: object, path: Path, model_class: type, model_schema: type) -> object: + """load a paste + """ + + row = path.joinpath(proto.pid.hex()) + + if not row.exists(): + + return None + + cells = {} + for column in ['data', 'data_hash', 'sub', 'timestamp', 'lifetime', 'encoding']: + + cell = row.joinpath(column) + cell_schema = getattr(model_schema, column) + + if not cell.exists(): + cells[column] = None + elif cell_schema == bytes: + cells[column] = cell.read_bytes() + else: + cells[column] = literal_eval(cell.read_text()) + + return model_class( + proto.pid, + cells['sub'], + cells['data'], + cells['data_hash'], + cells['timestamp'], + cells['lifetime'], + cells['encoding']) + + +def dump(model: object, path: Path, model_schema: type) -> None: + """dump a paste + """ + + row = path.joinpath(model.pid.hex()) + row.mkdir(parents=True, exist_ok=True) + + for column in ['data', 'data_hash', 'sub', 'timestamp', 'lifetime', 'encoding']: + + cell = row.joinpath(column) + cell_schema = getattr(model_schema, column) + cell_value = getattr(model, column) + + if not cell_value: + continue + elif cell_schema == bytes: + cell.write_bytes(getattr(model, column)) + else: + cell.write_text(str(getattr(model, column))) + + +def delete(proto: object, path: Path) -> bool: + + + row = path.joinpath(proto.pid.hex()) + + if row.exists(): + + _rm_tree(row) + + +def init(path: Path): + + return None + + +def _rm_tree(pth: Path): + for child in pth.iterdir(): + if child.is_file(): + child.unlink() + else: + rm_tree(child) + pth.rmdir() \ No newline at end of file diff --git a/src/httpaste/backend/file/user.py b/src/httpaste/backend/file/user.py new file mode 100644 index 0000000..54761c8 --- /dev/null +++ b/src/httpaste/backend/file/user.py @@ -0,0 +1,79 @@ +"""Filesystem backend user model interface + +emulates a table database with base directory acting as the row, and files +acting as cells. +""" +from pathlib import Path +from ast import literal_eval + + +def load(proto: object, path: Path, model_class: type, model_schema: type) -> object: + """load a paste + """ + + row = path.joinpath(proto.sub.hex()) + + if not row.exists(): + + return None + + cells = {} + for column in ['key_hash', 'index']: + + cell = row.joinpath(column) + + if getattr(model_schema, column) == bytes: + + cells[column] = cell.read_bytes() + else: + + cells[column] = literal_eval(cell.read_text()) + + return model_class( + proto.sub, + cells['key_hash'], + cells['index']) + + +def dump(model: object, path: Path, model_schema: object) -> None: + """dump a paste + """ + + row = path.joinpath(model.sub.hex()) + row.mkdir(parents=True, exist_ok=True) + + for column in ['key_hash', 'index']: + + cell = row.joinpath(column) + + if getattr(model_schema, column) == bytes: + + cell.write_bytes(getattr(model, column)) + else: + + cell.write_text(getattr(model, column)) + + +def delete(proto: object, path: Path) -> bool: + """delete a paste + """ + + row = path.joinpath(proto.sub.hex()) + + if row.exists(): + + _rm_tree(row) + + +def init(path: Path): + + return None + + +def _rm_tree(pth: Path): + for child in pth.iterdir(): + if child.is_file(): + child.unlink() + else: + rm_tree(child) + pth.rmdir() \ No newline at end of file diff --git a/src/httpaste/backend/sqlite/__init__.py b/src/httpaste/backend/sqlite/__init__.py new file mode 100644 index 0000000..df19ad9 --- /dev/null +++ b/src/httpaste/backend/sqlite/__init__.py @@ -0,0 +1,86 @@ +"""SQLite backend +""" +from sqlite3 import Connection, Row, connect +from typing import NamedTuple, Optional + +from . import user +from . import paste + + +class Parameters(NamedTuple): + """SQLite backend parameters + """ + + path: str + connection: Optional[object] = None + + +class User(object): + """SQLite user model backend + """ + + connection: Connection + + def __init__(self, parameters: Parameters, model_class:type): + + self.model_class = model_class + + self.connection = get_connection(parameters) + + def load(self, proto: object): + + return user.load(proto, self.connection, self.model_class) + + def dump(self, model: object): + + return user.dump(model, self.connection) + + def delete(self, proto: object): + + return user.delete(proto, self.connection) + + def init(self): + + return user.init(self.connection) + +class Paste(object): + """SQLite paste model backend + """ + + connection: Connection + + def __init__(self, parameters: Parameters, model_class:type): + + self.model_class = model_class + + self.connection = get_connection(parameters) + + def load(self, proto: object): + + return paste.load(proto, self.connection, self.model_class) + + def dump(self, model: object): + + return paste.dump(model, self.connection) + + def delete(self, proto: object): + + return paste.delete(proto, self.connection) + + def init(self): + + return paste.init(self.connection) + + +def get_connection(parameters: Parameters): + """get an sqlite connection object + """ + + if parameters.connection: + + return parameters.connection + + connection = connect(parameters.path, check_same_thread=False) + connection.row_factory = Row + + return connection diff --git a/src/httpaste/backend/sqlite/paste.py b/src/httpaste/backend/sqlite/paste.py new file mode 100644 index 0000000..4cd6abb --- /dev/null +++ b/src/httpaste/backend/sqlite/paste.py @@ -0,0 +1,71 @@ +"""SQlite backend paste model interface +""" +from os import path +from sqlite3 import Connection + + +def load(proto: object, connection: Connection, model_class: type): + """load a paste + """ + + cur = connection.cursor() + + cur.execute( + 'SELECT pid, data, data_hash, sub, timestamp, lifetime, encoding FROM pastes WHERE pid=?', + (proto.pid, + )) + + result = cur.fetchone() + + if result: + + return model_class( + result['pid'], + result['sub'], + result['data'], + result['data_hash'], + result['timestamp'], + result['lifetime'], + result['encoding']) + + return None + + +def dump(model: object, connection: Connection): + """dump a paste + """ + + cur = connection.cursor() + + cur.execute( + '''INSERT INTO pastes (pid, data, data_hash, sub, timestamp, lifetime, encoding) + VALUES (?,?,?,?,?,?,?)''', + (model.pid, + model.data, + model.data_hash, + model.sub, + model.timestamp, + model.lifetime, + model.encoding)) + + connection.commit() + + +def delete(proto: object, connection: Connection) -> bool: + + cur = connection.cursor() + + cur.execute('''DELETE FROM pastes WHERE pid=?''', (proto.pid,)) + + connection.commit() + + +def init(connection: Connection): + + cur = connection.cursor() + + with open(path.join(path.dirname(__file__), 'paste.sql'), 'r') as fh: + + cur.execute(fh.read()) + + connection.commit() \ No newline at end of file diff --git a/src/httpaste/backend/sqlite/paste.sql b/src/httpaste/backend/sqlite/paste.sql new file mode 100644 index 0000000..5788d5b --- /dev/null +++ b/src/httpaste/backend/sqlite/paste.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS "pastes" ( + "id" BLOB NOT NULL UNIQUE, + "data" BLOB NOT NULL, + "data_hash" BLOB NOT NULL, + "sub" BLOB UNIQUE, + "timestamp" INTEGER NOT NULL, + "lifetime" INTEGER NOT NULL, + "encoding" TEXT + PRIMARY KEY("id") +); \ No newline at end of file diff --git a/src/httpaste/backend/sqlite/user.py b/src/httpaste/backend/sqlite/user.py new file mode 100644 index 0000000..2cb3e3f --- /dev/null +++ b/src/httpaste/backend/sqlite/user.py @@ -0,0 +1,55 @@ +"""SQlite backend user model interface +""" +from os import path +from sqlite3 import Connection +from httpaste.model import User + + +def load(proto: User, connection: Connection): + """load a user + """ + + cur = connection.cursor() + + cur.execute( + 'SELECT sub, key_hash, paste_index FROM users WHERE sub=?', (proto.sub,)) + + result = cur.fetchone() + + if result: + + return User(result['sub'], result['key_hash'], result['paste_index']) + + return None + + +def dump(model: User, connection: Connection): + """dump a user + """ + + cur = connection.cursor() + + cur.execute('''INSERT OR REPLACE INTO users (sub, key_hash, paste_index) + VALUES (?,?,?)''', (model.sub, model.key_hash, model.index)) + + connection.commit() + + +def delete(proto: object, connection: Connection) -> bool: + + cur = connection.cursor() + + cur.execute('''DELETE FROM users WHERE sub=?''', (proto.sub,)) + + connection.commit() + + +def init(connection: Connection): + + cur = connection.cursor() + + with open(path.join(path.dirname(__file__), 'user.sql'), 'r') as fh: + + cur.execute(fh.read()) + + connection.commit() \ No newline at end of file diff --git a/src/httpaste/backend/sqlite/user.sql b/src/httpaste/backend/sqlite/user.sql new file mode 100644 index 0000000..1d5b947 --- /dev/null +++ b/src/httpaste/backend/sqlite/user.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS "users" ( + "sub" BLOB NOT NULL UNIQUE, + "key_hash" BLOB NOT NULL, + "paste_index" BLOB, + PRIMARY KEY("sub") +); \ No newline at end of file diff --git a/src/httpaste/cgi.py b/src/httpaste/cgi.py new file mode 100755 index 0000000..c28d730 --- /dev/null +++ b/src/httpaste/cgi.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +"""httpaste CGI entrypoint +""" +from wsgiref.handlers import CGIHandler +from httpaste import load_config, get_flask_app, get_config_path + +config, server_config = load_config(get_config_path()) + +application = get_flask_app(config, server_config) + +CGIHandler().run(application) diff --git a/src/httpaste/controller/__init__.py b/src/httpaste/controller/__init__.py new file mode 100644 index 0000000..96e2f25 --- /dev/null +++ b/src/httpaste/controller/__init__.py @@ -0,0 +1,90 @@ +"""NAME + + httpaste - secure, easy-to-use, anonymous production pastebin + +SYNOPSIS + + HTTP [POST/PUT/DELETE/GET] {url} + +DESCRIPTION + + TODO + +EXAMPLES + + POST Public Paste + + $ echo '#My public paste' | curl {url}paste/public \\ + -F 'data=<-' + {url}/paste/private/I0ah7fyA + + + GET Public Paste + + $ curl {url}/paste/public/I0ah7fyA + #My public paste + + + POST Private Paste + + $ echo '#My private paste' | curl {url}paste/private \\ + -F 'data=<-' \\ + -u myusername:mypassword + {url}paste/private/4FtNL75g + + + GET Private Paste + + $ curl {url}paste/private -u myusername:mypassword + #My private paste + + + POST Paste (with non-default expiration) + + $ echo '#My paste expires in 20 minutes' | curl \\ + {url}paste/public?lifetime=20 \\ + -F 'data=<-' \\ + {url}paste/public/xMxEmNi8 + + + POST Paste (with expiration after first read) + + $ echo '#My paste expires after first read' | curl \\ + {url}paste/public?lifetime=-1 \\ + -F 'data=<-' \\ + {url}paste/public/4FtNL75g + + + GET Paste (with shell syntax highlighting) + + $ curl {url}paste/public/I0ah7fyA?syntax=json + #My public paste + + + GET Paste (with line numbers) + + $ curl {url}paste/public/I0ah7fyA?syntax=shell&linenos=true + 0001: #My public paste + 0002: + + + GET Paste (with formatted output) + + $ curl {url}paste/public/I0ah7fyA?syntax=shell&format=html + --HTML OUTPUT WITH INLINE CSS-- + +SEE ALSO + + https://bitbucket.org/victorykit/httpaste + + https://paste.victoryk.it + +""" +import connexion + + +def get(**kwargs): + + print(dir(connexion.request)) + + return __doc__.format(url=connexion.request.url), 200 diff --git a/src/httpaste/controller/index.html b/src/httpaste/controller/index.html new file mode 100644 index 0000000..af93aa0 --- /dev/null +++ b/src/httpaste/controller/index.html @@ -0,0 +1,14 @@ + + + + httpaste + + +
+
Data
+
+
Lifetime
+
+
+ + \ No newline at end of file diff --git a/src/httpaste/controller/paste/__init__.py b/src/httpaste/controller/paste/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/httpaste/controller/paste/private.py b/src/httpaste/controller/paste/private.py new file mode 100644 index 0000000..ea71727 --- /dev/null +++ b/src/httpaste/controller/paste/private.py @@ -0,0 +1,154 @@ +from typing import TypedDict, Optional + +import connexion +from flask import current_app + +from httpaste.helper.exception import BadRequestHttpException, GoneHttpException, NotFoundHttpException +import httpaste.model +import httpaste.model.paste +import httpaste.model.user + + +class Form(TypedDict): + data: str + rsa_public_key: Optional[str] + + +class TokenInfo(TypedDict): + sub: str + token: str + + +class Lifetime(int): + """ + """ + + +class PostRequest(TypedDict): + body: Form + token_info: TokenInfo + lifetime: Lifetime + + +class GetRequest(TypedDict): + id: str + token_info: TokenInfo + + +def search(**kwargs): + """ + """ + + print(args) + + return 'Hallo', 200 + + +def put(**kwargs): + """ + """ + + print(args) + + return 'Hallo', 200 + + +def delete(**kwargs): + """ + """ + + pid = httpaste.model.PasteKey(request['id'].encode('utf-8')) + key = httpaste.model.MasterKey(request['token_info'].get('master_key')) + sub = httpaste.model.Sub(request['token_info'].get('sub')) + + pkey = httpaste.model.user.load_paste_key( + pid, + sub, + key, + current_app.httpaste.backend.user, + current_app.httpaste.salt) + + httpaste.model.paste.remove_safe(pid, sub, pkey, current_app.httpaste.backend.paste, current_app.httpaste.salt) + + return None, 200 + + +def get(**kwargs): + """ + """ + + request = GetRequest(**kwargs) + + pid = httpaste.model.PasteKey(request['id'].encode('utf-8')) + key = httpaste.model.MasterKey(request['token_info'].get('master_key')) + sub = httpaste.model.Sub(request['token_info'].get('sub')) + + pkey = httpaste.model.user.load_paste_key( + pid, + sub, + key, + current_app.httpaste.backend.user, + current_app.httpaste.salt) + + try: + data, lifetime, encoding = httpaste.model.paste.get_safe( + pid, + pkey, + sub, + current_app.httpaste.backend.paste, + current_app.httpaste.salt) + except httpaste.model.paste.PasteLifetimeException as e: + + httpaste.model.paste.remove_safe(pid, sub, pkey, current_app.httpaste.backend.paste, current_app.httpaste.salt) + + raise GoneHttpException(str(e)) + except httpaste.model.paste.PasteNotFoundException as e: + + raise NotFoundHttpException(str(e)) + + if lifetime < 0: + + httpaste.model.paste.remove_safe(pid, sub, pkey, current_app.httpaste.backend.paste, current_app.httpaste.salt) + + return data.decode('utf-8'), 200 + + +def post(**kwargs): + """ + """ + + request = PostRequest(**kwargs) + + if not request['body'].get('data'): + + raise BadRequestHttpException('form field \'data\' missing.') + + encoding = request.get('encoding', 'utf-8') + + if encoding not in ['utf-8', 'utf-16', 'ascii']: + + try: + data = httpaste.model.paste.PasteData(decode(request['body'].get('data'), encoding)) + encoding = None + except DecodeException as e: + + raise BadRequestException(str(e)) + else: + + data = httpaste.model.paste.PasteData( + request['body']['data'].encode(encoding)) + + request.setdefault('lifetime', current_app.httpaste.paste_lifetime) + key = httpaste.model.MasterKey(request['token_info'].get('master_key')) + sub = httpaste.model.Sub(request['token_info'].get('sub')) + lifetime = httpaste.model.PasteLifetime(request['lifetime']) + + pid, pkey = httpaste.model.paste.create_safe(data, lifetime, sub, encoding, + current_app.httpaste.backend.paste, + current_app.httpaste.salt) + + httpaste.model.user.dump_paste_key(pid, pkey, sub, key, + current_app.httpaste.backend.user, + current_app.httpaste.salt) + + return '/'.join((connexion.request.url, pid.decode('utf-8'))) + '\n', 200 diff --git a/src/httpaste/controller/paste/public.py b/src/httpaste/controller/paste/public.py new file mode 100644 index 0000000..8c6bf05 --- /dev/null +++ b/src/httpaste/controller/paste/public.py @@ -0,0 +1,125 @@ +from typing import TypedDict, Optional + +import connexion +from connexion.lifecycle import ConnexionResponse +from flask import current_app + +from httpaste.helper.exception import BadRequestHttpException, \ + GoneHttpException, \ + NotFoundHttpException, \ + ForbiddenHttpException +import httpaste.model.paste +from httpaste.helper.syntax import highlight +from httpaste.helper.common import decode, DecodeException + +class Form(TypedDict): + data: str + + +class TokenInfo(TypedDict): + sub: str + token: str + + +class PostRequest(TypedDict): + body: Form + token_info: TokenInfo + + +class GetRequest(TypedDict): + id: str + token_info: TokenInfo + + +def search(**kwargs): + """ + """ + + print(args) + + return 'Hallo', 200 + + + + + +def get(**kwargs): + """ + """ + + request = GetRequest(**kwargs) + + pid = httpaste.model.PasteKey(request['id'].encode('utf-8')) + syntax = request.get('syntax') + formatter = request.get('format', 'terminal256') + linenos = request.get('linenos', False) + mime = request.get('mime', 'text/plain') + + try: + + data, lifetime, encoding = httpaste.model.paste.get(pid, + current_app.httpaste.backend.paste, + current_app.httpaste.salt) + except httpaste.model.paste.PasteLifetimeException as e: + + httpaste.model.paste.remove(pid, current_app.httpaste.backend.paste) + + raise GoneHttpException(str(e)) from e + except httpaste.model.paste.PasteNotFoundException as e: + + raise NotFoundHttpException(str(e)) + except httpaste.model.paste.PasteSubException as e: + + raise ForbiddenHttpException(str(e)) + + if lifetime < 0: + + httpaste.model.paste.remove(pid, current_app.httpaste.backend.paste) + + if syntax: + + data = highlight(data, str(syntax), formatter, linenos) + + if encoding: + + data = data.decode(encoding) + + return ConnexionResponse( + status_code=200, + content_type=mime, + body=data + ) + + +def post(**kwargs): + """ + """ + + request = PostRequest(**kwargs) + request.setdefault('lifetime', current_app.httpaste.paste_lifetime) + + if not request['body'].get('data'): + + raise BadRequestHttpException('form field \'data\' missing.') + + encoding = request.get('encoding', 'utf-8') + + if encoding not in ['utf-8', 'utf-16', 'ascii']: + + try: + data = httpaste.model.PasteData(decode(request['body'].get('data'), encoding)) + encoding = None + except DecodeException as e: + + raise BadRequestHttpException(str(e)) + else: + + data = httpaste.model.PasteData(request['body']['data'].encode(encoding)) + + lifetime = httpaste.model.PasteLifetime(request['lifetime']) + + pid = httpaste.model.paste.create(data, lifetime, encoding, + current_app.httpaste.backend.paste, + current_app.httpaste.salt) + + return '/'.join((connexion.request.url, pid.decode('utf-8'))) + '\n', 200 diff --git a/src/httpaste/fcgi.py b/src/httpaste/fcgi.py new file mode 100755 index 0000000..e193e16 --- /dev/null +++ b/src/httpaste/fcgi.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +"""httpaste FastCGI entrypoint +""" +from flup.server.fcgi import WSGIServer +from httpaste import load_config, get_flask_app, get_config_path + +config, server_config = load_config(get_config_path()) + +application = get_flask_app(config, server_config) + +if __name__ == '__main__': + + WSGIServer(application, bindAddress=server_config.bind_address).run() diff --git a/src/httpaste/helper/common.py b/src/httpaste/helper/common.py new file mode 100644 index 0000000..4ae9c71 --- /dev/null +++ b/src/httpaste/helper/common.py @@ -0,0 +1,23 @@ +from random import choice +from base64 import b64decode + + +class DecodeException(BaseException): + """ + """ + + +def generate_random_string(length: int, charset: str) -> str: + + return ''.join(choice(charset) for _ in range(length)) + + +def decode(data:str, encoding:str) -> bytes: + + if encoding == 'base64': + + return b64decode(data.encode('ascii')) + + else: + + raise DecodeException(f'unknown encoding \'{encoding}\'.') \ No newline at end of file diff --git a/src/httpaste/helper/crypto.py b/src/httpaste/helper/crypto.py new file mode 100755 index 0000000..d9f2713 --- /dev/null +++ b/src/httpaste/helper/crypto.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""Cryptography +""" +import hashlib +import base64 + +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +from cryptography.fernet import Fernet, InvalidToken + +from httpaste import Config + + +class DecryptionException(BaseException): + """ + """ + + +def shash(data: bytes, key: bytes, salt: bytes): + """get a signed/keyed/salted data hash + + :param data: bytes to hash + :oaram + """ + + return hashlib.blake2b(data, key=key, salt=salt).digest() + + +def dhash(data: bytes): + """get a data hash + + :param data: bytes to hash + """ + + return hashlib.sha512(data).digest() + + +def derive_key(main_key: str, salt: bytes = Config.salt) -> bytes: + """derive a key from a main key + + :param main_key: main key to derive from + :param salt: randomization salt + """ + + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=salt, + iterations=390000, + ) + + return base64.urlsafe_b64encode(kdf.derive(main_key)) + + +def encrypt(data: bytes, key: bytes, salt: bytes) -> bytes: + """encrypt a data block + + :param data: data block + :param key: password to encrypt with + :param salt: randomization salt + """ + + return Fernet(derive_key(key, salt)).encrypt(data) + + +def decrypt(data: bytes, key: bytes, salt: bytes): + """encrypt a data block + + :param data: data block + :param key: password to encrypt with + :param salt: randomization salt + """ + + try: + + return Fernet(derive_key(key, salt)).decrypt(data) + + except InvalidToken as e: + + raise DecryptionException('unable to decrypt') from e \ No newline at end of file diff --git a/src/httpaste/helper/exception.py b/src/httpaste/helper/exception.py new file mode 100644 index 0000000..cda1269 --- /dev/null +++ b/src/httpaste/helper/exception.py @@ -0,0 +1,65 @@ + +class BadRequestHttpException(RuntimeError): + def __init__(self, msg=None): + super().__init__(msg) + + @staticmethod + def render(error): + return { + "detail": str(error), + "status": 400, + "title": "Bad Request", + }, 400 + + +class UnauthorizedHttpException(RuntimeError): + def __init__(self, msg=None): + super().__init__(msg) + + @staticmethod + def render(error): + return { + "detail": str(error), + "status": 401, + "title": "Unauthorized s", + }, 401 + + + +class ForbiddenHttpException(RuntimeError): + def __init__(self, msg=None): + super().__init__(msg) + + @staticmethod + def render(error): + return { + "detail": str(error), + "status": 403, + "title": "Forbidden", + }, 403 + + +class GoneHttpException(RuntimeError): + def __init__(self, msg=None): + super().__init__(msg) + + @staticmethod + def render(error): + return { + "detail": str(error), + "status": 410, + "title": "Gone", + }, 410 + + +class NotFoundHttpException(RuntimeError): + def __init__(self, msg=None): + super().__init__(msg) + + @staticmethod + def render(error): + return { + "detail": str(error), + "status": 404, + "title": "Not Found", + }, 404 \ No newline at end of file diff --git a/src/httpaste/helper/syntax.py b/src/httpaste/helper/syntax.py new file mode 100644 index 0000000..0675fbc --- /dev/null +++ b/src/httpaste/helper/syntax.py @@ -0,0 +1,16 @@ +from pygments.lexers import get_lexer_by_name, find_lexer_class_by_name +from pygments.formatters import find_formatter_class, HtmlFormatter + + +def highlight(data:str, lexer_alias:str, format_alias:str, linenos:bool=False): + + from pygments import highlight + + if format_alias == 'html': + + formatter = HtmlFormatter(noclasses=True, linenos=linenos) + else: + + formatter = find_formatter_class(format_alias)(linenos=linenos) + + return highlight(data, get_lexer_by_name(lexer_alias), formatter) \ No newline at end of file diff --git a/src/httpaste/model/__init__.py b/src/httpaste/model/__init__.py new file mode 100644 index 0000000..9341cb4 --- /dev/null +++ b/src/httpaste/model/__init__.py @@ -0,0 +1,135 @@ +"""Model +""" +from typing import NamedTuple, Optional, Dict, Union + + +class PasteDataSchema: + """Paste Interface schema between Model and Backend + """ + pid = bytes + data = bytes + data_hash = bytes + sub = bytes + timestamp = int + lifetime = int + encoding = str + + +class UserDataSchema: + """User Interface Schema between Model and Backend + """ + sub= bytes + key_hash= bytes + index= bytes + + +class Backend(object): + """Backend + """ + parameter_class: str + + +class Salt(bytes): + """Salt + """ + + +class PasteData(PasteDataSchema.data): + """Paste Data + """ + + +class PasteHash(PasteDataSchema.data_hash): + """Paste Data Hash + """ + + +class PasteTimestamp(PasteDataSchema.timestamp): + """Paste Timestamp + """ + + +class PasteEncoding(PasteDataSchema.encoding): + """ + """ + + +class PasteLifetime(PasteDataSchema.lifetime): + """Paste Lifetime + """ + + +class PasteSub(PasteDataSchema.sub): + """Hashed user id + """ + + +class KeyHash(UserDataSchema.key_hash): + """User Master Key Hash + """ + + +class PasteKey(bytes): + """Paste encryption key + """ + + +class PasteId(PasteDataSchema.pid): + """Paste unique identifier + """ + + +class MasterKey(bytes): + """User's master encryption key + """ + + +class Sub(UserDataSchema.sub): + """User id + """ + + +class Index(Dict[PasteId, PasteKey]): + """User Paste Index + """ + + +class SerializedIndex(UserDataSchema.index): + """User Paste Index (serialized) + """ + + +class User(NamedTuple): + """Global User Model (and Prototype) + + non-optional values are prototype values + """ + + #: user id + sub: Sub + #: user's master key hash + key_hash: Optional[KeyHash] = None + #: user's paste index + index: Optional[Union[Index, SerializedIndex]] = None + + +class Paste(NamedTuple): + """Global Paste Model (and Prototype) + + non-optional values are prototype values + """ + + #: paste id + pid: PasteId + #: paste owner + sub: Optional[PasteSub] = None + #: paste data + data: Optional[PasteData] = None + #: paste data hash + data_hash: Optional[PasteHash] = None + #: paste timestamp + timestamp: Optional[PasteTimestamp] = None + #: paste lifetime + lifetime: Optional[PasteLifetime] = None + #: paste encoding + encoding: Optional[PasteEncoding] = None \ No newline at end of file diff --git a/src/httpaste/model/paste.py b/src/httpaste/model/paste.py new file mode 100755 index 0000000..b308195 --- /dev/null +++ b/src/httpaste/model/paste.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 +"""paste model interface +""" +import json +from typing import Optional, Tuple +import time + +from httpaste import Config +from httpaste.helper.crypto import dhash, shash, encrypt, decrypt +from httpaste.model import Paste, PasteId, Sub, MasterKey, PasteKey, Salt, \ + PasteData, PasteHash, PasteTimestamp, PasteSub, \ + PasteLifetime, PasteEncoding +from httpaste.helper.common import generate_random_string + + +class PasteNotFoundException(BaseException): + """Paste Exception + """ + + +class PasteSubException(BaseException): + """Paste Sub Exception + """ + + +class PasteChecksumException(BaseException): + """Paste Checksum Exception + """ + + +class PasteLifetimeException(BaseException): + """Paste Lifetime Exception + """ + + +def generate_paste_id( + length: int = Config.paste_id_size, + charset: str = Config.paste_id_charset) -> bytes: + """generate a paste id + + :param length: length of id + :param charset: character set of id + """ + + return generate_random_string(length, charset).encode('utf-8') + + +def generate_paste_key( + length: int = Config.paste_key_size, + charset: str = Config.paste_key_charset) -> bytes: + """generate a paste encryption key + + :param length: length of key + :param charset: character set of key + """ + + return generate_random_string(length, charset).encode('utf-8') + + +def load(proto: Paste, backend: object) -> Optional[Paste]: + """load a paste model + + :param proto: paste model prototype + :param backend: model backend object + """ + + safe_pid = PasteId(dhash(proto.pid)) + + model = backend.load(Paste(safe_pid)) + + if not model: + + raise PasteNotFoundException('Paste does not exist') + + if proto.sub and model.sub != shash(proto.sub, model.data_hash, proto.pid) or not proto.sub and model.sub: + + raise PasteSubException('Paste not owned by user') + + if model.lifetime >= 0 and model.timestamp + (60 * model.lifetime) < int(time.time()): + + raise PasteLifetimeException('Paste expired') + + return model + + +def load_safe( + proto: Paste, + key: PasteKey, + backend: object, + salt: Salt = Config.salt): + """load an encrypted paste model + + :param proto: paste model prototype + :param paste_key: paste encryption key + :param backend: model backend object + """ + + model = load(proto, backend) + + data = decrypt(model.data, key, salt) + + if model.data_hash and dhash(data) != model.data_hash: + + raise PasteChecksumException('Paste data scrambled') + + return Paste( + proto.pid, + proto.sub, + data, + model.data_hash, + model.timestamp, + model.lifetime, + model.encoding) + + +def dump(model: Paste, backend: object) -> None: + """dump a paste model + + :param model: paste model + :param backend: model backend object + """ + + backend.dump(model) + + +def delete(proto: Paste, backend: object) -> None: + """delete a paste model + """ + + try: + model = load(proto, backend) + except PasteLifetimeException: + pass + + safe_pid = PasteId(dhash(proto.pid)) + + backend.delete(Paste(safe_pid)) + + +def delete_safe(proto: Paste, key:PasteKey, backend: object,salt: Salt = Config.salt) -> None: + """ + """ + + try: + model = load_safe(proto, key, backend, salt) + except PasteLifetimeException: + pass + + safe_pid = PasteId(dhash(proto.pid)) + + backend.delete(Paste(safe_pid)) + + +def create( + data: PasteData, + lifetime: PasteLifetime, + encoding: PasteEncoding, + backend: object, + salt: Salt = Config.salt) -> PasteId: + """create an unencrypted paste + + :param data: paste data + :param lifetime: paste expiration (in minutes) + :param backend: model backend object + """ + + pid = PasteId(generate_paste_id()) + safe_pid = PasteId(dhash(pid)) + data_hash = PasteHash(dhash(data)) + sub = None + timestamp = PasteTimestamp(int(time.time())) + + safe_data = PasteData(encrypt(data, pid, salt)) + + model = Paste(safe_pid, sub, safe_data, data_hash, timestamp, lifetime, encoding) + + dump(model, backend) + + return pid + + +def create_safe(data: PasteData, + lifetime: PasteLifetime, + sub: Sub, + encoding: PasteEncoding, + backend: object, + salt: Salt = Config.salt) -> Tuple[PasteId, + PasteKey]: + """create an encrypted paste + + :param data: paste data + :param lifetime: paste expiration (in minutes) + :param sub: paste owner id + :param backend: model backend object + :param salt: randomization salt + """ + + pid = PasteId(generate_paste_id()) + safe_pid = PasteId(dhash(pid)) + pkey = PasteKey(generate_paste_key()) + data_hash = PasteHash(dhash(data)) + safe_sub = PasteSub(shash(sub, data_hash, pid)) + timestamp = PasteTimestamp(int(time.time())) + + safe_data = PasteData(encrypt(data, pkey, salt)) + + dump(Paste( + safe_pid, + safe_sub, + safe_data, + data_hash, + timestamp, + lifetime, + encoding + ), backend) + + return pid, pkey + + +def remove(pid: PasteId, backend: object): + """conveniently delete an unencrypted paste + """ + + proto = Paste(pid) + + delete(proto, backend) + + +def remove_safe(pid: PasteId, sub:Sub, key:PasteKey, backend:object,salt: Salt = Config.salt): + + proto = Paste(pid, sub) + + delete_safe(proto, key, backend, salt) + + +def get(pid: PasteId, backend: object, salt: Salt = Config.salt) -> PasteData: + """conveniently load an unencrypted paste + """ + + model = load(Paste(pid), backend) + + data = decrypt(model.data, pid, salt) + + return PasteData(data), model.lifetime, model.encoding + + +def get_safe( + pid: PasteId, + pkey: PasteKey, + sub: Sub, + backend: object, + salt: Salt = Config.salt) -> PasteData: + """conveniently load an encrypted paste + """ + + model = load_safe(Paste(pid, sub), pkey, backend, salt) + + return PasteData(model.data), model.lifetime, model.encoding diff --git a/src/httpaste/model/user.py b/src/httpaste/model/user.py new file mode 100755 index 0000000..747518c --- /dev/null +++ b/src/httpaste/model/user.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +"""user model interface +""" +import hashlib +import sqlite3 +import os +import base64 +import json +from typing import NamedTuple, Dict, Optional, Union + +from flask import g, current_app + +from httpaste import Config +from httpaste.helper.crypto import dhash, shash, encrypt, decrypt, derive_key, DecryptionException +from httpaste.model import User, KeyHash, Index, SerializedIndex, Salt, \ + PasteKey, PasteId, MasterKey, Sub + + +class AuthenticationError(BaseException): + """Authentication Error + """ + + +class IndexError(BaseException): + """Index Decryption Error + """ + + +def load( + proto: User, + master_key: str, + backend: object, + salt: Salt = Config.salt) -> Optional[User]: + """load user model + + :param model: user model prototype + :param master_key: user's master key + :param backend: user model backend + :param salt: randomization salt + """ + + model = backend.load(proto) + + if not model: + return None + + try: + return User( + *model[:-1], + Index(**json.loads(decrypt(model.index, master_key, salt))) + ) + except DecryptionException as e: + + raise IndexError('unable to decrypt user index') from e + + +def dump( + model: User, + key: MasterKey, + backend: object, + salt: Salt = Config.salt) -> None: + """dump a user model + + :param model: user model + :param key: user's master key + :param backend: user model backend + :param salt: randomization salt + """ + + if not isinstance(model.index, Index): + + raise BaseException('index serialization pre-processing not allowed.') + + serialized_index = json.dumps(model.index).encode('utf-8') + + safe_index = SerializedIndex(encrypt(serialized_index, key, salt)) + + backend.dump(User(*model[:-1], safe_index)) + + +def load_paste_key( + pid: PasteId, + sub: Sub, + key: MasterKey, + backend: object, salt: Salt = Config.salt) -> Optional[PasteKey]: + """load a user paste key + + :param pid: paste id + :param sub: user id + :param key: user's master key + :param backend: user model backend + :param salt: randomization salt + """ + + for k, v in load(User(sub), key, backend, salt).index.items(): + + if bytes.fromhex(k) == pid: + + return PasteKey(bytes.fromhex(v)) + + return None + + +def dump_paste_key( + pid: PasteId, + pkey: PasteKey, + sub: Sub, + key: MasterKey, + backend: object, + salt: str = Config.salt) -> None: + """dump a user paste key + + :param pid: paste id + :param key: paste key + :param sub: user id + :param key: user's master key + :param backend: user model backend + """ + + model = load(User(sub), key, backend, salt) + + dump(User(*model[:-1], Index({ + **model.index, + **{pid.hex(): pkey.hex()} + })), key, backend, salt) + + +def authenticate( + user_id: bytes, + password: bytes, + backend: object, + salt: Salt = Config.salt): + """authenticate a user + + :param user_id: human-readable user id + :param password: clear text password + """ + + sub = Sub(dhash(user_id)) + key = MasterKey(derive_key(password, salt)) + key_hash = KeyHash(dhash(key)) + + proto = User(sub) + + try: + model = load(proto, key, backend, salt) + except IndexError as e: + raise AuthenticationError('you dun goofed') + + if not model: + + model = User(sub, key_hash, Index({})) + dump(model, key, backend, salt) + else: + + if model.key_hash != key_hash: + + raise AuthenticationError('you dun goofed') + + return { + 'sub': sub, + 'master_key': key + } + + +def _authenticate_connexion(user_id: str, password: str): + + from httpaste.helper.exception import ForbiddenHttpException + from flask import current_app + + try: + + return authenticate(user_id.encode('utf-8'), password.encode('utf-8'), + current_app.httpaste.backend.user, + current_app.httpaste.salt) + except AuthenticationError as e: + + raise ForbiddenHttpException('You shall not pass!') from e diff --git a/src/httpaste/schema/httpaste.openapi.json b/src/httpaste/schema/httpaste.openapi.json new file mode 100644 index 0000000..a8961cf --- /dev/null +++ b/src/httpaste/schema/httpaste.openapi.json @@ -0,0 +1,322 @@ +{ + "openapi": "3.0.3", + "info": { + "version": "0.0.0a1", + "title": "httpaste", + "description": "secure and simple pasting over HTTP", + "contact": { + "name": "Tiara Rodney (victoryk.it)", + "email": "t.rodney@victoryk.it" + }, + "license": { + "name": "GPL 3.0", + "url": "https://www.gnu.org/licenses/gpl-3.0-standalone.html" + } + }, + "paths": { + "/": { + "get": { + "description": "get description", + "responses": { + "200": { + "description": "", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + }, + }, + "/paste/public": { + "get": { + "description": "get last 100 public pastes", + "responses": { + "200": { + "description": "", + } + } + }, + "post": { + "description": "create a new public paste", + "requestBody": { + "$ref": "#/components/requestBodies/pastePost" + }, + "security": [ + {} + ], + "parameters": [ + { + "$ref": "#/components/parameters/lifetime" + }, + { + "$ref": "#/components/parameters/encoding" + } + ], + "responses": { + "200": { + "description": "", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/paste/private": { + "get": { + "description": "get private paste ids", + "responses": { + "200": { + "description": "", + } + } + }, + "post": { + "description": "create a new private paste", + "requestBody": { + "$ref": "#/components/requestBodies/pastePost" + }, + "security": [ + { + "basicAuth": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/lifetime" + } + ], + "responses": { + "200": { + "description": "", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/paste/private/{id}": { + "get": { + "description": "get a private paste", + "security": [ + { + "basicAuth": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/id" + }, + { + "$ref": "#/components/parameters/syntax" + } + ], + "responses": { + "200": { + "description": "", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "put": { + "description": "update an existing private paste", + "security": [ + { + "basicAuth": [] + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/pastePost" + }, + "parameters": [ + { + "$ref": "#/components/parameters/id" + }, + { + "$ref": "#/components/parameters/lifetime" + } + ], + "responses": { + "200": { + "description": "", + } + } + }, + "delete": { + "description": "delete an existing private paste", + "security": [ + { + "basicAuth": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/id" + } + ], + "responses": { + "200": { + "description": "", + } + } + } + }, + "/paste/public/{id}": { + "get": { + "description": "get a public paste", + "security": [ + {} + ], + "parameters": [ + { + "$ref": "#/components/parameters/id" + }, + { + "$ref": "#/components/parameters/syntax" + }, + { + "$ref": "#/components/parameters/format" + }, + { + "$ref": "#/components/parameters/linenos" + }, + { + "$ref": "#/components/parameters/mime" + } + ], + "responses": { + "200": { + "description": "", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + }, + "components": { + "requestBodies": { + "pastePost": { + "description": "Optional description in *Markdown*", + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "string", + "format": "binary" + }, + "rsa_public_key": { + "description": "RSA public key", + "type": "string" + } + }, + "required": [ + "data" + ] + } + } + } + } + }, + "parameters": { + "id": { + "description": "id of paste", + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + "lifetime": { + "description": "paste lifetime/expiration (in minutes)", + "name": "lifetime", + "in": "query", + "required": false, + "schema": { + "type": "number" + } + }, + "syntax": { + "description": "syntax highlighting language", + "name": "syntax", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + "format": { + "description": "output format of syntax highlighting", + "name": "format", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + "linenos": { + "description": "syntax highlighting with line numbers", + "name": "linenos", + "in": "query", + "required": false, + "schema": { + "type": "boolean" + } + }, + "encoding": { + "description": "syntax highlighting with line numbers", + "name": "encoding", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": ["base64"] + } + }, + "mime": { + "description": "response mime type", + "name": "mime", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + }, + "securitySchemes": { + "basicAuth": { + "type": "http", + "scheme": "basic", + "x-basicInfoFunc": "httpaste.model.user._authenticate_connexion" + } + } + } +} \ No newline at end of file diff --git a/src/httpaste/wsgi.py b/src/httpaste/wsgi.py new file mode 100755 index 0000000..e45f821 --- /dev/null +++ b/src/httpaste/wsgi.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +"""httpaste WSGI entrypoint +""" +from httpaste import load_config, get_flask_app, get_config_path + +config, server_config = load_config(get_config_path) + +application = get_flask_app(config, server_config) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/httpaste/__init__.py b/tests/httpaste/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/httpaste/model/__init__.py b/tests/httpaste/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/httpaste/model/test_user.py b/tests/httpaste/model/test_user.py new file mode 100755 index 0000000..fc2a407 --- /dev/null +++ b/tests/httpaste/model/test_user.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +import pytest +from collections import namedtuple + +import base64 +from cryptography.fernet import Fernet, InvalidToken +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + + +def _kdf(passwd): + + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=b'#', + iterations=390000, + ) + + return base64.urlsafe_b64encode(kdf.derive(passwd)) + + +def _encrypt(data:bytes, passwd:str): + + return Fernet(_kdf(passwd)).encrypt(data) + + +def _decrypt(data:bytes, passwd:str): + + return Fernet(_kdf(passwd)).decrypt(data) + + +@pytest.fixture +def module(): + + from httpaste.model import user + + return user + + + +@pytest.fixture +def encrypt(): + + return _encrypt + + +def decrypt(): + + return _decrypt + + +class Test_load(): + + @pytest.fixture(autouse=True) + def setup(self, module): + + self.func = module.load + + def test_default(self, module, encrypt): + + master_key = b'test' + key_hash = b'foobar-hash' + data = {'foo': 'bar'} + sdata = b'{"foo": "bar"}' + sub = b'foobar-sub' + _model = module.Model(sub, key_hash, encrypt(sdata, master_key)) + backend_mock = namedtuple('Backend', ['load',])(load=lambda m: _model) + + result = self.func(module.Model(sub), master_key, backend_mock) + + assert result.sub == sub + assert result.key_hash == key_hash + assert result.index == data + + + def test_index_not_map(self, module, encrypt): + + master_key = b'test' + key_hash = b'foobar-hash' + sdata = b'"foo"' + sub = b'foobar-sub' + _model = module.Model(sub, key_hash, encrypt(sdata, master_key)) + backend_mock = namedtuple('Backend', ['load',])(load=lambda m: _model) + + with pytest.raises(TypeError): + self.func(module.Model(sub), master_key, backend_mock) + + + def test_decryption_error(self, module): + + master_key = b'test' + key_hash = b'foobar-hash' + sub = b'foobar-sub' + _model = module.Model(sub, key_hash, b'34powkgopk') + backend_mock = namedtuple('Backend', ['load',])(load=lambda m: _model) + + with pytest.raises(InvalidToken): + self.func(module.Model(sub), master_key, backend_mock) \ No newline at end of file diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..524fc47 --- /dev/null +++ b/tox.ini @@ -0,0 +1,151 @@ +[tox] +skipsdist = true +minversion = 3.7.0 +isolated_build = True +envlist = lint, test, build, docs + + +[testenv:shell] +description = launch python shell +setenv = PYTHONPATH = {toxinidir}/src +deps = + {toxinidir} +commands = + python3 + +[testenv:lint] +description = lint with pylint +setenv = PYTHONPATH = {toxinidir}/src +deps = + {toxinidir} + pylint >= 2.12.2, < 3 +commands = + python3 -m pylint {toxinidir}/src {posargs} + + +[testenv:test] +description = test suite with pytest +setenv = PYTHONPATH = {toxinidir}/src +deps = + {toxinidir} + pytest + pytest-cov +commands = + pytest --junitxml=test-reports/full.xml --cov-report term --cov-report html:test-reports/coverage --cov=taskdog {posargs} + + +[testenv:build] +description = build and package +basepython = python3 +setenv = PYTHONPATH = {toxinidir}/src +deps = + build >= 0.5.1, < 1 +commands = + python3 -m build {posargs} + + +[testenv:docs] +description = build documentation +basepython = python3 +setenv = PYTHONPATH = {toxinidir}/src +allowlist_externals = + sphinx-build + plantuml +deps = + {toxinidir} + sphinx >= 4.3.2, < 5 + sphinx-rtd-theme >= 1.0.0, < 2 + sphinx-markdown-builder >= 0.5.4, < 1 + sphinxcontrib-plantuml >= 0.22, < 1 + sphinx-autodoc-typehints >= 1.17.0, < 2 + sphinx-argparse +commands = + sphinx-build -d "{toxinidir}/docs/_tree/html" docs "build/docs" --color -W -bhtml {posargs} + sphinx-build -d "{toxinidir}/docs/_tree/_" docs . --color -W -bmarkdown -treadme {posargs} + + +[testenv:diff] +description = diff +allowlist_externals = + git +commands = + git diff --exit-code {posargs} + + +[testenv:format] +description = format code +basepython = python3 +deps = + autopep8 >= 1.6.0, < 2 +commands = + python3 -m autopep8 -v src/ {posargs} + + +[testenv:graph] +description = build documentation +basepython = python3 +allowlist_externals = + graphviz + /bin/sh + pyan +deps = + {toxinidir} + pyan3 +commands = + pyan --root {toxinidir}/src --uses --no-defines --colored --grouped --annotated --html + + +[testenv:publish-testpypi] +description = publish to pypi test repository +passenv = + #https://twine.readthedocs.io/en/stable/#environment-variables + TWINE_USERNAME + TWINE_PASSWORD + TWINE_REPOSITORY + TWINE_REPOSITORY_URL + TWINE_CERT + TWINE_NON_INTERACTIVE +deps = + twine +commands = + python3 -m twine upload --repository testpypi "dist/taskdog*" + + +[testenv:publish] +description = publish to pypi repository +passenv = + #https://twine.readthedocs.io/en/stable/#environment-variables + TWINE_USERNAME + TWINE_PASSWORD + TWINE_REPOSITORY + TWINE_REPOSITORY_URL + TWINE_CERT + TWINE_NON_INTERACTIVE +deps = + twine +commands = + python3 -m twine upload "dist/*" + + +[testenv:publish-docs] +description = publish documentation +setenv = + tmppath = {envdir}/git/vicytorykit.bitbucket.io +passenv = + #https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/ + BITBUCKET_REPO_SLUG +allowlist_externals = + /bin/sh + /bin/rm + /bin/cp + /bin/mkdir + /usr/bin/git +commands = + python3 -c "exec('import os\nif \'BITBUCKET_REPO_SLUG\' not in os.environ.keys(): exit(1)')" + rm -rf {env:tmppath} + git clone git@bitbucket.org:victorykit/victorykit.bitbucket.io.git {env:tmppath} + mkdir -p "{env:tmppath}/{env:BITBUCKET_REPO_SLUG}" + cp -r dist/docs/ "{env:tmppath}/{env:BITBUCKET_REPO_SLUG}" + sh -c "cd {env:tmppath}; git add {env:BITBUCKET_REPO_SLUG}" + sh -c "cd {env:tmppath}; git -c 'user.name=victoryk.it Bot' -c 'user.email=commits-noreply@victoryk.it' commit -m 'updated {env:BITBUCKET_REPO_SLUG}'" + sh -c "cd {env:tmppath}; git push"