├── .github └── workflows │ └── main.yml ├── EULA.md ├── LICENSE.md ├── NOTICE ├── Privacy-Policy.md ├── README.md ├── SECURITY.md ├── command ├── make_certificate.sh └── openssl.cnf └── goat_functional_tests ├── .net ├── ApiTest.cs └── ApiTests.csproj ├── Insomnia_goat.json ├── custom_har.py ├── goat-rest-assured ├── pom.xml ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── pynt │ │ │ └── rest │ │ │ └── App.java │ └── test │ │ └── java │ │ └── com │ │ └── pynt │ │ └── rest │ │ └── AppTest.java └── target │ ├── classes │ └── com │ │ └── pynt │ │ └── rest │ │ └── App.class │ └── test-classes │ └── com │ └── pynt │ └── rest │ └── AppTest.class ├── goat.har ├── goat.jmx ├── goat.postman_collection.json ├── goat_burp.xml ├── goat_functional_test.py ├── goat_test.go ├── jest.test.js ├── k8s ├── README.md ├── pynt-manifests │ ├── app.yaml │ └── secret.yaml ├── run_scan.sh └── scan-trigger │ └── job.yaml ├── open_api_spec_3.json └── selenium ├── crapi_selenium.py └── requirements.txt /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: goat end 2 end functional tests 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | comment: 6 | type: string 7 | default: "API Security tests" 8 | 9 | env: 10 | PYNT_ID: ${{ secrets.YOURPYNTID }} 11 | 12 | jobs: 13 | api-security: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: get goat funtional tests 18 | run: | 19 | curl https://raw.githubusercontent.com/pynt-io/pynt/main/goat_functional_tests/goat_functional_test.py -o goat_e2e.py 20 | - name: Run goat functional tests 21 | run: | 22 | python3 -m pip install pytest 23 | pytest goat_e2e.py 24 | - name: install pynt cli 25 | run: | 26 | python3 -m pip install --upgrade pyntcli 27 | - name: run pynt on the funcional tests 28 | run: | 29 | pynt command --cmd "pytest goat_e2e.py" --return-error=errors-only 30 | 31 | -------------------------------------------------------------------------------- /EULA.md: -------------------------------------------------------------------------------- 1 | End-User License Agreement ("Agreement") 2 | = 3 | 4 | Last updated: June 28, 2022 5 | 6 | Please read this End-User License Agreement carefully before downloading or using Pynt. 7 | 8 | Interpretation and Definitions 9 | ============================== 10 | 11 | Interpretation 12 | -------------- 13 | 14 | The words of which the initial letter is capitalized have meanings defined 15 | under the following conditions. The following definitions shall have the same 16 | meaning regardless of whether they appear in singular or in plural. 17 | 18 | Definitions 19 | ----------- 20 | 21 | For the purposes of this End-User License Agreement: 22 | 23 | * Agreement means this End-User License Agreement that forms the entire 24 | agreement between You and the Company regarding the use of the 25 | Application. This Agreement has been created with the help of the 26 | [TermsFeed EULA Generator](https://www.termsfeed.com/eula-generator/). 27 | 28 | * Application means the software program provided by the Company downloaded 29 | by You to a Device, named Pynt 30 | 31 | * Company (referred to as either "the Company", "We", "Us" or "Our" in this 32 | Agreement) refers to OVERCAST SECURITY LTD, Mishmar Hayarden 9, Rishon 33 | Lezion. 34 | 35 | * Content refers to content such as text, images, or other information that 36 | can be posted, uploaded, linked to or otherwise made available by You, 37 | regardless of the form of that content. 38 | 39 | * Country refers to: Israel 40 | 41 | * Device means any device that can access the Application such as a 42 | computer, a cellphone or a digital tablet. 43 | 44 | * Third-Party Services means any services or content (including data, 45 | information, applications and other products services) provided by a 46 | third-party that may be displayed, included or made available by the 47 | Application. 48 | 49 | * You means the individual accessing or using the Application or the 50 | company, or other legal entity on behalf of which such individual is 51 | accessing or using the Application, as applicable. 52 | 53 | 54 | Acknowledgment 55 | ============== 56 | 57 | By clicking the "I Agree" button, downloading or using the Application, You 58 | are agreeing to be bound by the terms and conditions of this Agreement. If You 59 | do not agree to the terms of this Agreement, do not click on the "I Agree" 60 | button, do not download or do not use the Application. 61 | 62 | This Agreement is a legal document between You and the Company and it governs 63 | your use of the Application made available to You by the Company. 64 | 65 | The Application is licensed, not sold, to You by the Company for use strictly 66 | in accordance with the terms of this Agreement. 67 | 68 | License 69 | ======= 70 | 71 | Scope of License 72 | ---------------- 73 | 74 | The Company grants You a revocable, non-exclusive, non-transferable, limited 75 | license to download, install and use the Application strictly in accordance 76 | with the terms of this Agreement. 77 | 78 | The license that is granted to You by the Company is solely for your personal, 79 | non-commercial purposes strictly in accordance with the terms of this 80 | Agreement. 81 | 82 | Third-Party Services 83 | ==================== 84 | 85 | The Application may display, include or make available third-party content 86 | (including data, information, applications and other products services) or 87 | provide links to third-party websites or services. 88 | 89 | You acknowledge and agree that the Company shall not be responsible for any 90 | Third-party Services, including their accuracy, completeness, timeliness, 91 | validity, copyright compliance, legality, decency, quality or any other aspect 92 | thereof. The Company does not assume and shall not have any liability or 93 | responsibility to You or any other person or entity for any Third-party 94 | Services. 95 | 96 | You must comply with applicable Third parties' Terms of agreement when using 97 | the Application. Third-party Services and links thereto are provided solely as 98 | a convenience to You and You access and use them entirely at your own risk and 99 | subject to such third parties' Terms and conditions. 100 | 101 | Term and Termination 102 | ==================== 103 | 104 | This Agreement shall remain in effect until terminated by You or the Company. 105 | The Company may, in its sole discretion, at any time and for any or no reason, 106 | suspend or terminate this Agreement with or without prior notice. 107 | 108 | This Agreement will terminate immediately, without prior notice from the 109 | Company, in the event that you fail to comply with any provision of this 110 | Agreement. You may also terminate this Agreement by deleting the Application 111 | and all copies thereof from your Device or from your computer. 112 | 113 | Upon termination of this Agreement, You shall cease all use of the Application 114 | and delete all copies of the Application from your Device. 115 | 116 | Termination of this Agreement will not limit any of the Company's rights or 117 | remedies at law or in equity in case of breach by You (during the term of this 118 | Agreement) of any of your obligations under the present Agreement. 119 | 120 | Indemnification 121 | =============== 122 | 123 | You agree to indemnify and hold the Company and its parents, subsidiaries, 124 | affiliates, officers, employees, agents, partners and licensors (if any) 125 | harmless from any claim or demand, including reasonable attorneys' fees, due 126 | to or arising out of your: (a) use of the Application; (b) violation of this 127 | Agreement or any law or regulation; or (c) violation of any right of a third 128 | party. 129 | 130 | No Warranties 131 | ============= 132 | 133 | The Application is provided to You "AS IS" and "AS AVAILABLE" and with all 134 | faults and defects without warranty of any kind. To the maximum extent 135 | permitted under applicable law, the Company, on its own behalf and on behalf 136 | of its affiliates and its and their respective licensors and service 137 | providers, expressly disclaims all warranties, whether express, implied, 138 | statutory or otherwise, with respect to the Application, including all implied 139 | warranties of merchantability, fitness for a particular purpose, title and 140 | non-infringement, and warranties that may arise out of course of dealing, 141 | course of performance, usage or trade practice. Without limitation to the 142 | foregoing, the Company provides no warranty or undertaking, and makes no 143 | representation of any kind that the Application will meet your requirements, 144 | achieve any intended results, be compatible or work with any other software, 145 | applications, systems or services, operate without interruption, meet any 146 | performance or reliability standards or be error free or that any errors or 147 | defects can or will be corrected. 148 | 149 | Without limiting the foregoing, neither the Company nor any of the company's 150 | provider makes any representation or warranty of any kind, express or implied: 151 | (i) as to the operation or availability of the Application, or the 152 | information, content, and materials or products included thereon; (ii) that 153 | the Application will be uninterrupted or error-free; (iii) as to the accuracy, 154 | reliability, or currency of any information or content provided through the 155 | Application; or (iv) that the Application, its servers, the content, or 156 | e-mails sent from or on behalf of the Company are free of viruses, scripts, 157 | trojan horses, worms, malware, timebombs or other harmful components. 158 | 159 | Some jurisdictions do not allow the exclusion of certain types of warranties 160 | or limitations on applicable statutory rights of a consumer, so some or all of 161 | the above exclusions and limitations may not apply to You. But in such a case 162 | the exclusions and limitations set forth in this section shall be applied to 163 | the greatest extent enforceable under applicable law. To the extent any 164 | warranty exists under law that cannot be disclaimed, the Company shall be 165 | solely responsible for such warranty. 166 | 167 | Limitation of Liability 168 | ======================= 169 | 170 | Notwithstanding any damages that You might incur, the entire liability of the 171 | Company and any of its suppliers under any provision of this Agreement and 172 | your exclusive remedy for all of the foregoing shall be limited to the amount 173 | actually paid by You for the Application or through the Application or 100 USD 174 | if You haven't purchased anything through the Application. 175 | 176 | To the maximum extent permitted by applicable law, in no event shall the 177 | Company or its suppliers be liable for any special, incidental, indirect, or 178 | consequential damages whatsoever (including, but not limited to, damages for 179 | loss of profits, loss of data or other information, for business interruption, 180 | for personal injury, loss of privacy arising out of or in any way related to 181 | the use of or inability to use the Application, third-party software and/or 182 | third-party hardware used with the Application, or otherwise in connection 183 | with any provision of this Agreement), even if the Company or any supplier has 184 | been advised of the possibility of such damages and even if the remedy fails 185 | of its essential purpose. 186 | 187 | Some states/jurisdictions do not allow the exclusion or limitation of 188 | incidental or consequential damages, so the above limitation or exclusion may 189 | not apply to You. 190 | 191 | Severability and Waiver 192 | ======================= 193 | 194 | Severability 195 | ------------ 196 | 197 | If any provision of this Agreement is held to be unenforceable or invalid, 198 | such provision will be changed and interpreted to accomplish the objectives of 199 | such provision to the greatest extent possible under applicable law and the 200 | remaining provisions will continue in full force and effect. 201 | 202 | Waiver 203 | ------ 204 | 205 | Except as provided herein, the failure to exercise a right or to require 206 | performance of an obligation under this Agreement shall not effect a party's 207 | ability to exercise such right or require such performance at any time 208 | thereafter nor shall the waiver of a breach constitute a waiver of any 209 | subsequent breach. 210 | 211 | Product Claims 212 | ============== 213 | 214 | The Company does not make any warranties concerning the Application. 215 | 216 | United States Legal Compliance 217 | ============================== 218 | 219 | You represent and warrant that (i) You are not located in a country that is 220 | subject to the United States government embargo, or that has been designated 221 | by the United States government as a "terrorist supporting" country, and (ii) 222 | You are not listed on any United States government list of prohibited or 223 | restricted parties. 224 | 225 | Changes to this Agreement 226 | ========================= 227 | 228 | The Company reserves the right, at its sole discretion, to modify or replace 229 | this Agreement at any time. If a revision is material we will provide at least 230 | 30 days' notice prior to any new terms taking effect. What constitutes a 231 | material change will be determined at the sole discretion of the Company. 232 | 233 | By continuing to access or use the Application after any revisions become 234 | effective, You agree to be bound by the revised terms. If You do not agree to 235 | the new terms, You are no longer authorized to use the Application. 236 | 237 | Governing Law 238 | ============= 239 | 240 | The laws of the Country, excluding its conflicts of law rules, shall govern 241 | this Agreement and your use of the Application. Your use of the Application 242 | may also be subject to other local, state, national, or international laws. 243 | 244 | Entire Agreement 245 | ================ 246 | 247 | The Agreement constitutes the entire agreement between You and the Company 248 | regarding your use of the Application and supersedes all prior and 249 | contemporaneous written or oral agreements between You and the Company. 250 | 251 | You may be subject to additional terms and conditions that apply when You use 252 | or purchase other Company's services, which the Company will provide to You at 253 | the time of such use or purchase. 254 | 255 | Contact Us 256 | ========== 257 | 258 | If you have any questions about this Agreement, You can contact Us: 259 | 260 | * By email: support@pynt.io 261 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 OVERCAST SECURITY LTD 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Pynt 2 | Copyright 2022 OVERCAST SECURITY LTD 3 | 4 | This product includes software developed at 5 | OVERCAST SECURITY LTD (https://www.pynt.io/). 6 | 7 | This product includes software from the newman project (Appache 2.0 License) 8 | * Copyright 2016 Postdot Technologies, Inc. All rights reserved. 9 | 10 | This product includes software from the mitmproxy (MIT License) 11 | * Copyright 2013 Aldo Cortesi. All rights reserved. 12 | -------------------------------------------------------------------------------- /Privacy-Policy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | Last updated: June 06, 2023 4 | 5 | This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your information when You use the Service and tells You about Your privacy rights and how the law protects You. 6 | 7 | We use Your Personal data to provide and improve the Service. By using the Service, You agree to the collection and use of information in accordance with this Privacy Policy. This Privacy Policy has been created with the help of the [Privacy Policy Generator](https://www.privacypolicies.com/privacy-policy-generator/). 8 | 9 | # Interpretation and Definitions 10 | 11 | ## Interpretation 12 | 13 | The words of which the initial letter is capitalized have meanings defined under the following conditions. The following definitions shall have the same meaning regardless of whether they appear in singular or in plural. 14 | 15 | ## Definitions 16 | 17 | For the purposes of this Privacy Policy: 18 | 19 | - __Account__ means a unique account created for You to access our Service or parts of our Service. 20 | - __Affiliate__ means an entity that controls, is controlled by or is under common control with a party, where "control" means ownership of 50% or more of the shares, equity interest or other securities entitled to vote for election of directors or other managing authority. 21 | 22 | - __Application__ refers to Pynt, the software program provided by the Company. 23 | 24 | 25 | 26 | - __Company__ (referred to as either "the Company", "We", "Us" or "Our" in this Agreement) refers to Overcast Security LTD., Begin 154, Tel Aviv, Israel. 27 | 28 | 29 | - __Cookies__ are small files that are placed on Your computer, mobile device or any other device by a website, containing the details of Your browsing history on that website among its many uses. 30 | 31 | - __Country__ refers to: Israel 32 | 33 | - __Device__ means any device that can access the Service such as a computer, a cellphone or a digital tablet. 34 | 35 | 36 | 37 | - __Personal Data__ is any information that relates to an identified or identifiable individual. 38 | 39 | 40 | 41 | - __Service__ refers to the Application or the Website or both. 42 | 43 | - __Service Provider__ means any natural or legal person who processes the data on behalf of the Company. It refers to third-party companies or individuals employed by the Company to facilitate the Service, to provide the Service on behalf of the Company, to perform services related to the Service or to assist the Company in analyzing how the Service is used. 44 | 45 | 46 | - __Third-party Social Media Service__ refers to any website or any social network website through which a User can log in or create an account to use the Service. 47 | 48 | - __Usage Data__ refers to data collected automatically, either generated by the use of the Service or from the Service infrastructure itself (for example, the duration of a page visit). 49 | 50 | - __Website__ refers to Pynt, accessible from www.pynt.io 51 | 52 | - __You__ means the individual accessing or using the Service, or the company, or other legal entity on behalf of which such individual is accessing or using the Service, as applicable. 53 | 54 | 55 | 56 | # Collecting and Using Your Personal Data 57 | 58 | ## Types of Data Collected 59 | 60 | ### Personal Data 61 | 62 | While using Our Service, We may ask You to provide Us with certain personally identifiable information that can be used to contact or identify You. Personally identifiable information may include, but is not limited to: 63 | 64 | - Email address 65 | - First name and last name 66 | - Phone number 67 | - Address, State, Province, ZIP/Postal code, City 68 | 69 | 70 | - Usage Data 71 | 72 | 73 | 74 | ### Usage Data 75 | 76 | Usage Data is collected automatically when using the Service. 77 | 78 | Usage Data may include information such as Your Device's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that You visit, the time and date of Your visit, the time spent on those pages, unique device identifiers and other diagnostic data. 79 | 80 | When You access the Service by or through a mobile device, We may collect certain information automatically, including, but not limited to, the type of mobile device You use, Your mobile device unique ID, the IP address of Your mobile device, Your mobile operating system, the type of mobile Internet browser You use, unique device identifiers and other diagnostic data. 81 | 82 | We may also collect information that Your browser sends whenever You visit our Service or when You access the Service by or through a mobile device. 83 | 84 | 85 | ### Information from Third-Party Social Media Services 86 | 87 | The Company allows You to create an account and log in to use the Service through the following Third-party Social Media Services: 88 | 89 | - Google 90 | - Facebook 91 | - Twitter 92 | - LinkedIn 93 | 94 | If You decide to register through or otherwise grant us access to a Third-Party Social Media Service, We may collect Personal data that is already associated with Your Third-Party Social Media Service's account, such as Your name, Your email address, Your activities or Your contact list associated with that account. 95 | 96 | You may also have the option of sharing additional information with the Company through Your Third-Party Social Media Service's account. If You choose to provide such information and Personal Data, during registration or otherwise, You are giving the Company permission to use, share, and store it in a manner consistent with this Privacy Policy. 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | ### Tracking Technologies and Cookies 105 | 106 | We use Cookies and similar tracking technologies to track the activity on Our Service and store certain information. Tracking technologies used are beacons, tags, and scripts to collect and track information and to improve and analyze Our Service. The technologies We use may include: 107 | 108 | - __Cookies or Browser Cookies.__ A cookie is a small file placed on Your Device. You can instruct Your browser to refuse all Cookies or to indicate when a Cookie is being sent. However, if You do not accept Cookies, You may not be able to use some parts of our Service. Unless you have adjusted Your browser setting so that it will refuse Cookies, our Service may use Cookies. 109 | - __Web Beacons.__ Certain sections of our Service and our emails may contain small electronic files known as web beacons (also referred to as clear gifs, pixel tags, and single-pixel gifs) that permit the Company, for example, to count users who have visited those pages or opened an email and for other related website statistics (for example, recording the popularity of a certain section and verifying system and server integrity). 110 | 111 | Cookies can be "Persistent" or "Session" Cookies. Persistent Cookies remain on Your personal computer or mobile device when You go offline, while Session Cookies are deleted as soon as You close Your web browser. Learn more about cookies on the [Privacy Policies website](https://www.privacypolicies.com/blog/privacy-policy-template/#Use_Of_Cookies_Log_Files_And_Tracking) article. 112 | 113 | We use both Session and Persistent Cookies for the purposes set out below: 114 | 115 | - __Necessary / Essential Cookies__ 116 | 117 | Type: Session Cookies 118 | 119 | Administered by: Us 120 | 121 | Purpose: These Cookies are essential to provide You with services available through the Website and to enable You to use some of its features. They help to authenticate users and prevent fraudulent use of user accounts. Without these Cookies, the services that You have asked for cannot be provided, and We only use these Cookies to provide You with those services. 122 | - __Cookies Policy / Notice Acceptance Cookies__ 123 | 124 | Type: Persistent Cookies 125 | 126 | Administered by: Us 127 | 128 | Purpose: These Cookies identify if users have accepted the use of cookies on the Website. 129 | - __Functionality Cookies__ 130 | 131 | Type: Persistent Cookies 132 | 133 | Administered by: Us 134 | 135 | Purpose: These Cookies allow us to remember choices You make when You use the Website, such as remembering your login details or language preference. The purpose of these Cookies is to provide You with a more personal experience and to avoid You having to re-enter your preferences every time You use the Website. 136 | 137 | 138 | 139 | For more information about the cookies we use and your choices regarding cookies, please visit our Cookies Policy or the Cookies section of our Privacy Policy. 140 | 141 | 142 | ## Use of Your Personal Data 143 | 144 | The Company may use Personal Data for the following purposes: 145 | 146 | - __To provide and maintain our Service__, including to monitor the usage of our Service. 147 | - __To manage Your Account:__ to manage Your registration as a user of the Service. The Personal Data You provide can give You access to different functionalities of the Service that are available to You as a registered user. 148 | - __For the performance of a contract:__ the development, compliance and undertaking of the purchase contract for the products, items or services You have purchased or of any other contract with Us through the Service. 149 | - __To contact You:__ To contact You by email, telephone calls, SMS, or other equivalent forms of electronic communication, such as a mobile application's push notifications regarding updates or informative communications related to the functionalities, products or contracted services, including the security updates, when necessary or reasonable for their implementation. 150 | - __To provide You__ with news, special offers and general information about other goods, services and events which we offer that are similar to those that you have already purchased or enquired about unless You have opted not to receive such information. 151 | - __To manage Your requests:__ To attend and manage Your requests to Us. 152 | 153 | - __For business transfers:__ We may use Your information to evaluate or conduct a merger, divestiture, restructuring, reorganization, dissolution, or other sale or transfer of some or all of Our assets, whether as a going concern or as part of bankruptcy, liquidation, or similar proceeding, in which Personal Data held by Us about our Service users is among the assets transferred. 154 | - __For other purposes__: We may use Your information for other purposes, such as data analysis, identifying usage trends, determining the effectiveness of our promotional campaigns and to evaluate and improve our Service, products, services, marketing and your experience. 155 | 156 | We may share Your personal information in the following situations: 157 | 158 | - __With Service Providers:__ We may share Your personal information with Service Providers to monitor and analyze the use of our Service, to contact You. 159 | - __For business transfers:__ We may share or transfer Your personal information in connection with, or during negotiations of, any merger, sale of Company assets, financing, or acquisition of all or a portion of Our business to another company. 160 | - __With Affiliates:__ We may share Your information with Our affiliates, in which case we will require those affiliates to honor this Privacy Policy. Affiliates include Our parent company and any other subsidiaries, joint venture partners or other companies that We control or that are under common control with Us. 161 | - __With business partners:__ We may share Your information with Our business partners to offer You certain products, services or promotions. 162 | - __With other users:__ when You share personal information or otherwise interact in the public areas with other users, such information may be viewed by all users and may be publicly distributed outside. If You interact with other users or register through a Third-Party Social Media Service, Your contacts on the Third-Party Social Media Service may see Your name, profile, pictures and description of Your activity. Similarly, other users will be able to view descriptions of Your activity, communicate with You and view Your profile. 163 | - __With Your consent__: We may disclose Your personal information for any other purpose with Your consent. 164 | 165 | ## Retention of Your Personal Data 166 | 167 | The Company will retain Your Personal Data only for as long as is necessary for the purposes set out in this Privacy Policy. We will retain and use Your Personal Data to the extent necessary to comply with our legal obligations (for example, if we are required to retain your data to comply with applicable laws), resolve disputes, and enforce our legal agreements and policies. 168 | 169 | The Company will also retain Usage Data for internal analysis purposes. Usage Data is generally retained for a shorter period of time, except when this data is used to strengthen the security or to improve the functionality of Our Service, or We are legally obligated to retain this data for longer time periods. 170 | 171 | ## Transfer of Your Personal Data 172 | 173 | Your information, including Personal Data, is processed at the Company's operating offices and in any other places where the parties involved in the processing are located. It means that this information may be transferred to — and maintained on — computers located outside of Your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from Your jurisdiction. 174 | 175 | Your consent to this Privacy Policy followed by Your submission of such information represents Your agreement to that transfer. 176 | 177 | The Company will take all steps reasonably necessary to ensure that Your data is treated securely and in accordance with this Privacy Policy and no transfer of Your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of Your data and other personal information. 178 | 179 | ## Delete Your Personal Data 180 | 181 | You have the right to delete or request that We assist in deleting the Personal Data that We have collected about You. 182 | 183 | Our Service may give You the ability to delete certain information about You from within the Service. 184 | 185 | You may update, amend, or delete Your information at any time by signing in to Your Account, if you have one, and visiting the account settings section that allows you to manage Your personal information. You may also contact Us to request access to, correct, or delete any personal information that You have provided to Us. 186 | 187 | Please note, however, that We may need to retain certain information when we have a legal obligation or lawful basis to do so. 188 | 189 | ## Disclosure of Your Personal Data 190 | 191 | ### Business Transactions 192 | 193 | If the Company is involved in a merger, acquisition or asset sale, Your Personal Data may be transferred. We will provide notice before Your Personal Data is transferred and becomes subject to a different Privacy Policy. 194 | 195 | ### Law enforcement 196 | 197 | Under certain circumstances, the Company may be required to disclose Your Personal Data if required to do so by law or in response to valid requests by public authorities (e.g. a court or a government agency). 198 | 199 | ### Other legal requirements 200 | 201 | The Company may disclose Your Personal Data in the good faith belief that such action is necessary to: 202 | 203 | - Comply with a legal obligation 204 | - Protect and defend the rights or property of the Company 205 | - Prevent or investigate possible wrongdoing in connection with the Service 206 | - Protect the personal safety of Users of the Service or the public 207 | - Protect against legal liability 208 | 209 | ## Security of Your Personal Data 210 | 211 | The security of Your Personal Data is important to Us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While We strive to use commercially acceptable means to protect Your Personal Data, We cannot guarantee its absolute security. 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | # Children's Privacy 229 | 230 | Our Service does not address anyone under the age of 13. We do not knowingly collect personally identifiable information from anyone under the age of 13. If You are a parent or guardian and You are aware that Your child has provided Us with Personal Data, please contact Us. If We become aware that We have collected Personal Data from anyone under the age of 13 without verification of parental consent, We take steps to remove that information from Our servers. 231 | 232 | If We need to rely on consent as a legal basis for processing Your information and Your country requires consent from a parent, We may require Your parent's consent before We collect and use that information. 233 | 234 | 235 | # Links to Other Websites 236 | 237 | Our Service may contain links to other websites that are not operated by Us. If You click on a third party link, You will be directed to that third party's site. We strongly advise You to review the Privacy Policy of every site You visit. 238 | 239 | We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services. 240 | 241 | # Changes to this Privacy Policy 242 | 243 | We may update Our Privacy Policy from time to time. We will notify You of any changes by posting the new Privacy Policy on this page. 244 | 245 | We will let You know via email and/or a prominent notice on Our Service, prior to the change becoming effective and update the "Last updated" date at the top of this Privacy Policy. 246 | 247 | You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page. 248 | 249 | # Contact Us 250 | 251 | If you have any questions about this Privacy Policy, You can contact us: 252 | 253 | 254 | - By email: support@pynt.io 255 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Pynt logo 01b 3 |

4 | 5 | ## Description: 6 | 7 | Pynt [(pynt.io)](https://www.pynt.io/) is a **free API security testing solution**. Pynt brings API security to developers and testers. 8 | 9 | Pynt empowers developers and testers to build secure APIs from the very start of the development process. 10 | 11 | Pynt’s developer-first approach allows organizations to secure the assets behind their APIs before they are released into production, ensuring that their products are secure at their most vulnerable components - APIs. 12 | 13 | Pynt’s API solution carries out automated hacks of your APIs to find the most critical issues and zero day vulnerabilities in less than two minutes, with no configuration required. 14 | 15 | Pynt seamlessly integrates into existing development tools and CI/CD workflows. You choose how to use Pynt: 16 | - Start from [Pynt's area in Postman public workspace](https://www.postman.com/pynt-io/workspace/pynt/overview) 17 | - As [Newman CLI wrapper](https://docs.pynt.io/documentation/onboarding/getting-started/pynt-newman) 18 | - As part of [GitHub actions](https://github.com/pynt-io/pynt-newman) 19 | 20 | ## EULA and Privacy Policy 21 | 22 | Please read the [EULA](https://github.com/pynt-io/pynt/blob/main/EULA.md) and the [privacy policy](https://github.com/pynt-io/pynt/blob/main/Privacy-Policy.md) carefully before downloading or using Pynt. 23 | 24 | ## Need Support? 25 | 26 | If you have questions or need any help, please email us at support@pynt.io. 27 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security and Disclosure Information Policy for Pynt's 2 | 3 | At Pynt, we are committed to maintaining the security of our systems and our customers' data. 4 | We believe in transparent and responsible disclosure of security vulnerabilities. 5 | Our policy outlines how we handle security vulnerabilities and related disclosures. 6 | 7 | * [Reporting a Vulnerability](#Reporting-a-Vulnerability) 8 | * [Security Announcements](#Security-Announcements) 9 | * [Security Vulnerability Response](#Security-Vulnerability-Response) 10 | 11 | ## Reporting a Vulnerability 12 | 13 | If you discover a security vulnerability within any of Pynt's services or products, we encourage you to report it to us as soon as possible. 14 | We request that you follow these guidelines: 15 | Email us at support@pynt.io with a detailed description of the vulnerability. 16 | Please provide sufficient details to enable us to reproduce and verify the issue. 17 | Do not disclose the vulnerability publicly or to any third parties before it is resolved. 18 | 19 | ## Security Announcements 20 | 21 | Pynt will make public announcements regarding significant security issues that affect our products or services. 22 | These announcements will be made through our slack community channel. 23 | You can join here [https://www.pynt.io/community](url) 24 | 25 | ## Security Vulnerability Response 26 | 27 | Upon receiving a vulnerability report, Pynt's security team will investigate the issue. 28 | We aim to acknowledge receipt of your report within 3 working days. 29 | Our team will work diligently to verify the vulnerability and assess its impact. 30 | We will communicate with the reporter throughout the resolution process. 31 | Once the vulnerability is resolved, we will provide an advisory to our users with details about the issue and the steps taken to address it. 32 | We appreciate the efforts of security researchers and users who report vulnerabilities in a responsible manner. 33 | Our aim is to collaborate effectively to ensure the security and privacy of our users and systems. 34 | -------------------------------------------------------------------------------- /command/make_certificate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Create root CA 4 | openssl req -x509 -new -nodes -newkey rsa:4096 -keyout rootCA.key -sha256 -days 1024 -out rootCA.crt -subj "/C=US/ST=US/O=Self Signed/CN=Self Signed Root CA" -config openssl.cnf -extensions rootCA_ext 5 | cp rootCA.key rootCA.pem 6 | cat rootCA.crt >> rootCA.pem -------------------------------------------------------------------------------- /command/openssl.cnf: -------------------------------------------------------------------------------- 1 | [ req ] 2 | default_bits = 4096 3 | distinguished_name = req_distinguished_name 4 | string_mask = utf8only 5 | default_md = sha512 6 | 7 | [ req_distinguished_name ] 8 | countryName = Country Name (2 letter code) 9 | stateOrProvinceName = State or Province Name 10 | localityName = Locality Name 11 | 0.organizationName = Organization Name 12 | organizationalUnitName = Organizational Unit Name 13 | commonName = Common Name 14 | emailAddress = Email Address 15 | 16 | [ rootCA_ext ] 17 | subjectKeyIdentifier = hash 18 | authorityKeyIdentifier = keyid:always,issuer 19 | basicConstraints = critical, CA:true 20 | keyUsage = critical, digitalSignature, cRLSign, keyCertSign -------------------------------------------------------------------------------- /goat_functional_tests/.net/ApiTest.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net.Http; 5 | using System.Net.Http.Headers; 6 | using System.Text.Json; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | using System.Net.Http.Json; 10 | 11 | public class ApiTest : IClassFixture 12 | { 13 | private readonly HttpClient _client; 14 | private readonly ApiState _state; 15 | private const string BaseUrl = "http://44.202.3.35:6000"; 16 | 17 | public class ApiState 18 | { 19 | public string? JamesToken { get; set; } 20 | public string? LarsToken { get; set; } 21 | public string? JamesUid { get; set; } 22 | public string? LarsUid { get; set; } 23 | } 24 | 25 | public ApiTest(ApiState state) 26 | { 27 | _client = new HttpClient(); 28 | _state = state; 29 | } 30 | 31 | private async Task SendAsync(HttpRequestMessage request, string? token = null) 32 | { 33 | if (!string.IsNullOrEmpty(token)) 34 | request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); 35 | 36 | return await _client.SendAsync(request); 37 | } 38 | 39 | private async Task Login(string userName, string password) 40 | { 41 | var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/login") 42 | { 43 | Content = JsonContent.Create(new { userName, password }) 44 | }; 45 | return await SendAsync(request); 46 | } 47 | 48 | private async Task GetAccount(string token) 49 | { 50 | var request = new HttpRequestMessage(HttpMethod.Get, $"{BaseUrl}/account"); 51 | return await SendAsync(request, token); 52 | } 53 | 54 | private async Task GetTransactions(string token, string uid, int limit) 55 | { 56 | var url = $"{BaseUrl}/transactions?limit={limit}&userId={uid}"; 57 | var request = new HttpRequestMessage(HttpMethod.Get, url); 58 | return await SendAsync(request, token); 59 | } 60 | 61 | [Fact] 62 | public async Task James_Can_Login() 63 | { 64 | var response = await Login("James", "ILoveGuitars"); 65 | Assert.True(response.IsSuccessStatusCode); 66 | 67 | var json = await response.Content.ReadAsStringAsync(); 68 | _state.JamesToken = JsonDocument.Parse(json).RootElement.GetProperty("token").GetString(); 69 | } 70 | 71 | [Fact] 72 | public async Task Get_James_User_Info() 73 | { 74 | if (string.IsNullOrEmpty(_state.JamesToken)) 75 | { 76 | var loginResp = await Login("James", "ILoveGuitars"); 77 | var loginJson = await loginResp.Content.ReadAsStringAsync(); 78 | _state.JamesToken = JsonDocument.Parse(loginJson).RootElement.GetProperty("token").GetString(); 79 | } 80 | 81 | var response = await GetAccount(_state.JamesToken!); 82 | Assert.True(response.IsSuccessStatusCode); 83 | 84 | var json = await response.Content.ReadAsStringAsync(); 85 | _state.JamesUid = JsonDocument.Parse(json).RootElement.GetProperty("userId").GetString(); 86 | } 87 | 88 | [Fact] 89 | public async Task Get_James_User_Info_From_GraphQL() 90 | { 91 | if (string.IsNullOrEmpty(_state.JamesToken) || string.IsNullOrEmpty(_state.JamesUid)) 92 | { 93 | await Get_James_User_Info(); 94 | } 95 | 96 | var query = new { query = "query { me { userId } }" }; 97 | var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/graphql") 98 | { 99 | Content = JsonContent.Create(query) 100 | }; 101 | var response = await SendAsync(request, _state.JamesToken); 102 | 103 | Assert.True(response.IsSuccessStatusCode); 104 | 105 | var json = await response.Content.ReadAsStringAsync(); 106 | var root = JsonDocument.Parse(json).RootElement; 107 | var userId = root.GetProperty("data").GetProperty("me").GetProperty("userId").GetString(); 108 | 109 | Assert.Equal(_state.JamesUid, userId); 110 | } 111 | 112 | [Fact] 113 | public async Task Get_James_Transactions() 114 | { 115 | if (string.IsNullOrEmpty(_state.JamesToken) || string.IsNullOrEmpty(_state.JamesUid)) 116 | { 117 | await Get_James_User_Info(); 118 | } 119 | 120 | var resp5 = await GetTransactions(_state.JamesToken!, _state.JamesUid!, 5); 121 | Assert.True(resp5.IsSuccessStatusCode); 122 | var json5 = JsonDocument.Parse(await resp5.Content.ReadAsStringAsync()); 123 | Assert.Equal(5, json5.RootElement.GetArrayLength()); 124 | 125 | var resp10 = await GetTransactions(_state.JamesToken!, _state.JamesUid!, 10); 126 | Assert.True(resp10.IsSuccessStatusCode); 127 | var json10 = JsonDocument.Parse(await resp10.Content.ReadAsStringAsync()); 128 | Assert.Equal(10, json10.RootElement.GetArrayLength()); 129 | } 130 | 131 | [Fact] 132 | public async Task Lars_Can_Login() 133 | { 134 | var response = await Login("Lars", "ILoveDrums"); 135 | Assert.True(response.IsSuccessStatusCode); 136 | 137 | var json = await response.Content.ReadAsStringAsync(); 138 | _state.LarsToken = JsonDocument.Parse(json).RootElement.GetProperty("token").GetString(); 139 | } 140 | 141 | [Fact] 142 | public async Task Get_Lars_User_Info() 143 | { 144 | if (string.IsNullOrEmpty(_state.LarsToken)) 145 | { 146 | var loginResp = await Login("Lars", "ILoveDrums"); 147 | var loginJson = await loginResp.Content.ReadAsStringAsync(); 148 | _state.LarsToken = JsonDocument.Parse(loginJson).RootElement.GetProperty("token").GetString(); 149 | } 150 | 151 | var response = await GetAccount(_state.LarsToken!); 152 | Assert.True(response.IsSuccessStatusCode); 153 | 154 | var json = await response.Content.ReadAsStringAsync(); 155 | _state.LarsUid = JsonDocument.Parse(json).RootElement.GetProperty("userId").GetString(); 156 | } 157 | 158 | [Fact] 159 | public async Task Get_Lars_Transactions() 160 | { 161 | if (string.IsNullOrEmpty(_state.LarsToken) || string.IsNullOrEmpty(_state.LarsUid)) 162 | { 163 | await Get_Lars_User_Info(); 164 | } 165 | 166 | var response = await GetTransactions(_state.LarsToken!, _state.LarsUid!, 5); 167 | Assert.True(response.IsSuccessStatusCode); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /goat_functional_tests/.net/ApiTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | false 5 | 6 | 7 | 8 | 9 | 10 | 11 | all 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /goat_functional_tests/Insomnia_goat.json: -------------------------------------------------------------------------------- 1 | {"_type":"export","__export_format":4,"__export_date":"2024-03-11T21:16:09.483Z","__export_source":"insomnia.desktop.app:v8.6.1","resources":[{"_id":"req_f7c47a8ff8724742bbdc3b90ca8b80e6","parentId":"wrk_4acf91a1c985464e8188f6201ba284ad","modified":1709629204455,"created":1709628969060,"url":"44.202.3.35:6000/login","name":"User Login","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"userName\": \"James\",\n \"password\": \"ILoveGuitars\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","disabled":false,"value":"application/json"}],"authentication":{},"metaSortKey":-1709628969161,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"wrk_4acf91a1c985464e8188f6201ba284ad","parentId":null,"modified":1709628493411,"created":1709628493411,"name":"goat.json","description":"","scope":"design","_type":"workspace"},{"_id":"req_e2c8009d29734c94beba6606a7102932","parentId":"wrk_4acf91a1c985464e8188f6201ba284ad","modified":1709629374738,"created":1709628969061,"url":"44.202.3.35:6000/account","name":"Get User Account","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{"type":"bearer","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJhYTc4NmI5ZS03NDZiLTQ4M2EtYTI2YS0xMDVlZjBmNDY2ZTYifQ.5wiIckUlguqJRUY36szfN0K3FLsfT34tXey_K4JPYIk"},"metaSortKey":-1709628969061,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_17d2a800731242448691da595ef21d67","parentId":"wrk_4acf91a1c985464e8188f6201ba284ad","modified":1709629556154,"created":1709628969061,"url":"44.202.3.35:6000/transactions","name":"Get User Transactions","description":"","method":"GET","body":{},"parameters":[{"name":"limit","disabled":false,"value":"5","id":"pair_dce9cd12b69d45ed8601db38290a9fee"},{"name":"userId","disabled":false,"value":"aa786b9e-746b-483a-a26a-105ef0f466e6","id":"pair_a824fe0d5b684413a58b50c76d37f1b8"}],"headers":[],"authentication":{"type":"bearer","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJhYTc4NmI5ZS03NDZiLTQ4M2EtYTI2YS0xMDVlZjBmNDY2ZTYifQ.5wiIckUlguqJRUY36szfN0K3FLsfT34tXey_K4JPYIk"},"metaSortKey":-1709628969061,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_f7f36a94fab140f8b8cd26e93bb33798","parentId":"wrk_4acf91a1c985464e8188f6201ba284ad","modified":1709629571981,"created":1709629566910,"url":"44.202.3.35:6000/transactions","name":"Get User Transactions 15","description":"","method":"GET","body":{},"parameters":[{"name":"limit","disabled":false,"value":"15","id":"pair_dce9cd12b69d45ed8601db38290a9fee"},{"name":"userId","disabled":false,"value":"aa786b9e-746b-483a-a26a-105ef0f466e6","id":"pair_a824fe0d5b684413a58b50c76d37f1b8"}],"headers":[],"authentication":{"type":"bearer","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJhYTc4NmI5ZS03NDZiLTQ4M2EtYTI2YS0xMDVlZjBmNDY2ZTYifQ.5wiIckUlguqJRUY36szfN0K3FLsfT34tXey_K4JPYIk"},"metaSortKey":-1709593477293.5,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"env_6be94f05db8838025d804771ce391fef210a189a","parentId":"wrk_4acf91a1c985464e8188f6201ba284ad","modified":1709628493423,"created":1709628493423,"name":"Base Environment","data":{},"dataPropertyOrder":null,"color":null,"isPrivate":false,"metaSortKey":1709628493423,"_type":"environment"},{"_id":"jar_6be94f05db8838025d804771ce391fef210a189a","parentId":"wrk_4acf91a1c985464e8188f6201ba284ad","modified":1709628493424,"created":1709628493424,"name":"Default Jar","cookies":[],"_type":"cookie_jar"},{"_id":"spc_8cab8c21b998463f8f84d5d0706cd66f","parentId":"wrk_4acf91a1c985464e8188f6201ba284ad","modified":1709628544522,"created":1709628544522,"fileName":"New Document","contents":"{\n \"swagger\": \"2.0\",\n \"info\": {\n \"version\": \"1.0.0\",\n \"title\": \"Goat API\",\n \"description\": \"Goat by Pynt is a vulnerable application example, used to demonstrate API security risks.\"\n },\n \"paths\": {\n \"/login\": {\n \"post\": {\n \"summary\": \"User Login\",\n \"consumes\": [\"application/json\"],\n \"produces\": [\"application/json\"],\n \"parameters\": [\n {\n \"name\": \"body\",\n \"in\": \"body\",\n \"description\": \"User credentials\",\n \"required\": true,\n \"schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"userName\": {\n \"type\": \"string\"\n },\n \"password\": {\n \"type\": \"string\"\n }\n }\n }\n }\n ],\n \"responses\": {\n \"200\": {\n \"description\": \"Successful login\",\n \"schema\": {\n \"type\": \"object\"\n }\n }\n }\n }\n },\n \"/account\": {\n \"get\": {\n \"summary\": \"Get User Account\",\n \"produces\": [\"application/json\"],\n \"responses\": {\n \"200\": {\n \"description\": \"Successful account retrieval\",\n \"schema\": {\n \"type\": \"object\"\n }\n }\n }\n }\n },\n \"/transactions\": {\n \"get\": {\n \"summary\": \"Get User Transactions\",\n \"produces\": [\"application/json\"],\n \"parameters\": [\n {\n \"name\": \"limit\",\n \"in\": \"query\",\n \"description\": \"Limit the number of transactions\",\n \"type\": \"integer\",\n \"format\": \"int32\"\n },\n {\n \"name\": \"userId\",\n \"in\": \"query\",\n \"description\": \"User ID\",\n \"type\": \"string\"\n }\n ],\n \"responses\": {\n \"200\": {\n \"description\": \"Successful transactions retrieval\",\n \"schema\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n }\n }\n }\n }\n }\n }\n }\n ","contentType":"yaml","_type":"api_spec"},{"_id":"uts_20b5cbaaeed24f359df033aeb8b8a191","parentId":"wrk_4acf91a1c985464e8188f6201ba284ad","modified":1709628654135,"created":1709628599608,"name":"goat tests","metaSortKey":-1709628599608,"_type":"unit_test_suite"},{"_id":"env_21f823b0f7ab40fd89005473af6e0431","parentId":"env_6be94f05db8838025d804771ce391fef210a189a","modified":1709628969060,"created":1709628969060,"name":"Swagger env","data":{"base_path":"","scheme":"http","host":""},"dataPropertyOrder":null,"color":null,"isPrivate":false,"metaSortKey":1709628969060,"_type":"environment"},{"_id":"ut_5c5a1ba1bc544d16982ad0f2313c593d","parentId":"uts_20b5cbaaeed24f359df033aeb8b8a191","modified":1709629619472,"created":1709628605905,"requestId":"req_f7f36a94fab140f8b8cd26e93bb33798","name":"Returns 200","code":"const response1 = await insomnia.send();\nexpect(response1.status).to.equal(200);","metaSortKey":-1709628605905,"_type":"unit_test"},{"_id":"ut_2e348a41f66a4faa9401eb318eb48698","parentId":"uts_20b5cbaaeed24f359df033aeb8b8a191","modified":1709629614952,"created":1709629587474,"requestId":"req_17d2a800731242448691da595ef21d67","name":"Returns 200","code":"const response1 = await insomnia.send();\nexpect(response1.status).to.equal(200);","metaSortKey":-1709629587474,"_type":"unit_test"},{"_id":"ut_ff0c5f06be5d44018169d965a042b601","parentId":"uts_20b5cbaaeed24f359df033aeb8b8a191","modified":1709629597181,"created":1709629588449,"requestId":"req_e2c8009d29734c94beba6606a7102932","name":"Returns 200","code":"const response1 = await insomnia.send();\nexpect(response1.status).to.equal(200);","metaSortKey":-1709629588449,"_type":"unit_test"},{"_id":"ut_d7211762defa4c4eb4f7fe1e9ad2d4e0","parentId":"uts_20b5cbaaeed24f359df033aeb8b8a191","modified":1709629620574,"created":1709629590003,"requestId":"req_f7c47a8ff8724742bbdc3b90ca8b80e6","name":"Returns 200","code":"const response1 = await insomnia.send();\nexpect(response1.status).to.equal(200);","metaSortKey":-1709629590003,"_type":"unit_test"}]} -------------------------------------------------------------------------------- /goat_functional_tests/custom_har.py: -------------------------------------------------------------------------------- 1 | """ 2 | Write flow objects to a HAR file 3 | this file uses the same logic as the HAR export in mitmproxy (savehar.py) 4 | pynt additions: 5 | - saves the body of GET requests as well (not supported by mitmproxy) 6 | - buffered writes *as data comes in* 7 | - "done" marker written to a different file (`pynt_ready_marker`) 8 | pynt flags: 9 | - pynthar 10 | - pynt_ready_marker 11 | 12 | command: 13 | mitmdump -s custom_har.py --set pynthar= --set pynt_ready_marker= 14 | 15 | to run the original mitmproxy har export: 16 | command: 17 | mitmdump --set hardump= 18 | """ 19 | 20 | import base64 21 | import json 22 | from datetime import datetime 23 | from datetime import timezone 24 | from io import TextIOWrapper 25 | from typing import Any 26 | 27 | from mitmproxy import ctx 28 | from mitmproxy import exceptions 29 | from mitmproxy import flowfilter 30 | from mitmproxy import http 31 | from mitmproxy.addonmanager import Loader 32 | from mitmproxy.connection import Server 33 | from mitmproxy.coretypes.multidict import _MultiDict 34 | from mitmproxy.utils import human 35 | from mitmproxy.utils import strutils 36 | 37 | 38 | class HARWriter: 39 | MAX_SIZE = 1073741824 # 1GB = 1024 * 1024 * 1024 40 | def __init__(self, har_file_path: str, ready_marker_path: str) -> None: 41 | self.ready_marker_path = ready_marker_path 42 | self.entries: list[dict] = [] 43 | self.entries_written = 0 44 | self.entries_dropped = 0 45 | 46 | self.har_file: TextIOWrapper = open(har_file_path, "w", encoding="utf-8") 47 | self.bytes_written = self.har_file.write("""{"log":{"version":"1.2","pages":[],"entries":[""") 48 | 49 | def write(self, entry: dict) -> None: 50 | if self.bytes_written > HARWriter.MAX_SIZE: 51 | self.entries_dropped += 1 52 | return 53 | 54 | if self.entries_written > 0: 55 | self.bytes_written += self.har_file.write(",\n") 56 | self.bytes_written += self.har_file.write(json.dumps(entry)) 57 | self.entries_written += 1 58 | 59 | def end(self) -> tuple[int, int, int]: 60 | self.bytes_written += self.har_file.write("]}}") 61 | self.har_file.flush() 62 | self.har_file.close() 63 | 64 | if self.ready_marker_path != "": 65 | with open(self.ready_marker_path, "w", encoding="utf-8") as marker: 66 | marker.write(json.dumps({ 67 | "bytes_written": self.bytes_written, 68 | "entries_written": self.entries_written, 69 | "entries_dropped": self.entries_dropped, 70 | })) 71 | 72 | return [self.bytes_written, self.entries_written, self.entries_dropped] 73 | 74 | 75 | class PyntSaveHar: 76 | def __init__(self) -> None: 77 | self.filt: flowfilter.TFilter | None = None 78 | 79 | self.servers_seen: set[Server] = set() 80 | self.har_writer: HARWriter | None = None 81 | 82 | def load(self, loader: Loader): 83 | loader.add_option( 84 | name="pynthar", 85 | typespec=str, 86 | default=".", 87 | help="use this pynt script to save HAR file", 88 | ) 89 | 90 | loader.add_option( 91 | name="pynt_ready_marker", 92 | typespec=str, 93 | default="", 94 | help="file path for a marker file indicating the HAR file is ready", 95 | ) 96 | 97 | def configure(self, updated): 98 | if "save_stream_filter" in updated: 99 | if ctx.options.save_stream_filter: 100 | try: 101 | self.filt = flowfilter.parse(ctx.options.save_stream_filter) 102 | except ValueError as e: 103 | raise exceptions.OptionsError(str(e)) from e 104 | else: 105 | self.filt = None 106 | 107 | def response(self, flow: http.HTTPFlow) -> None: 108 | # websocket flows will receive a websocket_end, 109 | # we don't want to persist them here already 110 | if flow.websocket is None: 111 | self._save_flow(flow) 112 | 113 | def error(self, flow: http.HTTPFlow) -> None: 114 | self.response(flow) 115 | 116 | def websocket_end(self, flow: http.HTTPFlow) -> None: 117 | self._save_flow(flow) 118 | 119 | def _save_flow(self, flow: http.HTTPFlow) -> None: 120 | if self.har_writer and isinstance(flow, http.HTTPFlow): 121 | flow_matches = self.filt is None or self.filt(flow) 122 | if flow_matches and not hasattr(flow, 'pynt_skip_har'): 123 | self.har_writer.write(self.flow_entry(flow)) 124 | 125 | def running(self): 126 | if ctx.options.pynthar: 127 | print(f"Writing to HAR file {ctx.options.pynthar}") 128 | self.har_writer = HARWriter(ctx.options.pynthar, ctx.options.pynt_ready_marker) 129 | 130 | def done(self): 131 | if self.har_writer: 132 | bytes_written, entries_written, entries_dropped = self.har_writer.end() 133 | print(f"HAR file saved with {entries_written} entries ({human.pretty_size(bytes_written)}).\n" + 134 | f"{entries_dropped} entries dropped.") 135 | self.har_writer = None 136 | 137 | def flow_entry(self, flow: http.HTTPFlow) -> dict: 138 | """Creates HAR entry from flow""" 139 | 140 | if flow.server_conn in self.servers_seen: 141 | connect_time = -1.0 142 | ssl_time = -1.0 143 | elif flow.server_conn.timestamp_tcp_setup: 144 | assert flow.server_conn.timestamp_start 145 | connect_time = 1000 * ( 146 | flow.server_conn.timestamp_tcp_setup - flow.server_conn.timestamp_start 147 | ) 148 | 149 | if flow.server_conn.timestamp_tls_setup: 150 | ssl_time = 1000 * ( 151 | flow.server_conn.timestamp_tls_setup 152 | - flow.server_conn.timestamp_tcp_setup 153 | ) 154 | else: 155 | ssl_time = -1.0 156 | self.servers_seen.add(flow.server_conn) 157 | else: 158 | connect_time = -1.0 159 | ssl_time = -1.0 160 | 161 | if flow.request.timestamp_end: 162 | send = 1000 * (flow.request.timestamp_end - flow.request.timestamp_start) 163 | else: 164 | send = 0 165 | 166 | if flow.response and flow.request.timestamp_end: 167 | wait = 1000 * (flow.response.timestamp_start - flow.request.timestamp_end) 168 | else: 169 | wait = 0 170 | 171 | if flow.response and flow.response.timestamp_end: 172 | receive = 1000 * ( 173 | flow.response.timestamp_end - flow.response.timestamp_start 174 | ) 175 | 176 | else: 177 | receive = 0 178 | 179 | timings: dict[str, float | None] = { 180 | "connect": connect_time, 181 | "ssl": ssl_time, 182 | "send": send, 183 | "receive": receive, 184 | "wait": wait, 185 | } 186 | 187 | if flow.response: 188 | try: 189 | content = flow.response.content 190 | except ValueError: 191 | content = flow.response.raw_content 192 | response_body_size = ( 193 | len(flow.response.raw_content) if flow.response.raw_content else 0 194 | ) 195 | response_body_decoded_size = len(content) if content else 0 196 | response_body_compression = response_body_decoded_size - response_body_size 197 | response = { 198 | "status": flow.response.status_code, 199 | "statusText": flow.response.reason, 200 | "httpVersion": flow.response.http_version, 201 | "cookies": self.format_response_cookies(flow.response), 202 | "headers": self.format_multidict(flow.response.headers), 203 | "content": { 204 | "size": response_body_size, 205 | "compression": response_body_compression, 206 | "mimeType": flow.response.headers.get("Content-Type", ""), 207 | }, 208 | "redirectURL": flow.response.headers.get("Location", ""), 209 | "headersSize": len(str(flow.response.headers)), 210 | "bodySize": response_body_size, 211 | } 212 | if content and strutils.is_mostly_bin(content): 213 | response["content"]["text"] = base64.b64encode(content).decode() 214 | response["content"]["encoding"] = "base64" 215 | else: 216 | text_content = flow.response.get_text(strict=False) 217 | if text_content is None: 218 | response["content"]["text"] = "" 219 | else: 220 | response["content"]["text"] = text_content 221 | else: 222 | response = { 223 | "status": 0, 224 | "statusText": "", 225 | "httpVersion": "", 226 | "headers": [], 227 | "cookies": [], 228 | "content": {}, 229 | "redirectURL": "", 230 | "headersSize": -1, 231 | "bodySize": -1, 232 | "_transferSize": 0, 233 | "_error": None, 234 | } 235 | if flow.error: 236 | response["_error"] = flow.error.msg 237 | 238 | if flow.request.method == "CONNECT": 239 | url = f"https://{flow.request.pretty_url}/" 240 | else: 241 | url = flow.request.pretty_url 242 | 243 | entry: dict[str, Any] = { 244 | "startedDateTime": datetime.fromtimestamp( 245 | flow.request.timestamp_start, timezone.utc 246 | ).isoformat(), 247 | "time": sum(v for v in timings.values() if v is not None and v >= 0), 248 | "request": { 249 | "method": flow.request.method, 250 | "url": url, 251 | "httpVersion": flow.request.http_version, 252 | "cookies": self.format_multidict(flow.request.cookies), 253 | "headers": self.format_multidict(flow.request.headers), 254 | "queryString": self.format_multidict(flow.request.query), 255 | "headersSize": len(str(flow.request.headers)), 256 | "bodySize": len(flow.request.content) if flow.request.content else 0, 257 | }, 258 | "response": response, 259 | "cache": {}, 260 | "timings": timings, 261 | } 262 | 263 | if flow.request.method in ["POST", "GET", "PUT", "PATCH"]: 264 | params = self.format_multidict(flow.request.urlencoded_form) 265 | entry["request"]["postData"] = { 266 | "mimeType": flow.request.headers.get("Content-Type", ""), 267 | "text": flow.request.get_text(strict=False), 268 | "params": params, 269 | } 270 | 271 | if flow.server_conn.peername: 272 | entry["serverIPAddress"] = str(flow.server_conn.peername[0]) 273 | 274 | websocket_messages = [] 275 | if flow.websocket: 276 | for message in flow.websocket.messages: 277 | if message.is_text: 278 | data = message.text 279 | else: 280 | data = base64.b64encode(message.content).decode() 281 | websocket_message = { 282 | "type": "send" if message.from_client else "receive", 283 | "time": message.timestamp, 284 | "opcode": message.type.value, 285 | "data": data, 286 | } 287 | websocket_messages.append(websocket_message) 288 | 289 | entry["_resourceType"] = "websocket" 290 | entry["_webSocketMessages"] = websocket_messages 291 | return entry 292 | 293 | def format_response_cookies(self, response: http.Response) -> list[dict]: 294 | """Formats the response's cookie header to list of cookies""" 295 | cookie_list = response.cookies.items(multi=True) 296 | rv = [] 297 | for name, (value, attrs) in cookie_list: 298 | cookie = { 299 | "name": name, 300 | "value": value, 301 | "path": attrs.get("path", "/"), 302 | "domain": attrs.get("domain", ""), 303 | "httpOnly": "httpOnly" in attrs, 304 | "secure": "secure" in attrs, 305 | } 306 | # TODO: handle expires attribute here. 307 | # This is not quite trivial because we need to parse random date formats. 308 | # For now, we just ignore the attribute. 309 | 310 | if "sameSite" in attrs: 311 | cookie["sameSite"] = attrs["sameSite"] 312 | 313 | rv.append(cookie) 314 | return rv 315 | 316 | def format_multidict(self, obj: _MultiDict[str, str]) -> list[dict]: 317 | return [{"name": k, "value": v} for k, v in obj.items(multi=True)] 318 | 319 | 320 | addons = [PyntSaveHar()] 321 | -------------------------------------------------------------------------------- /goat_functional_tests/goat-rest-assured/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | com.pynt.rest 8 | goat-rest 9 | 1.0-SNAPSHOT 10 | 11 | goat-rest 12 | 13 | http://www.example.com 14 | 15 | 16 | UTF-8 17 | 1.7 18 | 1.7 19 | 20 | 21 | 22 | 23 | junit 24 | junit 25 | 4.11 26 | test 27 | 28 | 29 | org.json 30 | json 31 | 20090211 32 | 33 | 34 | io.rest-assured 35 | rest-assured 36 | 5.0.0 37 | test 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | maven-clean-plugin 47 | 3.1.0 48 | 49 | 50 | 51 | maven-resources-plugin 52 | 3.0.2 53 | 54 | 55 | maven-compiler-plugin 56 | 3.8.0 57 | 58 | 59 | maven-surefire-plugin 60 | 2.22.1 61 | 62 | 63 | maven-jar-plugin 64 | 3.0.2 65 | 66 | 67 | maven-install-plugin 68 | 2.5.2 69 | 70 | 71 | maven-deploy-plugin 72 | 2.8.2 73 | 74 | 75 | 76 | maven-site-plugin 77 | 3.7.1 78 | 79 | 80 | maven-project-info-reports-plugin 81 | 3.0.0 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /goat_functional_tests/goat-rest-assured/src/main/java/com/pynt/rest/App.java: -------------------------------------------------------------------------------- 1 | package com.pynt.rest; 2 | 3 | /** 4 | * Hello world! 5 | * 6 | */ 7 | public class App 8 | { 9 | public static void main( String[] args ) 10 | { 11 | System.out.println( "Hello World!" ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /goat_functional_tests/goat-rest-assured/src/test/java/com/pynt/rest/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.pynt.rest; 2 | 3 | import org.junit.Test; 4 | import org.junit.runners.MethodSorters; 5 | import org.json.JSONObject; 6 | import org.json.JSONException; 7 | import org.junit.FixMethodOrder; 8 | 9 | import static io.restassured.RestAssured.* ; 10 | import static io.restassured.matcher.RestAssuredMatchers.* ; 11 | import static org.hamcrest.Matchers.* ; 12 | 13 | /** 14 | * Unit test for simple App. 15 | */ 16 | @FixMethodOrder( MethodSorters.NAME_ASCENDING ) 17 | public class AppTest 18 | { 19 | 20 | static String jamesToken; 21 | static String larsToken; 22 | static String jamesUid; 23 | static String larsUid; 24 | 25 | /** 26 | * Rigorous Test :-) 27 | */ 28 | @Test 29 | public void step1_testJamesCanLogin() 30 | { 31 | baseURI = "http://44.202.3.35"; 32 | port = 6000; 33 | 34 | try 35 | { 36 | JSONObject body = new JSONObject() 37 | .put("userName", "James") 38 | .put("password", "ILoveGuitars"); 39 | 40 | jamesToken = "Bearer " + 41 | given() 42 | .contentType("application/json") 43 | .body(body.toString()) 44 | .when() 45 | .post("/login") 46 | .then() 47 | .statusCode(200) 48 | .extract() 49 | .response().getBody().jsonPath().getString("token"); 50 | 51 | System.out.println(jamesToken); 52 | } catch (JSONException e) { 53 | System.out.println(e); 54 | } 55 | 56 | } 57 | 58 | @Test 59 | public void step2_testGetJamesAccount() 60 | { 61 | baseURI = "http://44.202.3.35"; 62 | port = 6000; 63 | System.out.println("Will use james token " + jamesToken); 64 | jamesUid = 65 | given() 66 | .header("Authorization", jamesToken) 67 | .when() 68 | .get("/account") 69 | .then() 70 | .statusCode(200) 71 | .extract() 72 | .response().getBody().jsonPath().getString("userId"); 73 | 74 | System.out.println(jamesUid); 75 | 76 | } 77 | 78 | @Test 79 | public void step3_testGetJamesTransactions() 80 | { 81 | baseURI = "http://44.202.3.35"; 82 | port = 6000; 83 | 84 | String resp = 85 | given() 86 | .header("Authorization", jamesToken) 87 | .queryParam("userId", jamesUid) 88 | .queryParam("limit", 5) 89 | .when() 90 | .get("/transactions") 91 | .then() 92 | .statusCode(200) 93 | .extract() 94 | .response().getBody().asString(); 95 | 96 | System.out.println(resp); 97 | } 98 | 99 | @Test 100 | public void step4_testGetMoreOfJamesTransactions() 101 | { 102 | baseURI = "http://44.202.3.35"; 103 | port = 6000; 104 | 105 | String resp = 106 | given() 107 | .header("Authorization", jamesToken) 108 | .queryParam("userId", jamesUid) 109 | .queryParam("limit", 10) 110 | .when() 111 | .get("/transactions") 112 | .then() 113 | .statusCode(200) 114 | .extract() 115 | .response().getBody().asString(); 116 | 117 | System.out.println(resp); 118 | } 119 | 120 | @Test 121 | public void step5_testLarsCanLogin() 122 | { 123 | baseURI = "http://44.202.3.35"; 124 | port = 6000; 125 | 126 | try 127 | { 128 | JSONObject body = new JSONObject() 129 | .put("userName", "Lars") 130 | .put("password", "ILoveDrums"); 131 | 132 | larsToken = "Bearer " + 133 | given() 134 | .contentType("application/json") 135 | .body(body.toString()) 136 | .when() 137 | .post("/login") 138 | .then() 139 | .statusCode(200) 140 | .extract() 141 | .response().getBody().jsonPath().getString("token"); 142 | 143 | System.out.println(larsToken); 144 | } catch (JSONException e) { 145 | System.out.println(e); 146 | } 147 | } 148 | 149 | 150 | @Test 151 | public void step6_testGetLarsAccount() 152 | { 153 | baseURI = "http://44.202.3.35"; 154 | port = 6000; 155 | 156 | larsUid = 157 | given() 158 | .header("Authorization", larsToken) 159 | .when() 160 | .get("/account") 161 | .then() 162 | .statusCode(200) 163 | .extract() 164 | .response().getBody().jsonPath().getString("userId"); 165 | 166 | System.out.println(larsUid); 167 | 168 | } 169 | 170 | @Test 171 | public void step7_testGetLarsTransactions() 172 | { 173 | baseURI = "http://44.202.3.35"; 174 | port = 6000; 175 | 176 | String resp = 177 | given() 178 | .header("Authorization", larsToken) 179 | .queryParam("userId", larsUid) 180 | .queryParam("limit", 5) 181 | .when() 182 | .get("/transactions") 183 | .then() 184 | .statusCode(200) 185 | .extract() 186 | .response().getBody().asString(); 187 | 188 | System.out.println(resp); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /goat_functional_tests/goat-rest-assured/target/classes/com/pynt/rest/App.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pynt-io/pynt/8c7eda6aa07ec19c9c85958c26bcee3f899ac232/goat_functional_tests/goat-rest-assured/target/classes/com/pynt/rest/App.class -------------------------------------------------------------------------------- /goat_functional_tests/goat-rest-assured/target/test-classes/com/pynt/rest/AppTest.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pynt-io/pynt/8c7eda6aa07ec19c9c85958c26bcee3f899ac232/goat_functional_tests/goat-rest-assured/target/test-classes/com/pynt/rest/AppTest.class -------------------------------------------------------------------------------- /goat_functional_tests/goat.har: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "version": "1.2", 4 | "creator": { 5 | "name": "mitmproxy har_dump", 6 | "version": "0.1", 7 | "comment": "mitmproxy version mitmproxy 8.1.1" 8 | }, 9 | "entries": [ 10 | { 11 | "startedDateTime": "2024-12-12T11:09:53.502387+00:00", 12 | "time": 458, 13 | "request": { 14 | "method": "POST", 15 | "url": "http://44.202.3.35:6000/login", 16 | "httpVersion": "HTTP/1.1", 17 | "cookies": [], 18 | "headers": [ 19 | { 20 | "name": "Host", 21 | "value": "44.202.3.35:6000" 22 | }, 23 | { 24 | "name": "Accept-Encoding", 25 | "value": "identity" 26 | }, 27 | { 28 | "name": "User-Agent", 29 | "value": "python-urllib3/2.2.3" 30 | }, 31 | { 32 | "name": "Content-Length", 33 | "value": "49" 34 | }, 35 | { 36 | "name": "Content-Type", 37 | "value": "application/json" 38 | } 39 | ], 40 | "queryString": [], 41 | "headersSize": 184, 42 | "bodySize": 49, 43 | "postData": { 44 | "mimeType": "application/json", 45 | "text": "{\"userName\": \"James\", \"password\": \"ILoveGuitars\"}", 46 | "params": [] 47 | } 48 | }, 49 | "response": { 50 | "status": 200, 51 | "statusText": "OK", 52 | "httpVersion": "HTTP/1.1", 53 | "cookies": [], 54 | "headers": [ 55 | { 56 | "name": "Server", 57 | "value": "gunicorn" 58 | }, 59 | { 60 | "name": "Date", 61 | "value": "Thu, 12 Dec 2024 11:09:53 GMT" 62 | }, 63 | { 64 | "name": "Connection", 65 | "value": "keep-alive" 66 | }, 67 | { 68 | "name": "Content-Type", 69 | "value": "application/json" 70 | }, 71 | { 72 | "name": "Content-Length", 73 | "value": "176" 74 | } 75 | ], 76 | "content": { 77 | "size": 176, 78 | "compression": 0, 79 | "mimeType": "application/json", 80 | "text": "{\"token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI2MDZjZGExNy1jYWU0LTQ2MzAtYjVhZi00OTc3ZWFkMGFhMTUifQ.Os83ApT54EccwY7qraFuYFZeNzXPUy0T8r_56-W8UN4\",\"type\":\"Bearer\"}\n" 81 | }, 82 | "redirectURL": "", 83 | "headersSize": 179, 84 | "bodySize": 176 85 | }, 86 | "cache": {}, 87 | "timings": { 88 | "send": 1, 89 | "receive": 2, 90 | "wait": 304, 91 | "connect": 151, 92 | "ssl": -1 93 | }, 94 | "serverIPAddress": "44.202.3.35" 95 | }, 96 | { 97 | "startedDateTime": "2024-12-12T11:09:53.824815+00:00", 98 | "time": 476, 99 | "request": { 100 | "method": "GET", 101 | "url": "http://44.202.3.35:6000/account", 102 | "httpVersion": "HTTP/1.1", 103 | "cookies": [], 104 | "headers": [ 105 | { 106 | "name": "Host", 107 | "value": "44.202.3.35:6000" 108 | }, 109 | { 110 | "name": "Accept-Encoding", 111 | "value": "identity" 112 | }, 113 | { 114 | "name": "User-Agent", 115 | "value": "python-urllib3/2.2.3" 116 | }, 117 | { 118 | "name": "Authorization", 119 | "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI2MDZjZGExNy1jYWU0LTQ2MzAtYjVhZi00OTc3ZWFkMGFhMTUifQ.Os83ApT54EccwY7qraFuYFZeNzXPUy0T8r_56-W8UN4" 120 | } 121 | ], 122 | "queryString": [], 123 | "headersSize": 295, 124 | "bodySize": 0, 125 | "postData": { 126 | "mimeType": "", 127 | "text": "", 128 | "params": [] 129 | } 130 | }, 131 | "response": { 132 | "status": 200, 133 | "statusText": "OK", 134 | "httpVersion": "HTTP/1.1", 135 | "cookies": [], 136 | "headers": [ 137 | { 138 | "name": "Server", 139 | "value": "gunicorn" 140 | }, 141 | { 142 | "name": "Date", 143 | "value": "Thu, 12 Dec 2024 11:09:54 GMT" 144 | }, 145 | { 146 | "name": "Connection", 147 | "value": "keep-alive" 148 | }, 149 | { 150 | "name": "Content-Type", 151 | "value": "application/json" 152 | }, 153 | { 154 | "name": "Content-Length", 155 | "value": "124" 156 | } 157 | ], 158 | "content": { 159 | "size": 124, 160 | "compression": 0, 161 | "mimeType": "application/json", 162 | "text": "{\"address\":\"2nd metal boulevard\",\"firstName\":\"James\",\"lastName\":\"Hetfield\",\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"}\n" 163 | }, 164 | "redirectURL": "", 165 | "headersSize": 179, 166 | "bodySize": 124 167 | }, 168 | "cache": {}, 169 | "timings": { 170 | "send": 0, 171 | "receive": 1, 172 | "wait": 317, 173 | "connect": 158, 174 | "ssl": -1 175 | }, 176 | "serverIPAddress": "44.202.3.35" 177 | }, 178 | { 179 | "startedDateTime": "2024-12-12T11:09:54.157546+00:00", 180 | "time": 445, 181 | "request": { 182 | "method": "POST", 183 | "url": "http://44.202.3.35:6000/graphql", 184 | "httpVersion": "HTTP/1.1", 185 | "cookies": [], 186 | "headers": [ 187 | { 188 | "name": "Host", 189 | "value": "44.202.3.35:6000" 190 | }, 191 | { 192 | "name": "Accept-Encoding", 193 | "value": "identity" 194 | }, 195 | { 196 | "name": "User-Agent", 197 | "value": "python-urllib3/2.2.3" 198 | }, 199 | { 200 | "name": "Authorization", 201 | "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI2MDZjZGExNy1jYWU0LTQ2MzAtYjVhZi00OTc3ZWFkMGFhMTUifQ.Os83ApT54EccwY7qraFuYFZeNzXPUy0T8r_56-W8UN4" 202 | }, 203 | { 204 | "name": "Content-Length", 205 | "value": "88" 206 | }, 207 | { 208 | "name": "Content-Type", 209 | "value": "application/json" 210 | } 211 | ], 212 | "queryString": [], 213 | "headersSize": 363, 214 | "bodySize": 88, 215 | "postData": { 216 | "mimeType": "application/json", 217 | "text": "{\"query\": \"query {\\n me {\\n userId\\n }\\n }\"}", 218 | "params": [] 219 | } 220 | }, 221 | "response": { 222 | "status": 200, 223 | "statusText": "OK", 224 | "httpVersion": "HTTP/1.1", 225 | "cookies": [], 226 | "headers": [ 227 | { 228 | "name": "Server", 229 | "value": "gunicorn" 230 | }, 231 | { 232 | "name": "Date", 233 | "value": "Thu, 12 Dec 2024 11:09:54 GMT" 234 | }, 235 | { 236 | "name": "Connection", 237 | "value": "keep-alive" 238 | }, 239 | { 240 | "name": "Content-Type", 241 | "value": "application/json" 242 | }, 243 | { 244 | "name": "Content-Length", 245 | "value": "65" 246 | } 247 | ], 248 | "content": { 249 | "size": 65, 250 | "compression": 0, 251 | "mimeType": "application/json", 252 | "text": "{\"data\":{\"me\":{\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"}}}" 253 | }, 254 | "redirectURL": "", 255 | "headersSize": 178, 256 | "bodySize": 65 257 | }, 258 | "cache": {}, 259 | "timings": { 260 | "send": 0, 261 | "receive": 2, 262 | "wait": 297, 263 | "connect": 146, 264 | "ssl": -1 265 | }, 266 | "serverIPAddress": "44.202.3.35" 267 | }, 268 | { 269 | "startedDateTime": "2024-12-12T11:09:54.469972+00:00", 270 | "time": 443, 271 | "request": { 272 | "method": "GET", 273 | "url": "http://44.202.3.35:6000/transactions?limit=5&userId=606cda17-cae4-4630-b5af-4977ead0aa15", 274 | "httpVersion": "HTTP/1.1", 275 | "cookies": [], 276 | "headers": [ 277 | { 278 | "name": "Host", 279 | "value": "44.202.3.35:6000" 280 | }, 281 | { 282 | "name": "Accept-Encoding", 283 | "value": "identity" 284 | }, 285 | { 286 | "name": "User-Agent", 287 | "value": "python-urllib3/2.2.3" 288 | }, 289 | { 290 | "name": "Authorization", 291 | "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI2MDZjZGExNy1jYWU0LTQ2MzAtYjVhZi00OTc3ZWFkMGFhMTUifQ.Os83ApT54EccwY7qraFuYFZeNzXPUy0T8r_56-W8UN4" 292 | } 293 | ], 294 | "queryString": [ 295 | { 296 | "name": "limit", 297 | "value": "5" 298 | }, 299 | { 300 | "name": "userId", 301 | "value": "606cda17-cae4-4630-b5af-4977ead0aa15" 302 | } 303 | ], 304 | "headersSize": 295, 305 | "bodySize": 0, 306 | "postData": { 307 | "mimeType": "", 308 | "text": "", 309 | "params": [] 310 | } 311 | }, 312 | "response": { 313 | "status": 200, 314 | "statusText": "OK", 315 | "httpVersion": "HTTP/1.1", 316 | "cookies": [], 317 | "headers": [ 318 | { 319 | "name": "Server", 320 | "value": "gunicorn" 321 | }, 322 | { 323 | "name": "Date", 324 | "value": "Thu, 12 Dec 2024 11:09:54 GMT" 325 | }, 326 | { 327 | "name": "Connection", 328 | "value": "keep-alive" 329 | }, 330 | { 331 | "name": "Content-Type", 332 | "value": "application/json" 333 | }, 334 | { 335 | "name": "Content-Length", 336 | "value": "601" 337 | } 338 | ], 339 | "content": { 340 | "size": 601, 341 | "compression": 0, 342 | "mimeType": "application/json", 343 | "text": "[{\"_id\":{\"$oid\":\"668a50814ee8ae95562a0d69\"},\"amount\":25755,\"to\":\"James\",\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a0d6a\"},\"amount\":94608,\"to\":\"James\",\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a0d6b\"},\"amount\":49005,\"to\":\"James\",\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a0d6c\"},\"amount\":41814,\"to\":\"Cliff\",\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a0d6d\"},\"amount\":55839,\"to\":\"Json\",\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"}]\n" 344 | }, 345 | "redirectURL": "", 346 | "headersSize": 179, 347 | "bodySize": 601 348 | }, 349 | "cache": {}, 350 | "timings": { 351 | "send": 1, 352 | "receive": 1, 353 | "wait": 296, 354 | "connect": 145, 355 | "ssl": -1 356 | }, 357 | "serverIPAddress": "44.202.3.35" 358 | }, 359 | { 360 | "startedDateTime": "2024-12-12T11:09:54.779917+00:00", 361 | "time": 432, 362 | "request": { 363 | "method": "GET", 364 | "url": "http://44.202.3.35:6000/transactions?limit=10&userId=606cda17-cae4-4630-b5af-4977ead0aa15", 365 | "httpVersion": "HTTP/1.1", 366 | "cookies": [], 367 | "headers": [ 368 | { 369 | "name": "Host", 370 | "value": "44.202.3.35:6000" 371 | }, 372 | { 373 | "name": "Accept-Encoding", 374 | "value": "identity" 375 | }, 376 | { 377 | "name": "User-Agent", 378 | "value": "python-urllib3/2.2.3" 379 | }, 380 | { 381 | "name": "Authorization", 382 | "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI2MDZjZGExNy1jYWU0LTQ2MzAtYjVhZi00OTc3ZWFkMGFhMTUifQ.Os83ApT54EccwY7qraFuYFZeNzXPUy0T8r_56-W8UN4" 383 | } 384 | ], 385 | "queryString": [ 386 | { 387 | "name": "limit", 388 | "value": "10" 389 | }, 390 | { 391 | "name": "userId", 392 | "value": "606cda17-cae4-4630-b5af-4977ead0aa15" 393 | } 394 | ], 395 | "headersSize": 295, 396 | "bodySize": 0, 397 | "postData": { 398 | "mimeType": "", 399 | "text": "", 400 | "params": [] 401 | } 402 | }, 403 | "response": { 404 | "status": 200, 405 | "statusText": "OK", 406 | "httpVersion": "HTTP/1.1", 407 | "cookies": [], 408 | "headers": [ 409 | { 410 | "name": "Server", 411 | "value": "gunicorn" 412 | }, 413 | { 414 | "name": "Date", 415 | "value": "Thu, 12 Dec 2024 11:09:55 GMT" 416 | }, 417 | { 418 | "name": "Connection", 419 | "value": "keep-alive" 420 | }, 421 | { 422 | "name": "Content-Type", 423 | "value": "application/json" 424 | }, 425 | { 426 | "name": "Content-Length", 427 | "value": "1196" 428 | } 429 | ], 430 | "content": { 431 | "size": 1196, 432 | "compression": 0, 433 | "mimeType": "application/json", 434 | "text": "[{\"_id\":{\"$oid\":\"668a50814ee8ae95562a0d69\"},\"amount\":25755,\"to\":\"James\",\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a0d6a\"},\"amount\":94608,\"to\":\"James\",\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a0d6b\"},\"amount\":49005,\"to\":\"James\",\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a0d6c\"},\"amount\":41814,\"to\":\"Cliff\",\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a0d6d\"},\"amount\":55839,\"to\":\"Json\",\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a0d6e\"},\"amount\":77896,\"to\":\"Json\",\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a0d6f\"},\"amount\":9981,\"to\":\"Lars\",\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a0d70\"},\"amount\":63374,\"to\":\"Cliff\",\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a0d71\"},\"amount\":42017,\"to\":\"Lars\",\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a0d72\"},\"amount\":65956,\"to\":\"Json\",\"userId\":\"606cda17-cae4-4630-b5af-4977ead0aa15\"}]\n" 435 | }, 436 | "redirectURL": "", 437 | "headersSize": 180, 438 | "bodySize": 1196 439 | }, 440 | "cache": {}, 441 | "timings": { 442 | "send": 1, 443 | "receive": 1, 444 | "wait": 289, 445 | "connect": 141, 446 | "ssl": -1 447 | }, 448 | "serverIPAddress": "44.202.3.35" 449 | }, 450 | { 451 | "startedDateTime": "2024-12-12T11:09:55.087755+00:00", 452 | "time": 443, 453 | "request": { 454 | "method": "POST", 455 | "url": "http://44.202.3.35:6000/login", 456 | "httpVersion": "HTTP/1.1", 457 | "cookies": [], 458 | "headers": [ 459 | { 460 | "name": "Host", 461 | "value": "44.202.3.35:6000" 462 | }, 463 | { 464 | "name": "Accept-Encoding", 465 | "value": "identity" 466 | }, 467 | { 468 | "name": "User-Agent", 469 | "value": "python-urllib3/2.2.3" 470 | }, 471 | { 472 | "name": "Content-Length", 473 | "value": "46" 474 | }, 475 | { 476 | "name": "Content-Type", 477 | "value": "application/json" 478 | } 479 | ], 480 | "queryString": [], 481 | "headersSize": 184, 482 | "bodySize": 46, 483 | "postData": { 484 | "mimeType": "application/json", 485 | "text": "{\"userName\": \"Lars\", \"password\": \"ILoveDrums\"}", 486 | "params": [] 487 | } 488 | }, 489 | "response": { 490 | "status": 200, 491 | "statusText": "OK", 492 | "httpVersion": "HTTP/1.1", 493 | "cookies": [], 494 | "headers": [ 495 | { 496 | "name": "Server", 497 | "value": "gunicorn" 498 | }, 499 | { 500 | "name": "Date", 501 | "value": "Thu, 12 Dec 2024 11:09:55 GMT" 502 | }, 503 | { 504 | "name": "Connection", 505 | "value": "keep-alive" 506 | }, 507 | { 508 | "name": "Content-Type", 509 | "value": "application/json" 510 | }, 511 | { 512 | "name": "Content-Length", 513 | "value": "176" 514 | } 515 | ], 516 | "content": { 517 | "size": 176, 518 | "compression": 0, 519 | "mimeType": "application/json", 520 | "text": "{\"token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI1ZjkwMDE3Ny01MmQzLTRiMTAtYjU5ZC1jMzY5NjhmZmVmMmEifQ.1_erx7lj6MOTjrjoDOAcwdlYeKjHO-XTP2mgIWIisLA\",\"type\":\"Bearer\"}\n" 521 | }, 522 | "redirectURL": "", 523 | "headersSize": 179, 524 | "bodySize": 176 525 | }, 526 | "cache": {}, 527 | "timings": { 528 | "send": 0, 529 | "receive": 1, 530 | "wait": 296, 531 | "connect": 146, 532 | "ssl": -1 533 | }, 534 | "serverIPAddress": "44.202.3.35" 535 | }, 536 | { 537 | "startedDateTime": "2024-12-12T11:09:55.399556+00:00", 538 | "time": 466, 539 | "request": { 540 | "method": "GET", 541 | "url": "http://44.202.3.35:6000/account", 542 | "httpVersion": "HTTP/1.1", 543 | "cookies": [], 544 | "headers": [ 545 | { 546 | "name": "Host", 547 | "value": "44.202.3.35:6000" 548 | }, 549 | { 550 | "name": "Accept-Encoding", 551 | "value": "identity" 552 | }, 553 | { 554 | "name": "User-Agent", 555 | "value": "python-urllib3/2.2.3" 556 | }, 557 | { 558 | "name": "Authorization", 559 | "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI1ZjkwMDE3Ny01MmQzLTRiMTAtYjU5ZC1jMzY5NjhmZmVmMmEifQ.1_erx7lj6MOTjrjoDOAcwdlYeKjHO-XTP2mgIWIisLA" 560 | } 561 | ], 562 | "queryString": [], 563 | "headersSize": 295, 564 | "bodySize": 0, 565 | "postData": { 566 | "mimeType": "", 567 | "text": "", 568 | "params": [] 569 | } 570 | }, 571 | "response": { 572 | "status": 200, 573 | "statusText": "OK", 574 | "httpVersion": "HTTP/1.1", 575 | "cookies": [], 576 | "headers": [ 577 | { 578 | "name": "Server", 579 | "value": "gunicorn" 580 | }, 581 | { 582 | "name": "Date", 583 | "value": "Thu, 12 Dec 2024 11:09:55 GMT" 584 | }, 585 | { 586 | "name": "Connection", 587 | "value": "keep-alive" 588 | }, 589 | { 590 | "name": "Content-Type", 591 | "value": "application/json" 592 | }, 593 | { 594 | "name": "Content-Length", 595 | "value": "120" 596 | } 597 | ], 598 | "content": { 599 | "size": 120, 600 | "compression": 0, 601 | "mimeType": "application/json", 602 | "text": "{\"address\":\"3rd bad drummer st\",\"firstName\":\"Lars\",\"lastName\":\"Ulrich\",\"userId\":\"5f900177-52d3-4b10-b59d-c36968ffef2a\"}\n" 603 | }, 604 | "redirectURL": "", 605 | "headersSize": 179, 606 | "bodySize": 120 607 | }, 608 | "cache": {}, 609 | "timings": { 610 | "send": 1, 611 | "receive": 2, 612 | "wait": 310, 613 | "connect": 153, 614 | "ssl": -1 615 | }, 616 | "serverIPAddress": "44.202.3.35" 617 | }, 618 | { 619 | "startedDateTime": "2024-12-12T11:09:55.727052+00:00", 620 | "time": 441, 621 | "request": { 622 | "method": "GET", 623 | "url": "http://44.202.3.35:6000/transactions?limit=5&userId=5f900177-52d3-4b10-b59d-c36968ffef2a", 624 | "httpVersion": "HTTP/1.1", 625 | "cookies": [], 626 | "headers": [ 627 | { 628 | "name": "Host", 629 | "value": "44.202.3.35:6000" 630 | }, 631 | { 632 | "name": "Accept-Encoding", 633 | "value": "identity" 634 | }, 635 | { 636 | "name": "User-Agent", 637 | "value": "python-urllib3/2.2.3" 638 | }, 639 | { 640 | "name": "Authorization", 641 | "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI1ZjkwMDE3Ny01MmQzLTRiMTAtYjU5ZC1jMzY5NjhmZmVmMmEifQ.1_erx7lj6MOTjrjoDOAcwdlYeKjHO-XTP2mgIWIisLA" 642 | } 643 | ], 644 | "queryString": [ 645 | { 646 | "name": "limit", 647 | "value": "5" 648 | }, 649 | { 650 | "name": "userId", 651 | "value": "5f900177-52d3-4b10-b59d-c36968ffef2a" 652 | } 653 | ], 654 | "headersSize": 295, 655 | "bodySize": 0, 656 | "postData": { 657 | "mimeType": "", 658 | "text": "", 659 | "params": [] 660 | } 661 | }, 662 | "response": { 663 | "status": 200, 664 | "statusText": "OK", 665 | "httpVersion": "HTTP/1.1", 666 | "cookies": [], 667 | "headers": [ 668 | { 669 | "name": "Server", 670 | "value": "gunicorn" 671 | }, 672 | { 673 | "name": "Date", 674 | "value": "Thu, 12 Dec 2024 11:09:56 GMT" 675 | }, 676 | { 677 | "name": "Connection", 678 | "value": "keep-alive" 679 | }, 680 | { 681 | "name": "Content-Type", 682 | "value": "application/json" 683 | }, 684 | { 685 | "name": "Content-Length", 686 | "value": "600" 687 | } 688 | ], 689 | "content": { 690 | "size": 600, 691 | "compression": 0, 692 | "mimeType": "application/json", 693 | "text": "[{\"_id\":{\"$oid\":\"668a50814ee8ae95562a0599\"},\"amount\":13965,\"to\":\"Robert\",\"userId\":\"5f900177-52d3-4b10-b59d-c36968ffef2a\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a059a\"},\"amount\":80790,\"to\":\"Json\",\"userId\":\"5f900177-52d3-4b10-b59d-c36968ffef2a\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a059b\"},\"amount\":27926,\"to\":\"Json\",\"userId\":\"5f900177-52d3-4b10-b59d-c36968ffef2a\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a059c\"},\"amount\":35163,\"to\":\"Cliff\",\"userId\":\"5f900177-52d3-4b10-b59d-c36968ffef2a\"},{\"_id\":{\"$oid\":\"668a50814ee8ae95562a059d\"},\"amount\":73171,\"to\":\"Lars\",\"userId\":\"5f900177-52d3-4b10-b59d-c36968ffef2a\"}]\n" 694 | }, 695 | "redirectURL": "", 696 | "headersSize": 179, 697 | "bodySize": 600 698 | }, 699 | "cache": {}, 700 | "timings": { 701 | "send": 0, 702 | "receive": 1, 703 | "wait": 295, 704 | "connect": 145, 705 | "ssl": -1 706 | }, 707 | "serverIPAddress": "44.202.3.35" 708 | } 709 | ] 710 | } 711 | } -------------------------------------------------------------------------------- /goat_functional_tests/goat.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 2 12 | 1 13 | false 14 | continue 15 | 16 | 1 17 | false 18 | 19 | 20 | 21 | 22 | 23 | user 24 | password 25 | 26 | 27 | 28 | James 29 | ILoveGuitars 30 | 31 | 32 | Lars 33 | ILoveDrums 34 | 35 | 36 | false 37 | 38 | 39 | 40 | goat.staging.pynt.io 41 | 443 42 | HTTPS 43 | login 44 | true 45 | POST 46 | true 47 | true 48 | 49 | 50 | 51 | false 52 | { 53 | "userName": "${user}", 54 | "password": "${password}" 55 | } 56 | = 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | Content-Type 66 | application/json 67 | 68 | 69 | 70 | 71 | 72 | auth_token 73 | $.token 74 | 75 | 1234 76 | 77 | 78 | 79 | 80 | goat.staging.pynt.io 81 | 443 82 | https 83 | /account 84 | true 85 | GET 86 | true 87 | false 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | Content-Type 97 | application/json 98 | 99 | 100 | Authorization 101 | Bearer ${auth_token} 102 | 103 | 104 | 105 | 106 | 107 | USERID 108 | $.userId 109 | 110 | 111 | 112 | 113 | 114 | goat.staging.pynt.io 115 | 443 116 | https 117 | /transactions?limit=5&userId=${USERID} 118 | true 119 | GET 120 | true 121 | false 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | Content-Type 131 | application/json 132 | 133 | 134 | Authorization 135 | Bearer ${auth_token} 136 | 137 | 138 | 139 | 140 | 141 | 142 | goat.staging.pynt.io 143 | 443 144 | https 145 | /transactions?limit=10&userId=${USERID} 146 | true 147 | GET 148 | true 149 | false 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | Content-Type 159 | application/json 160 | 161 | 162 | Authorization 163 | Bearer ${auth_token} 164 | 165 | 166 | 167 | 168 | 169 | 170 | false 171 | 172 | saveConfig 173 | 174 | 175 | true 176 | true 177 | true 178 | 179 | true 180 | true 181 | true 182 | true 183 | false 184 | true 185 | true 186 | false 187 | false 188 | false 189 | true 190 | false 191 | false 192 | false 193 | true 194 | 0 195 | true 196 | true 197 | true 198 | true 199 | true 200 | true 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /goat_functional_tests/goat.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "589b43ad-51db-43a7-be89-2b6ef747f9fa", 4 | "name": "goat", 5 | "description": "Goat by Pynt is a vulnerable application example, used to demonstrate the most critical and common API security risks.\n\nFork Goat to your workspace and provide it as a variable to Pynt collection to detect its vulnerabilities.", 6 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 7 | "_exporter_id": "19517062" 8 | }, 9 | "item": [ 10 | { 11 | "name": "1st user login", 12 | "event": [ 13 | { 14 | "listen": "test", 15 | "script": { 16 | "exec": [ 17 | "var res = pm.response.json()", 18 | "pm.environment.set('AUTH_TOKEN',res['token'])", 19 | "console.log(res['token'])", 20 | "", 21 | "pm.test(\"Status code is 200\", () => {", 22 | " pm.response.to.have.status(200);", 23 | "});" 24 | ], 25 | "type": "text/javascript" 26 | } 27 | } 28 | ], 29 | "request": { 30 | "auth": { 31 | "type": "noauth" 32 | }, 33 | "method": "POST", 34 | "header": [], 35 | "body": { 36 | "mode": "raw", 37 | "raw": "{\n \"userName\": \"James\",\n \"password\": \"ILoveGuitars\"\n}", 38 | "options": { 39 | "raw": { 40 | "language": "json" 41 | } 42 | } 43 | }, 44 | "url": { 45 | "raw": "{{baseUrl}}/login", 46 | "host": [ 47 | "{{baseUrl}}" 48 | ], 49 | "path": [ 50 | "login" 51 | ] 52 | } 53 | }, 54 | "response": [] 55 | }, 56 | { 57 | "name": "1st user account", 58 | "event": [ 59 | { 60 | "listen": "test", 61 | "script": { 62 | "exec": [ 63 | "var res = pm.response.json()", 64 | "pm.environment.set('USERID',res['userId'])", 65 | "", 66 | "pm.test(\"Status code is 200\", () => {", 67 | " pm.response.to.have.status(200);", 68 | "});" 69 | ], 70 | "type": "text/javascript" 71 | } 72 | } 73 | ], 74 | "protocolProfileBehavior": { 75 | "disableBodyPruning": true 76 | }, 77 | "request": { 78 | "auth": { 79 | "type": "bearer", 80 | "bearer": [ 81 | { 82 | "key": "token", 83 | "value": "{{AUTH_TOKEN}}", 84 | "type": "string" 85 | } 86 | ] 87 | }, 88 | "method": "GET", 89 | "header": [], 90 | "body": { 91 | "mode": "raw", 92 | "raw": "", 93 | "options": { 94 | "raw": { 95 | "language": "javascript" 96 | } 97 | } 98 | }, 99 | "url": { 100 | "raw": "{{baseUrl}}/account", 101 | "host": [ 102 | "{{baseUrl}}" 103 | ], 104 | "path": [ 105 | "account" 106 | ] 107 | } 108 | }, 109 | "response": [] 110 | }, 111 | { 112 | "name": "1st user transactions 5", 113 | "event": [ 114 | { 115 | "listen": "test", 116 | "script": { 117 | "exec": [ 118 | "", 119 | "pm.test(\"Status code is 200\", () => {", 120 | " pm.response.to.have.status(200);", 121 | "});", 122 | "", 123 | "", 124 | "pm.test(\"Returns 5 objects\", () => {", 125 | " pm.response.json().length == 5;", 126 | "});" 127 | ], 128 | "type": "text/javascript" 129 | } 130 | } 131 | ], 132 | "protocolProfileBehavior": { 133 | "disableBodyPruning": true 134 | }, 135 | "request": { 136 | "auth": { 137 | "type": "bearer", 138 | "bearer": [ 139 | { 140 | "key": "token", 141 | "value": "{{AUTH_TOKEN}}", 142 | "type": "string" 143 | } 144 | ] 145 | }, 146 | "method": "GET", 147 | "header": [], 148 | "body": { 149 | "mode": "raw", 150 | "raw": "", 151 | "options": { 152 | "raw": { 153 | "language": "json" 154 | } 155 | } 156 | }, 157 | "url": { 158 | "raw": "{{baseUrl}}/transactions?limit=5&userId={{USERID}}", 159 | "host": [ 160 | "{{baseUrl}}" 161 | ], 162 | "path": [ 163 | "transactions" 164 | ], 165 | "query": [ 166 | { 167 | "key": "limit", 168 | "value": "5" 169 | }, 170 | { 171 | "key": "userId", 172 | "value": "{{USERID}}" 173 | } 174 | ] 175 | } 176 | }, 177 | "response": [] 178 | }, 179 | { 180 | "name": "1st user transactions 15", 181 | "event": [ 182 | { 183 | "listen": "test", 184 | "script": { 185 | "exec": [ 186 | "", 187 | "pm.test(\"Status code is 200\", () => {", 188 | " pm.response.to.have.status(200);", 189 | "});", 190 | "", 191 | "pm.test(\"Returns 15 objects\", () => {", 192 | " pm.response.json().length == 15;", 193 | "});" 194 | ], 195 | "type": "text/javascript" 196 | } 197 | } 198 | ], 199 | "protocolProfileBehavior": { 200 | "disableBodyPruning": true 201 | }, 202 | "request": { 203 | "auth": { 204 | "type": "bearer", 205 | "bearer": [ 206 | { 207 | "key": "token", 208 | "value": "{{AUTH_TOKEN}}", 209 | "type": "string" 210 | } 211 | ] 212 | }, 213 | "method": "GET", 214 | "header": [], 215 | "body": { 216 | "mode": "raw", 217 | "raw": "", 218 | "options": { 219 | "raw": { 220 | "language": "json" 221 | } 222 | } 223 | }, 224 | "url": { 225 | "raw": "{{baseUrl}}/transactions?limit=10&userId={{USERID}}", 226 | "host": [ 227 | "{{baseUrl}}" 228 | ], 229 | "path": [ 230 | "transactions" 231 | ], 232 | "query": [ 233 | { 234 | "key": "limit", 235 | "value": "10" 236 | }, 237 | { 238 | "key": "userId", 239 | "value": "{{USERID}}" 240 | } 241 | ] 242 | } 243 | }, 244 | "response": [] 245 | }, 246 | { 247 | "name": "2nd user login", 248 | "event": [ 249 | { 250 | "listen": "test", 251 | "script": { 252 | "exec": [ 253 | "var res = pm.response.json()", 254 | "pm.environment.set('AUTH_TOKEN',res['token'])", 255 | "console.log(res['token'])", 256 | "", 257 | "pm.test(\"Status code is 200\", () => {", 258 | " pm.response.to.have.status(200);", 259 | "});" 260 | ], 261 | "type": "text/javascript" 262 | } 263 | } 264 | ], 265 | "request": { 266 | "auth": { 267 | "type": "noauth" 268 | }, 269 | "method": "POST", 270 | "header": [], 271 | "body": { 272 | "mode": "raw", 273 | "raw": "{\n \"userName\": \"Lars\",\n \"password\": \"ILoveDrums\"\n}", 274 | "options": { 275 | "raw": { 276 | "language": "json" 277 | } 278 | } 279 | }, 280 | "url": { 281 | "raw": "{{baseUrl}}/login", 282 | "host": [ 283 | "{{baseUrl}}" 284 | ], 285 | "path": [ 286 | "login" 287 | ] 288 | } 289 | }, 290 | "response": [] 291 | }, 292 | { 293 | "name": "2nd user account", 294 | "event": [ 295 | { 296 | "listen": "test", 297 | "script": { 298 | "exec": [ 299 | "var res = pm.response.json()", 300 | "pm.environment.set('USERID',res['userId'])", 301 | "", 302 | "pm.test(\"Status code is 200\", () => {", 303 | " pm.response.to.have.status(200);", 304 | "});" 305 | ], 306 | "type": "text/javascript" 307 | } 308 | } 309 | ], 310 | "request": { 311 | "auth": { 312 | "type": "bearer", 313 | "bearer": [ 314 | { 315 | "key": "token", 316 | "value": "{{AUTH_TOKEN}}", 317 | "type": "string" 318 | } 319 | ] 320 | }, 321 | "method": "GET", 322 | "header": [], 323 | "url": { 324 | "raw": "{{baseUrl}}/account", 325 | "host": [ 326 | "{{baseUrl}}" 327 | ], 328 | "path": [ 329 | "account" 330 | ] 331 | } 332 | }, 333 | "response": [] 334 | }, 335 | { 336 | "name": "2nd user account (via GraphQL)", 337 | "request": { 338 | "auth": { 339 | "type": "bearer", 340 | "bearer": [ 341 | { 342 | "key": "token", 343 | "value": "{{AUTH_TOKEN}}", 344 | "type": "string" 345 | } 346 | ] 347 | }, 348 | "method": "POST", 349 | "header": [], 350 | "body": { 351 | "mode": "graphql", 352 | "graphql": { 353 | "query": "query {\n me {\n userId\n }\n}", 354 | "variables": "" 355 | } 356 | }, 357 | "url": { 358 | "raw": "{{baseUrl}}/graphql", 359 | "host": [ 360 | "{{baseUrl}}" 361 | ], 362 | "path": [ 363 | "graphql" 364 | ] 365 | } 366 | }, 367 | "response": [] 368 | }, 369 | { 370 | "name": "2nd user transactions 5", 371 | "event": [ 372 | { 373 | "listen": "test", 374 | "script": { 375 | "exec": [ 376 | "", 377 | "pm.test(\"Status code is 200\", () => {", 378 | " pm.response.to.have.status(200);", 379 | "});", 380 | "", 381 | "", 382 | "pm.test(\"Returns 5 objects\", () => {", 383 | " pm.response.json().length == 5;", 384 | "});" 385 | ], 386 | "type": "text/javascript" 387 | } 388 | } 389 | ], 390 | "request": { 391 | "auth": { 392 | "type": "bearer", 393 | "bearer": [ 394 | { 395 | "key": "token", 396 | "value": "{{AUTH_TOKEN}}", 397 | "type": "string" 398 | } 399 | ] 400 | }, 401 | "method": "GET", 402 | "header": [], 403 | "url": { 404 | "raw": "{{baseUrl}}/transactions?limit=5&userId={{USERID}}", 405 | "host": [ 406 | "{{baseUrl}}" 407 | ], 408 | "path": [ 409 | "transactions" 410 | ], 411 | "query": [ 412 | { 413 | "key": "limit", 414 | "value": "5" 415 | }, 416 | { 417 | "key": "userId", 418 | "value": "{{USERID}}" 419 | } 420 | ] 421 | } 422 | }, 423 | "response": [] 424 | } 425 | ], 426 | "event": [ 427 | { 428 | "listen": "prerequest", 429 | "script": { 430 | "type": "text/javascript", 431 | "exec": [ 432 | "" 433 | ] 434 | } 435 | }, 436 | { 437 | "listen": "test", 438 | "script": { 439 | "type": "text/javascript", 440 | "exec": [ 441 | "" 442 | ] 443 | } 444 | } 445 | ], 446 | "variable": [ 447 | { 448 | "key": "baseUrl", 449 | "value": "http://44.202.3.35:6000", 450 | "type": "string" 451 | } 452 | ] 453 | } -------------------------------------------------------------------------------- /goat_functional_tests/goat_burp.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ]> 25 | 26 | 27 | 28 | 29 | 44.202.3.35 30 | 6000 31 | http 32 | 33 | 34 | null 35 | 36 | 200 37 | 1961 38 | JSON 39 | 40 | 41 | 42 | 43 | 44 | 45 | 44.202.3.35 46 | 6000 47 | http 48 | 49 | 50 | null 51 | 52 | 200 53 | 767 54 | JSON 55 | 56 | 57 | 58 | 59 | 60 | 61 | 44.202.3.35 62 | 6000 63 | http 64 | 65 | 66 | null 67 | 68 | 200 69 | 291 70 | JSON 71 | 72 | 73 | 74 | 75 | 76 | 77 | 44.202.3.35 78 | 6000 79 | http 80 | 81 | 82 | null 83 | 84 | 200 85 | 343 86 | JSON 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /goat_functional_tests/goat_functional_test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import pytest 3 | import os 4 | def pytest_namespace(): 5 | return {"james_token": None, "lars_token": None, "james_uid": None, "lars_uid": None} 6 | 7 | 8 | @pytest.fixture 9 | def james_token(): 10 | assert pytest.james_token 11 | return pytest.james_token 12 | 13 | @pytest.fixture 14 | def lars_token(): 15 | assert pytest.lars_token 16 | return pytest.lars_token 17 | 18 | @pytest.fixture 19 | def james_uid(): 20 | assert pytest.james_uid 21 | return pytest.james_uid 22 | 23 | @pytest.fixture 24 | def lars_uid(): 25 | assert pytest.lars_uid 26 | return pytest.lars_uid 27 | 28 | class TestLogin: 29 | 30 | def setup_class(self) -> None: 31 | self.base_url = "http://44.202.3.35:6000" 32 | 33 | def _send_request(self, request: requests.Request, authorization = None): 34 | s = requests.session() 35 | if authorization: 36 | auth_header = {"Authorization": "Bearer " + authorization} 37 | request.headers.update(auth_header) 38 | 39 | return s.send(request.prepare()) 40 | 41 | def login(self, user_name, password): 42 | return self._send_request(requests.Request(url=self.base_url + "/login", 43 | json={"userName": user_name, 44 | "password": password}, 45 | method="POST")) 46 | 47 | 48 | def get_accont(self, authorization): 49 | return self._send_request(requests.Request(url=self.base_url + "/account", method="GET"), authorization=authorization) 50 | 51 | def get_transactions(self, authorization, uid, limit): 52 | return self._send_request(requests.Request(url=self.base_url + "/transactions", 53 | params={"limit": limit, "userId": uid}, 54 | method="GET"), 55 | authorization=authorization) 56 | 57 | 58 | def test_james_can_login(self): 59 | resp = self.login("James", "ILoveGuitars") 60 | assert resp.status_code == 200 61 | pytest.james_token = resp.json()["token"] 62 | 63 | 64 | def test_get_james_user_info(self, james_token): 65 | resp = self.get_accont(james_token) 66 | assert resp.status_code == 200 67 | pytest.james_uid = resp.json()["userId"] 68 | 69 | def test_get_james_user_info_from_graphql(self, james_token, james_uid): 70 | query = """query { 71 | me { 72 | userId 73 | } 74 | }""" 75 | resp = self._send_request(requests.Request( 76 | url=self.base_url + "/graphql", 77 | method="POST", 78 | json={"query": query} 79 | ), authorization=james_token) 80 | 81 | assert resp.status_code == 200 82 | james_info = resp.json()["data"]["me"] 83 | assert james_info["userId"] == james_uid 84 | 85 | 86 | def test_get_james_transactions(self, james_token, james_uid): 87 | resp = self.get_transactions(james_token, james_uid, limit=5) 88 | assert resp.status_code == 200 89 | assert len(resp.json()) == 5 90 | resp = self.get_transactions(james_token, james_uid, limit=10) 91 | assert resp.status_code == 200 92 | assert len(resp.json()) == 10 93 | 94 | def test_lars_can_login(self): 95 | resp = self.login("Lars", "ILoveDrums") 96 | assert resp.status_code == 200 97 | pytest.lars_token = resp.json()["token"] 98 | 99 | 100 | def test_get_lars_user_info(self, lars_token): 101 | resp = self.get_accont(lars_token) 102 | assert resp.status_code == 200 103 | pytest.lars_uid = resp.json()["userId"] 104 | 105 | 106 | def test_get_lars_transactions(self, lars_token, lars_uid): 107 | resp = self.get_transactions(lars_token, lars_uid, limit=5) 108 | assert resp.status_code == 200 109 | -------------------------------------------------------------------------------- /goat_functional_tests/goat_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | "strconv" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | var ( 15 | jamesToken string 16 | larsToken string 17 | jamesUID string 18 | larsUID string 19 | ) 20 | 21 | const baseURL = "http://44.202.3.35:6000" 22 | 23 | type loginResponse struct { 24 | Token string `json:"token"` 25 | } 26 | 27 | type accountResponse struct { 28 | UserID string `json:"userId"` 29 | } 30 | 31 | type gqlResponse struct { 32 | Data struct { 33 | Me accountResponse `json:"me"` 34 | } `json:"data"` 35 | } 36 | 37 | func sendRequest(req *http.Request, authorization string) (*http.Response, error) { 38 | client := &http.Client{} 39 | if authorization != "" { 40 | req.Header.Set("Authorization", "Bearer "+authorization) 41 | } 42 | return client.Do(req) 43 | } 44 | 45 | func login(userName, password string) (*http.Response, error) { 46 | body := map[string]string{ 47 | "userName": userName, 48 | "password": password, 49 | } 50 | jsonBody, _ := json.Marshal(body) 51 | req, _ := http.NewRequest("POST", baseURL+"/login", bytes.NewBuffer(jsonBody)) 52 | req.Header.Set("Content-Type", "application/json") 53 | return sendRequest(req, "") 54 | } 55 | 56 | func getAccount(authorization string) (*http.Response, error) { 57 | req, _ := http.NewRequest("GET", baseURL+"/account", nil) 58 | return sendRequest(req, authorization) 59 | } 60 | 61 | func getAccountViaGraphql(authorization string) (*http.Response, error) { 62 | query := map[string]string{ 63 | "query": "query { me { userId } }", 64 | } 65 | jsonQuery, _ := json.Marshal(query) 66 | 67 | req, _ := http.NewRequest("POST", baseURL+"/graphql", bytes.NewBuffer(jsonQuery)) 68 | req.Header.Set("Content-Type", "application/json") 69 | return sendRequest(req, authorization) 70 | } 71 | 72 | func getTransactions(authorization, uid string, limit int) (*http.Response, error) { 73 | req, _ := http.NewRequest("GET", baseURL+"/transactions?limit="+strconv.Itoa(limit)+"&userId="+uid, nil) 74 | return sendRequest(req, authorization) 75 | } 76 | 77 | func TestJamesCanLogin(t *testing.T) { 78 | resp, err := login("James", "ILoveGuitars") 79 | assert.NoError(t, err) 80 | assert.Equal(t, http.StatusOK, resp.StatusCode) 81 | 82 | body, _ := ioutil.ReadAll(resp.Body) 83 | var loginResp loginResponse 84 | json.Unmarshal(body, &loginResp) 85 | jamesToken = loginResp.Token 86 | } 87 | 88 | func TestGetJamesUserInfo(t *testing.T) { 89 | assert.NotNil(t, jamesToken) 90 | resp, err := getAccount(jamesToken) 91 | assert.NoError(t, err) 92 | assert.Equal(t, http.StatusOK, resp.StatusCode) 93 | 94 | body, _ := ioutil.ReadAll(resp.Body) 95 | var accountResp accountResponse 96 | json.Unmarshal(body, &accountResp) 97 | jamesUID = accountResp.UserID 98 | } 99 | 100 | func TestGetJamesTransactions(t *testing.T) { 101 | assert.NotNil(t, jamesToken) 102 | assert.NotNil(t, jamesUID) 103 | 104 | resp, err := getTransactions(jamesToken, jamesUID, 5) 105 | assert.NoError(t, err) 106 | assert.Equal(t, http.StatusOK, resp.StatusCode) 107 | 108 | body, _ := ioutil.ReadAll(resp.Body) 109 | var transactions []map[string]interface{} 110 | json.Unmarshal(body, &transactions) 111 | assert.Len(t, transactions, 5) 112 | 113 | resp, err = getTransactions(jamesToken, jamesUID, 10) 114 | assert.NoError(t, err) 115 | assert.Equal(t, http.StatusOK, resp.StatusCode) 116 | 117 | body, _ = ioutil.ReadAll(resp.Body) 118 | json.Unmarshal(body, &transactions) 119 | assert.Len(t, transactions, 10) 120 | } 121 | 122 | func TestLarsCanLogin(t *testing.T) { 123 | resp, err := login("Lars", "ILoveDrums") 124 | assert.NoError(t, err) 125 | assert.Equal(t, http.StatusOK, resp.StatusCode) 126 | 127 | body, _ := ioutil.ReadAll(resp.Body) 128 | var loginResp loginResponse 129 | json.Unmarshal(body, &loginResp) 130 | larsToken = loginResp.Token 131 | } 132 | 133 | func TestGetLarsUserInfo(t *testing.T) { 134 | assert.NotNil(t, larsToken) 135 | resp, err := getAccount(larsToken) 136 | assert.NoError(t, err) 137 | assert.Equal(t, http.StatusOK, resp.StatusCode) 138 | 139 | body, _ := ioutil.ReadAll(resp.Body) 140 | var accountResp accountResponse 141 | json.Unmarshal(body, &accountResp) 142 | larsUID = accountResp.UserID 143 | } 144 | 145 | func TestGetLarsUserInfoViaGraphql(t *testing.T) { 146 | assert.NotNil(t, larsToken) 147 | assert.NotNil(t, larsUID) 148 | 149 | resp, err := getAccountViaGraphql(larsToken) 150 | assert.NoError(t, err) 151 | assert.Equal(t, http.StatusOK, resp.StatusCode) 152 | 153 | body, _ := ioutil.ReadAll(resp.Body) 154 | var graphqlResponse gqlResponse 155 | json.Unmarshal(body, &graphqlResponse) 156 | assert.Equal(t, larsUID, graphqlResponse.Data.Me.UserID) 157 | } 158 | 159 | func TestGetLarsTransactions(t *testing.T) { 160 | assert.NotNil(t, larsToken) 161 | assert.NotNil(t, larsUID) 162 | 163 | resp, err := getTransactions(larsToken, larsUID, 5) 164 | assert.NoError(t, err) 165 | assert.Equal(t, http.StatusOK, resp.StatusCode) 166 | 167 | body, _ := ioutil.ReadAll(resp.Body) 168 | var transactions []map[string]interface{} 169 | json.Unmarshal(body, &transactions) 170 | assert.Len(t, transactions, 5) 171 | } 172 | -------------------------------------------------------------------------------- /goat_functional_tests/jest.test.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | // Assuming axios is set up for making HTTP requests 4 | 5 | describe('GOAT Application Functional Tests', () => { 6 | let jamesToken, larsToken, jamesUid, larsUid; 7 | const baseUrl = "http://44.202.3.35:6000"; 8 | 9 | // Helper function to login users 10 | async function loginUser(userName, password) { 11 | const response = await axios.post(`${baseUrl}/login`, { userName, password }); 12 | return response; 13 | } 14 | 15 | // Helper function to get account info 16 | async function getAccount(token) { 17 | const response = await axios.get(`${baseUrl}/account`, { headers: { Authorization: `Bearer ${token}` } }); 18 | return response; 19 | } 20 | 21 | // Helper function to get transactions 22 | async function getTransactions(token, uid, limit) { 23 | const response = await axios.get(`${baseUrl}/transactions`, { 24 | params: { limit, userId: uid }, 25 | headers: { Authorization: `Bearer ${token}` } 26 | }); 27 | return response; 28 | } 29 | 30 | beforeAll(async () => { 31 | // Login both users before all tests 32 | const jamesLoginResponse = await loginUser("James", "ILoveGuitars"); 33 | jamesToken = jamesLoginResponse.data.token; 34 | 35 | const larsLoginResponse = await loginUser("Lars", "ILoveDrums"); 36 | larsToken = larsLoginResponse.data.token; 37 | }); 38 | 39 | test('James can login and get user info', async () => { 40 | const accountResponse = await getAccount(jamesToken); 41 | expect(accountResponse.status).toBe(200); 42 | jamesUid = accountResponse.data.userId; 43 | }); 44 | 45 | test('James can get transactions', async () => { 46 | const transactionsResponse = await getTransactions(jamesToken, jamesUid, 5); 47 | expect(transactionsResponse.status).toBe(200); 48 | expect(transactionsResponse.data.length).toBe(5); 49 | 50 | const moreTransactionsResponse = await getTransactions(jamesToken, jamesUid, 10); 51 | expect(moreTransactionsResponse.status).toBe(200); 52 | expect(moreTransactionsResponse.data.length).toBe(10); 53 | }); 54 | 55 | test('Lars can login and get user info', async () => { 56 | const accountResponse = await getAccount(larsToken); 57 | expect(accountResponse.status).toBe(200); 58 | larsUid = accountResponse.data.userId; 59 | }); 60 | 61 | test('Lars can get transactions', async () => { 62 | const transactionsResponse = await getTransactions(larsToken, larsUid, 5); 63 | expect(transactionsResponse.status).toBe(200); 64 | expect(transactionsResponse.data.length).toBe(5); 65 | }); 66 | 67 | // Add any additional tests following the patterns above 68 | }); 69 | -------------------------------------------------------------------------------- /goat_functional_tests/k8s/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes (k8s) Configuration 2 | 3 | This directory contains the Kubernetes (k8s) configuration files for the Goat Functional Tests. 4 | 5 | ## Purpose 6 | 7 | The purpose of the k8s content is to provide the necessary configuration files to deploy and run the Goat Functional Tests on a Kubernetes cluster. These tests are designed to validate the functionality and performance of the Goat application in a Kubernetes environment. 8 | 9 | ## Prerequisites 10 | 11 | Before running the Goat Functional Tests, ensure that you have the following prerequisites: 12 | 13 | - A running Kubernetes cluster 14 | - `kubectl` command-line tool installed and configured to connect to the cluster 15 | 16 | ## Usage 17 | 18 | To use the k8s content and run the Goat Functional Tests, follow these steps: 19 | 20 | 1. Clone the repository to your local machine: 21 | 22 | ```bash 23 | git clone https://github.com/pynt-io/pynt.git 24 | cd pynt/goat_functional_tests/k8s && ./run_scan.sh $PYNT_ID 25 | ``` -------------------------------------------------------------------------------- /goat_functional_tests/k8s/pynt-manifests/app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: pynt 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: pynt 10 | template: 11 | metadata: 12 | labels: 13 | app: pynt 14 | spec: 15 | containers: 16 | - name: pynt 17 | image: ghcr.io/pynt-io/pynt:v1-latest 18 | args: ["proxy"] 19 | env: 20 | - name: PYNT_ID 21 | valueFrom: 22 | secretKeyRef: 23 | name: pynt-creds 24 | key: pyntid 25 | --- 26 | apiVersion: v1 27 | kind: Service 28 | metadata: 29 | name: pynt 30 | spec: 31 | selector: 32 | app: pynt 33 | ports: 34 | - name: proxy 35 | protocol: TCP 36 | port: 6666 37 | targetPort: 6666 38 | - name: controller 39 | protocol: TCP 40 | port: 5001 41 | targetPort: 5001 42 | -------------------------------------------------------------------------------- /goat_functional_tests/k8s/pynt-manifests/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: pynt-creds 5 | type: Opaque 6 | data: 7 | pyntid: PYNT_ID 8 | -------------------------------------------------------------------------------- /goat_functional_tests/k8s/run_scan.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# -eq 0 ]; then 4 | echo "No argument provided. Please pass your pynt-id as an argument." 5 | exit 1 6 | fi 7 | 8 | # Store the value from the argument in a variable 9 | arg_value=$1 10 | 11 | namespace=pynt-scan 12 | 13 | # Encode the arg value as base64 14 | encoded_value=$(echo -n "$arg_value" | base64) 15 | 16 | escaped_value=$(printf '%s\n' "$encoded_value" | sed -e 's/[\/&]/\\&/g') 17 | 18 | # Replace PYNT_ID with encoded_value in the secret.yaml file 19 | sed -i '' -e "s/PYNT_ID/$escaped_value/g" pynt-manifests/secret.yaml 20 | 21 | kubectl create namespace $namespace 22 | kubectl apply -f pynt-manifests/secret.yaml -n $namespace 23 | kubectl apply -f pynt-manifests/app.yaml -n $namespace 24 | kubectl apply -f scan-trigger/job.yaml -n $namespace -------------------------------------------------------------------------------- /goat_functional_tests/k8s/scan-trigger/job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: scan-trigger-deployment 5 | spec: 6 | template: 7 | metadata: 8 | labels: 9 | app: scan-trigger 10 | spec: 11 | restartPolicy: Never 12 | containers: 13 | - name: scan-trigger 14 | image: alpine:latest 15 | command: ["/bin/sh", "-c"] 16 | args: 17 | - | 18 | apk add --no-cache curl 19 | apk add --no-cache wget 20 | apk add --no-cache python3 21 | apk add --no-cache py3-pip 22 | apk add --no-cache jq 23 | apk add --no-cache py3-pytest 24 | apk add --no-cache py3-requests 25 | 26 | base_url="http://pynt" 27 | applicationId="" 28 | testName="goat test" 29 | 30 | # get the pytest file 31 | wget https://raw.githubusercontent.com/pynt-io/pynt/main/goat_functional_tests/goat_functional_test.py -O goat.py 32 | 33 | # Start Pynt proxy 34 | scan_id=$(curl -X PUT $base_url:5001/api/proxy/start) 35 | scan_id_param=$(echo "$scan_id" | jq -r '.scanId') 36 | 37 | echo "Scan ID: $scan_id_param" 38 | 39 | export HTTP_PROXY=$base_url:6666 40 | export HTTPS_PROXY=$base_url:6666 41 | pytest goat.py 42 | export HTTP_PROXY="" 43 | export HTTPS_PROXY="" 44 | 45 | # Stop Pynt proxy and start Pynt scan 46 | json_payload=$(printf '{"scanId": "%s", "applicationId": "%s", "testName": "%s"}' "$scan_id_param" "$applicationId" "$testName") 47 | curl -X PUT "$base_url:5001/api/proxy/stop" -d "$json_payload" -H "Content-Type: application/json" 48 | 49 | temp_output="temp_output.txt" 50 | final_output="final_output.html" 51 | 52 | sleep 1 53 | 54 | # Polling loop 55 | while true; do 56 | # Check of Pynt scan completed 57 | status_code=$(curl -o "$temp_output" -s -w "%{http_code}\n" $base_url:5001/api/report?scanId=$scan_id_param) 58 | 59 | # Check if the status code is 200 - meaning Pynt has finished 60 | if [ "$status_code" -eq 200 ]; then 61 | echo "Pynt scan completed ! The request returned a 200 status code. Output saved to $final_output" 62 | 63 | # Move the temporary file to the final output file 64 | mv "$temp_output" "$final_output" 65 | break # Exit the loop 66 | else 67 | echo "Waiting for a 200 status code... Received status code: $status_code" 68 | fi 69 | 70 | sleep 1 71 | done 72 | 73 | # Clean up the temporary file if it still exists 74 | if [ -f "$temp_output" ]; then 75 | rm "$temp_output" 76 | fi 77 | -------------------------------------------------------------------------------- /goat_functional_tests/open_api_spec_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.0", 3 | "info": { 4 | "title": "Example API", 5 | "description": "API for managing user login, accounts, and transactions.", 6 | "version": "1.0.0" 7 | }, 8 | "servers": [ 9 | { 10 | "url": "http://44.202.3.35:6000", 11 | "description": "Local development server" 12 | } 13 | ], 14 | "paths": { 15 | "/login": { 16 | "post": { 17 | "summary": "User login", 18 | "requestBody": { 19 | "required": true, 20 | "content": { 21 | "application/json": { 22 | "schema": { 23 | "type": "object", 24 | "properties": { 25 | "userName": { 26 | "type": "string", 27 | "example": "James" 28 | }, 29 | "password": { 30 | "type": "string", 31 | "example": "ILoveGuitars" 32 | } 33 | }, 34 | "required": ["userName", "password"] 35 | } 36 | } 37 | } 38 | }, 39 | "responses": { 40 | "200": { 41 | "description": "Successful login", 42 | "content": { 43 | "application/json": { 44 | "schema": { 45 | "type": "object", 46 | "properties": { 47 | "token": { 48 | "type": "string", 49 | "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." 50 | } 51 | } 52 | } 53 | } 54 | } 55 | }, 56 | "401": { 57 | "description": "Unauthorized, invalid credentials" 58 | } 59 | } 60 | } 61 | }, 62 | "/account": { 63 | "get": { 64 | "summary": "Get user account details", 65 | "security": [ 66 | { 67 | "bearerAuth": [] 68 | } 69 | ], 70 | "responses": { 71 | "200": { 72 | "description": "User account information", 73 | "content": { 74 | "application/json": { 75 | "schema": { 76 | "type": "object", 77 | "properties": { 78 | "userId": { 79 | "type": "string", 80 | "example": "12345" 81 | }, 82 | "userName": { 83 | "type": "string", 84 | "example": "James" 85 | } 86 | } 87 | } 88 | } 89 | } 90 | }, 91 | "401": { 92 | "description": "Unauthorized, invalid token" 93 | } 94 | } 95 | } 96 | }, 97 | "/transactions": { 98 | "get": { 99 | "summary": "Get user transactions", 100 | "security": [ 101 | { 102 | "bearerAuth": [] 103 | } 104 | ], 105 | "parameters": [ 106 | { 107 | "name": "limit", 108 | "in": "query", 109 | "required": true, 110 | "schema": { 111 | "type": "integer", 112 | "example": 5 113 | } 114 | }, 115 | { 116 | "name": "userId", 117 | "in": "query", 118 | "required": true, 119 | "schema": { 120 | "type": "string", 121 | "example": "12345" 122 | } 123 | } 124 | ], 125 | "responses": { 126 | "200": { 127 | "description": "List of transactions", 128 | "content": { 129 | "application/json": { 130 | "schema": { 131 | "type": "array", 132 | "items": { 133 | "type": "object", 134 | "properties": { 135 | "transactionId": { 136 | "type": "string", 137 | "example": "txn_12345" 138 | }, 139 | "amount": { 140 | "type": "number", 141 | "format": "float", 142 | "example": 100.5 143 | } 144 | } 145 | } 146 | } 147 | } 148 | } 149 | }, 150 | "401": { 151 | "description": "Unauthorized, invalid token" 152 | } 153 | } 154 | } 155 | } 156 | }, 157 | "components": { 158 | "securitySchemes": { 159 | "bearerAuth": { 160 | "type": "http", 161 | "scheme": "bearer", 162 | "bearerFormat": "JWT" 163 | } 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /goat_functional_tests/selenium/crapi_selenium.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | import time 4 | import os 5 | import requests 6 | from selenium import webdriver 7 | from selenium.webdriver.common.by import By 8 | from selenium.webdriver.common.action_chains import ActionChains 9 | from selenium.webdriver.firefox.service import Service 10 | from selenium.webdriver.firefox.options import Options 11 | from faker import Faker 12 | 13 | def get_webdriver(browser): 14 | if browser == "CHROME": 15 | chrome_options = webdriver.ChromeOptions() 16 | pynt = os.environ.get("RUNNING_FROM_PYNT", "") 17 | if pynt == "True": 18 | # This section is only when running with Pynt 19 | chrome_options.add_argument('--proxy-server=http://127.0.0.1:6666') 20 | chrome_options.add_argument('--proxy-bypass-list=<-loopback>') 21 | chrome_options.add_argument("--ignore-certificate-errors") 22 | 23 | return webdriver.Chrome(options=chrome_options) 24 | 25 | def register_user(): 26 | url = "http://localhost:8888/identity/api/auth/signup" 27 | fake = Faker() 28 | name = f"{fake.first_name()}.{fake.last_name()}" 29 | email = f"{name}@example.com" 30 | number = fake.msisdn()[:10] 31 | password = fake.password(length=16) 32 | 33 | payload = json.dumps({ 34 | "name": name, 35 | "email": email, 36 | "number": number, 37 | "password": password 38 | }) 39 | 40 | headers = { 41 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36', 42 | 'Content-Type': 'application/json', 43 | 'Accept': '*/*' 44 | } 45 | 46 | response = requests.request("POST", url, headers=headers, data=payload) 47 | print(response.text, email) 48 | return email, password 49 | 50 | def login_user(email, password): 51 | url = "http://localhost:8888/identity/api/auth/login" 52 | 53 | payload = json.dumps({ 54 | "email": email, 55 | "password": password 56 | }) 57 | headers = { 58 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36', 59 | 'Content-Type': 'application/json', 60 | 'Accept': '*/*' 61 | } 62 | 63 | response = requests.request("POST", url, headers=headers, data=payload) 64 | 65 | return response.json().get("token", "") 66 | 67 | def register_vehicle(email): 68 | url = f"http://localhost:8025/api/v2/search?kind=to&query={email}&limit=10" 69 | response = requests.get(url) 70 | if response.status_code == 200: 71 | json_data = response.json() 72 | mails = json_data.get("items", []) 73 | 74 | if len(mails) > 0: 75 | mail = mails[0] 76 | mbody = re.sub(r"[^a-zA-Z0-9<>:]*\n", "", mail['Raw']['Data']) 77 | 78 | vintext = re.search(r'VIN(.*)Pincode', mbody, re.I) 79 | if vintext: 80 | vin_match = re.search(r'>([A-Za-z0-9]+)<', vintext.group(1)) 81 | if vin_match: 82 | VIN = vin_match.group(1) 83 | 84 | pintext = re.search(r'Pincode(.*)We\'re', mbody) 85 | if pintext: 86 | pin_match = re.search(r'>([0-9]+)<', pintext.group(1)) 87 | if pin_match: 88 | PIN = pin_match.group(1) 89 | vehicle = {"VIN": VIN, "PIN": PIN} 90 | print(vehicle) 91 | return vehicle 92 | else: 93 | print(f"Request failed with status code: {response.status_code}") 94 | return None 95 | 96 | def add_vehicle(vehicle, token): 97 | url = "http://localhost:8888/identity/api/v2/vehicle/add_vehicle" 98 | payload = json.dumps({ 99 | "vin": vehicle["VIN"], 100 | "pincode": vehicle["PIN"], 101 | }) 102 | headers = { 103 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36', 104 | 'Content-Type': 'application/json', 105 | 'Accept': '*/*', 106 | 'Authorization': f'Bearer {token}' 107 | } 108 | response = requests.request("POST", url, headers=headers, data=payload) 109 | print(response.text) 110 | 111 | def get_vehicle(token): 112 | url = "http://localhost:8888/identity/api/v2/vehicle/vehicles" 113 | headers = { 114 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36', 115 | 'Content-Type': 'application/json', 116 | 'Accept': '*/*', 117 | 'Authorization': f'Bearer {token}' 118 | } 119 | response = requests.request("GET", url, headers=headers) 120 | print(response.text) 121 | 122 | def login_page(driver, creds): 123 | driver.get("http://localhost:8888/login") 124 | driver.implicitly_wait(10.5) 125 | text_box = driver.find_element(by=By.ID, value="basic_email") 126 | pass_box = driver.find_element(by=By.ID, value="basic_password") 127 | button = driver.find_element(By.CSS_SELECTOR, "button[type='submit']") 128 | text_box.send_keys(creds[0]) 129 | pass_box.send_keys(creds[1]) 130 | button.click() 131 | time.sleep(3) 132 | 133 | def dashboard_page(driver): 134 | button_element = driver.find_element(By.XPATH, '//button[contains(@class, "ant-btn ant-btn-round ant-btn-primary ant-btn-lg") and .//span[@aria-label="sync"]]') 135 | actions = ActionChains(driver) 136 | actions.move_to_element(button_element).click().perform() 137 | 138 | refresh_button = driver.find_element(By.XPATH, "//button[.//span[text()='Refresh Location']]") 139 | refresh_button.click() 140 | time.sleep(3) 141 | 142 | def logout(driver): 143 | div_element = driver.find_element(By.XPATH, '//div[contains(@class, "ant-dropdown-trigger nav-items") and .//span[@aria-label="down"]]') 144 | actions = ActionChains(driver) 145 | actions.move_to_element(div_element).perform() 146 | logout_button = driver.find_element(By.XPATH, '//span[contains(@class, "ant-dropdown-menu-title-content") and .//span[@aria-label="logout"] and contains(., "Logout")]') 147 | actions.move_to_element(logout_button).click().perform() 148 | 149 | def teardown(driver): 150 | driver.quit() 151 | 152 | def run_tests(): 153 | # Two users are registered and logged in 154 | for _ in range(2): 155 | driver = get_webdriver("CHROME") 156 | creds = register_user() 157 | token = login_user(creds[0], creds[1]) 158 | vehicle = register_vehicle(creds[0]) 159 | add_vehicle(vehicle, token) 160 | get_vehicle(token) 161 | time.sleep(3) 162 | login_page(driver, creds) 163 | dashboard_page(driver) 164 | logout(driver) 165 | teardown(driver) 166 | 167 | if __name__ == "__main__": 168 | run_tests() 169 | -------------------------------------------------------------------------------- /goat_functional_tests/selenium/requirements.txt: -------------------------------------------------------------------------------- 1 | selenium 2 | faker --------------------------------------------------------------------------------