New upstream version 3.17.0
Sophie Brun
2 years ago
0 | * MOD only show settings of this version in faraday-manage settings | |
1 | * FIX update minimum version of click dependency |
0 | Jul 2nd, 2021 |
0 | * ADD `--data` parameter to `faraday-manage settings` | |
1 | * MOD Process report files in a separate process | |
2 | * MOD Make `bulk_create` requests asynchronous |
0 | Aug 10th, 2021 |
8 | 8 | * _header.md_, md format lines at the beginning of the release file |
9 | 9 | * _footer.md_, md format lines at the ending of the release file |
10 | 10 | * _changelog.py_, a python file, which will generate the release file |
11 | * The python file process is: | |
12 | * Iterate over all the version folder in sorted order, joining all .md files in only one ( _white/pink/black.md_ ) in the proper version folder. | |
11 | * The python file process is: | |
12 | * Iterate over all the version folder in sorted order, joining all .json files in only one .md ( | |
13 | _community/prof/corp.md_ ) in the proper version folder. | |
13 | 14 | * Generate the release file as header/v0file.md/.../vnfile.md/footer |
14 | 15 | * The release step-by-step generation should be: |
15 | 16 | 1. Checkout white/master and go to CHANGELOG/ |
29 | 30 | 1. Replace _old **RELEASE.md**_ with new generated file |
30 | 31 | 1. Git add CHANGELOG/ |
31 | 32 | 1. Commit & push |
33 | ||
34 | As for faraday 3.15.0, the changelog file changed to .json format with this structure: | |
35 | ```json | |
36 | { | |
37 | "level": "community|prof|corp", | |
38 | "md": "<changelog text>" | |
39 | } | |
40 | ``` |
0 | 0 | New features in the latest update |
1 | 1 | ===================================== |
2 | 2 | |
3 | ||
4 | 3.17.0 [Aug 10th, 2021]: | |
5 | --- | |
6 | * ADD `--data` parameter to `faraday-manage settings` | |
7 | * MOD Process report files in a separate process | |
8 | * MOD Make `bulk_create` requests asynchronous | |
9 | ||
10 | 3.16.1 [Jul 2nd, 2021]: | |
11 | --- | |
12 | * MOD only show settings of this version in faraday-manage settings | |
13 | * FIX update minimum version of click dependency | |
3 | 14 | |
4 | 15 | 3.16.0 [Jun 29th, 2021]: |
5 | 16 | --- |
0 | GNU GENERAL PUBLIC LICENSE | |
1 | Version 3, 29 June 2007 | |
2 | ||
3 | Copyright (C) 2007 Free Software Foundation, Inc. [http://fsf.org/] | |
4 | Everyone is permitted to copy and distribute verbatim copies | |
5 | of this license document, but changing it is not allowed. | |
6 | ||
7 | Preamble | |
8 | ||
9 | The GNU General Public License is a free, copyleft license for | |
10 | software and other kinds of works. | |
11 | ||
12 | The licenses for most software and other practical works are designed | |
13 | to take away your freedom to share and change the works. By contrast, | |
14 | the GNU General Public License is intended to guarantee your freedom to | |
15 | share and change all versions of a program--to make sure it remains free | |
16 | software for all its users. We, the Free Software Foundation, use the | |
17 | GNU General Public License for most of our software; it applies also to | |
18 | any other work released this way by its authors. You can apply it to | |
19 | your programs, too. | |
20 | ||
21 | When we speak of free software, we are referring to freedom, not | |
22 | price. Our General Public Licenses are designed to make sure that you | |
23 | have the freedom to distribute copies of free software (and charge for | |
24 | them if you wish), that you receive source code or can get it if you | |
25 | want it, that you can change the software or use pieces of it in new | |
26 | free programs, and that you know you can do these things. | |
27 | ||
28 | To protect your rights, we need to prevent others from denying you | |
29 | these rights or asking you to surrender the rights. Therefore, you have | |
30 | certain responsibilities if you distribute copies of the software, or if | |
31 | you modify it: responsibilities to respect the freedom of others. | |
32 | ||
33 | For example, if you distribute copies of such a program, whether | |
34 | gratis or for a fee, you must pass on to the recipients the same | |
35 | freedoms that you received. You must make sure that they, too, receive | |
36 | or can get the source code. And you must show them these terms so they | |
37 | know their rights. | |
38 | ||
39 | Developers that use the GNU GPL protect your rights with two steps: | |
40 | (1) assert copyright on the software, and (2) offer you this License | |
41 | giving you legal permission to copy, distribute and/or modify it. | |
42 | ||
43 | For the developers' and authors' protection, the GPL clearly explains | |
44 | that there is no warranty for this free software. For both users' and | |
45 | authors' sake, the GPL requires that modified versions be marked as | |
46 | changed, so that their problems will not be attributed erroneously to | |
47 | authors of previous versions. | |
48 | ||
49 | Some devices are designed to deny users access to install or run | |
50 | modified versions of the software inside them, although the manufacturer | |
51 | can do so. This is fundamentally incompatible with the aim of | |
52 | protecting users' freedom to change the software. The systematic | |
53 | pattern of such abuse occurs in the area of products for individuals to | |
54 | use, which is precisely where it is most unacceptable. Therefore, we | |
55 | have designed this version of the GPL to prohibit the practice for those | |
56 | products. If such problems arise substantially in other domains, we | |
57 | stand ready to extend this provision to those domains in future versions | |
58 | of the GPL, as needed to protect the freedom of users. | |
59 | ||
60 | Finally, every program is threatened constantly by software patents. | |
61 | States should not allow patents to restrict development and use of | |
62 | software on general-purpose computers, but in those that do, we wish to | |
63 | avoid the special danger that patents applied to a free program could | |
64 | make it effectively proprietary. To prevent this, the GPL assures that | |
65 | patents cannot be used to render the program non-free. | |
66 | ||
67 | The precise terms and conditions for copying, distribution and | |
68 | modification follow. | |
69 | ||
70 | TERMS AND CONDITIONS | |
71 | ||
72 | 0. Definitions. | |
73 | ||
74 | "This License" refers to version 3 of the GNU General Public License. | |
75 | ||
76 | "Copyright" also means copyright-like laws that apply to other kinds of | |
77 | works, such as semiconductor masks. | |
78 | ||
79 | "The Program" refers to any copyrightable work licensed under this | |
80 | License. Each licensee is addressed as "you". "Licensees" and | |
81 | "recipients" may be individuals or organizations. | |
82 | ||
83 | To "modify" a work means to copy from or adapt all or part of the work | |
84 | in a fashion requiring copyright permission, other than the making of an | |
85 | exact copy. The resulting work is called a "modified version" of the | |
86 | earlier work or a work "based on" the earlier work. | |
87 | ||
88 | A "covered work" means either the unmodified Program or a work based | |
89 | on the Program. | |
90 | ||
91 | To "propagate" a work means to do anything with it that, without | |
92 | permission, would make you directly or secondarily liable for | |
93 | infringement under applicable copyright law, except executing it on a | |
94 | computer or modifying a private copy. Propagation includes copying, | |
95 | distribution (with or without modification), making available to the | |
96 | public, and in some countries other activities as well. | |
97 | ||
98 | To "convey" a work means any kind of propagation that enables other | |
99 | parties to make or receive copies. Mere interaction with a user through | |
100 | a computer network, with no transfer of a copy, is not conveying. | |
101 | ||
102 | An interactive user interface displays "Appropriate Legal Notices" | |
103 | to the extent that it includes a convenient and prominently visible | |
104 | feature that (1) displays an appropriate copyright notice, and (2) | |
105 | tells the user that there is no warranty for the work (except to the | |
106 | extent that warranties are provided), that licensees may convey the | |
107 | work under this License, and how to view a copy of this License. If | |
108 | the interface presents a list of user commands or options, such as a | |
109 | menu, a prominent item in the list meets this criterion. | |
110 | ||
111 | 1. Source Code. | |
112 | ||
113 | The "source code" for a work means the preferred form of the work | |
114 | for making modifications to it. "Object code" means any non-source | |
115 | form of a work. | |
116 | ||
117 | A "Standard Interface" means an interface that either is an official | |
118 | standard defined by a recognized standards body, or, in the case of | |
119 | interfaces specified for a particular programming language, one that | |
120 | is widely used among developers working in that language. | |
121 | ||
122 | The "System Libraries" of an executable work include anything, other | |
123 | than the work as a whole, that (a) is included in the normal form of | |
124 | packaging a Major Component, but which is not part of that Major | |
125 | Component, and (b) serves only to enable use of the work with that | |
126 | Major Component, or to implement a Standard Interface for which an | |
127 | implementation is available to the public in source code form. A | |
128 | "Major Component", in this context, means a major essential component | |
129 | (kernel, window system, and so on) of the specific operating system | |
130 | (if any) on which the executable work runs, or a compiler used to | |
131 | produce the work, or an object code interpreter used to run it. | |
132 | ||
133 | The "Corresponding Source" for a work in object code form means all | |
134 | the source code needed to generate, install, and (for an executable | |
135 | work) run the object code and to modify the work, including scripts to | |
136 | control those activities. However, it does not include the work's | |
137 | System Libraries, or general-purpose tools or generally available free | |
138 | programs which are used unmodified in performing those activities but | |
139 | which are not part of the work. For example, Corresponding Source | |
140 | includes interface definition files associated with source files for | |
141 | the work, and the source code for shared libraries and dynamically | |
142 | linked subprograms that the work is specifically designed to require, | |
143 | such as by intimate data communication or control flow between those | |
144 | subprograms and other parts of the work. | |
145 | ||
146 | The Corresponding Source need not include anything that users | |
147 | can regenerate automatically from other parts of the Corresponding | |
148 | Source. | |
149 | ||
150 | The Corresponding Source for a work in source code form is that | |
151 | same work. | |
152 | ||
153 | 2. Basic Permissions. | |
154 | ||
155 | All rights granted under this License are granted for the term of | |
156 | copyright on the Program, and are irrevocable provided the stated | |
157 | conditions are met. This License explicitly affirms your unlimited | |
158 | permission to run the unmodified Program. The output from running a | |
159 | covered work is covered by this License only if the output, given its | |
160 | content, constitutes a covered work. This License acknowledges your | |
161 | rights of fair use or other equivalent, as provided by copyright law. | |
162 | ||
163 | You may make, run and propagate covered works that you do not | |
164 | convey, without conditions so long as your license otherwise remains | |
165 | in force. You may convey covered works to others for the sole purpose | |
166 | of having them make modifications exclusively for you, or provide you | |
167 | with facilities for running those works, provided that you comply with | |
168 | the terms of this License in conveying all material for which you do | |
169 | not control copyright. Those thus making or running the covered works | |
170 | for you must do so exclusively on your behalf, under your direction | |
171 | and control, on terms that prohibit them from making any copies of | |
172 | your copyrighted material outside their relationship with you. | |
173 | ||
174 | Conveying under any other circumstances is permitted solely under | |
175 | the conditions stated below. Sublicensing is not allowed; section 10 | |
176 | makes it unnecessary. | |
177 | ||
178 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. | |
179 | ||
180 | No covered work shall be deemed part of an effective technological | |
181 | measure under any applicable law fulfilling obligations under article | |
182 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or | |
183 | similar laws prohibiting or restricting circumvention of such | |
184 | measures. | |
185 | ||
186 | When you convey a covered work, you waive any legal power to forbid | |
187 | circumvention of technological measures to the extent such circumvention | |
188 | is effected by exercising rights under this License with respect to | |
189 | the covered work, and you disclaim any intention to limit operation or | |
190 | modification of the work as a means of enforcing, against the work's | |
191 | users, your or third parties' legal rights to forbid circumvention of | |
192 | technological measures. | |
193 | ||
194 | 4. Conveying Verbatim Copies. | |
195 | ||
196 | You may convey verbatim copies of the Program's source code as you | |
197 | receive it, in any medium, provided that you conspicuously and | |
198 | appropriately publish on each copy an appropriate copyright notice; | |
199 | keep intact all notices stating that this License and any | |
200 | non-permissive terms added in accord with section 7 apply to the code; | |
201 | keep intact all notices of the absence of any warranty; and give all | |
202 | recipients a copy of this License along with the Program. | |
203 | ||
204 | You may charge any price or no price for each copy that you convey, | |
205 | and you may offer support or warranty protection for a fee. | |
206 | ||
207 | 5. Conveying Modified Source Versions. | |
208 | ||
209 | You may convey a work based on the Program, or the modifications to | |
210 | produce it from the Program, in the form of source code under the | |
211 | terms of section 4, provided that you also meet all of these conditions: | |
212 | ||
213 | a) The work must carry prominent notices stating that you modified | |
214 | it, and giving a relevant date. | |
215 | ||
216 | b) The work must carry prominent notices stating that it is | |
217 | released under this License and any conditions added under section | |
218 | 7. This requirement modifies the requirement in section 4 to | |
219 | "keep intact all notices". | |
220 | ||
221 | c) You must license the entire work, as a whole, under this | |
222 | License to anyone who comes into possession of a copy. This | |
223 | License will therefore apply, along with any applicable section 7 | |
224 | additional terms, to the whole of the work, and all its parts, | |
225 | regardless of how they are packaged. This License gives no | |
226 | permission to license the work in any other way, but it does not | |
227 | invalidate such permission if you have separately received it. | |
228 | ||
229 | d) If the work has interactive user interfaces, each must display | |
230 | Appropriate Legal Notices; however, if the Program has interactive | |
231 | interfaces that do not display Appropriate Legal Notices, your | |
232 | work need not make them do so. | |
233 | ||
234 | A compilation of a covered work with other separate and independent | |
235 | works, which are not by their nature extensions of the covered work, | |
236 | and which are not combined with it such as to form a larger program, | |
237 | in or on a volume of a storage or distribution medium, is called an | |
238 | "aggregate" if the compilation and its resulting copyright are not | |
239 | used to limit the access or legal rights of the compilation's users | |
240 | beyond what the individual works permit. Inclusion of a covered work | |
241 | in an aggregate does not cause this License to apply to the other | |
242 | parts of the aggregate. | |
243 | ||
244 | 6. Conveying Non-Source Forms. | |
245 | ||
246 | You may convey a covered work in object code form under the terms | |
247 | of sections 4 and 5, provided that you also convey the | |
248 | machine-readable Corresponding Source under the terms of this License, | |
249 | in one of these ways: | |
250 | ||
251 | a) Convey the object code in, or embodied in, a physical product | |
252 | (including a physical distribution medium), accompanied by the | |
253 | Corresponding Source fixed on a durable physical medium | |
254 | customarily used for software interchange. | |
255 | ||
256 | b) Convey the object code in, or embodied in, a physical product | |
257 | (including a physical distribution medium), accompanied by a | |
258 | written offer, valid for at least three years and valid for as | |
259 | long as you offer spare parts or customer support for that product | |
260 | model, to give anyone who possesses the object code either (1) a | |
261 | copy of the Corresponding Source for all the software in the | |
262 | product that is covered by this License, on a durable physical | |
263 | medium customarily used for software interchange, for a price no | |
264 | more than your reasonable cost of physically performing this | |
265 | conveying of source, or (2) access to copy the | |
266 | Corresponding Source from a network server at no charge. | |
267 | ||
268 | c) Convey individual copies of the object code with a copy of the | |
269 | written offer to provide the Corresponding Source. This | |
270 | alternative is allowed only occasionally and noncommercially, and | |
271 | only if you received the object code with such an offer, in accord | |
272 | with subsection 6b. | |
273 | ||
274 | d) Convey the object code by offering access from a designated | |
275 | place (gratis or for a charge), and offer equivalent access to the | |
276 | Corresponding Source in the same way through the same place at no | |
277 | further charge. You need not require recipients to copy the | |
278 | Corresponding Source along with the object code. If the place to | |
279 | copy the object code is a network server, the Corresponding Source | |
280 | may be on a different server (operated by you or a third party) | |
281 | that supports equivalent copying facilities, provided you maintain | |
282 | clear directions next to the object code saying where to find the | |
283 | Corresponding Source. Regardless of what server hosts the | |
284 | Corresponding Source, you remain obligated to ensure that it is | |
285 | available for as long as needed to satisfy these requirements. | |
286 | ||
287 | e) Convey the object code using peer-to-peer transmission, provided | |
288 | you inform other peers where the object code and Corresponding | |
289 | Source of the work are being offered to the general public at no | |
290 | charge under subsection 6d. | |
291 | ||
292 | A separable portion of the object code, whose source code is excluded | |
293 | from the Corresponding Source as a System Library, need not be | |
294 | included in conveying the object code work. | |
295 | ||
296 | A "User Product" is either (1) a "consumer product", which means any | |
297 | tangible personal property which is normally used for personal, family, | |
298 | or household purposes, or (2) anything designed or sold for incorporation | |
299 | into a dwelling. In determining whether a product is a consumer product, | |
300 | doubtful cases shall be resolved in favor of coverage. For a particular | |
301 | product received by a particular user, "normally used" refers to a | |
302 | typical or common use of that class of product, regardless of the status | |
303 | of the particular user or of the way in which the particular user | |
304 | actually uses, or expects or is expected to use, the product. A product | |
305 | is a consumer product regardless of whether the product has substantial | |
306 | commercial, industrial or non-consumer uses, unless such uses represent | |
307 | the only significant mode of use of the product. | |
308 | ||
309 | "Installation Information" for a User Product means any methods, | |
310 | procedures, authorization keys, or other information required to install | |
311 | and execute modified versions of a covered work in that User Product from | |
312 | a modified version of its Corresponding Source. The information must | |
313 | suffice to ensure that the continued functioning of the modified object | |
314 | code is in no case prevented or interfered with solely because | |
315 | modification has been made. | |
316 | ||
317 | If you convey an object code work under this section in, or with, or | |
318 | specifically for use in, a User Product, and the conveying occurs as | |
319 | part of a transaction in which the right of possession and use of the | |
320 | User Product is transferred to the recipient in perpetuity or for a | |
321 | fixed term (regardless of how the transaction is characterized), the | |
322 | Corresponding Source conveyed under this section must be accompanied | |
323 | by the Installation Information. But this requirement does not apply | |
324 | if neither you nor any third party retains the ability to install | |
325 | modified object code on the User Product (for example, the work has | |
326 | been installed in ROM). | |
327 | ||
328 | The requirement to provide Installation Information does not include a | |
329 | requirement to continue to provide support service, warranty, or updates | |
330 | for a work that has been modified or installed by the recipient, or for | |
331 | the User Product in which it has been modified or installed. Access to a | |
332 | network may be denied when the modification itself materially and | |
333 | adversely affects the operation of the network or violates the rules and | |
334 | protocols for communication across the network. | |
335 | ||
336 | Corresponding Source conveyed, and Installation Information provided, | |
337 | in accord with this section must be in a format that is publicly | |
338 | documented (and with an implementation available to the public in | |
339 | source code form), and must require no special password or key for | |
340 | unpacking, reading or copying. | |
341 | ||
342 | 7. Additional Terms. | |
343 | ||
344 | "Additional permissions" are terms that supplement the terms of this | |
345 | License by making exceptions from one or more of its conditions. | |
346 | Additional permissions that are applicable to the entire Program shall | |
347 | be treated as though they were included in this License, to the extent | |
348 | that they are valid under applicable law. If additional permissions | |
349 | apply only to part of the Program, that part may be used separately | |
350 | under those permissions, but the entire Program remains governed by | |
351 | this License without regard to the additional permissions. | |
352 | ||
353 | When you convey a copy of a covered work, you may at your option | |
354 | remove any additional permissions from that copy, or from any part of | |
355 | it. (Additional permissions may be written to require their own | |
356 | removal in certain cases when you modify the work.) You may place | |
357 | additional permissions on material, added by you to a covered work, | |
358 | for which you have or can give appropriate copyright permission. | |
359 | ||
360 | Notwithstanding any other provision of this License, for material you | |
361 | add to a covered work, you may (if authorized by the copyright holders of | |
362 | that material) supplement the terms of this License with terms: | |
363 | ||
364 | a) Disclaiming warranty or limiting liability differently from the | |
365 | terms of sections 15 and 16 of this License; or | |
366 | ||
367 | b) Requiring preservation of specified reasonable legal notices or | |
368 | author attributions in that material or in the Appropriate Legal | |
369 | Notices displayed by works containing it; or | |
370 | ||
371 | c) Prohibiting misrepresentation of the origin of that material, or | |
372 | requiring that modified versions of such material be marked in | |
373 | reasonable ways as different from the original version; or | |
374 | ||
375 | d) Limiting the use for publicity purposes of names of licensors or | |
376 | authors of the material; or | |
377 | ||
378 | e) Declining to grant rights under trademark law for use of some | |
379 | trade names, trademarks, or service marks; or | |
380 | ||
381 | f) Requiring indemnification of licensors and authors of that | |
382 | material by anyone who conveys the material (or modified versions of | |
383 | it) with contractual assumptions of liability to the recipient, for | |
384 | any liability that these contractual assumptions directly impose on | |
385 | those licensors and authors. | |
386 | ||
387 | All other non-permissive additional terms are considered "further | |
388 | restrictions" within the meaning of section 10. If the Program as you | |
389 | received it, or any part of it, contains a notice stating that it is | |
390 | governed by this License along with a term that is a further | |
391 | restriction, you may remove that term. If a license document contains | |
392 | a further restriction but permits relicensing or conveying under this | |
393 | License, you may add to a covered work material governed by the terms | |
394 | of that license document, provided that the further restriction does | |
395 | not survive such relicensing or conveying. | |
396 | ||
397 | If you add terms to a covered work in accord with this section, you | |
398 | must place, in the relevant source files, a statement of the | |
399 | additional terms that apply to those files, or a notice indicating | |
400 | where to find the applicable terms. | |
401 | ||
402 | Additional terms, permissive or non-permissive, may be stated in the | |
403 | form of a separately written license, or stated as exceptions; | |
404 | the above requirements apply either way. | |
405 | ||
406 | 8. Termination. | |
407 | ||
408 | You may not propagate or modify a covered work except as expressly | |
409 | provided under this License. Any attempt otherwise to propagate or | |
410 | modify it is void, and will automatically terminate your rights under | |
411 | this License (including any patent licenses granted under the third | |
412 | paragraph of section 11). | |
413 | ||
414 | However, if you cease all violation of this License, then your | |
415 | license from a particular copyright holder is reinstated (a) | |
416 | provisionally, unless and until the copyright holder explicitly and | |
417 | finally terminates your license, and (b) permanently, if the copyright | |
418 | holder fails to notify you of the violation by some reasonable means | |
419 | prior to 60 days after the cessation. | |
420 | ||
421 | Moreover, your license from a particular copyright holder is | |
422 | reinstated permanently if the copyright holder notifies you of the | |
423 | violation by some reasonable means, this is the first time you have | |
424 | received notice of violation of this License (for any work) from that | |
425 | copyright holder, and you cure the violation prior to 30 days after | |
426 | your receipt of the notice. | |
427 | ||
428 | Termination of your rights under this section does not terminate the | |
429 | licenses of parties who have received copies or rights from you under | |
430 | this License. If your rights have been terminated and not permanently | |
431 | reinstated, you do not qualify to receive new licenses for the same | |
432 | material under section 10. | |
433 | ||
434 | 9. Acceptance Not Required for Having Copies. | |
435 | ||
436 | You are not required to accept this License in order to receive or | |
437 | run a copy of the Program. Ancillary propagation of a covered work | |
438 | occurring solely as a consequence of using peer-to-peer transmission | |
439 | to receive a copy likewise does not require acceptance. However, | |
440 | nothing other than this License grants you permission to propagate or | |
441 | modify any covered work. These actions infringe copyright if you do | |
442 | not accept this License. Therefore, by modifying or propagating a | |
443 | covered work, you indicate your acceptance of this License to do so. | |
444 | ||
445 | 10. Automatic Licensing of Downstream Recipients. | |
446 | ||
447 | Each time you convey a covered work, the recipient automatically | |
448 | receives a license from the original licensors, to run, modify and | |
449 | propagate that work, subject to this License. You are not responsible | |
450 | for enforcing compliance by third parties with this License. | |
451 | ||
452 | An "entity transaction" is a transaction transferring control of an | |
453 | organization, or substantially all assets of one, or subdividing an | |
454 | organization, or merging organizations. If propagation of a covered | |
455 | work results from an entity transaction, each party to that | |
456 | transaction who receives a copy of the work also receives whatever | |
457 | licenses to the work the party's predecessor in interest had or could | |
458 | give under the previous paragraph, plus a right to possession of the | |
459 | Corresponding Source of the work from the predecessor in interest, if | |
460 | the predecessor has it or can get it with reasonable efforts. | |
461 | ||
462 | You may not impose any further restrictions on the exercise of the | |
463 | rights granted or affirmed under this License. For example, you may | |
464 | not impose a license fee, royalty, or other charge for exercise of | |
465 | rights granted under this License, and you may not initiate litigation | |
466 | (including a cross-claim or counterclaim in a lawsuit) alleging that | |
467 | any patent claim is infringed by making, using, selling, offering for | |
468 | sale, or importing the Program or any portion of it. | |
469 | ||
470 | 11. Patents. | |
471 | ||
472 | A "contributor" is a copyright holder who authorizes use under this | |
473 | License of the Program or a work on which the Program is based. The | |
474 | work thus licensed is called the contributor's "contributor version". | |
475 | ||
476 | A contributor's "essential patent claims" are all patent claims | |
477 | owned or controlled by the contributor, whether already acquired or | |
478 | hereafter acquired, that would be infringed by some manner, permitted | |
479 | by this License, of making, using, or selling its contributor version, | |
480 | but do not include claims that would be infringed only as a | |
481 | consequence of further modification of the contributor version. For | |
482 | purposes of this definition, "control" includes the right to grant | |
483 | patent sublicenses in a manner consistent with the requirements of | |
484 | this License. | |
485 | ||
486 | Each contributor grants you a non-exclusive, worldwide, royalty-free | |
487 | patent license under the contributor's essential patent claims, to | |
488 | make, use, sell, offer for sale, import and otherwise run, modify and | |
489 | propagate the contents of its contributor version. | |
490 | ||
491 | In the following three paragraphs, a "patent license" is any express | |
492 | agreement or commitment, however denominated, not to enforce a patent | |
493 | (such as an express permission to practice a patent or covenant not to | |
494 | sue for patent infringement). To "grant" such a patent license to a | |
495 | party means to make such an agreement or commitment not to enforce a | |
496 | patent against the party. | |
497 | ||
498 | If you convey a covered work, knowingly relying on a patent license, | |
499 | and the Corresponding Source of the work is not available for anyone | |
500 | to copy, free of charge and under the terms of this License, through a | |
501 | publicly available network server or other readily accessible means, | |
502 | then you must either (1) cause the Corresponding Source to be so | |
503 | available, or (2) arrange to deprive yourself of the benefit of the | |
504 | patent license for this particular work, or (3) arrange, in a manner | |
505 | consistent with the requirements of this License, to extend the patent | |
506 | license to downstream recipients. "Knowingly relying" means you have | |
507 | actual knowledge that, but for the patent license, your conveying the | |
508 | covered work in a country, or your recipient's use of the covered work | |
509 | in a country, would infringe one or more identifiable patents in that | |
510 | country that you have reason to believe are valid. | |
511 | ||
512 | If, pursuant to or in connection with a single transaction or | |
513 | arrangement, you convey, or propagate by procuring conveyance of, a | |
514 | covered work, and grant a patent license to some of the parties | |
515 | receiving the covered work authorizing them to use, propagate, modify | |
516 | or convey a specific copy of the covered work, then the patent license | |
517 | you grant is automatically extended to all recipients of the covered | |
518 | work and works based on it. | |
519 | ||
520 | A patent license is "discriminatory" if it does not include within | |
521 | the scope of its coverage, prohibits the exercise of, or is | |
522 | conditioned on the non-exercise of one or more of the rights that are | |
523 | specifically granted under this License. You may not convey a covered | |
524 | work if you are a party to an arrangement with a third party that is | |
525 | in the business of distributing software, under which you make payment | |
526 | to the third party based on the extent of your activity of conveying | |
527 | the work, and under which the third party grants, to any of the | |
528 | parties who would receive the covered work from you, a discriminatory | |
529 | patent license (a) in connection with copies of the covered work | |
530 | conveyed by you (or copies made from those copies), or (b) primarily | |
531 | for and in connection with specific products or compilations that | |
532 | contain the covered work, unless you entered into that arrangement, | |
533 | or that patent license was granted, prior to 28 March 2007. | |
534 | ||
535 | Nothing in this License shall be construed as excluding or limiting | |
536 | any implied license or other defenses to infringement that may | |
537 | otherwise be available to you under applicable patent law. | |
538 | ||
539 | 12. No Surrender of Others' Freedom. | |
540 | ||
541 | If conditions are imposed on you (whether by court order, agreement or | |
542 | otherwise) that contradict the conditions of this License, they do not | |
543 | excuse you from the conditions of this License. If you cannot convey a | |
544 | covered work so as to satisfy simultaneously your obligations under this | |
545 | License and any other pertinent obligations, then as a consequence you may | |
546 | not convey it at all. For example, if you agree to terms that obligate you | |
547 | to collect a royalty for further conveying from those to whom you convey | |
548 | the Program, the only way you could satisfy both those terms and this | |
549 | License would be to refrain entirely from conveying the Program. | |
550 | ||
551 | 13. Use with the GNU Affero General Public License. | |
552 | ||
553 | Notwithstanding any other provision of this License, you have | |
554 | permission to link or combine any covered work with a work licensed | |
555 | under version 3 of the GNU Affero General Public License into a single | |
556 | combined work, and to convey the resulting work. The terms of this | |
557 | License will continue to apply to the part which is the covered work, | |
558 | but the special requirements of the GNU Affero General Public License, | |
559 | section 13, concerning interaction through a network will apply to the | |
560 | combination as such. | |
561 | ||
562 | 14. Revised Versions of this License. | |
563 | ||
564 | The Free Software Foundation may publish revised and/or new versions of | |
565 | the GNU General Public License from time to time. Such new versions will | |
566 | be similar in spirit to the present version, but may differ in detail to | |
567 | address new problems or concerns. | |
568 | ||
569 | Each version is given a distinguishing version number. If the | |
570 | Program specifies that a certain numbered version of the GNU General | |
571 | Public License "or any later version" applies to it, you have the | |
572 | option of following the terms and conditions either of that numbered | |
573 | version or of any later version published by the Free Software | |
574 | Foundation. If the Program does not specify a version number of the | |
575 | GNU General Public License, you may choose any version ever published | |
576 | by the Free Software Foundation. | |
577 | ||
578 | If the Program specifies that a proxy can decide which future | |
579 | versions of the GNU General Public License can be used, that proxy's | |
580 | public statement of acceptance of a version permanently authorizes you | |
581 | to choose that version for the Program. | |
582 | ||
583 | Later license versions may give you additional or different | |
584 | permissions. However, no additional obligations are imposed on any | |
585 | author or copyright holder as a result of your choosing to follow a | |
586 | later version. | |
587 | ||
588 | 15. Disclaimer of Warranty. | |
589 | ||
590 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY | |
591 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT | |
592 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY | |
593 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, | |
594 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
595 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM | |
596 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF | |
597 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. | |
598 | ||
599 | 16. Limitation of Liability. | |
600 | ||
601 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | |
602 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS | |
603 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY | |
604 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE | |
605 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF | |
606 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD | |
607 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), | |
608 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF | |
609 | SUCH DAMAGES. | |
610 | ||
611 | 17. Interpretation of Sections 15 and 16. | |
612 | ||
613 | If the disclaimer of warranty and limitation of liability provided | |
614 | above cannot be given local legal effect according to their terms, | |
615 | reviewing courts shall apply local law that most closely approximates | |
616 | an absolute waiver of all civil liability in connection with the | |
617 | Program, unless a warranty or assumption of liability accompanies a | |
618 | copy of the Program in return for a fee. | |
619 | ||
620 | END OF TERMS AND CONDITIONS | |
621 | ||
622 | _________________________________________________________________________________ | |
623 | ||
624 | This license does not apply to the following components and any component which may not be included in the following file: 'doc/LIBRARY_LICENSE' |
36 | 36 | For more information about the installation, check out our [Installation Wiki](https://github.com/infobyte/faraday/wiki/Install-Guide). |
37 | 37 | |
38 | 38 | ## Development |
39 | ||
40 | You need Python 3.6+ and postgres to run the faraday server. | |
41 | 39 | |
42 | 40 | If you want to develop for Faraday, please follow our [development setup for linux](https://github.com/infobyte/faraday/wiki/Development-setup) or [development setup for OSX](https://github.com/infobyte/faraday/wiki/Development-Installation-OSX). |
43 | 41 |
0 | 0 | New features in the latest update |
1 | 1 | ===================================== |
2 | 2 | |
3 | ||
4 | 3.17.0 [Aug 10th, 2021]: | |
5 | --- | |
6 | * ADD `--data` parameter to `faraday-manage settings` | |
7 | * MOD Process report files in a separate process | |
8 | * MOD Make `bulk_create` requests asynchronous | |
9 | ||
10 | 3.16.1 [Jul 2nd, 2021]: | |
11 | --- | |
12 | * MOD only show settings of this version in faraday-manage settings | |
13 | * FIX update minimum version of click dependency | |
3 | 14 | |
4 | 15 | 3.16.0 [Jun 29th, 2021]: |
5 | 16 | --- |
0 | GNU GENERAL PUBLIC LICENSE | |
1 | Version 3, 29 June 2007 | |
2 | ||
3 | Copyright (C) 2007 Free Software Foundation, Inc. [http://fsf.org/] | |
4 | Everyone is permitted to copy and distribute verbatim copies | |
5 | of this license document, but changing it is not allowed. | |
6 | ||
7 | Preamble | |
8 | ||
9 | The GNU General Public License is a free, copyleft license for | |
10 | software and other kinds of works. | |
11 | ||
12 | The licenses for most software and other practical works are designed | |
13 | to take away your freedom to share and change the works. By contrast, | |
14 | the GNU General Public License is intended to guarantee your freedom to | |
15 | share and change all versions of a program--to make sure it remains free | |
16 | software for all its users. We, the Free Software Foundation, use the | |
17 | GNU General Public License for most of our software; it applies also to | |
18 | any other work released this way by its authors. You can apply it to | |
19 | your programs, too. | |
20 | ||
21 | When we speak of free software, we are referring to freedom, not | |
22 | price. Our General Public Licenses are designed to make sure that you | |
23 | have the freedom to distribute copies of free software (and charge for | |
24 | them if you wish), that you receive source code or can get it if you | |
25 | want it, that you can change the software or use pieces of it in new | |
26 | free programs, and that you know you can do these things. | |
27 | ||
28 | To protect your rights, we need to prevent others from denying you | |
29 | these rights or asking you to surrender the rights. Therefore, you have | |
30 | certain responsibilities if you distribute copies of the software, or if | |
31 | you modify it: responsibilities to respect the freedom of others. | |
32 | ||
33 | For example, if you distribute copies of such a program, whether | |
34 | gratis or for a fee, you must pass on to the recipients the same | |
35 | freedoms that you received. You must make sure that they, too, receive | |
36 | or can get the source code. And you must show them these terms so they | |
37 | know their rights. | |
38 | ||
39 | Developers that use the GNU GPL protect your rights with two steps: | |
40 | (1) assert copyright on the software, and (2) offer you this License | |
41 | giving you legal permission to copy, distribute and/or modify it. | |
42 | ||
43 | For the developers' and authors' protection, the GPL clearly explains | |
44 | that there is no warranty for this free software. For both users' and | |
45 | authors' sake, the GPL requires that modified versions be marked as | |
46 | changed, so that their problems will not be attributed erroneously to | |
47 | authors of previous versions. | |
48 | ||
49 | Some devices are designed to deny users access to install or run | |
50 | modified versions of the software inside them, although the manufacturer | |
51 | can do so. This is fundamentally incompatible with the aim of | |
52 | protecting users' freedom to change the software. The systematic | |
53 | pattern of such abuse occurs in the area of products for individuals to | |
54 | use, which is precisely where it is most unacceptable. Therefore, we | |
55 | have designed this version of the GPL to prohibit the practice for those | |
56 | products. If such problems arise substantially in other domains, we | |
57 | stand ready to extend this provision to those domains in future versions | |
58 | of the GPL, as needed to protect the freedom of users. | |
59 | ||
60 | Finally, every program is threatened constantly by software patents. | |
61 | States should not allow patents to restrict development and use of | |
62 | software on general-purpose computers, but in those that do, we wish to | |
63 | avoid the special danger that patents applied to a free program could | |
64 | make it effectively proprietary. To prevent this, the GPL assures that | |
65 | patents cannot be used to render the program non-free. | |
66 | ||
67 | The precise terms and conditions for copying, distribution and | |
68 | modification follow. | |
69 | ||
70 | TERMS AND CONDITIONS | |
71 | ||
72 | 0. Definitions. | |
73 | ||
74 | "This License" refers to version 3 of the GNU General Public License. | |
75 | ||
76 | "Copyright" also means copyright-like laws that apply to other kinds of | |
77 | works, such as semiconductor masks. | |
78 | ||
79 | "The Program" refers to any copyrightable work licensed under this | |
80 | License. Each licensee is addressed as "you". "Licensees" and | |
81 | "recipients" may be individuals or organizations. | |
82 | ||
83 | To "modify" a work means to copy from or adapt all or part of the work | |
84 | in a fashion requiring copyright permission, other than the making of an | |
85 | exact copy. The resulting work is called a "modified version" of the | |
86 | earlier work or a work "based on" the earlier work. | |
87 | ||
88 | A "covered work" means either the unmodified Program or a work based | |
89 | on the Program. | |
90 | ||
91 | To "propagate" a work means to do anything with it that, without | |
92 | permission, would make you directly or secondarily liable for | |
93 | infringement under applicable copyright law, except executing it on a | |
94 | computer or modifying a private copy. Propagation includes copying, | |
95 | distribution (with or without modification), making available to the | |
96 | public, and in some countries other activities as well. | |
97 | ||
98 | To "convey" a work means any kind of propagation that enables other | |
99 | parties to make or receive copies. Mere interaction with a user through | |
100 | a computer network, with no transfer of a copy, is not conveying. | |
101 | ||
102 | An interactive user interface displays "Appropriate Legal Notices" | |
103 | to the extent that it includes a convenient and prominently visible | |
104 | feature that (1) displays an appropriate copyright notice, and (2) | |
105 | tells the user that there is no warranty for the work (except to the | |
106 | extent that warranties are provided), that licensees may convey the | |
107 | work under this License, and how to view a copy of this License. If | |
108 | the interface presents a list of user commands or options, such as a | |
109 | menu, a prominent item in the list meets this criterion. | |
110 | ||
111 | 1. Source Code. | |
112 | ||
113 | The "source code" for a work means the preferred form of the work | |
114 | for making modifications to it. "Object code" means any non-source | |
115 | form of a work. | |
116 | ||
117 | A "Standard Interface" means an interface that either is an official | |
118 | standard defined by a recognized standards body, or, in the case of | |
119 | interfaces specified for a particular programming language, one that | |
120 | is widely used among developers working in that language. | |
121 | ||
122 | The "System Libraries" of an executable work include anything, other | |
123 | than the work as a whole, that (a) is included in the normal form of | |
124 | packaging a Major Component, but which is not part of that Major | |
125 | Component, and (b) serves only to enable use of the work with that | |
126 | Major Component, or to implement a Standard Interface for which an | |
127 | implementation is available to the public in source code form. A | |
128 | "Major Component", in this context, means a major essential component | |
129 | (kernel, window system, and so on) of the specific operating system | |
130 | (if any) on which the executable work runs, or a compiler used to | |
131 | produce the work, or an object code interpreter used to run it. | |
132 | ||
133 | The "Corresponding Source" for a work in object code form means all | |
134 | the source code needed to generate, install, and (for an executable | |
135 | work) run the object code and to modify the work, including scripts to | |
136 | control those activities. However, it does not include the work's | |
137 | System Libraries, or general-purpose tools or generally available free | |
138 | programs which are used unmodified in performing those activities but | |
139 | which are not part of the work. For example, Corresponding Source | |
140 | includes interface definition files associated with source files for | |
141 | the work, and the source code for shared libraries and dynamically | |
142 | linked subprograms that the work is specifically designed to require, | |
143 | such as by intimate data communication or control flow between those | |
144 | subprograms and other parts of the work. | |
145 | ||
146 | The Corresponding Source need not include anything that users | |
147 | can regenerate automatically from other parts of the Corresponding | |
148 | Source. | |
149 | ||
150 | The Corresponding Source for a work in source code form is that | |
151 | same work. | |
152 | ||
153 | 2. Basic Permissions. | |
154 | ||
155 | All rights granted under this License are granted for the term of | |
156 | copyright on the Program, and are irrevocable provided the stated | |
157 | conditions are met. This License explicitly affirms your unlimited | |
158 | permission to run the unmodified Program. The output from running a | |
159 | covered work is covered by this License only if the output, given its | |
160 | content, constitutes a covered work. This License acknowledges your | |
161 | rights of fair use or other equivalent, as provided by copyright law. | |
162 | ||
163 | You may make, run and propagate covered works that you do not | |
164 | convey, without conditions so long as your license otherwise remains | |
165 | in force. You may convey covered works to others for the sole purpose | |
166 | of having them make modifications exclusively for you, or provide you | |
167 | with facilities for running those works, provided that you comply with | |
168 | the terms of this License in conveying all material for which you do | |
169 | not control copyright. Those thus making or running the covered works | |
170 | for you must do so exclusively on your behalf, under your direction | |
171 | and control, on terms that prohibit them from making any copies of | |
172 | your copyrighted material outside their relationship with you. | |
173 | ||
174 | Conveying under any other circumstances is permitted solely under | |
175 | the conditions stated below. Sublicensing is not allowed; section 10 | |
176 | makes it unnecessary. | |
177 | ||
178 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. | |
179 | ||
180 | No covered work shall be deemed part of an effective technological | |
181 | measure under any applicable law fulfilling obligations under article | |
182 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or | |
183 | similar laws prohibiting or restricting circumvention of such | |
184 | measures. | |
185 | ||
186 | When you convey a covered work, you waive any legal power to forbid | |
187 | circumvention of technological measures to the extent such circumvention | |
188 | is effected by exercising rights under this License with respect to | |
189 | the covered work, and you disclaim any intention to limit operation or | |
190 | modification of the work as a means of enforcing, against the work's | |
191 | users, your or third parties' legal rights to forbid circumvention of | |
192 | technological measures. | |
193 | ||
194 | 4. Conveying Verbatim Copies. | |
195 | ||
196 | You may convey verbatim copies of the Program's source code as you | |
197 | receive it, in any medium, provided that you conspicuously and | |
198 | appropriately publish on each copy an appropriate copyright notice; | |
199 | keep intact all notices stating that this License and any | |
200 | non-permissive terms added in accord with section 7 apply to the code; | |
201 | keep intact all notices of the absence of any warranty; and give all | |
202 | recipients a copy of this License along with the Program. | |
203 | ||
204 | You may charge any price or no price for each copy that you convey, | |
205 | and you may offer support or warranty protection for a fee. | |
206 | ||
207 | 5. Conveying Modified Source Versions. | |
208 | ||
209 | You may convey a work based on the Program, or the modifications to | |
210 | produce it from the Program, in the form of source code under the | |
211 | terms of section 4, provided that you also meet all of these conditions: | |
212 | ||
213 | a) The work must carry prominent notices stating that you modified | |
214 | it, and giving a relevant date. | |
215 | ||
216 | b) The work must carry prominent notices stating that it is | |
217 | released under this License and any conditions added under section | |
218 | 7. This requirement modifies the requirement in section 4 to | |
219 | "keep intact all notices". | |
220 | ||
221 | c) You must license the entire work, as a whole, under this | |
222 | License to anyone who comes into possession of a copy. This | |
223 | License will therefore apply, along with any applicable section 7 | |
224 | additional terms, to the whole of the work, and all its parts, | |
225 | regardless of how they are packaged. This License gives no | |
226 | permission to license the work in any other way, but it does not | |
227 | invalidate such permission if you have separately received it. | |
228 | ||
229 | d) If the work has interactive user interfaces, each must display | |
230 | Appropriate Legal Notices; however, if the Program has interactive | |
231 | interfaces that do not display Appropriate Legal Notices, your | |
232 | work need not make them do so. | |
233 | ||
234 | A compilation of a covered work with other separate and independent | |
235 | works, which are not by their nature extensions of the covered work, | |
236 | and which are not combined with it such as to form a larger program, | |
237 | in or on a volume of a storage or distribution medium, is called an | |
238 | "aggregate" if the compilation and its resulting copyright are not | |
239 | used to limit the access or legal rights of the compilation's users | |
240 | beyond what the individual works permit. Inclusion of a covered work | |
241 | in an aggregate does not cause this License to apply to the other | |
242 | parts of the aggregate. | |
243 | ||
244 | 6. Conveying Non-Source Forms. | |
245 | ||
246 | You may convey a covered work in object code form under the terms | |
247 | of sections 4 and 5, provided that you also convey the | |
248 | machine-readable Corresponding Source under the terms of this License, | |
249 | in one of these ways: | |
250 | ||
251 | a) Convey the object code in, or embodied in, a physical product | |
252 | (including a physical distribution medium), accompanied by the | |
253 | Corresponding Source fixed on a durable physical medium | |
254 | customarily used for software interchange. | |
255 | ||
256 | b) Convey the object code in, or embodied in, a physical product | |
257 | (including a physical distribution medium), accompanied by a | |
258 | written offer, valid for at least three years and valid for as | |
259 | long as you offer spare parts or customer support for that product | |
260 | model, to give anyone who possesses the object code either (1) a | |
261 | copy of the Corresponding Source for all the software in the | |
262 | product that is covered by this License, on a durable physical | |
263 | medium customarily used for software interchange, for a price no | |
264 | more than your reasonable cost of physically performing this | |
265 | conveying of source, or (2) access to copy the | |
266 | Corresponding Source from a network server at no charge. | |
267 | ||
268 | c) Convey individual copies of the object code with a copy of the | |
269 | written offer to provide the Corresponding Source. This | |
270 | alternative is allowed only occasionally and noncommercially, and | |
271 | only if you received the object code with such an offer, in accord | |
272 | with subsection 6b. | |
273 | ||
274 | d) Convey the object code by offering access from a designated | |
275 | place (gratis or for a charge), and offer equivalent access to the | |
276 | Corresponding Source in the same way through the same place at no | |
277 | further charge. You need not require recipients to copy the | |
278 | Corresponding Source along with the object code. If the place to | |
279 | copy the object code is a network server, the Corresponding Source | |
280 | may be on a different server (operated by you or a third party) | |
281 | that supports equivalent copying facilities, provided you maintain | |
282 | clear directions next to the object code saying where to find the | |
283 | Corresponding Source. Regardless of what server hosts the | |
284 | Corresponding Source, you remain obligated to ensure that it is | |
285 | available for as long as needed to satisfy these requirements. | |
286 | ||
287 | e) Convey the object code using peer-to-peer transmission, provided | |
288 | you inform other peers where the object code and Corresponding | |
289 | Source of the work are being offered to the general public at no | |
290 | charge under subsection 6d. | |
291 | ||
292 | A separable portion of the object code, whose source code is excluded | |
293 | from the Corresponding Source as a System Library, need not be | |
294 | included in conveying the object code work. | |
295 | ||
296 | A "User Product" is either (1) a "consumer product", which means any | |
297 | tangible personal property which is normally used for personal, family, | |
298 | or household purposes, or (2) anything designed or sold for incorporation | |
299 | into a dwelling. In determining whether a product is a consumer product, | |
300 | doubtful cases shall be resolved in favor of coverage. For a particular | |
301 | product received by a particular user, "normally used" refers to a | |
302 | typical or common use of that class of product, regardless of the status | |
303 | of the particular user or of the way in which the particular user | |
304 | actually uses, or expects or is expected to use, the product. A product | |
305 | is a consumer product regardless of whether the product has substantial | |
306 | commercial, industrial or non-consumer uses, unless such uses represent | |
307 | the only significant mode of use of the product. | |
308 | ||
309 | "Installation Information" for a User Product means any methods, | |
310 | procedures, authorization keys, or other information required to install | |
311 | and execute modified versions of a covered work in that User Product from | |
312 | a modified version of its Corresponding Source. The information must | |
313 | suffice to ensure that the continued functioning of the modified object | |
314 | code is in no case prevented or interfered with solely because | |
315 | modification has been made. | |
316 | ||
317 | If you convey an object code work under this section in, or with, or | |
318 | specifically for use in, a User Product, and the conveying occurs as | |
319 | part of a transaction in which the right of possession and use of the | |
320 | User Product is transferred to the recipient in perpetuity or for a | |
321 | fixed term (regardless of how the transaction is characterized), the | |
322 | Corresponding Source conveyed under this section must be accompanied | |
323 | by the Installation Information. But this requirement does not apply | |
324 | if neither you nor any third party retains the ability to install | |
325 | modified object code on the User Product (for example, the work has | |
326 | been installed in ROM). | |
327 | ||
328 | The requirement to provide Installation Information does not include a | |
329 | requirement to continue to provide support service, warranty, or updates | |
330 | for a work that has been modified or installed by the recipient, or for | |
331 | the User Product in which it has been modified or installed. Access to a | |
332 | network may be denied when the modification itself materially and | |
333 | adversely affects the operation of the network or violates the rules and | |
334 | protocols for communication across the network. | |
335 | ||
336 | Corresponding Source conveyed, and Installation Information provided, | |
337 | in accord with this section must be in a format that is publicly | |
338 | documented (and with an implementation available to the public in | |
339 | source code form), and must require no special password or key for | |
340 | unpacking, reading or copying. | |
341 | ||
342 | 7. Additional Terms. | |
343 | ||
344 | "Additional permissions" are terms that supplement the terms of this | |
345 | License by making exceptions from one or more of its conditions. | |
346 | Additional permissions that are applicable to the entire Program shall | |
347 | be treated as though they were included in this License, to the extent | |
348 | that they are valid under applicable law. If additional permissions | |
349 | apply only to part of the Program, that part may be used separately | |
350 | under those permissions, but the entire Program remains governed by | |
351 | this License without regard to the additional permissions. | |
352 | ||
353 | When you convey a copy of a covered work, you may at your option | |
354 | remove any additional permissions from that copy, or from any part of | |
355 | it. (Additional permissions may be written to require their own | |
356 | removal in certain cases when you modify the work.) You may place | |
357 | additional permissions on material, added by you to a covered work, | |
358 | for which you have or can give appropriate copyright permission. | |
359 | ||
360 | Notwithstanding any other provision of this License, for material you | |
361 | add to a covered work, you may (if authorized by the copyright holders of | |
362 | that material) supplement the terms of this License with terms: | |
363 | ||
364 | a) Disclaiming warranty or limiting liability differently from the | |
365 | terms of sections 15 and 16 of this License; or | |
366 | ||
367 | b) Requiring preservation of specified reasonable legal notices or | |
368 | author attributions in that material or in the Appropriate Legal | |
369 | Notices displayed by works containing it; or | |
370 | ||
371 | c) Prohibiting misrepresentation of the origin of that material, or | |
372 | requiring that modified versions of such material be marked in | |
373 | reasonable ways as different from the original version; or | |
374 | ||
375 | d) Limiting the use for publicity purposes of names of licensors or | |
376 | authors of the material; or | |
377 | ||
378 | e) Declining to grant rights under trademark law for use of some | |
379 | trade names, trademarks, or service marks; or | |
380 | ||
381 | f) Requiring indemnification of licensors and authors of that | |
382 | material by anyone who conveys the material (or modified versions of | |
383 | it) with contractual assumptions of liability to the recipient, for | |
384 | any liability that these contractual assumptions directly impose on | |
385 | those licensors and authors. | |
386 | ||
387 | All other non-permissive additional terms are considered "further | |
388 | restrictions" within the meaning of section 10. If the Program as you | |
389 | received it, or any part of it, contains a notice stating that it is | |
390 | governed by this License along with a term that is a further | |
391 | restriction, you may remove that term. If a license document contains | |
392 | a further restriction but permits relicensing or conveying under this | |
393 | License, you may add to a covered work material governed by the terms | |
394 | of that license document, provided that the further restriction does | |
395 | not survive such relicensing or conveying. | |
396 | ||
397 | If you add terms to a covered work in accord with this section, you | |
398 | must place, in the relevant source files, a statement of the | |
399 | additional terms that apply to those files, or a notice indicating | |
400 | where to find the applicable terms. | |
401 | ||
402 | Additional terms, permissive or non-permissive, may be stated in the | |
403 | form of a separately written license, or stated as exceptions; | |
404 | the above requirements apply either way. | |
405 | ||
406 | 8. Termination. | |
407 | ||
408 | You may not propagate or modify a covered work except as expressly | |
409 | provided under this License. Any attempt otherwise to propagate or | |
410 | modify it is void, and will automatically terminate your rights under | |
411 | this License (including any patent licenses granted under the third | |
412 | paragraph of section 11). | |
413 | ||
414 | However, if you cease all violation of this License, then your | |
415 | license from a particular copyright holder is reinstated (a) | |
416 | provisionally, unless and until the copyright holder explicitly and | |
417 | finally terminates your license, and (b) permanently, if the copyright | |
418 | holder fails to notify you of the violation by some reasonable means | |
419 | prior to 60 days after the cessation. | |
420 | ||
421 | Moreover, your license from a particular copyright holder is | |
422 | reinstated permanently if the copyright holder notifies you of the | |
423 | violation by some reasonable means, this is the first time you have | |
424 | received notice of violation of this License (for any work) from that | |
425 | copyright holder, and you cure the violation prior to 30 days after | |
426 | your receipt of the notice. | |
427 | ||
428 | Termination of your rights under this section does not terminate the | |
429 | licenses of parties who have received copies or rights from you under | |
430 | this License. If your rights have been terminated and not permanently | |
431 | reinstated, you do not qualify to receive new licenses for the same | |
432 | material under section 10. | |
433 | ||
434 | 9. Acceptance Not Required for Having Copies. | |
435 | ||
436 | You are not required to accept this License in order to receive or | |
437 | run a copy of the Program. Ancillary propagation of a covered work | |
438 | occurring solely as a consequence of using peer-to-peer transmission | |
439 | to receive a copy likewise does not require acceptance. However, | |
440 | nothing other than this License grants you permission to propagate or | |
441 | modify any covered work. These actions infringe copyright if you do | |
442 | not accept this License. Therefore, by modifying or propagating a | |
443 | covered work, you indicate your acceptance of this License to do so. | |
444 | ||
445 | 10. Automatic Licensing of Downstream Recipients. | |
446 | ||
447 | Each time you convey a covered work, the recipient automatically | |
448 | receives a license from the original licensors, to run, modify and | |
449 | propagate that work, subject to this License. You are not responsible | |
450 | for enforcing compliance by third parties with this License. | |
451 | ||
452 | An "entity transaction" is a transaction transferring control of an | |
453 | organization, or substantially all assets of one, or subdividing an | |
454 | organization, or merging organizations. If propagation of a covered | |
455 | work results from an entity transaction, each party to that | |
456 | transaction who receives a copy of the work also receives whatever | |
457 | licenses to the work the party's predecessor in interest had or could | |
458 | give under the previous paragraph, plus a right to possession of the | |
459 | Corresponding Source of the work from the predecessor in interest, if | |
460 | the predecessor has it or can get it with reasonable efforts. | |
461 | ||
462 | You may not impose any further restrictions on the exercise of the | |
463 | rights granted or affirmed under this License. For example, you may | |
464 | not impose a license fee, royalty, or other charge for exercise of | |
465 | rights granted under this License, and you may not initiate litigation | |
466 | (including a cross-claim or counterclaim in a lawsuit) alleging that | |
467 | any patent claim is infringed by making, using, selling, offering for | |
468 | sale, or importing the Program or any portion of it. | |
469 | ||
470 | 11. Patents. | |
471 | ||
472 | A "contributor" is a copyright holder who authorizes use under this | |
473 | License of the Program or a work on which the Program is based. The | |
474 | work thus licensed is called the contributor's "contributor version". | |
475 | ||
476 | A contributor's "essential patent claims" are all patent claims | |
477 | owned or controlled by the contributor, whether already acquired or | |
478 | hereafter acquired, that would be infringed by some manner, permitted | |
479 | by this License, of making, using, or selling its contributor version, | |
480 | but do not include claims that would be infringed only as a | |
481 | consequence of further modification of the contributor version. For | |
482 | purposes of this definition, "control" includes the right to grant | |
483 | patent sublicenses in a manner consistent with the requirements of | |
484 | this License. | |
485 | ||
486 | Each contributor grants you a non-exclusive, worldwide, royalty-free | |
487 | patent license under the contributor's essential patent claims, to | |
488 | make, use, sell, offer for sale, import and otherwise run, modify and | |
489 | propagate the contents of its contributor version. | |
490 | ||
491 | In the following three paragraphs, a "patent license" is any express | |
492 | agreement or commitment, however denominated, not to enforce a patent | |
493 | (such as an express permission to practice a patent or covenant not to | |
494 | sue for patent infringement). To "grant" such a patent license to a | |
495 | party means to make such an agreement or commitment not to enforce a | |
496 | patent against the party. | |
497 | ||
498 | If you convey a covered work, knowingly relying on a patent license, | |
499 | and the Corresponding Source of the work is not available for anyone | |
500 | to copy, free of charge and under the terms of this License, through a | |
501 | publicly available network server or other readily accessible means, | |
502 | then you must either (1) cause the Corresponding Source to be so | |
503 | available, or (2) arrange to deprive yourself of the benefit of the | |
504 | patent license for this particular work, or (3) arrange, in a manner | |
505 | consistent with the requirements of this License, to extend the patent | |
506 | license to downstream recipients. "Knowingly relying" means you have | |
507 | actual knowledge that, but for the patent license, your conveying the | |
508 | covered work in a country, or your recipient's use of the covered work | |
509 | in a country, would infringe one or more identifiable patents in that | |
510 | country that you have reason to believe are valid. | |
511 | ||
512 | If, pursuant to or in connection with a single transaction or | |
513 | arrangement, you convey, or propagate by procuring conveyance of, a | |
514 | covered work, and grant a patent license to some of the parties | |
515 | receiving the covered work authorizing them to use, propagate, modify | |
516 | or convey a specific copy of the covered work, then the patent license | |
517 | you grant is automatically extended to all recipients of the covered | |
518 | work and works based on it. | |
519 | ||
520 | A patent license is "discriminatory" if it does not include within | |
521 | the scope of its coverage, prohibits the exercise of, or is | |
522 | conditioned on the non-exercise of one or more of the rights that are | |
523 | specifically granted under this License. You may not convey a covered | |
524 | work if you are a party to an arrangement with a third party that is | |
525 | in the business of distributing software, under which you make payment | |
526 | to the third party based on the extent of your activity of conveying | |
527 | the work, and under which the third party grants, to any of the | |
528 | parties who would receive the covered work from you, a discriminatory | |
529 | patent license (a) in connection with copies of the covered work | |
530 | conveyed by you (or copies made from those copies), or (b) primarily | |
531 | for and in connection with specific products or compilations that | |
532 | contain the covered work, unless you entered into that arrangement, | |
533 | or that patent license was granted, prior to 28 March 2007. | |
534 | ||
535 | Nothing in this License shall be construed as excluding or limiting | |
536 | any implied license or other defenses to infringement that may | |
537 | otherwise be available to you under applicable patent law. | |
538 | ||
539 | 12. No Surrender of Others' Freedom. | |
540 | ||
541 | If conditions are imposed on you (whether by court order, agreement or | |
542 | otherwise) that contradict the conditions of this License, they do not | |
543 | excuse you from the conditions of this License. If you cannot convey a | |
544 | covered work so as to satisfy simultaneously your obligations under this | |
545 | License and any other pertinent obligations, then as a consequence you may | |
546 | not convey it at all. For example, if you agree to terms that obligate you | |
547 | to collect a royalty for further conveying from those to whom you convey | |
548 | the Program, the only way you could satisfy both those terms and this | |
549 | License would be to refrain entirely from conveying the Program. | |
550 | ||
551 | 13. Use with the GNU Affero General Public License. | |
552 | ||
553 | Notwithstanding any other provision of this License, you have | |
554 | permission to link or combine any covered work with a work licensed | |
555 | under version 3 of the GNU Affero General Public License into a single | |
556 | combined work, and to convey the resulting work. The terms of this | |
557 | License will continue to apply to the part which is the covered work, | |
558 | but the special requirements of the GNU Affero General Public License, | |
559 | section 13, concerning interaction through a network will apply to the | |
560 | combination as such. | |
561 | ||
562 | 14. Revised Versions of this License. | |
563 | ||
564 | The Free Software Foundation may publish revised and/or new versions of | |
565 | the GNU General Public License from time to time. Such new versions will | |
566 | be similar in spirit to the present version, but may differ in detail to | |
567 | address new problems or concerns. | |
568 | ||
569 | Each version is given a distinguishing version number. If the | |
570 | Program specifies that a certain numbered version of the GNU General | |
571 | Public License "or any later version" applies to it, you have the | |
572 | option of following the terms and conditions either of that numbered | |
573 | version or of any later version published by the Free Software | |
574 | Foundation. If the Program does not specify a version number of the | |
575 | GNU General Public License, you may choose any version ever published | |
576 | by the Free Software Foundation. | |
577 | ||
578 | If the Program specifies that a proxy can decide which future | |
579 | versions of the GNU General Public License can be used, that proxy's | |
580 | public statement of acceptance of a version permanently authorizes you | |
581 | to choose that version for the Program. | |
582 | ||
583 | Later license versions may give you additional or different | |
584 | permissions. However, no additional obligations are imposed on any | |
585 | author or copyright holder as a result of your choosing to follow a | |
586 | later version. | |
587 | ||
588 | 15. Disclaimer of Warranty. | |
589 | ||
590 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY | |
591 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT | |
592 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY | |
593 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, | |
594 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
595 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM | |
596 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF | |
597 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. | |
598 | ||
599 | 16. Limitation of Liability. | |
600 | ||
601 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | |
602 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS | |
603 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY | |
604 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE | |
605 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF | |
606 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD | |
607 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), | |
608 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF | |
609 | SUCH DAMAGES. | |
610 | ||
611 | 17. Interpretation of Sections 15 and 16. | |
612 | ||
613 | If the disclaimer of warranty and limitation of liability provided | |
614 | above cannot be given local legal effect according to their terms, | |
615 | reviewing courts shall apply local law that most closely approximates | |
616 | an absolute waiver of all civil liability in connection with the | |
617 | Program, unless a warranty or assumption of liability accompanies a | |
618 | copy of the Program in return for a fee. | |
619 | ||
620 | END OF TERMS AND CONDITIONS | |
621 | ||
622 | _________________________________________________________________________________ | |
623 | ||
624 | This license does not apply to the following components and any component which may not be included in the following file: 'doc/LIBRARY_LICENSE' | |
625 |
1 | 1 | # Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) |
2 | 2 | # See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | __version__ = '3.16.0' | |
4 | __version__ = '3.17.0' | |
5 | 5 | __license_version__ = __version__ |
10 | 10 | import platform |
11 | 11 | import logging |
12 | 12 | |
13 | os.environ['FARADAY_MANAGE_RUNNING'] = "1" | |
13 | 14 | # If is linux and its installed with deb or rpm, it must run with a user in the faraday group |
14 | 15 | if platform.system() == "Linux": |
15 | 16 | import grp |
278 | 279 | |
279 | 280 | |
280 | 281 | @click.command(help="Manage settings") |
281 | @click.option('-a', '--action', type=click.Choice(['show', 'update', 'list'], case_sensitive=False), default='list', show_default=True) | |
282 | @click.option('-a', '--action', type=click.Choice(['show', 'update', 'list'], case_sensitive=False), | |
283 | default='list', show_default=True, help="Action") | |
284 | @click.option('--data', type=str, required=False, callback=manage_settings.settings_format_validation, | |
285 | help="Settings config in json") | |
282 | 286 | @click.argument('name', required=False) |
283 | def settings(action, name): | |
284 | manage_settings.manage(action.lower(), name) | |
287 | def settings(action, data, name): | |
288 | manage_settings.manage(action.lower(), data, name) | |
285 | 289 | |
286 | 290 | |
287 | 291 | cli.add_command(show_urls) |
0 | """Fix severities ordering | |
1 | ||
2 | Revision ID: 247a90b029f2 | |
3 | Revises: 89115e133f0a | |
4 | Create Date: 2021-08-02 12:52:34.453541+00:00 | |
5 | ||
6 | """ | |
7 | from alembic import op | |
8 | ||
9 | # revision identifiers, used by Alembic. | |
10 | revision = '247a90b029f2' | |
11 | down_revision = 'a9fcf8444c79' | |
12 | branch_labels = None | |
13 | depends_on = None | |
14 | ||
15 | ||
16 | def upgrade(): | |
17 | op.execute("ALTER TYPE vulnerability_severity rename to vulnerability_severity_temporal") | |
18 | op.execute("CREATE TYPE vulnerability_severity AS ENUM (" | |
19 | " 'unclassified', 'informational', 'low', 'medium', 'high', 'critical')") | |
20 | op.execute("ALTER TABLE vulnerability_template ALTER severity TYPE vulnerability_severity " | |
21 | "USING severity::TEXT::vulnerability_severity") | |
22 | op.execute("ALTER TABLE vulnerability ALTER severity TYPE vulnerability_severity " | |
23 | "USING severity::TEXT::vulnerability_severity") | |
24 | op.execute("DROP TYPE vulnerability_severity_temporal") | |
25 | ||
26 | ||
27 | def downgrade(): | |
28 | op.execute("ALTER TYPE vulnerability_severity rename to vulnerability_severity_temporal") | |
29 | op.execute("CREATE TYPE vulnerability_severity AS ENUM (" | |
30 | " 'critical', 'high', 'medium', 'low', 'informational', 'unclassified' )") | |
31 | op.execute("ALTER TABLE vulnerability_template ALTER severity TYPE vulnerability_severity" | |
32 | " USING severity::TEXT::vulnerability_severity") | |
33 | op.execute("ALTER TABLE vulnerability ALTER severity TYPE vulnerability_severity" | |
34 | " USING severity::TEXT::vulnerability_severity") | |
35 | op.execute("DROP TYPE vulnerability_severity_temporal") |
+267
-0
0 | """add_notification_event_and_subscription_model | |
1 | ||
2 | Revision ID: a9fcf8444c79 | |
3 | Revises: a4def820a5bb | |
4 | Create Date: 2021-05-28 15:47:13.781453+00:00 | |
5 | ||
6 | """ | |
7 | from alembic import op | |
8 | import sqlalchemy as sa | |
9 | from faraday.server.fields import JSONType | |
10 | ||
11 | # Added manually for inserts | |
12 | from sqlalchemy import orm | |
13 | from faraday.server.models import (NotificationSubscription, | |
14 | NotificationSubscriptionWebSocketConfig, | |
15 | User) | |
16 | ||
17 | # revision identifiers, used by Alembic. | |
18 | revision = 'a9fcf8444c79' | |
19 | down_revision = '97a9348d0406' | |
20 | branch_labels = None | |
21 | depends_on = None | |
22 | ||
23 | ||
24 | def upgrade(): | |
25 | # ### commands auto generated by Alembic - please adjust! ### | |
26 | op.create_table('event_type', | |
27 | sa.Column('id', sa.Integer(), nullable=False), | |
28 | sa.Column('name', sa.String(length=64), nullable=False), | |
29 | sa.Column('async_event', sa.Boolean(), nullable=True), | |
30 | sa.PrimaryKeyConstraint('id'), | |
31 | sa.UniqueConstraint('name') | |
32 | ) | |
33 | ||
34 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'new_workspace\', False)') | |
35 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'new_agent\', True)') | |
36 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'new_user\', False)') | |
37 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'new_agentexecution\', True)') | |
38 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'new_executivereport\', True)') | |
39 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'new_vulnerability\', False)') | |
40 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'new_command\', True)') | |
41 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'new_comment\', False)') | |
42 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'update_workspace\', False)') | |
43 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'update_agent\', False)') | |
44 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'update_user\', False)') | |
45 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'update_executivereport\', True)') | |
46 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'update_vulnerability\', False)') | |
47 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'delete_workspace\', False)') | |
48 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'delete_agent\', False)') | |
49 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'delete_user\', False)') | |
50 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'delete_executivereport\', False)') | |
51 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'delete_vulnerability\', False)') | |
52 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'new_vulnerabilityweb\', False)') | |
53 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'update_vulnerabilityweb\', False)') | |
54 | op.execute('INSERT INTO event_type (name, async_event) VALUES (\'delete_vulnerabilityweb\', False)') | |
55 | ||
56 | op.create_table('object_type', | |
57 | sa.Column('id', sa.Integer(), nullable=False), | |
58 | sa.Column('name', sa.String(length=64), nullable=False), | |
59 | sa.PrimaryKeyConstraint('id'), | |
60 | sa.UniqueConstraint('name') | |
61 | ) | |
62 | ||
63 | op.execute('INSERT INTO object_type ("name") VALUES (\'vulnerability\')') | |
64 | op.execute('INSERT INTO object_type ("name") VALUES (\'vulnerabilityweb\')') | |
65 | op.execute('INSERT INTO object_type ("name") VALUES (\'host\')') | |
66 | op.execute('INSERT INTO object_type ("name") VALUES (\'credential\')') | |
67 | op.execute('INSERT INTO object_type ("name") VALUES (\'service\')') | |
68 | op.execute('INSERT INTO object_type ("name") VALUES (\'source_code\')') | |
69 | op.execute('INSERT INTO object_type ("name") VALUES (\'comment\')') | |
70 | op.execute('INSERT INTO object_type ("name") VALUES (\'executivereport\')') | |
71 | op.execute('INSERT INTO object_type ("name") VALUES (\'workspace\')') | |
72 | op.execute('INSERT INTO object_type ("name") VALUES (\'task\')') | |
73 | op.execute('INSERT INTO object_type ("name") VALUES (\'agent\')') | |
74 | op.execute('INSERT INTO object_type ("name") VALUES (\'agentexecution\')') | |
75 | op.execute('INSERT INTO object_type ("name") VALUES (\'command\')') | |
76 | op.execute('INSERT INTO object_type ("name") VALUES (\'user\')') | |
77 | ||
78 | op.create_table('notification_subscription', | |
79 | sa.Column('create_date', sa.DateTime(), nullable=True), | |
80 | sa.Column('update_date', sa.DateTime(), nullable=True), | |
81 | sa.Column('id', sa.Integer(), nullable=False), | |
82 | sa.Column('event_type_id', sa.Integer(), nullable=False), | |
83 | sa.Column('creator_id', sa.Integer(), nullable=True), | |
84 | sa.Column('update_user_id', sa.Integer(), nullable=True), | |
85 | sa.ForeignKeyConstraint(['creator_id'], ['faraday_user.id'], ondelete='SET NULL'), | |
86 | sa.ForeignKeyConstraint(['event_type_id'], ['event_type.id'], ), | |
87 | sa.ForeignKeyConstraint(['update_user_id'], ['faraday_user.id'], ondelete='SET NULL'), | |
88 | sa.PrimaryKeyConstraint('id') | |
89 | ) | |
90 | op.create_index(op.f('ix_notification_subscription_event_type_id'), 'notification_subscription', ['event_type_id'], unique=False) | |
91 | op.create_table('notification_allowed_roles', | |
92 | sa.Column('notification_subscription_id', sa.Integer(), nullable=False), | |
93 | sa.Column('allowed_role_id', sa.Integer(), nullable=False), | |
94 | sa.ForeignKeyConstraint(['allowed_role_id'], ['faraday_role.id'], ), | |
95 | sa.ForeignKeyConstraint(['notification_subscription_id'], ['notification_subscription.id'], ) | |
96 | ) | |
97 | op.create_table('notification_event', | |
98 | sa.Column('id', sa.Integer(), nullable=False), | |
99 | sa.Column('event_type_id', sa.Integer(), nullable=False), | |
100 | sa.Column('object_id', sa.Integer(), nullable=False), | |
101 | sa.Column('object_type_id', sa.Integer(), nullable=False), | |
102 | sa.Column('notification_data', JSONType(), nullable=False), | |
103 | sa.Column('create_date', sa.DateTime(), nullable=True), | |
104 | sa.Column('workspace_id', sa.Integer(), nullable=True), | |
105 | sa.ForeignKeyConstraint(['event_type_id'], ['event_type.id'], ), | |
106 | sa.ForeignKeyConstraint(['object_type_id'], ['object_type.id'], ), | |
107 | sa.ForeignKeyConstraint(['workspace_id'], ['workspace.id'], ), | |
108 | sa.PrimaryKeyConstraint('id') | |
109 | ) | |
110 | op.create_index(op.f('ix_notification_event_event_type_id'), 'notification_event', ['event_type_id'], unique=False) | |
111 | op.create_index(op.f('ix_notification_event_object_type_id'), 'notification_event', ['object_type_id'], unique=False) | |
112 | op.create_index(op.f('ix_notification_event_workspace_id'), 'notification_event', ['workspace_id'], unique=False) | |
113 | op.create_table('notification_subscription_config_base', | |
114 | sa.Column('id', sa.Integer(), nullable=False), | |
115 | sa.Column('subscription_id', sa.Integer(), nullable=False), | |
116 | sa.Column('role_level', sa.Boolean(), nullable=True), | |
117 | sa.Column('workspace_level', sa.Boolean(), nullable=True), | |
118 | sa.Column('active', sa.Boolean(), nullable=True), | |
119 | sa.Column('type', sa.String(length=24), nullable=True), | |
120 | sa.ForeignKeyConstraint(['subscription_id'], ['notification_subscription.id'], ), | |
121 | sa.PrimaryKeyConstraint('id'), | |
122 | sa.UniqueConstraint('subscription_id', 'type', name='uix_subscriptionid_type') | |
123 | ) | |
124 | op.create_index(op.f('ix_notification_subscription_config_base_subscription_id'), 'notification_subscription_config_base', ['subscription_id'], unique=False) | |
125 | op.create_table('notification_base', | |
126 | sa.Column('id', sa.Integer(), nullable=False), | |
127 | sa.Column('notification_event_id', sa.Integer(), nullable=False), | |
128 | sa.Column('notification_subscription_config_id', sa.Integer(), nullable=False), | |
129 | sa.Column('type', sa.String(length=24), nullable=True), | |
130 | sa.ForeignKeyConstraint(['notification_event_id'], ['notification_event.id'], ), | |
131 | sa.ForeignKeyConstraint(['notification_subscription_config_id'], ['notification_subscription_config_base.id'], ), | |
132 | sa.PrimaryKeyConstraint('id') | |
133 | ) | |
134 | op.create_index(op.f('ix_notification_base_notification_event_id'), 'notification_base', ['notification_event_id'], unique=False) | |
135 | op.create_index(op.f('ix_notification_base_notification_subscription_config_id'), 'notification_base', ['notification_subscription_config_id'], unique=False) | |
136 | op.create_table('notification_subscription_mail_config', | |
137 | sa.Column('id', sa.Integer(), nullable=False), | |
138 | sa.Column('email', sa.String(length=50), nullable=True), | |
139 | sa.Column('user_notified_id', sa.Integer(), nullable=True), | |
140 | sa.ForeignKeyConstraint(['id'], ['notification_subscription_config_base.id'], ), | |
141 | sa.ForeignKeyConstraint(['user_notified_id'], ['faraday_user.id'], ), | |
142 | sa.PrimaryKeyConstraint('id') | |
143 | ) | |
144 | op.create_index(op.f('ix_notification_subscription_mail_config_user_notified_id'), 'notification_subscription_mail_config', ['user_notified_id'], unique=False) | |
145 | op.create_table('notification_subscription_webhook_config', | |
146 | sa.Column('id', sa.Integer(), nullable=False), | |
147 | sa.Column('url', sa.String(length=50), nullable=False), | |
148 | sa.ForeignKeyConstraint(['id'], ['notification_subscription_config_base.id'], ), | |
149 | sa.PrimaryKeyConstraint('id') | |
150 | ) | |
151 | op.create_table('notification_subscription_websocket_config', | |
152 | sa.Column('id', sa.Integer(), nullable=False), | |
153 | sa.Column('user_notified_id', sa.Integer(), nullable=True), | |
154 | sa.ForeignKeyConstraint(['id'], ['notification_subscription_config_base.id'], ), | |
155 | sa.ForeignKeyConstraint(['user_notified_id'], ['faraday_user.id'], ), | |
156 | sa.PrimaryKeyConstraint('id') | |
157 | ) | |
158 | op.create_index(op.f('ix_notification_subscription_websocket_config_user_notified_id'), 'notification_subscription_websocket_config', ['user_notified_id'], unique=False) | |
159 | op.create_table('mail_notification', | |
160 | sa.Column('id', sa.Integer(), nullable=False), | |
161 | sa.ForeignKeyConstraint(['id'], ['notification_base.id'], ), | |
162 | sa.PrimaryKeyConstraint('id') | |
163 | ) | |
164 | op.create_table('webhook_notification', | |
165 | sa.Column('id', sa.Integer(), nullable=False), | |
166 | sa.ForeignKeyConstraint(['id'], ['notification_base.id'], ), | |
167 | sa.PrimaryKeyConstraint('id') | |
168 | ) | |
169 | op.create_table('websocket_notification', | |
170 | sa.Column('id', sa.Integer(), nullable=False), | |
171 | sa.Column('user_notified_id', sa.Integer(), nullable=True), | |
172 | sa.Column('mark_read', sa.Boolean(), nullable=True), | |
173 | sa.ForeignKeyConstraint(['id'], ['notification_base.id'], ), | |
174 | sa.ForeignKeyConstraint(['user_notified_id'], ['faraday_user.id'], ), | |
175 | sa.PrimaryKeyConstraint('id') | |
176 | ) | |
177 | op.create_index(op.f('ix_websocket_notification_mark_read'), 'websocket_notification', ['mark_read'], unique=False) | |
178 | op.create_index(op.f('ix_websocket_notification_user_notified_id'), 'websocket_notification', ['user_notified_id'], unique=False) | |
179 | # ### end Alembic commands ### | |
180 | ||
181 | # Added manually for inserts | |
182 | bind = op.get_bind() | |
183 | session = orm.Session(bind=bind) | |
184 | ||
185 | admin = User.ADMIN_ROLE | |
186 | pentester = User.PENTESTER_ROLE | |
187 | asset_owner = User.ASSET_OWNER_ROLE | |
188 | client = User.CLIENT_ROLE | |
189 | ||
190 | default_initial_notifications_config = [ | |
191 | # Workspace | |
192 | {'roles': [admin], 'event_types': ['new_workspace', 'update_workspace', 'delete_workspace']}, | |
193 | # Users | |
194 | {'roles': [admin], 'event_types': ['new_user', 'update_user', 'delete_user']}, | |
195 | # Agents | |
196 | {'roles': [admin, pentester], 'event_types': ['new_agent', 'update_agent', 'delete_agent']}, | |
197 | # Reports | |
198 | {'roles': [admin, pentester, asset_owner], | |
199 | 'event_types': ['new_executivereport', 'update_executivereport', 'delete_executivereport']}, | |
200 | # Agent execution | |
201 | {'roles': [admin, pentester, asset_owner], 'event_types': ['new_agentexecution']}, | |
202 | # Commands | |
203 | {'roles': [admin, pentester, asset_owner], 'event_types': ['new_command']}, | |
204 | # Vulnerability | |
205 | {'roles': [admin, pentester, asset_owner, client], | |
206 | 'event_types': ['new_vulnerability', 'update_vulnerability', 'delete_vulnerability']}, | |
207 | # Vulnerability Web | |
208 | {'roles': [admin, pentester, asset_owner, client], | |
209 | 'event_types': ['new_vulnerabilityweb', 'update_vulnerabilityweb', 'delete_vulnerabilityweb']}, | |
210 | # Comments | |
211 | {'roles': [admin, pentester, asset_owner, client], 'event_types': ['new_comment']}, | |
212 | ] | |
213 | ||
214 | allowed_roles = sa.table( | |
215 | 'notification_allowed_roles', | |
216 | sa.column('notification_subscription_id', sa.Integer), | |
217 | sa.column('allowed_role_id', sa.Integer) | |
218 | ) | |
219 | ||
220 | res = bind.execute('SELECT name, id FROM event_type').fetchall() | |
221 | event_type_ids = dict(res) | |
222 | ||
223 | res = bind.execute('SELECT name, id FROM faraday_role').fetchall() | |
224 | role_ids = dict(res) | |
225 | ||
226 | for config in default_initial_notifications_config: | |
227 | for event_type in config['event_types']: | |
228 | n = NotificationSubscription(event_type_id=event_type_ids[event_type]) | |
229 | session.add(n) | |
230 | session.commit() | |
231 | ns = NotificationSubscriptionWebSocketConfig(subscription=n, active=True, role_level=True) | |
232 | session.add(ns) | |
233 | session.commit() | |
234 | for role_name in config['roles']: | |
235 | op.execute( | |
236 | allowed_roles.insert().values({'notification_subscription_id': n.id, 'allowed_role_id': role_ids[role_name]}) | |
237 | ) | |
238 | ||
239 | ||
240 | def downgrade(): | |
241 | # ### commands auto generated by Alembic - please adjust! ### | |
242 | op.drop_index(op.f('ix_websocket_notification_user_notified_id'), table_name='websocket_notification') | |
243 | op.drop_index(op.f('ix_websocket_notification_mark_read'), table_name='websocket_notification') | |
244 | op.drop_table('websocket_notification') | |
245 | op.drop_table('webhook_notification') | |
246 | op.drop_table('mail_notification') | |
247 | op.drop_index(op.f('ix_notification_subscription_websocket_config_user_notified_id'), table_name='notification_subscription_websocket_config') | |
248 | op.drop_table('notification_subscription_websocket_config') | |
249 | op.drop_table('notification_subscription_webhook_config') | |
250 | op.drop_index(op.f('ix_notification_subscription_mail_config_user_notified_id'), table_name='notification_subscription_mail_config') | |
251 | op.drop_table('notification_subscription_mail_config') | |
252 | op.drop_index(op.f('ix_notification_base_notification_subscription_config_id'), table_name='notification_base') | |
253 | op.drop_index(op.f('ix_notification_base_notification_event_id'), table_name='notification_base') | |
254 | op.drop_table('notification_base') | |
255 | op.drop_index(op.f('ix_notification_subscription_config_base_subscription_id'), table_name='notification_subscription_config_base') | |
256 | op.drop_table('notification_subscription_config_base') | |
257 | op.drop_index(op.f('ix_notification_event_workspace_id'), table_name='notification_event') | |
258 | op.drop_index(op.f('ix_notification_event_object_type_id'), table_name='notification_event') | |
259 | op.drop_index(op.f('ix_notification_event_event_type_id'), table_name='notification_event') | |
260 | op.drop_table('notification_event') | |
261 | op.drop_table('notification_allowed_roles') | |
262 | op.drop_index(op.f('ix_notification_subscription_event_type_id'), table_name='notification_subscription') | |
263 | op.drop_table('notification_subscription') | |
264 | op.drop_table('object_type') | |
265 | op.drop_table('event_type') | |
266 | # ### end Alembic commands ### |
13 | 13 | import datetime |
14 | 14 | from collections import defaultdict |
15 | 15 | from flask_classful import FlaskView |
16 | from sqlalchemy.orm import joinedload, undefer | |
16 | from sqlalchemy.orm import joinedload, undefer, with_expression | |
17 | 17 | from sqlalchemy.orm.exc import NoResultFound, ObjectDeletedError |
18 | 18 | from sqlalchemy.inspection import inspect |
19 | 19 | from sqlalchemy import func, desc, asc |
20 | 20 | from marshmallow import Schema, EXCLUDE, fields |
21 | 21 | from marshmallow.validate import Length |
22 | 22 | from marshmallow_sqlalchemy import ModelConverter |
23 | from marshmallow_sqlalchemy.schema import ModelSchemaMeta, ModelSchemaOpts | |
23 | from marshmallow_sqlalchemy.schema import SQLAlchemyAutoSchemaOpts, SQLAlchemyAutoSchemaMeta | |
24 | 24 | from sqlalchemy.sql.elements import BooleanClauseList |
25 | 25 | from webargs.flaskparser import FlaskParser |
26 | 26 | from webargs.core import ValidationError |
27 | 27 | from flask_classful import route |
28 | 28 | import flask_login |
29 | 29 | |
30 | from faraday.server.models import Workspace, db, Command, CommandObject, count_vulnerability_severities | |
30 | from faraday.server.models import (Workspace, | |
31 | db, | |
32 | Command, | |
33 | CommandObject, | |
34 | count_vulnerability_severities, | |
35 | _make_vuln_count_property, | |
36 | _make_active_agents_count_property) | |
31 | 37 | from faraday.server.schemas import NullToBlankString |
32 | 38 | from faraday.server.utils.database import ( |
33 | 39 | get_conflict_object, |
747 | 753 | if severity_count and 'group_by' not in filters: |
748 | 754 | filter_query = count_vulnerability_severities(filter_query, self.model_class, |
749 | 755 | all_severities=True, host_vulns=host_vulns) |
756 | ||
757 | filter_query = filter_query.options( | |
758 | with_expression( | |
759 | Workspace.vulnerability_web_count, | |
760 | _make_vuln_count_property('vulnerability_web', use_column_property=False), | |
761 | ), | |
762 | with_expression( | |
763 | Workspace.vulnerability_standard_count, | |
764 | _make_vuln_count_property('vulnerability', use_column_property=False) | |
765 | ), | |
766 | with_expression( | |
767 | Workspace.vulnerability_code_count, | |
768 | _make_vuln_count_property('vulnerability_code', use_column_property=False), | |
769 | ), | |
770 | with_expression( | |
771 | Workspace.active_agents_count, | |
772 | _make_active_agents_count_property(), | |
773 | ), | |
774 | ) | |
750 | 775 | |
751 | 776 | return filter_query |
752 | 777 | |
1564 | 1589 | kwargs['validate'].append(Length(min=1)) |
1565 | 1590 | |
1566 | 1591 | |
1567 | class CustomModelSchemaOpts(ModelSchemaOpts): | |
1592 | class CustomSQLAlchemyAutoSchemaOpts(SQLAlchemyAutoSchemaOpts): | |
1568 | 1593 | def __init__(self, *args, **kwargs): |
1569 | 1594 | super().__init__(*args, **kwargs) |
1570 | 1595 | self.model_converter = CustomModelConverter |
1589 | 1614 | fields.DateTime.SERIALIZATION_FUNCS['iso'] = old_isoformat |
1590 | 1615 | |
1591 | 1616 | |
1592 | class AutoSchema(Schema, metaclass=ModelSchemaMeta): | |
1617 | class AutoSchema(Schema, metaclass=SQLAlchemyAutoSchemaMeta): | |
1593 | 1618 | """ |
1594 | 1619 | A Marshmallow schema that does field introspection based on |
1595 | 1620 | the SQLAlchemy model specified in Meta.model. |
1596 | 1621 | Unlike the marshmallow_sqlalchemy ModelSchema, it doesn't change |
1597 | 1622 | the serialization and deserialization proccess. |
1598 | 1623 | """ |
1599 | OPTIONS_CLASS = CustomModelSchemaOpts | |
1624 | OPTIONS_CLASS = CustomSQLAlchemyAutoSchemaOpts | |
1600 | 1625 | |
1601 | 1626 | # Use NullToBlankString instead of fields.String by default on text fields |
1602 | 1627 | TYPE_MAPPING = Schema.TYPE_MAPPING.copy() |
0 | 0 | import logging |
1 | 1 | from datetime import datetime, timedelta |
2 | 2 | from typing import Type, Optional |
3 | import string | |
4 | import random | |
5 | import json | |
3 | 6 | |
4 | 7 | import time |
5 | 8 | import flask_login |
40 | 43 | ) |
41 | 44 | from faraday.server.api.base import AutoSchema, GenericWorkspacedView |
42 | 45 | from faraday.server.api.modules.websocket_auth import require_agent_token |
43 | from faraday.server.utils.bulk_create import add_creator | |
46 | from faraday.server.config import CONST_FARADAY_HOME_PATH | |
44 | 47 | |
45 | 48 | bulk_create_api = flask.Blueprint('bulk_create_api', __name__) |
46 | 49 | |
189 | 192 | ) |
190 | 193 | command = fields.Nested( |
191 | 194 | BulkCommandSchema(), |
192 | required=False, | |
195 | required=True, | |
193 | 196 | ) |
194 | 197 | execution_id = fields.Integer(attribute='execution_id') |
195 | 198 | |
384 | 387 | run_date = None |
385 | 388 | logger.debug("Run date (%s) is greater than allowed", run_date) |
386 | 389 | except ValueError: |
387 | logger.error("Error converting run_date to a valid date") | |
388 | flask.abort(400, "Invalid run_date") | |
390 | logger.error("Error converting [%s] to a valid date", run_date_string) | |
389 | 391 | else: |
390 | 392 | run_date = None |
391 | 393 | (created, vuln) = get_or_create(ws, model_class, vuln_data) |
460 | 462 | 404: |
461 | 463 | description: Workspace not found |
462 | 464 | """ |
463 | data = self._parse_data(self._get_schema_instance({}), flask.request) | |
465 | from faraday.server.threads.reports_processor import REPORTS_QUEUE # pylint: disable=import-outside-toplevel | |
464 | 466 | |
465 | 467 | if flask_login.current_user.is_anonymous: |
466 | 468 | agent = require_agent_token() |
469 | data = self._parse_data(self._get_schema_instance({}), flask.request) | |
470 | json_data = flask.request.json | |
471 | if flask_login.current_user.is_anonymous: | |
467 | 472 | workspace = self._get_workspace(workspace_name) |
468 | 473 | |
469 | 474 | if not workspace or workspace not in agent.workspaces: |
470 | 475 | flask.abort(404, f"No such workspace: {workspace_name}") |
471 | 476 | |
472 | 477 | if "execution_id" not in data: |
473 | flask.abort(400, "'execution_id' argument expected") | |
478 | flask.abort(400, "argument expected: execution_id") | |
474 | 479 | |
475 | 480 | execution_id = data["execution_id"] |
476 | 481 | |
523 | 528 | |
524 | 529 | _update_command(command, data['command']) |
525 | 530 | db.session.flush() |
531 | if data['hosts']: | |
532 | json_data['command'] = data["command"] | |
533 | json_data['command']["start_date"] = data["command"]["start_date"].isoformat() | |
534 | if 'end_date' in data["command"]: | |
535 | json_data['command']["end_date"] = data["command"]["end_date"].isoformat() | |
526 | 536 | |
527 | 537 | else: |
528 | 538 | workspace = self._get_workspace(workspace_name) |
529 | creator_user = flask_login.current_user | |
530 | data = add_creator(data, creator_user) | |
531 | ||
532 | if 'command' in data: | |
533 | command = Command(**(data['command'])) | |
534 | command.workspace = workspace | |
535 | db.session.add(command) | |
536 | db.session.commit() | |
537 | else: | |
538 | # Here the data won't appear in the activity field | |
539 | command = None | |
540 | ||
541 | bulk_create(workspace, command, data, True, False) | |
539 | command = Command(**(data['command'])) | |
540 | command.workspace = workspace | |
541 | db.session.add(command) | |
542 | db.session.commit() | |
543 | if data['hosts']: | |
544 | # Create random file | |
545 | chars = string.ascii_uppercase + string.digits | |
546 | random_prefix = ''.join(random.choice(chars) for x in range(30)) # nosec | |
547 | json_file = f"{random_prefix}.json" | |
548 | file_path = CONST_FARADAY_HOME_PATH / 'uploaded_reports' \ | |
549 | / json_file | |
550 | with file_path.open('w') as output: | |
551 | json.dump(json_data, output) | |
552 | logger.info("Create tmp json file for bulk_create: %s", file_path) | |
553 | user_id = flask_login.current_user.id if not flask_login.current_user.is_anonymous else None | |
554 | REPORTS_QUEUE.put( | |
555 | ( | |
556 | workspace.name, | |
557 | command.id, | |
558 | file_path, | |
559 | None, | |
560 | user_id | |
561 | ) | |
562 | ) | |
542 | 563 | return flask.jsonify( |
543 | 564 | { |
544 | 565 | "message": "Created", |
22 | 22 | |
23 | 23 | from faraday.server.utils.web import gzipped |
24 | 24 | from faraday.server.models import Workspace, Command, db |
25 | from faraday.server import config | |
26 | ||
25 | from faraday.settings.reports import ReportsSettings | |
27 | 26 | from faraday_plugins.plugins.manager import PluginsManager, ReportAnalyzer |
28 | 27 | |
29 | 28 | upload_api = Blueprint('upload_reports', __name__) |
30 | 29 | |
31 | 30 | logger = logging.getLogger(__name__) |
32 | ||
33 | plugins_manager = PluginsManager(config.faraday_server.custom_plugins_folder) | |
34 | report_analyzer = ReportAnalyzer(plugins_manager) | |
35 | 31 | |
36 | 32 | |
37 | 33 | @gzipped |
90 | 86 | 500)) |
91 | 87 | else: |
92 | 88 | logger.info(f"Get plugin for file: {file_path}") |
89 | plugins_manager = PluginsManager(ReportsSettings.settings.custom_plugins_folder) | |
90 | report_analyzer = ReportAnalyzer(plugins_manager) | |
93 | 91 | plugin = report_analyzer.get_plugin(file_path) |
94 | 92 | if not plugin: |
95 | 93 | logger.info("Could not get plugin for file") |
4 | 4 | import os |
5 | 5 | import string |
6 | 6 | import datetime |
7 | ||
8 | 7 | import bleach |
9 | 8 | import pyotp |
10 | 9 | import requests |
15 | 14 | |
16 | 15 | from faraday.settings import load_settings |
17 | 16 | from faraday.server.config import LOCAL_CONFIG_FILE, copy_default_config_to_local |
17 | from faraday.server.extensions import socketio | |
18 | 18 | from faraday.server.models import User, Role |
19 | 19 | from configparser import ConfigParser, NoSectionError, NoOptionError, DuplicateSectionError |
20 | 20 | |
100 | 100 | from faraday.server.api.modules.export_data import export_data_api # pylint:disable=import-outside-toplevel |
101 | 101 | # Custom reset password |
102 | 102 | from faraday.server.api.modules.auth import auth # pylint:disable=import-outside-toplevel |
103 | from faraday.server.websockets import websockets # pylint:disable=import-outside-toplevel | |
103 | 104 | from faraday.server.api.modules.settings_reports import reports_settings_api # pylint:disable=import-outside-toplevel |
104 | 105 | from faraday.server.api.modules.settings_dashboard import \ |
105 | 106 | dashboard_settings_api # pylint:disable=import-outside-toplevel |
119 | 120 | app.register_blueprint(comment_api) |
120 | 121 | app.register_blueprint(upload_api) |
121 | 122 | app.register_blueprint(websocket_auth_api) |
123 | app.register_blueprint(websockets) | |
124 | ||
122 | 125 | app.register_blueprint(exploits_api) |
123 | 126 | app.register_blueprint(custom_fields_schema_api) |
124 | 127 | app.register_blueprint(agent_api) |
444 | 447 | register_handlers(app) |
445 | 448 | |
446 | 449 | app.view_functions['agent_creation_api.AgentCreationView:post'].is_public = True |
450 | ||
451 | register_extensions(app) | |
447 | 452 | load_settings() |
453 | ||
448 | 454 | return app |
455 | ||
456 | ||
457 | def register_extensions(app): | |
458 | socketio.init_app(app) | |
449 | 459 | |
450 | 460 | |
451 | 461 | def minify_json_output(app): |
122 | 122 | "{yellow}WARNING{white}: Can't create administrator user.".format( |
123 | 123 | yellow=Fore.YELLOW, white=Fore.WHITE)) |
124 | 124 | raise |
125 | ||
126 | def _create_initial_notifications_config(self): | |
127 | from faraday.server.models import (db, # pylint:disable=import-outside-toplevel | |
128 | Role, # pylint:disable=import-outside-toplevel | |
129 | NotificationSubscription, # pylint:disable=import-outside-toplevel | |
130 | NotificationSubscriptionWebSocketConfig, # pylint:disable=import-outside-toplevel | |
131 | EventType, # pylint:disable=import-outside-toplevel | |
132 | User, # pylint:disable=import-outside-toplevel | |
133 | ObjectType) # pylint:disable=import-outside-toplevel | |
134 | ||
135 | admin = User.ADMIN_ROLE | |
136 | pentester = User.PENTESTER_ROLE | |
137 | asset_owner = User.ASSET_OWNER_ROLE | |
138 | client = User.CLIENT_ROLE | |
139 | ||
140 | default_initial_notifications_config = [ | |
141 | # Workspace | |
142 | {'roles': [admin], 'event_types': ['new_workspace', 'update_workspace', 'delete_workspace']}, | |
143 | # Users | |
144 | {'roles': [admin], 'event_types': ['new_user', 'update_user', 'delete_user']}, | |
145 | # Agents | |
146 | {'roles': [admin, pentester], 'event_types': ['new_agent', 'update_agent', 'delete_agent']}, | |
147 | # Reports | |
148 | {'roles': [admin, pentester, asset_owner], | |
149 | 'event_types': ['new_executivereport', 'update_executivereport', 'delete_executivereport']}, | |
150 | # Agent execution | |
151 | {'roles': [admin, pentester, asset_owner], 'event_types': ['new_agentexecution']}, | |
152 | # Commands | |
153 | {'roles': [admin, pentester, asset_owner], 'event_types': ['new_command']}, | |
154 | # Vulnerability | |
155 | {'roles': [admin, pentester, asset_owner, client], | |
156 | 'event_types': ['new_vulnerability', 'update_vulnerability', 'delete_vulnerability']}, | |
157 | # Vulnerability Web | |
158 | {'roles': [admin, pentester, asset_owner, client], | |
159 | 'event_types': ['new_vulnerabilityweb', 'update_vulnerabilityweb', 'delete_vulnerabilityweb']}, | |
160 | # Comments | |
161 | {'roles': [admin, pentester, asset_owner, client], 'event_types': ['new_comment']}, | |
162 | ] | |
163 | ||
164 | event_types = [('new_workspace', False), | |
165 | ('new_agent', True), | |
166 | ('new_user', False), | |
167 | ('new_agentexecution', True), | |
168 | ('new_executivereport', True), | |
169 | ('new_vulnerability', False), | |
170 | ('new_command', True), | |
171 | ('new_comment', False), | |
172 | ('update_workspace', False), | |
173 | ('update_agent', False), | |
174 | ('update_user', False), | |
175 | ('update_executivereport', True), | |
176 | ('update_vulnerability', False), | |
177 | ('delete_workspace', False), | |
178 | ('delete_agent', False), | |
179 | ('delete_user', False), | |
180 | ('delete_executivereport', False), | |
181 | ('delete_vulnerability', False), | |
182 | ('new_vulnerabilityweb', False), | |
183 | ('update_vulnerabilityweb', False), | |
184 | ('delete_vulnerabilityweb', False)] | |
185 | ||
186 | for event_type in event_types: | |
187 | event_type_obj = EventType(name=event_type[0], async_event=event_type[1]) | |
188 | db.session.add(event_type_obj) | |
189 | ||
190 | object_types = ['vulnerability', | |
191 | 'vulnerabilityweb', | |
192 | 'host', | |
193 | 'credential', | |
194 | 'service', | |
195 | 'source_code', | |
196 | 'comment', | |
197 | 'executivereport', | |
198 | 'workspace', | |
199 | 'task', | |
200 | 'agent', | |
201 | 'agentexecution', | |
202 | 'command', | |
203 | 'user'] | |
204 | ||
205 | for object_type in object_types: | |
206 | obj = ObjectType(name=object_type) | |
207 | db.session.add(obj) | |
208 | db.session.commit() | |
209 | ||
210 | for config in default_initial_notifications_config: | |
211 | for event_type in config['event_types']: | |
212 | allowed_roles_objs = Role.query.filter(Role.name.in_(config['roles'])).all() | |
213 | event_type_obj = EventType.query.filter(EventType.name == event_type).first() | |
214 | n = NotificationSubscription(event_type=event_type_obj, allowed_roles=allowed_roles_objs) | |
215 | db.session.add(n) | |
216 | db.session.commit() | |
217 | ns = NotificationSubscriptionWebSocketConfig(subscription=n, active=True, role_level=True) | |
218 | db.session.add(ns) | |
219 | db.session.commit() | |
125 | 220 | |
126 | 221 | def _create_admin_user(self, conn_string, choose_password, faraday_user_password): |
127 | 222 | engine = create_engine(conn_string) |
355 | 450 | command.stamp(alembic_cfg, "head") |
356 | 451 | # TODO ADD RETURN TO PREV DIR |
357 | 452 | self._create_roles(conn_string) |
453 | self._create_initial_notifications_config() |
0 | 0 | import sys |
1 | 1 | import click |
2 | import json | |
2 | 3 | |
3 | 4 | from faraday.server.web import get_app |
4 | 5 | from faraday.server.models import ( |
10 | 11 | from faraday.settings.exceptions import InvalidConfigurationError |
11 | 12 | |
12 | 13 | |
13 | def manage(action, name): | |
14 | def settings_format_validation(ctx, param, value): | |
15 | if value is not None: | |
16 | try: | |
17 | json_data = json.loads(value) | |
18 | except json.JSONDecodeError: | |
19 | raise click.BadParameter("data must be in json") | |
20 | else: | |
21 | return json_data | |
22 | ||
23 | ||
24 | def manage(action, json_data, name): | |
14 | 25 | load_settings() |
15 | 26 | if name: |
16 | 27 | name = name.lower() |
19 | 30 | if not name: |
20 | 31 | click.secho(f"You must indicate a settings name to {action}", fg="red") |
21 | 32 | sys.exit(1) |
22 | if name not in available_settings: | |
23 | click.secho(f'Invalid settings: {name}', fg='red') | |
24 | 33 | else: |
25 | settings = get_settings(name) | |
26 | if action == "show": | |
27 | click.secho(f"Settings for: {name}", fg="green") | |
28 | for key, value in settings.value.items(): | |
29 | click.secho(f"{key}: {value}") | |
30 | elif action == "update": | |
31 | new_settings = {} | |
32 | click.secho(f"Update settings for: {name}", fg="green") | |
33 | for key, value in settings.value.items(): | |
34 | if name not in available_settings: | |
35 | click.secho(f'Invalid settings: {name}', fg='red') | |
36 | return | |
37 | settings = get_settings(name) | |
38 | if action == "show": | |
39 | click.secho(f"Settings for: {name}", fg="green") | |
40 | for key, value in settings.value.items(): | |
41 | click.secho(f"{key}: {value}") | |
42 | elif action == "update": | |
43 | new_settings = {} | |
44 | click.secho(f"Update settings for: {name}", fg="green") | |
45 | for key, value in settings.value.items(): | |
46 | if json_data is not None: | |
47 | json_value = json_data.get(key, None) | |
48 | if type(json_value) != type(value) or json_value is None: | |
49 | click.secho(f"Missing or Invalid value for {key} [{json_value}]", fg="red") | |
50 | sys.exit(1) | |
51 | else: | |
52 | new_value = json_value | |
53 | else: | |
34 | 54 | new_value = click.prompt(f'{key}', default=value) |
35 | new_settings[key] = new_value | |
36 | try: | |
37 | settings.validate_configuration(new_settings) | |
38 | except InvalidConfigurationError as e: | |
39 | click.secho(f"Invalid configuration for: {name}", fg="red") | |
40 | click.secho(e, fg="red") | |
41 | sys.exit(1) | |
55 | new_settings[key] = new_value | |
56 | try: | |
57 | settings.validate_configuration(new_settings) | |
58 | except InvalidConfigurationError as e: | |
59 | click.secho(f"Invalid configuration for: {name}", fg="red") | |
60 | click.secho(e, fg="red") | |
61 | sys.exit(1) | |
62 | else: | |
63 | settings_message = "\n".join([f"{key}: {value}" for key, value in new_settings.items()]) | |
64 | confirm = json_data is not None | |
65 | if not confirm: | |
66 | confirm = click.confirm(f"Do you confirm your changes on {name}?" | |
67 | f"\n----------------------" | |
68 | f"\n{settings_message}\n", default=True) | |
69 | if confirm: | |
70 | with get_app().app_context(): | |
71 | saved_config, created = get_or_create(db.session, Configuration, key=settings.settings_key) | |
72 | if created: | |
73 | saved_config.value = settings.update_configuration(new_settings) | |
74 | else: | |
75 | # SQLAlchemy doesn't detect in-place mutations to the structure of a JSON type. | |
76 | # Thus, we make a deepcopy of the JSON so SQLAlchemy can detect the changes. | |
77 | saved_config.value = settings.update_configuration(new_settings, saved_config.value) | |
78 | db.session.commit() | |
79 | click.secho("Updated!!", fg='green') | |
42 | 80 | else: |
43 | settings_message = "\n".join([f"{key}: {value}" for key, value in new_settings.items()]) | |
44 | if click.confirm(f"Do you confirm your changes on {name}?" | |
45 | f"\n----------------------" | |
46 | f"\n{settings_message}\n", default=True): | |
47 | with get_app().app_context(): | |
48 | saved_config, created = get_or_create(db.session, Configuration, key=settings.settings_key) | |
49 | if created: | |
50 | saved_config.value = settings.update_configuration(new_settings) | |
51 | else: | |
52 | # SQLAlchemy doesn't detect in-place mutations to the structure of a JSON type. | |
53 | # Thus, we make a deepcopy of the JSON so SQLAlchemy can detect the changes. | |
54 | saved_config.value = settings.update_configuration(new_settings, saved_config.value) | |
55 | db.session.commit() | |
56 | click.secho("Updated!!", fg='green') | |
57 | else: | |
58 | click.secho("No changes where made to the settings", fg="green") | |
81 | click.secho("No changes where made to the settings", fg="green") | |
82 | ||
59 | 83 | else: |
60 | 84 | click.secho("Available settings:", fg="green") |
61 | 85 | for i in available_settings: |
82 | 82 | def parse(self, __parser): |
83 | 83 | for att in self.__dict__: |
84 | 84 | value = __parser.get(att) |
85 | if value is None: | |
86 | continue | |
85 | 87 | if isinstance(self.__dict__[att], bool): |
86 | 88 | if value in ("yes", "true", "t", "1", "True"): |
87 | 89 | self.__setattr__(att, True) |
88 | 90 | else: |
89 | 91 | self.__setattr__(att, False) |
90 | 92 | elif isinstance(self.__dict__[att], int): |
91 | if value: | |
92 | self.__setattr__(att, int(value)) | |
93 | self.__setattr__(att, int(value)) | |
94 | ||
93 | 95 | else: |
94 | if value: | |
95 | self.__setattr__(att, value) | |
96 | self.__setattr__(att, value) | |
96 | 97 | |
97 | 98 | def set(self, option_name, value): |
98 | 99 | return self.__setattr__(option_name, value) |
137 | 138 | self.agent_registration_secret = None |
138 | 139 | self.agent_token_expiration = 60 # Default as 1 min |
139 | 140 | self.debug = False |
140 | self.custom_plugins_folder = None | |
141 | self.ignore_info_severity = False | |
141 | self.reports_pool_size = 1 | |
142 | self.delete_report_after_process = True | |
142 | 143 | |
143 | 144 | |
144 | 145 | class StorageConfigObject(ConfigSection): |
81 | 81 | 'user' |
82 | 82 | ] |
83 | 83 | |
84 | NOTIFICATION_METHODS = [ | |
85 | 'mail', | |
86 | 'webhook', | |
87 | 'websocket' | |
88 | ] | |
89 | ||
84 | 90 | |
85 | 91 | class SQLAlchemy(OriginalSQLAlchemy): |
86 | 92 | """Override to fix issues when doing a rollback with sqlite driver |
475 | 481 | 'infeasible' |
476 | 482 | ] |
477 | 483 | SEVERITIES = [ |
484 | 'unclassified', | |
485 | 'informational', | |
486 | 'low', | |
487 | 'medium', | |
488 | 'high', | |
478 | 489 | 'critical', |
479 | 'high', | |
480 | 'medium', | |
481 | 'low', | |
482 | 'informational', | |
483 | 'unclassified', | |
484 | 490 | ] |
485 | 491 | |
486 | 492 | __abstract__ = True |
1774 | 1780 | |
1775 | 1781 | class User(db.Model, UserMixin): |
1776 | 1782 | __tablename__ = 'faraday_user' |
1777 | ROLES = ['admin', 'pentester', 'client', 'asset_owner'] | |
1783 | ADMIN_ROLE = 'admin' | |
1784 | PENTESTER_ROLE = 'pentester' | |
1785 | ASSET_OWNER_ROLE = 'asset_owner' | |
1786 | CLIENT_ROLE = 'client' | |
1787 | ROLES = [ADMIN_ROLE, PENTESTER_ROLE, ASSET_OWNER_ROLE, CLIENT_ROLE] | |
1778 | 1788 | OTP_STATES = ["disabled", "requested", "confirmed"] |
1779 | 1789 | |
1780 | 1790 | id = Column(Integer, primary_key=True) |
1900 | 1910 | __mapper_args__ = { |
1901 | 1911 | 'concrete': True |
1902 | 1912 | } |
1913 | ||
1903 | 1914 | template = relationship( |
1904 | 1915 | 'MethodologyTemplate', |
1905 | 1916 | backref=backref('tasks', cascade="all, delete-orphan")) |
2107 | 2118 | ) |
2108 | 2119 | |
2109 | 2120 | |
2121 | class ObjectType(db.Model): | |
2122 | __tablename__ = 'object_type' | |
2123 | id = Column(Integer, primary_key=True) | |
2124 | name = Column(String(64), unique=True, nullable=False) | |
2125 | ||
2126 | ||
2127 | class EventType(db.Model): | |
2128 | __tablename__ = 'event_type' | |
2129 | id = Column(Integer, primary_key=True) | |
2130 | name = Column(String(64), unique=True, nullable=False) | |
2131 | async_event = Column(Boolean, default=False) | |
2132 | ||
2133 | ||
2134 | allowed_roles_association = db.Table('notification_allowed_roles', | |
2135 | Column('notification_subscription_id', Integer, db.ForeignKey('notification_subscription.id'), nullable=False), | |
2136 | Column('allowed_role_id', Integer, db.ForeignKey('faraday_role.id'), nullable=False) | |
2137 | ) | |
2138 | ||
2139 | ||
2140 | class NotificationSubscription(Metadata): | |
2141 | __tablename__ = 'notification_subscription' | |
2142 | id = Column(Integer, primary_key=True) | |
2143 | event_type_id = Column(Integer, ForeignKey('event_type.id'), index=True, nullable=False) | |
2144 | event_type = relationship( | |
2145 | 'EventType', | |
2146 | backref=backref('event_type', cascade="all, delete-orphan") | |
2147 | ) | |
2148 | allowed_roles = relationship("Role", secondary=allowed_roles_association) | |
2149 | ||
2150 | ||
2151 | class NotificationSubscriptionConfigBase(db.Model): | |
2152 | __tablename__ = 'notification_subscription_config_base' | |
2153 | id = Column(Integer, primary_key=True) | |
2154 | subscription_id = Column(Integer, ForeignKey('notification_subscription.id'), index=True, nullable=False) | |
2155 | subscription = relationship( | |
2156 | 'NotificationSubscription', | |
2157 | backref=backref('notification_subscription_config', cascade="all, delete-orphan") | |
2158 | ) | |
2159 | ||
2160 | role_level = Column(Boolean, default=False) | |
2161 | workspace_level = Column(Boolean, default=False) | |
2162 | ||
2163 | active = Column(Boolean, default=True) | |
2164 | type = Column(String(24)) | |
2165 | ||
2166 | __mapper_args__ = { | |
2167 | 'polymorphic_on': type, | |
2168 | 'polymorphic_identity': 'base' | |
2169 | } | |
2170 | ||
2171 | __table_args__ = ( | |
2172 | UniqueConstraint('subscription_id', 'type', name='uix_subscriptionid_type'), | |
2173 | ) | |
2174 | ||
2175 | @property | |
2176 | def dst(self): | |
2177 | raise NotImplementedError('Notification subsciption base dst called. Must Be implemented.') | |
2178 | ||
2179 | ||
2180 | class NotificationSubscriptionMailConfig(NotificationSubscriptionConfigBase): | |
2181 | __tablename__ = 'notification_subscription_mail_config' | |
2182 | id = Column(Integer, ForeignKey('notification_subscription_config_base.id'), primary_key=True) | |
2183 | email = Column(String(50), nullable=True) | |
2184 | user_notified_id = Column(Integer, ForeignKey('faraday_user.id'), index=True, nullable=True) | |
2185 | user_notified = relationship( | |
2186 | 'User', | |
2187 | backref=backref('notification_subscription_mail_config', cascade="all, delete-orphan") | |
2188 | ) | |
2189 | ||
2190 | __mapper_args__ = { | |
2191 | 'polymorphic_identity': NOTIFICATION_METHODS[0] | |
2192 | } | |
2193 | ||
2194 | ||
2195 | class NotificationSubscriptionWebHookConfig(NotificationSubscriptionConfigBase): | |
2196 | __tablename__ = 'notification_subscription_webhook_config' | |
2197 | id = Column(Integer, ForeignKey('notification_subscription_config_base.id'), primary_key=True) | |
2198 | url = Column(String(50), nullable=False) | |
2199 | __mapper_args__ = { | |
2200 | 'polymorphic_identity': NOTIFICATION_METHODS[1] | |
2201 | } | |
2202 | ||
2203 | ||
2204 | class NotificationSubscriptionWebSocketConfig(NotificationSubscriptionConfigBase): | |
2205 | __tablename__ = 'notification_subscription_websocket_config' | |
2206 | id = Column(Integer, ForeignKey('notification_subscription_config_base.id'), primary_key=True) | |
2207 | user_notified_id = Column(Integer, ForeignKey('faraday_user.id'), index=True, nullable=True) | |
2208 | user_notified = relationship( | |
2209 | 'User', | |
2210 | backref=backref('notification_subscription_websocket_config', cascade="all, delete-orphan") | |
2211 | ) | |
2212 | __mapper_args__ = { | |
2213 | 'polymorphic_identity': NOTIFICATION_METHODS[2] | |
2214 | } | |
2215 | ||
2216 | ||
2217 | class NotificationEvent(db.Model): | |
2218 | __tablename__ = 'notification_event' | |
2219 | id = Column(Integer, primary_key=True) | |
2220 | event_type_id = Column(Integer, ForeignKey('event_type.id'), index=True, nullable=False) | |
2221 | event_type = relationship( | |
2222 | 'EventType', | |
2223 | backref=backref('notification_event_type', cascade="all, delete-orphan") | |
2224 | ) | |
2225 | object_id = Column(Integer, nullable=False) | |
2226 | object_type_id = Column(Integer, ForeignKey('object_type.id'), index=True, nullable=False) | |
2227 | object_type = relationship( | |
2228 | 'ObjectType', | |
2229 | backref=backref('notification_event_object_type', cascade="all, delete-orphan") | |
2230 | ) | |
2231 | ||
2232 | notification_data = Column(JSONType, nullable=False) | |
2233 | create_date = Column(DateTime, default=datetime.utcnow) | |
2234 | ||
2235 | workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=True) | |
2236 | workspace = relationship( | |
2237 | 'Workspace', | |
2238 | backref=backref('notification_event_workspace', cascade="all, delete-orphan"), | |
2239 | ) | |
2240 | ||
2241 | @property | |
2242 | def parent(self): | |
2243 | return | |
2244 | ||
2245 | ||
2246 | class NotificationBase(db.Model): | |
2247 | __tablename__ = 'notification_base' | |
2248 | id = Column(Integer, primary_key=True) | |
2249 | notification_event_id = Column(Integer, ForeignKey('notification_event.id'), index=True, nullable=False) | |
2250 | notification_event = relationship( | |
2251 | 'NotificationEvent', | |
2252 | backref=backref('notifications', cascade="all, delete-orphan"), | |
2253 | ) | |
2254 | notification_subscription_config_id = Column(Integer, ForeignKey('notification_subscription_config_base.id'), | |
2255 | index=True, nullable=False) | |
2256 | notification_subscription_config = relationship( | |
2257 | 'NotificationSubscriptionConfigBase', | |
2258 | backref=backref('notifications', cascade="all, delete-orphan"), | |
2259 | ) | |
2260 | ||
2261 | type = Column(String(24)) | |
2262 | ||
2263 | __mapper_args__ = { | |
2264 | 'polymorphic_on': type, | |
2265 | 'polymorphic_identity': 'base' | |
2266 | } | |
2267 | ||
2268 | ||
2269 | # TBI | |
2270 | class MailNotification(NotificationBase): | |
2271 | __tablename__ = 'mail_notification' | |
2272 | ||
2273 | id = Column(Integer, ForeignKey('notification_base.id'), primary_key=True) | |
2274 | ||
2275 | __mapper_args__ = { | |
2276 | 'polymorphic_identity': NOTIFICATION_METHODS[0] | |
2277 | } | |
2278 | ||
2279 | ||
2280 | # TBI | |
2281 | class WebHookNotification(NotificationBase): | |
2282 | __tablename__ = 'webhook_notification' | |
2283 | ||
2284 | id = Column(Integer, ForeignKey('notification_base.id'), primary_key=True) | |
2285 | ||
2286 | __mapper_args__ = { | |
2287 | 'polymorphic_identity': NOTIFICATION_METHODS[1] | |
2288 | } | |
2289 | ||
2290 | ||
2291 | class WebsocketNotification(NotificationBase): | |
2292 | __tablename__ = 'websocket_notification' | |
2293 | ||
2294 | id = Column(Integer, ForeignKey('notification_base.id'), primary_key=True) | |
2295 | user_notified_id = Column(Integer, ForeignKey('faraday_user.id'), index=True) | |
2296 | user_notified = relationship( | |
2297 | 'User', | |
2298 | backref=backref('notifications', cascade="all, delete-orphan") | |
2299 | ) | |
2300 | ||
2301 | mark_read = Column(Boolean, default=False, index=True) | |
2302 | ||
2303 | __mapper_args__ = { | |
2304 | 'polymorphic_identity': NOTIFICATION_METHODS[2] | |
2305 | } | |
2306 | ||
2307 | ||
2110 | 2308 | class Notification(db.Model): |
2111 | 2309 | __tablename__ = 'notification' |
2112 | 2310 | id = Column(Integer, primary_key=True) |
2113 | ||
2114 | 2311 | user_notified_id = Column(Integer, ForeignKey('faraday_user.id'), index=True, nullable=False) |
2115 | 2312 | user_notified = relationship( |
2116 | 2313 | 'User', |
0 | 0 | import logging |
1 | import os | |
1 | 2 | import threading |
2 | 3 | from pathlib import Path |
3 | 4 | from threading import Thread |
4 | 5 | from queue import Queue, Empty |
5 | from typing import Tuple | |
6 | from typing import Tuple, Optional | |
7 | import json | |
8 | import multiprocessing | |
6 | 9 | |
7 | 10 | from faraday_plugins.plugins.manager import PluginsManager |
8 | 11 | from faraday.server.api.modules.bulk_create import bulk_create, BulkCreateSchema |
10 | 13 | from faraday.server.models import Workspace, Command, User |
11 | 14 | from faraday.server.utils.bulk_create import add_creator |
12 | 15 | from faraday.settings.reports import ReportsSettings |
16 | from faraday.server.config import faraday_server | |
17 | ||
13 | 18 | logger = logging.getLogger(__name__) |
14 | 19 | |
15 | 20 | |
16 | 21 | REPORTS_QUEUE = Queue() |
22 | INTERVAL = 0.5 | |
17 | 23 | |
18 | INTERVAL = 0.5 | |
24 | ||
25 | def send_report_data(workspace_name: str, command_id: int, report_json: dict, | |
26 | user_id: Optional[int], set_end_date: bool): | |
27 | logger.info("Send Report data to workspace [%s]", workspace_name) | |
28 | from faraday.server.web import get_app # pylint:disable=import-outside-toplevel | |
29 | with get_app().app_context(): | |
30 | ws = Workspace.query.filter_by(name=workspace_name).one() | |
31 | command = Command.query.filter_by(id=command_id).one() | |
32 | schema = BulkCreateSchema() | |
33 | data = schema.load(report_json) | |
34 | if user_id: | |
35 | user = User.query.filter_by(id=user_id).one() | |
36 | data = add_creator(data, user) | |
37 | bulk_create(ws, command, data, True, set_end_date) | |
38 | ||
39 | ||
40 | def process_report(workspace_name: str, command_id: int, file_path: Path, | |
41 | plugin_id: Optional[int], user_id: Optional[int]): | |
42 | if plugin_id is not None: | |
43 | plugins_manager = PluginsManager(ReportsSettings.settings.custom_plugins_folder, | |
44 | ignore_info=ReportsSettings.settings.ignore_info_severity) | |
45 | logger.info(f"Reports Manager: [Custom plugins folder: " | |
46 | f"[{ReportsSettings.settings.custom_plugins_folder}]" | |
47 | f"[Ignore info severity: {ReportsSettings.settings.ignore_info_severity}]") | |
48 | plugin = plugins_manager.get_plugin(plugin_id) | |
49 | if plugin: | |
50 | try: | |
51 | logger.info(f"Processing report [{file_path}] with plugin [" | |
52 | f"{plugin.id}]") | |
53 | plugin.processReport(str(file_path)) | |
54 | vulns_data = plugin.get_data() | |
55 | del vulns_data['command']['duration'] | |
56 | except Exception as e: | |
57 | logger.error("Processing Error: %s", e) | |
58 | logger.exception(e) | |
59 | return | |
60 | else: | |
61 | logger.error(f"No plugin detected for report [{file_path}]") | |
62 | return | |
63 | else: | |
64 | try: | |
65 | with file_path.open("r") as f: | |
66 | vulns_data = json.load(f) | |
67 | except Exception as e: | |
68 | logger.error("Loading data from json file: %s [%s]", file_path, e) | |
69 | logger.exception(e) | |
70 | return | |
71 | if plugin_id is None: | |
72 | logger.debug("Removing file: %s", file_path) | |
73 | os.remove(file_path) | |
74 | else: | |
75 | if faraday_server.delete_report_after_process: | |
76 | os.remove(file_path) | |
77 | set_end_date = True | |
78 | try: | |
79 | send_report_data(workspace_name, command_id, vulns_data, user_id, set_end_date) | |
80 | logger.info("Report processing finished") | |
81 | except Exception as e: | |
82 | logger.exception(e) | |
83 | logger.error("Save Error: %s", e) | |
19 | 84 | |
20 | 85 | |
21 | 86 | class ReportsManager(Thread): |
23 | 88 | def __init__(self, upload_reports_queue, *args, **kwargs): |
24 | 89 | super().__init__(name="ReportsManager-Thread", daemon=True, *args, **kwargs) |
25 | 90 | self.upload_reports_queue = upload_reports_queue |
26 | self.plugins_manager = PluginsManager(ReportsSettings.settings.custom_plugins_folder, | |
27 | ignore_info=ReportsSettings.settings.ignore_info_severity) | |
28 | logger.info(f"Reports Manager: [Custom plugins folder: [{ReportsSettings.settings.custom_plugins_folder}]" | |
29 | f"[Ignore info severity: {ReportsSettings.settings.ignore_info_severity}]") | |
30 | 91 | self.__event = threading.Event() |
92 | self.processing_pool = multiprocessing.Pool(processes=faraday_server.reports_pool_size) | |
31 | 93 | |
32 | 94 | def stop(self): |
33 | 95 | logger.info("Reports Manager Thread [Stopping...]") |
34 | 96 | self.__event.set() |
35 | 97 | |
36 | def send_report_request(self, | |
37 | workspace_name: str, | |
38 | command_id: int, | |
39 | report_json: dict, | |
40 | user_id: int): | |
41 | logger.info("Send Report data to workspace [%s]", workspace_name) | |
42 | from faraday.server.web import get_app # pylint:disable=import-outside-toplevel | |
43 | with get_app().app_context(): | |
44 | ws = Workspace.query.filter_by(name=workspace_name).one() | |
45 | command = Command.query.filter_by(id=command_id).one() | |
46 | user = User.query.filter_by(id=user_id).one() | |
47 | schema = BulkCreateSchema() | |
48 | data = schema.load(report_json) | |
49 | data = add_creator(data, user) | |
50 | bulk_create(ws, command, data, True, True) | |
51 | ||
52 | def process_report(self, | |
53 | workspace_name: str, | |
54 | command_id: int, | |
55 | file_path: Path, | |
56 | plugin_id: int, | |
57 | user_id: int): | |
58 | plugin = self.plugins_manager.get_plugin(plugin_id) | |
59 | if plugin: | |
60 | try: | |
61 | logger.info(f"Processing report [{file_path}] with plugin [" | |
62 | f"{plugin.id}") | |
63 | plugin.processReport(str(file_path)) | |
64 | vulns_data = plugin.get_data() | |
65 | del vulns_data['command']['duration'] | |
66 | except Exception as e: | |
67 | logger.error("Processing Error: %s", e) | |
68 | logger.exception(e) | |
69 | else: | |
70 | try: | |
71 | self.send_report_request( | |
72 | workspace_name, command_id, vulns_data, user_id | |
73 | ) | |
74 | logger.info("Report processing finished") | |
75 | except Exception as e: | |
76 | logger.exception(e) | |
77 | logger.error("Save Error: %s", e) | |
78 | else: | |
79 | logger.info(f"No plugin detected for report [{file_path}]") | |
80 | ||
81 | 98 | def run(self): |
82 | logger.info("Reports Manager Thread [Start]") | |
83 | ||
99 | logger.info(f"Reports Manager Thread [Start] with Pool Size: {faraday_server.reports_pool_size}") | |
84 | 100 | while not self.__event.is_set(): |
85 | 101 | try: |
86 | 102 | tpl: Tuple[str, int, Path, int, int] = \ |
90 | 106 | |
91 | 107 | logger.info(f"Processing raw report {file_path}") |
92 | 108 | if file_path.is_file(): |
93 | self.process_report( | |
94 | workspace_name, | |
95 | command_id, | |
96 | file_path, | |
97 | plugin_id, | |
98 | user_id | |
99 | ) | |
109 | self.processing_pool.apply_async(process_report, | |
110 | (workspace_name, command_id, file_path, plugin_id, user_id)) | |
100 | 111 | else: |
101 | 112 | logger.warning(f"Report file [{file_path}] don't exists", |
102 | 113 | file_path) |
110 | 121 | continue |
111 | 122 | else: |
112 | 123 | logger.info("Reports Manager Thread [Stop]") |
124 | self.processing_pool.close() | |
125 | self.processing_pool.terminate() | |
126 | self.processing_pool.join() |
264 | 264 | def validate_order_and_group_by(self, data, **kwargs): |
265 | 265 | """ |
266 | 266 | We need to validate that if group_by is used, all the field |
267 | in the order_by are the same. | |
268 | When using different order_by fields with group, it will cause | |
267 | in the order_by must be in group_by fields. | |
268 | When using different order_by fields that are not in group by will cause | |
269 | 269 | an error on PostgreSQL |
270 | 270 | """ |
271 | 271 | if 'group_by' in data and 'order_by' in data: |
272 | group_by_fields = set() | |
273 | order_by_fields = set() | |
274 | for group_field in data['group_by']: | |
275 | group_by_fields.add(group_field['field']) | |
276 | for order_field in data['order_by']: | |
277 | order_by_fields.add(order_field['field']) | |
278 | ||
279 | if order_by_fields != group_by_fields: | |
280 | raise ValidationError('Can\'t group and order by with different fields. ') | |
281 | ||
272 | group_by_fields = set(group_field['field'] for group_field in data['group_by']) | |
273 | order_by_fields = set(order_field['field'] for order_field in data['order_by']) | |
274 | if not order_by_fields.issubset(group_by_fields): | |
275 | logger.error(f'All order fields ({order_by_fields}) must be in group by {group_by_fields}.') | |
276 | raise ValidationError(f'All order fields ({order_by_fields}) must be in group by {group_by_fields}.') | |
282 | 277 | return data |
283 | 278 | |
284 | 279 |
21 | 21 | |
22 | 22 | |
23 | 23 | def setup_logging(): |
24 | if os.environ.get('FARADAY_MANAGE_RUNNING'): | |
25 | return | |
24 | 26 | logger = logging.getLogger() |
25 | 27 | logger.setLevel(logging.DEBUG) |
26 | 28 |
25 | 25 | from sqlalchemy.orm.attributes import QueryableAttribute |
26 | 26 | from sqlalchemy.orm import ColumnProperty |
27 | 27 | |
28 | from faraday.server.models import User | |
28 | 29 | |
29 | 30 | logger = logging.getLogger(__name__) |
30 | 31 | |
451 | 452 | map_attr = { |
452 | 453 | 'creator': 'username', |
453 | 454 | } |
454 | if hasattr(getattr(field, 'prop'), 'entity'): | |
455 | if hasattr(field, 'prop') and hasattr(getattr(field, 'prop'), 'entity'): | |
455 | 456 | field = getattr(field.comparator.entity.class_, map_attr.get(field.prop.key, field.prop.key)) |
456 | 457 | |
457 | 458 | return opfunc(field, argument) |
495 | 496 | create_filt = QueryBuilder._create_filter |
496 | 497 | |
497 | 498 | def create_filters(filt): |
498 | if not getattr(filt, 'fieldname', False) \ | |
499 | or filt.fieldname.split('__')[0] in valid_model_fields: | |
500 | try: | |
501 | return create_filt(model, filt) | |
502 | except AttributeError as e: | |
503 | # Can't create the filter since the model or submodel does not have the attribute (usually mapper) | |
504 | raise AttributeError(f"Foreing field {filt.fieldname.split('__')[0]} not found in submodel") | |
499 | if not isinstance(filt, | |
500 | JunctionFilter) and '__' in filt.fieldname: | |
501 | return create_filt(model, filt) | |
502 | else: | |
503 | if not getattr(filt, 'fieldname', False) or \ | |
504 | filt.fieldname.split('__')[0] in valid_model_fields: | |
505 | try: | |
506 | return create_filt(model, filt) | |
507 | except AttributeError: | |
508 | # Can't create the filter since the model or submodel does not have the attribute (usually mapper) | |
509 | raise AttributeError(f"Foreing field {filt.fieldname.split('__')[0]} not found in submodel") | |
505 | 510 | raise AttributeError(f"Field {filt.fieldname} not found in model") |
506 | 511 | |
507 | 512 | return create_filters |
536 | 541 | documentation for :func:`_create_operation` for more information. |
537 | 542 | |
538 | 543 | """ |
544 | # TODO: Esto no se puede hacer abajo con el group by? | |
545 | joined_models = set() | |
546 | query = session.query(model) | |
547 | ||
539 | 548 | if search_params.group_by: |
540 | 549 | select_fields = [func.count()] |
541 | 550 | for groupby in search_params.group_by: |
542 | select_fields.append(getattr(model, groupby.field)) | |
543 | ||
544 | query = session.query(*select_fields) | |
545 | else: | |
546 | query = session.query(model) | |
551 | field_name = groupby.field | |
552 | if '__' in field_name: | |
553 | field_name, field_name_in_relation = field_name.split('__') | |
554 | relation = getattr(model, field_name) | |
555 | relation_model = relation.mapper.class_ | |
556 | field = getattr(relation_model, field_name_in_relation) | |
557 | if relation_model not in joined_models: | |
558 | if relation_model == User: | |
559 | query = query.join(relation_model, model.creator_id == relation_model.id) | |
560 | else: | |
561 | query = query.join(relation_model) | |
562 | joined_models.add(relation_model) | |
563 | select_fields.append(field) | |
564 | else: | |
565 | select_fields.append(getattr(model, groupby.field)) | |
566 | query = query.with_entities(*select_fields) | |
547 | 567 | |
548 | 568 | # This function call may raise an exception. |
549 | 569 | valid_model_fields = [] |
552 | 572 | valid_model_fields.append(str(orm_descriptor).split('.')[1]) |
553 | 573 | if isinstance(orm_descriptor, hybrid_property): |
554 | 574 | valid_model_fields.append(orm_descriptor.__name__) |
575 | valid_model_fields += [str(algo).split('.')[1] for algo in sqlalchemy_inspect(model).relationships] | |
555 | 576 | |
556 | 577 | filters_generator = map( # pylint: disable=W1636 |
557 | 578 | QueryBuilder.create_filters_func(model, valid_model_fields), |
568 | 589 | # parameters, order by primary key. |
569 | 590 | if not _ignore_order_by: |
570 | 591 | if search_params.order_by: |
571 | joined_models = set() | |
572 | 592 | for val in search_params.order_by: |
573 | 593 | field_name = val.field |
574 | 594 | if '__' in field_name: |
575 | field_name, field_name_in_relation = \ | |
576 | field_name.split('__') | |
595 | field_name, field_name_in_relation = field_name.split('__') | |
577 | 596 | relation = getattr(model, field_name) |
578 | 597 | relation_model = relation.mapper.class_ |
579 | 598 | field = getattr(relation_model, field_name_in_relation) |
580 | 599 | direction = getattr(field, val.direction) |
581 | 600 | if relation_model not in joined_models: |
582 | query = query.join(relation_model) | |
601 | query = query.join(*joined_models) | |
583 | 602 | joined_models.add(relation_model) |
584 | 603 | query = query.order_by(direction()) |
585 | 604 | else: |
595 | 614 | # Group the query. |
596 | 615 | if search_params.group_by: |
597 | 616 | for groupby in search_params.group_by: |
598 | field = getattr(model, groupby.field) | |
617 | field_name = groupby.field | |
618 | if '__' in field_name: | |
619 | field_name, field_name_in_relation = field_name.split('__') | |
620 | relation = getattr(model, field_name) | |
621 | relation_model = relation.mapper.class_ | |
622 | field = getattr(relation_model, field_name_in_relation) | |
623 | else: | |
624 | field = getattr(model, groupby.field) | |
599 | 625 | query = query.group_by(field) |
600 | 626 | |
601 | 627 | # Apply limit and offset to the query. |
0 | import functools | |
1 | from flask import Blueprint | |
2 | from flask_socketio import emit, disconnect | |
3 | import logging | |
4 | from flask_login import current_user | |
5 | ||
6 | logger = logging.getLogger(__name__) | |
7 | ||
8 | websockets = Blueprint('websockets', __name__) | |
9 | ||
10 | ||
11 | def authenticated_only(f): | |
12 | @functools.wraps(f) | |
13 | def wrapped(*args, **kwargs): | |
14 | if not current_user.is_authenticated: | |
15 | # Maybe we should return something more explicit | |
16 | disconnect() | |
17 | else: | |
18 | return f(*args, **kwargs) | |
19 | return wrapped | |
20 | ||
21 | ||
22 | @authenticated_only | |
23 | def on_connect(self): | |
24 | logger.debug(f'{current_user.username} connected') | |
25 | emit('connected', {'data': f'{current_user.username} connected successfully to notifications namespace'}) |
12 | 12 | |
13 | 13 | |
14 | 14 | def load_settings(): |
15 | import faraday.settings.smtp # pylint: disable=import-outside-toplevel | |
16 | import faraday.settings.dashboard # pylint: disable=import-outside-toplevel # noqa: F401 | |
15 | from faraday.settings.smtp import init_setting as smtp_init # pylint: disable=import-outside-toplevel | |
16 | smtp_init() | |
17 | from faraday.settings.dashboard import init_setting as dashboard_init # pylint: disable=import-outside-toplevel | |
18 | dashboard_init() | |
19 | from faraday.settings.reports import init_setting as reports_init # pylint: disable=import-outside-toplevel | |
20 | reports_init() |
19 | 19 | class ReportsSettings(Settings): |
20 | 20 | settings_id = "reports" |
21 | 21 | settings_key = f'{settings_id}_settings' |
22 | must_restart_threads = True | |
23 | 22 | schema = ReportsSettingSchema() |
24 | 23 | |
25 | 24 | def custom_validation(self, validated_config): |
34 | 33 | |
35 | 34 | def init_setting(): |
36 | 35 | ReportsSettings() |
37 | ||
38 | ||
39 | init_setting() |
66 | 66 | ./packages/apispec-webframeworks |
67 | 67 | { }; |
68 | 68 | |
69 | bidict = | |
70 | self.callPackage | |
71 | ./packages/bidict | |
72 | { }; | |
73 | ||
69 | 74 | bleach = |
70 | 75 | self.callPackage |
71 | 76 | ./packages/bleach |
77 | { }; | |
78 | ||
79 | click = | |
80 | self.callPackage | |
81 | ./packages/click | |
72 | 82 | { }; |
73 | 83 | |
74 | 84 | faraday-agent-parameters-types = |
111 | 121 | ./packages/flask-security-too |
112 | 122 | { }; |
113 | 123 | |
124 | flask-socketio = | |
125 | self.callPackage | |
126 | ./packages/flask-socketio | |
127 | { }; | |
128 | ||
114 | 129 | marshmallow = |
115 | 130 | self.callPackage |
116 | 131 | ./packages/marshmallow |
124 | 139 | pyotp = |
125 | 140 | self.callPackage |
126 | 141 | ./packages/pyotp |
142 | { }; | |
143 | ||
144 | python-engineio = | |
145 | self.callPackage | |
146 | ./packages/python-engineio | |
147 | { }; | |
148 | ||
149 | python-socketio = | |
150 | self.callPackage | |
151 | ./packages/python-socketio | |
127 | 152 | { }; |
128 | 153 | |
129 | 154 | simplekv = |
0 | # WARNING: This file was automatically generated. You should avoid editing it. | |
1 | # If you run pynixify again, the file will be either overwritten or | |
2 | # deleted, and you will lose the changes you made to it. | |
3 | ||
4 | { buildPythonPackage | |
5 | , fetchPypi | |
6 | , lib | |
7 | , setuptools_scm | |
8 | }: | |
9 | ||
10 | buildPythonPackage rec { | |
11 | pname = | |
12 | "bidict"; | |
13 | version = | |
14 | "0.21.2"; | |
15 | ||
16 | src = | |
17 | fetchPypi { | |
18 | inherit | |
19 | pname | |
20 | version; | |
21 | sha256 = | |
22 | "02dy0b1k7qlhn7ajyzkrvxhyhjj0hzcq6ws3zjml9hkdz5znz92g"; | |
23 | }; | |
24 | ||
25 | buildInputs = | |
26 | [ | |
27 | setuptools_scm | |
28 | ]; | |
29 | ||
30 | # TODO FIXME | |
31 | doCheck = | |
32 | false; | |
33 | ||
34 | meta = | |
35 | with lib; { | |
36 | description = | |
37 | "The bidirectional mapping library for Python."; | |
38 | homepage = | |
39 | "https://bidict.readthedocs.io"; | |
40 | }; | |
41 | } |
13 | 13 | pname = |
14 | 14 | "bleach"; |
15 | 15 | version = |
16 | "3.3.0"; | |
16 | "4.0.0"; | |
17 | 17 | |
18 | 18 | src = |
19 | 19 | fetchPypi { |
21 | 21 | pname |
22 | 22 | version; |
23 | 23 | sha256 = |
24 | "0cx4jyvd7hlaiiq2cq6vps689b978w3kyqqrvkckvs75743igcwq"; | |
24 | "1j3wnrzk5p4n6avbpjz2spw0rpbf6rrk9hzwa369k4y2d8f25agz"; | |
25 | 25 | }; |
26 | 26 | |
27 | 27 | propagatedBuildInputs = |
0 | # WARNING: This file was automatically generated. You should avoid editing it. | |
1 | # If you run pynixify again, the file will be either overwritten or | |
2 | # deleted, and you will lose the changes you made to it. | |
3 | ||
4 | { buildPythonPackage | |
5 | , fetchPypi | |
6 | , importlib-metadata | |
7 | , lib | |
8 | }: | |
9 | ||
10 | buildPythonPackage rec { | |
11 | pname = | |
12 | "click"; | |
13 | version = | |
14 | "8.0.1"; | |
15 | ||
16 | src = | |
17 | fetchPypi { | |
18 | inherit | |
19 | pname | |
20 | version; | |
21 | sha256 = | |
22 | "0ymdyf37acq4qxh038q0xx44qgj6y2kf0jd0ivvix6qij88w214c"; | |
23 | }; | |
24 | ||
25 | propagatedBuildInputs = | |
26 | [ | |
27 | importlib-metadata | |
28 | ]; | |
29 | ||
30 | # TODO FIXME | |
31 | doCheck = | |
32 | false; | |
33 | ||
34 | meta = | |
35 | with lib; | |
36 | { }; | |
37 | } |
13 | 13 | pname = |
14 | 14 | "faraday-agent-parameters-types"; |
15 | 15 | version = |
16 | "1.0.0"; | |
16 | "1.0.1"; | |
17 | 17 | |
18 | 18 | src = |
19 | 19 | fetchPypi { |
22 | 22 | pname = |
23 | 23 | "faraday_agent_parameters_types"; |
24 | 24 | sha256 = |
25 | "0qnm7q7561kwx54k23brkh5d5lkyqss6r31bvi4rmzs61pik5jvk"; | |
25 | "0q2cngxgkvl74mhkibvdsvjjrdfd7flxd6a4776wmxkkn0brzw66"; | |
26 | 26 | }; |
27 | 27 | |
28 | 28 | buildInputs = |
20 | 20 | pname = |
21 | 21 | "faraday-plugins"; |
22 | 22 | version = |
23 | "1.5.0"; | |
23 | "1.5.2"; | |
24 | 24 | |
25 | 25 | src = |
26 | 26 | fetchPypi { |
28 | 28 | pname |
29 | 29 | version; |
30 | 30 | sha256 = |
31 | "1wf313s2kricd44s4m0x62psk2xq69fp6n4qm0f7k1rrwilwdxyd"; | |
31 | "09gr3gsipnfvm222bj20n77hrys4gk57i30r9llj72hiicm2bb4k"; | |
32 | 32 | }; |
33 | 33 | |
34 | 34 | propagatedBuildInputs = |
24 | 24 | , flask-kvsession-fork |
25 | 25 | , flask-limiter |
26 | 26 | , flask-security-too |
27 | , flask-socketio | |
27 | 28 | , flask_login |
28 | 29 | , flask_mail |
29 | 30 | , flask_sqlalchemy |
63 | 64 | pname = |
64 | 65 | "faradaysec"; |
65 | 66 | version = |
66 | "3.16.0"; | |
67 | "3.17.0"; | |
67 | 68 | |
68 | 69 | src = |
69 | 70 | lib.cleanSource |
114 | 115 | apispec |
115 | 116 | apispec-webframeworks |
116 | 117 | pyyaml |
118 | flask-socketio | |
117 | 119 | pyotp |
118 | 120 | flask-limiter |
119 | 121 | flask_mail |
18 | 18 | pname = |
19 | 19 | "flask-security-too"; |
20 | 20 | version = |
21 | "4.0.1"; | |
21 | "4.1.0"; | |
22 | 22 | |
23 | 23 | src = |
24 | 24 | fetchPypi { |
27 | 27 | pname = |
28 | 28 | "Flask-Security-Too"; |
29 | 29 | sha256 = |
30 | "1q7izrmz84wwhmzs39zgjvr90vb22z3szsm8mp3a3qnb1377z5n2"; | |
30 | "109h08p0sljkspqay6i970hqbdwcnp7z59ql9nz0cdyg6m2czpk8"; | |
31 | 31 | }; |
32 | 32 | |
33 | 33 | propagatedBuildInputs = |
0 | # WARNING: This file was automatically generated. You should avoid editing it. | |
1 | # If you run pynixify again, the file will be either overwritten or | |
2 | # deleted, and you will lose the changes you made to it. | |
3 | ||
4 | { buildPythonPackage | |
5 | , fetchPypi | |
6 | , flask | |
7 | , lib | |
8 | , python-socketio | |
9 | }: | |
10 | ||
11 | buildPythonPackage rec { | |
12 | pname = | |
13 | "flask-socketio"; | |
14 | version = | |
15 | "5.1.1"; | |
16 | ||
17 | src = | |
18 | fetchPypi { | |
19 | inherit | |
20 | version; | |
21 | pname = | |
22 | "Flask-SocketIO"; | |
23 | sha256 = | |
24 | "1cgn86f2p7il4aiw153099jamxjq22dhg03s34mlzs96gb6amz8y"; | |
25 | }; | |
26 | ||
27 | propagatedBuildInputs = | |
28 | [ | |
29 | flask | |
30 | python-socketio | |
31 | ]; | |
32 | ||
33 | # TODO FIXME | |
34 | doCheck = | |
35 | false; | |
36 | ||
37 | meta = | |
38 | with lib; | |
39 | { }; | |
40 | } |
10 | 10 | pname = |
11 | 11 | "marshmallow"; |
12 | 12 | version = |
13 | "3.12.1"; | |
13 | "3.12.2"; | |
14 | 14 | |
15 | 15 | src = |
16 | 16 | fetchPypi { |
18 | 18 | pname |
19 | 19 | version; |
20 | 20 | sha256 = |
21 | "0h70m4z1kbcwsd0sv8cwlcmpj4dnblvr5vj18j7wa327f1dlfl40"; | |
21 | "1zyjjcscwhwa82424blyiihdihgs6c5wxnxv3h23lg6rvbz8sdkp"; | |
22 | 22 | }; |
23 | 23 | |
24 | 24 | # TODO FIXME |
12 | 12 | pname = |
13 | 13 | "marshmallow-sqlalchemy"; |
14 | 14 | version = |
15 | "0.25.0"; | |
15 | "0.26.1"; | |
16 | 16 | |
17 | 17 | src = |
18 | 18 | fetchPypi { |
20 | 20 | pname |
21 | 21 | version; |
22 | 22 | sha256 = |
23 | "0i39ckrixh1w9fmkm0wl868gvza72j5la0x6dd0cij9shf1iyjgi"; | |
23 | "0wval5lqak31zwrzmgi9c919lqk0dw1zxvwihif4nmaivrs5ylnq"; | |
24 | 24 | }; |
25 | 25 | |
26 | 26 | propagatedBuildInputs = |
0 | # WARNING: This file was automatically generated. You should avoid editing it. | |
1 | # If you run pynixify again, the file will be either overwritten or | |
2 | # deleted, and you will lose the changes you made to it. | |
3 | ||
4 | { buildPythonPackage | |
5 | , fetchPypi | |
6 | , lib | |
7 | }: | |
8 | ||
9 | buildPythonPackage rec { | |
10 | pname = | |
11 | "python-engineio"; | |
12 | version = | |
13 | "4.2.1"; | |
14 | ||
15 | src = | |
16 | fetchPypi { | |
17 | inherit | |
18 | pname | |
19 | version; | |
20 | sha256 = | |
21 | "0qps2bhis0ms8pbncsx6xwnyd6k5ffy5hbw68wjndmcfdndk446m"; | |
22 | }; | |
23 | ||
24 | # TODO FIXME | |
25 | doCheck = | |
26 | false; | |
27 | ||
28 | meta = | |
29 | with lib; | |
30 | { }; | |
31 | } |
0 | # WARNING: This file was automatically generated. You should avoid editing it. | |
1 | # If you run pynixify again, the file will be either overwritten or | |
2 | # deleted, and you will lose the changes you made to it. | |
3 | ||
4 | { bidict | |
5 | , buildPythonPackage | |
6 | , fetchPypi | |
7 | , lib | |
8 | , python-engineio | |
9 | }: | |
10 | ||
11 | buildPythonPackage rec { | |
12 | pname = | |
13 | "python-socketio"; | |
14 | version = | |
15 | "5.4.0"; | |
16 | ||
17 | src = | |
18 | fetchPypi { | |
19 | inherit | |
20 | pname | |
21 | version; | |
22 | sha256 = | |
23 | "0i15p94b592aa2h3vn3zs9qc8izv6kc4vmhjlkg9d3hn3yg7r06a"; | |
24 | }; | |
25 | ||
26 | propagatedBuildInputs = | |
27 | [ | |
28 | bidict | |
29 | python-engineio | |
30 | ]; | |
31 | ||
32 | # TODO FIXME | |
33 | doCheck = | |
34 | false; | |
35 | ||
36 | meta = | |
37 | with lib; | |
38 | { }; | |
39 | } |
2 | 2 | alembic>=0.9.9 |
3 | 3 | bcrypt>=3.1.4 |
4 | 4 | colorama>=0.3.9 |
5 | click>=5.1 | |
5 | click>=8.0.1 | |
6 | 6 | flask>=1.1 |
7 | 7 | Flask-SQLAlchemy>=2.3.1 |
8 | 8 | flask-classful>=0.14 |
11 | 11 | flask-login>=0.5.0 |
12 | 12 | Flask-Security-Too>=4.0.0 |
13 | 13 | bleach>=3.3.0 |
14 | marshmallow>=3.11.0 | |
14 | marshmallow>=3.11.0,<3.13.0 | |
15 | 15 | Pillow>=4.2.1 |
16 | 16 | psycopg2 |
17 | 17 | pgcli |
24 | 24 | tqdm>=4.15.0 |
25 | 25 | twisted>=18.9.0 |
26 | 26 | webargs>=7.0.0 |
27 | marshmallow-sqlalchemy==0.25.0 | |
27 | marshmallow-sqlalchemy>=0.26.1 | |
28 | 28 | filteralchemy-fork |
29 | 29 | filedepot>=0.5.0 |
30 | 30 | nplusone>=0.8.1 |
36 | 36 | apispec>=4.0.0 |
37 | 37 | apispec-webframeworks>=0.5.0 |
38 | 38 | pyyaml |
39 | Flask-SocketIO>=5.0.1 | |
39 | 40 | pyotp>=2.6.0 |
40 | 41 | Flask-Limiter |
41 | 42 | Flask-Mail |
0 | from datetime import datetime, timedelta, timezone | |
0 | from datetime import datetime, timedelta | |
1 | 1 | |
2 | 2 | import pytest |
3 | 3 | from marshmallow import ValidationError |
17 | 17 | ) |
18 | 18 | from faraday.server.api.modules import bulk_create as bc |
19 | 19 | from tests.factories import CustomFieldsSchemaFactory |
20 | from faraday.server.threads.reports_processor import REPORTS_QUEUE | |
20 | 21 | |
21 | 22 | host_data = { |
22 | 23 | "ip": "127.0.0.1", |
66 | 67 | 'command': 'pytest tests/test_api_bulk_create.py', |
67 | 68 | 'user': 'root', |
68 | 69 | 'hostname': 'pc', |
69 | 'start_date': '2014-12-22T03:12:58.019077+00:00', | |
70 | 'start_date': (datetime.utcnow() - timedelta(days=7)).isoformat(), | |
70 | 71 | } |
71 | 72 | |
72 | 73 | |
77 | 78 | def new_empty_command(workspace: Workspace): |
78 | 79 | command = Command() |
79 | 80 | command.workspace = workspace |
80 | command.start_date = datetime.now() | |
81 | command.start_date = datetime.utcnow() | |
81 | 82 | command.import_source = 'report' |
82 | 83 | command.tool = "In progress" |
83 | 84 | command.command = "In progress" |
87 | 88 | |
88 | 89 | def test_create_host(session, workspace): |
89 | 90 | assert count(Host, workspace) == 0 |
90 | bc.bulk_create(workspace, None, dict(hosts=[host_data])) | |
91 | command = new_empty_command(workspace) | |
92 | bc.bulk_create(workspace, command, dict(hosts=[host_data], command=command_data.copy())) | |
91 | 93 | db.session.commit() |
92 | 94 | host = Host.query.filter(Host.workspace == workspace).one() |
93 | 95 | assert host.ip == "127.0.0.1" |
96 | 98 | |
97 | 99 | def test_create_duplicated_hosts(session, workspace): |
98 | 100 | assert count(Host, workspace) == 0 |
99 | bc.bulk_create(workspace, None, dict(hosts=[host_data, host_data])) | |
101 | command = new_empty_command(workspace) | |
102 | bc.bulk_create(workspace, command, dict(hosts=[host_data, host_data], command=command_data.copy())) | |
100 | 103 | db.session.commit() |
101 | 104 | assert count(Host, workspace) == 1 |
102 | 105 | |
103 | 106 | |
104 | 107 | def test_create_host_add_hostnames(session, workspace): |
105 | 108 | assert count(Host, workspace) == 0 |
106 | bc.bulk_create(workspace, None, dict(hosts=[host_data])) | |
109 | command = new_empty_command(workspace) | |
110 | bc.bulk_create(workspace, command, dict(hosts=[host_data], command=command_data.copy())) | |
107 | 111 | db.session.commit() |
108 | 112 | host_copy = host_data.copy() |
109 | 113 | host_copy['hostnames'] = ["test3.org"] |
110 | bc.bulk_create(workspace, None, dict(hosts=[host_copy])) | |
114 | other_command = new_empty_command(workspace) | |
115 | bc.bulk_create(workspace, other_command, dict(hosts=[host_copy], | |
116 | command=command_data.copy())) | |
111 | 117 | db.session.commit() |
112 | 118 | host = Host.query.filter(Host.workspace == workspace).one() |
113 | 119 | assert host.ip == "127.0.0.1" |
123 | 129 | "description": host.description, |
124 | 130 | "hostnames": [hn.name for hn in host.hostnames] |
125 | 131 | } |
126 | bc.bulk_create(host.workspace, None, dict(hosts=[data])) | |
132 | command = new_empty_command(host.workspace) | |
133 | bc.bulk_create(host.workspace, command, dict(hosts=[data], | |
134 | command=command_data.copy())) | |
127 | 135 | assert count(Host, host.workspace) == 1 |
128 | 136 | |
129 | 137 | |
130 | 138 | def test_create_host_with_services(session, workspace): |
131 | 139 | host_data_ = host_data.copy() |
132 | 140 | host_data_['services'] = [service_data] |
133 | bc.bulk_create(workspace, None, dict(hosts=[host_data_])) | |
141 | command = new_empty_command(workspace) | |
142 | bc.bulk_create(workspace, command, dict(hosts=[host_data_], | |
143 | command=command_data.copy())) | |
134 | 144 | assert count(Host, workspace) == 1 |
135 | 145 | assert count(Service, workspace) == 1 |
136 | 146 | service = Service.query.filter(Service.workspace == workspace).one() |
271 | 281 | assert 'old' in vuln.references # it must preserve the old references |
272 | 282 | |
273 | 283 | |
284 | def test_bulk_create_on_closed_vuln(session, host, vulnerability_factory): | |
285 | vuln = vulnerability_factory.create( | |
286 | workspace=host.workspace, host=host, service=None, status="closed") | |
287 | session.add(vuln) | |
288 | session.commit() | |
289 | session.add(vuln) | |
290 | session.commit() | |
291 | vuln_data = { | |
292 | 'name': vuln.name, | |
293 | 'desc': vuln.description, | |
294 | 'severity': vuln.severity, | |
295 | 'type': 'Vulnerability', | |
296 | 'status': 'open' | |
297 | } | |
298 | ||
299 | data = { | |
300 | "ip": host.ip, | |
301 | "description": host.description, | |
302 | "hostnames": [hn.name for hn in host.hostnames], | |
303 | "vulnerabilities": [vuln_data] | |
304 | } | |
305 | command = new_empty_command(host.workspace) | |
306 | bc.bulk_create(host.workspace, command, dict(hosts=[data], | |
307 | command=command_data.copy())) | |
308 | vuln = Vulnerability.query.get(vuln.id) | |
309 | assert vuln.status == "re-opened" | |
310 | ||
311 | ||
312 | def test_bulk_create_endpoint_with_vuln_run_date(session, workspace): | |
313 | run_date = datetime.now() - timedelta(days=30) | |
314 | host_data_copy = host_data.copy() | |
315 | vuln_data_copy = vuln_data.copy() | |
316 | vuln_data_copy['run_date'] = run_date.timestamp() | |
317 | host_data_copy['vulnerabilities'] = [vuln_data_copy] | |
318 | command = new_empty_command(workspace) | |
319 | bc.bulk_create(workspace, command, dict(hosts=[host_data_copy], | |
320 | command=command_data.copy())) | |
321 | assert count(Host, workspace) == 1 | |
322 | assert count(VulnerabilityGeneric, workspace) == 1 | |
323 | vuln = Vulnerability.query.filter(Vulnerability.workspace == workspace).one() | |
324 | assert vuln.create_date.date() == run_date.date() | |
325 | ||
326 | ||
327 | def test_bulk_create_endpoint_with_future_vuln_run_date(session, workspace): | |
328 | run_date = datetime.now() + timedelta(days=30) | |
329 | host_data_copy = host_data.copy() | |
330 | vuln_data_copy = vuln_data.copy() | |
331 | vuln_data_copy['run_date'] = run_date.timestamp() | |
332 | host_data_copy['vulnerabilities'] = [vuln_data_copy] | |
333 | command = new_empty_command(workspace) | |
334 | bc.bulk_create(workspace, command, dict(hosts=[host_data_copy], | |
335 | command=command_data.copy())) | |
336 | assert count(Host, workspace) == 1 | |
337 | assert count(VulnerabilityGeneric, workspace) == 1 | |
338 | vuln = Vulnerability.query.filter(Vulnerability.workspace == workspace).one() | |
339 | assert vuln.create_date.date() < run_date.date() | |
340 | ||
341 | ||
342 | def test_bulk_create_endpoint_with_invalid_vuln_run_date(session, workspace): | |
343 | run_date = datetime.now() + timedelta(days=30) | |
344 | host_data_copy = host_data.copy() | |
345 | vuln_data_copy = vuln_data.copy() | |
346 | vuln_data_copy['run_date'] = "INVALID" | |
347 | host_data_copy['vulnerabilities'] = [vuln_data_copy] | |
348 | command = new_empty_command(workspace) | |
349 | with pytest.raises(Exception): | |
350 | bc.bulk_create(workspace, command, dict(hosts=[host_data_copy], | |
351 | command=command_data.copy())) | |
352 | ||
353 | ||
274 | 354 | @pytest.mark.skip(reason="unique constraing on credential isn't working") |
275 | 355 | def test_create_existing_host_cred(session, host, credential_factory): |
276 | 356 | cred = credential_factory.create( |
291 | 371 | def test_create_host_with_vuln(session, workspace): |
292 | 372 | host_data_ = host_data.copy() |
293 | 373 | host_data_['vulnerabilities'] = [vuln_data] |
294 | bc.bulk_create(workspace, None, dict(hosts=[host_data_])) | |
374 | command = new_empty_command(workspace) | |
375 | bc.bulk_create(workspace, command, dict(hosts=[host_data_], | |
376 | command=command_data.copy())) | |
295 | 377 | assert count(Host, workspace) == 1 |
296 | 378 | host = workspace.hosts[0] |
297 | 379 | assert count(Vulnerability, workspace) == 1 |
303 | 385 | def test_create_host_with_cred(session, workspace): |
304 | 386 | host_data_ = host_data.copy() |
305 | 387 | host_data_['credentials'] = [credential_data] |
306 | bc.bulk_create(workspace, None, dict(hosts=[host_data_])) | |
388 | command = new_empty_command(workspace) | |
389 | bc.bulk_create(workspace, command, dict(hosts=[host_data_], | |
390 | command=command_data.copy())) | |
307 | 391 | assert count(Host, workspace) == 1 |
308 | 392 | host = workspace.hosts[0] |
309 | 393 | assert count(Credential, workspace) == 1 |
620 | 704 | assert vuln.response == sanitized_response_text |
621 | 705 | |
622 | 706 | |
707 | def test_create_vuln_with_custom_fields(session, workspace): | |
708 | custom_field_schema = CustomFieldsSchemaFactory( | |
709 | field_name='changes', | |
710 | field_type='list', | |
711 | field_display_name='Changes', | |
712 | table_name='vulnerability' | |
713 | ) | |
714 | session.add(custom_field_schema) | |
715 | session.commit() | |
716 | host_data_ = host_data.copy() | |
717 | vuln_data_ = vuln_data.copy() | |
718 | vuln_data_['custom_fields'] = {'changes': ['1', '2', '3']} | |
719 | host_data_['vulnerabilities'] = [vuln_data_] | |
720 | command = new_empty_command(workspace) | |
721 | bc.bulk_create(workspace, command, dict(hosts=[host_data_], | |
722 | command=command_data.copy())) | |
723 | assert count(Host, workspace) == 1 | |
724 | assert count(Vulnerability, workspace) == 1 | |
725 | assert count(Command, workspace) == 1 | |
726 | for vuln in Vulnerability.query.filter(Vulnerability.workspace == workspace): | |
727 | assert vuln.custom_fields['changes'] == ['1', '2', '3'] | |
728 | ||
729 | ||
623 | 730 | class TestBulkCreateAPI: |
624 | 731 | |
625 | 732 | @pytest.mark.usefixtures('logged_user') |
626 | def test_bulk_create_endpoint(self, session, workspace, test_client, logged_user): | |
733 | def test_bulk_create_endpoint_add_to_queue(self, session, workspace, test_client, logged_user): | |
627 | 734 | assert count(Host, workspace) == 0 |
628 | 735 | assert count(VulnerabilityGeneric, workspace) == 0 |
629 | 736 | url = f'/v3/ws/{workspace.name}/bulk_create' |
638 | 745 | data=dict(hosts=[host_data_], command=command_data) |
639 | 746 | ) |
640 | 747 | assert res.status_code == 201, res.json |
641 | assert count(Host, workspace) == 1 | |
642 | assert count(Service, workspace) == 1 | |
643 | assert count(Vulnerability, workspace) == 2 | |
644 | 748 | assert count(Command, workspace) == 1 |
645 | host = Host.query.filter(Host.workspace == workspace).one() | |
646 | assert host.ip == "127.0.0.1" | |
647 | assert host.creator_id == logged_user.id | |
648 | assert set({hn.name for hn in host.hostnames}) == {"test.com", "test2.org"} | |
649 | assert len(host.services) == 1 | |
650 | assert len(host.vulnerabilities) == 1 | |
651 | assert len(host.services[0].vulnerabilities) == 1 | |
652 | service = Service.query.filter(Service.workspace == workspace).one() | |
653 | assert service.creator_id == logged_user.id | |
654 | credential = Credential.query.filter(Credential.workspace == workspace).one() | |
655 | assert credential.creator_id == logged_user.id | |
656 | command = Command.query.filter(Credential.workspace == workspace).one() | |
657 | assert command.creator_id == logged_user.id | |
658 | assert res.json["command_id"] == command.id | |
749 | assert len(REPORTS_QUEUE.queue) == 1 | |
750 | command = Command.query.filter(Command.workspace == workspace).one() | |
751 | queue_elem = REPORTS_QUEUE.get_nowait() | |
752 | assert queue_elem[0] == workspace.name | |
753 | assert queue_elem[1] == command.id | |
754 | assert queue_elem[3] is None | |
755 | assert queue_elem[4] == logged_user.id | |
659 | 756 | |
660 | 757 | @pytest.mark.usefixtures('logged_user') |
661 | def test_bulk_create_endpoint_run_over_closed_vuln(self, session, workspace, test_client): | |
662 | assert count(Host, workspace) == 0 | |
663 | assert count(VulnerabilityGeneric, workspace) == 0 | |
664 | url = f'/v3/ws/{workspace.name}/bulk_create' | |
665 | host_data_ = host_data.copy() | |
666 | host_data_['vulnerabilities'] = [vuln_data] | |
667 | res = test_client.post(url, data=dict(hosts=[host_data_])) | |
668 | assert res.status_code == 201, res.json | |
669 | assert count(Host, workspace) == 1 | |
670 | assert count(Vulnerability, workspace) == 1 | |
671 | host = Host.query.filter(Host.workspace == workspace).one() | |
672 | vuln = Vulnerability.query.filter(Vulnerability.workspace == workspace).one() | |
673 | assert host.ip == "127.0.0.1" | |
674 | assert set({hn.name for hn in host.hostnames}) == {"test.com", "test2.org"} | |
675 | assert vuln.status == "open" | |
676 | close_url = f'/v3/ws/{workspace.name}/vulns/{vuln.id}' | |
677 | res = test_client.get(close_url) | |
678 | vuln_data_del = res.json | |
679 | vuln_data_del["status"] = "closed" | |
680 | res = test_client.put(close_url, data=dict(vuln_data_del)) | |
681 | assert res.status_code == 200, res.json | |
682 | assert count(Host, workspace) == 1 | |
683 | assert count(Vulnerability, workspace) == 1 | |
684 | assert vuln.status == "closed" | |
685 | res = test_client.post(url, data=dict(hosts=[host_data_])) | |
686 | assert res.status_code == 201, res.json | |
687 | assert count(Host, workspace) == 1 | |
688 | assert count(Vulnerability, workspace) == 1 | |
689 | vuln = Vulnerability.query.filter(Vulnerability.workspace == workspace).one() | |
690 | assert vuln.status == "re-opened" | |
691 | ||
692 | @pytest.mark.usefixtures('logged_user') | |
693 | def test_bulk_create_endpoint_without_host_ip(self, session, workspace, test_client): | |
694 | url = f'/v3/ws/{workspace.name}/bulk_create' | |
695 | host_data_ = host_data.copy() | |
696 | host_data_.pop('ip') | |
697 | res = test_client.post(url, data=dict(hosts=[host_data_])) | |
698 | assert res.status_code == 400 | |
699 | ||
700 | def test_bulk_create_endpoints_fails_without_auth(self, session, workspace, test_client): | |
701 | url = f'/v3/ws/{workspace.name}/bulk_create' | |
702 | res = test_client.post(url, data=dict(hosts=[host_data])) | |
703 | assert res.status_code == 401 | |
704 | assert count(Host, workspace) == 0 | |
705 | ||
706 | @pytest.mark.parametrize('token_type', ['agent', 'token']) | |
707 | def test_bulk_create_endpoints_fails_with_invalid_token(self, token_type, workspace, test_client): | |
758 | def test_bulk_create_endpoint_with_invalid_data(self, session, workspace, test_client, logged_user): | |
759 | invalid_data = {} | |
708 | 760 | url = f'/v3/ws/{workspace.name}/bulk_create' |
709 | 761 | res = test_client.post( |
710 | 762 | url, |
711 | data=dict(hosts=[host_data]), | |
712 | headers=[("authorization", f"{token_type} 1234")] | |
763 | data=invalid_data | |
713 | 764 | ) |
714 | if token_type == 'token': | |
715 | # TODO change expected status code to 403 | |
716 | assert res.status_code == 401 | |
717 | else: | |
718 | assert res.status_code == 403 | |
719 | assert count(Host, workspace) == 0 | |
765 | assert res.status_code == 400, res.json | |
720 | 766 | |
721 | 767 | def test_bulk_create_with_agent_token_in_different_workspace_fails( |
722 | 768 | self, session, agent, second_workspace, test_client): |
729 | 775 | url = f'/v3/ws/{second_workspace.name}/bulk_create' |
730 | 776 | res = test_client.post( |
731 | 777 | url, |
732 | data=dict(hosts=[host_data]), | |
778 | data=dict(hosts=[host_data], command=command_data.copy()), | |
733 | 779 | headers=[("authorization", f"agent {agent.token}")] |
734 | 780 | ) |
735 | 781 | assert res.status_code == 404 |
752 | 798 | for workspace in agent.workspaces: |
753 | 799 | assert count(Host, workspace) == 0 |
754 | 800 | |
801 | @pytest.mark.parametrize('token_type', ['agent', 'token']) | |
802 | def test_bulk_create_endpoints_fails_with_invalid_token(self, token_type, workspace, test_client): | |
803 | url = f'/v3/ws/{workspace.name}/bulk_create' | |
804 | res = test_client.post( | |
805 | url, | |
806 | data=dict(hosts=[host_data], command=command_data.copy()), | |
807 | headers=[("authorization", f"{token_type} 1234")] | |
808 | ) | |
809 | if token_type == 'token': | |
810 | # TODO change expected status code to 403 | |
811 | assert res.status_code == 401 | |
812 | else: | |
813 | assert res.status_code == 403 | |
814 | assert count(Host, workspace) == 0 | |
815 | ||
816 | def test_bulk_create_endpoints_fails_without_auth(self, session, workspace, test_client): | |
817 | url = f'/v3/ws/{workspace.name}/bulk_create' | |
818 | res = test_client.post(url, data=dict(hosts=[host_data], | |
819 | command=command_data.copy())) | |
820 | assert res.status_code == 401 | |
821 | assert count(Host, workspace) == 0 | |
822 | ||
755 | 823 | def test_bulk_create_endpoint_with_agent_token_without_execution_id(self, session, agent, test_client): |
756 | 824 | session.add(agent) |
757 | 825 | session.commit() |
760 | 828 | url = f'/v3/ws/{workspace.name}/bulk_create' |
761 | 829 | res = test_client.post( |
762 | 830 | url, |
763 | data=dict(hosts=[host_data]), | |
831 | data=dict(hosts=[host_data], command=command_data.copy()), | |
764 | 832 | headers=[("authorization", f"agent {agent.token}")] |
765 | 833 | ) |
766 | 834 | assert res.status_code == 400 |
767 | assert b"\'execution_id\' argument expected" in res.data | |
835 | assert b"argument expected: execution_id" in res.data | |
768 | 836 | assert count(Host, workspace) == 0 |
769 | 837 | assert count(Command, workspace) == 0 |
770 | 838 | |
771 | @pytest.mark.parametrize('start_date', [None, datetime.now()]) | |
839 | def test_bulk_create_endpoint_with_agent_token_with_param(self, session, agent_execution, test_client): | |
840 | agent = agent_execution.executor.agent | |
841 | session.add(agent_execution) | |
842 | session.commit() | |
843 | for workspace in agent.workspaces: | |
844 | agent_execution.workspace = workspace | |
845 | agent_execution.command.workspace = workspace | |
846 | session.add(agent_execution) | |
847 | session.commit() | |
848 | assert count(Host, workspace) == 0 | |
849 | assert count(Command, workspace) == 1 | |
850 | url = f'/v3/ws/{workspace.name}/bulk_create' | |
851 | res = test_client.post( | |
852 | url, | |
853 | data=dict(hosts=[host_data], execution_id=agent_execution.id, | |
854 | command=command_data.copy()), | |
855 | headers=[("authorization", f"agent {agent.token}")] | |
856 | ) | |
857 | assert res.status_code == 201 | |
858 | assert count(Command, workspace) == 1 | |
859 | command = Command.query.filter(Command.workspace == workspace).one() | |
860 | assert command.tool == agent.name | |
861 | assert command.command == agent_execution.executor.name | |
862 | params = ', '.join([f'{key}={value}' for (key, value) in agent_execution.parameters_data.items()]) | |
863 | assert command.params == str(params) | |
864 | assert command.import_source == 'agent' | |
865 | command_id = res.json["command_id"] | |
866 | assert command.id == command_id | |
867 | assert command.id == agent_execution.command.id | |
868 | ||
869 | @pytest.mark.parametrize('start_date', [None, datetime.utcnow()]) | |
772 | 870 | @pytest.mark.parametrize('duration', [None, 1200]) |
773 | 871 | def test_bulk_create_endpoint_with_agent_token(self, |
774 | 872 | session, |
788 | 886 | session.add(extra_agent_execution) |
789 | 887 | session.commit() |
790 | 888 | |
791 | command_data = {} | |
889 | command_dict = {} | |
792 | 890 | if start_date: |
793 | command_data.update({ | |
891 | command_dict.update({ | |
794 | 892 | 'tool': agent.name, # Agent name |
795 | 893 | 'command': agent_execution.executor.name, |
796 | 894 | 'user': '', |
800 | 898 | 'start_date': str(start_date) |
801 | 899 | }) |
802 | 900 | if duration: |
803 | command_data.update({ | |
901 | command_dict.update({ | |
804 | 902 | 'tool': agent.name, # Agent name |
805 | 903 | 'command': agent_execution.executor.name, |
806 | 904 | 'user': '', |
814 | 912 | "hosts": [host_data], |
815 | 913 | "execution_id": -1 |
816 | 914 | } |
817 | if command_data: | |
818 | data_kwargs["command"] = command_data | |
915 | if command_dict: | |
916 | data_kwargs["command"] = command_dict | |
917 | else: | |
918 | data_kwargs["command"] = command_data.copy() | |
819 | 919 | |
820 | 920 | initial_host_count = Host.query.filter(Host.workspace == workspace and Host.creator_id is None).count() |
821 | 921 | assert count(Command, workspace) == 1 |
849 | 949 | |
850 | 950 | if start_date or duration is None: |
851 | 951 | assert res.status_code == 201, res.json |
852 | assert Host.query.filter(Host.workspace == workspace and Host.creator_id is None).count() == \ | |
853 | initial_host_count + 1 | |
854 | 952 | assert count(Command, workspace) == 1 |
855 | 953 | command = Command.query.filter(Command.workspace == workspace).one() |
856 | 954 | assert command.tool == agent.name |
868 | 966 | else: |
869 | 967 | assert res.status_code == 400, res.json |
870 | 968 | |
871 | def test_bulk_create_endpoint_with_agent_token_with_param(self, session, agent_execution, test_client): | |
872 | agent = agent_execution.executor.agent | |
873 | session.add(agent_execution) | |
874 | session.commit() | |
875 | for workspace in agent.workspaces: | |
876 | agent_execution.workspace = workspace | |
877 | agent_execution.command.workspace = workspace | |
878 | session.add(agent_execution) | |
879 | session.commit() | |
880 | assert count(Host, workspace) == 0 | |
881 | assert count(Command, workspace) == 1 | |
882 | url = f'/v3/ws/{workspace.name}/bulk_create' | |
883 | res = test_client.post( | |
884 | url, | |
885 | data=dict(hosts=[host_data], execution_id=agent_execution.id), | |
886 | headers=[("authorization", f"agent {agent.token}")] | |
887 | ) | |
888 | assert res.status_code == 201 | |
889 | assert count(Host, workspace) == 1 | |
890 | host = Host.query.filter(Host.workspace == workspace).one() | |
891 | assert host.creator_id is None | |
892 | assert count(Command, workspace) == 1 | |
893 | command = Command.query.filter(Command.workspace == workspace).one() | |
894 | assert command.tool == agent.name | |
895 | assert command.command == agent_execution.executor.name | |
896 | params = ', '.join([f'{key}={value}' for (key, value) in agent_execution.parameters_data.items()]) | |
897 | assert command.params == str(params) | |
898 | assert command.import_source == 'agent' | |
899 | command_id = res.json["command_id"] | |
900 | assert command.id == command_id | |
901 | assert command.id == agent_execution.command.id | |
902 | ||
903 | 969 | def test_bulk_create_endpoint_with_agent_token_readonly_workspace(self, session, agent, test_client): |
904 | 970 | for workspace in agent.workspaces: |
905 | 971 | workspace.readonly = True |
942 | 1008 | assert res.status_code == 400 |
943 | 1009 | |
944 | 1010 | @pytest.mark.usefixtures('logged_user') |
945 | def test_bulk_create_endpoint_with_vuln_run_date(self, session, workspace, test_client): | |
946 | assert count(Host, workspace) == 0 | |
947 | assert count(VulnerabilityGeneric, workspace) == 0 | |
948 | url = f'/v3/ws/{workspace.name}/bulk_create' | |
949 | run_date = datetime.now(timezone.utc) - timedelta(days=30) | |
950 | host_data_copy = host_data.copy() | |
951 | vuln_data_copy = vuln_data.copy() | |
952 | vuln_data_copy['run_date'] = run_date.timestamp() | |
953 | host_data_copy['vulnerabilities'] = [vuln_data_copy] | |
954 | res = test_client.post(url, data=dict(hosts=[host_data_copy])) | |
955 | assert res.status_code == 201, res.json | |
956 | assert count(Host, workspace) == 1 | |
957 | assert count(VulnerabilityGeneric, workspace) == 1 | |
958 | vuln = Vulnerability.query.filter(Vulnerability.workspace == workspace).one() | |
959 | assert vuln.create_date.date() == run_date.date() | |
960 | ||
961 | @pytest.mark.usefixtures('logged_user') | |
962 | def test_bulk_create_endpoint_with_vuln_future_run_date(self, session, workspace, test_client): | |
963 | assert count(Host, workspace) == 0 | |
964 | assert count(VulnerabilityGeneric, workspace) == 0 | |
965 | url = f'/v3/ws/{workspace.name}/bulk_create' | |
966 | run_date = datetime.now(timezone.utc) + timedelta(days=10) | |
967 | host_data_copy = host_data.copy() | |
968 | vuln_data_copy = vuln_data.copy() | |
969 | vuln_data_copy['run_date'] = run_date.timestamp() | |
970 | host_data_copy['vulnerabilities'] = [vuln_data_copy] | |
971 | res = test_client.post(url, data=dict(hosts=[host_data_copy])) | |
972 | assert res.status_code == 201, res.json | |
973 | assert count(Host, workspace) == 1 | |
974 | assert count(VulnerabilityGeneric, workspace) == 1 | |
975 | vuln = Vulnerability.query.filter(Vulnerability.workspace == workspace).one() | |
976 | print(vuln.create_date) | |
977 | assert vuln.create_date.date() < run_date.date() | |
978 | ||
979 | @pytest.mark.usefixtures('logged_user') | |
980 | def test_bulk_create_endpoint_with_invalid_vuln_run_date(self, session, workspace, test_client): | |
981 | assert count(Host, workspace) == 0 | |
982 | assert count(VulnerabilityGeneric, workspace) == 0 | |
983 | url = f'/v3/ws/{workspace.name}/bulk_create' | |
984 | host_data_copy = host_data.copy() | |
985 | vuln_data_copy = vuln_data.copy() | |
986 | vuln_data_copy['run_date'] = "INVALID_VALUE" | |
987 | host_data_copy['vulnerabilities'] = [vuln_data_copy] | |
988 | res = test_client.post(url, data=dict(hosts=[host_data_copy])) | |
989 | assert res.status_code == 400, res.json | |
990 | assert count(VulnerabilityGeneric, workspace) == 0 | |
991 | ||
992 | @pytest.mark.usefixtures('logged_user') | |
993 | 1011 | def test_bulk_create_endpoint_fails_with_list_in_NullToBlankString(self, session, workspace, test_client, |
994 | 1012 | logged_user): |
995 | 1013 | assert count(Host, workspace) == 0 |
1002 | 1020 | host_data_['default_gateway'] = ["localhost"] # Can not be a list |
1003 | 1021 | res = test_client.post(url, data=dict(hosts=[host_data_])) |
1004 | 1022 | assert res.status_code == 400, res.json |
1005 | assert count(Host, workspace) == 0 | |
1006 | assert count(Service, workspace) == 0 | |
1007 | assert count(Credential, workspace) == 0 | |
1008 | assert count(Vulnerability, workspace) == 0 | |
1009 | ||
1010 | @pytest.mark.usefixtures('logged_user') | |
1011 | def test_bulk_create_with_custom_fields_list(self, test_client, workspace, session, logged_user): | |
1012 | custom_field_schema = CustomFieldsSchemaFactory( | |
1013 | field_name='changes', | |
1014 | field_type='list', | |
1015 | field_display_name='Changes', | |
1016 | table_name='vulnerability' | |
1017 | ) | |
1018 | session.add(custom_field_schema) | |
1019 | session.commit() | |
1020 | ||
1021 | assert count(Host, workspace) == 0 | |
1022 | assert count(VulnerabilityGeneric, workspace) == 0 | |
1023 | url = f'/v3/ws/{workspace.name}/bulk_create' | |
1024 | host_data_ = host_data.copy() | |
1025 | service_data_ = service_data.copy() | |
1026 | vuln_data_ = vuln_data.copy() | |
1027 | vuln_data_['custom_fields'] = {'changes': ['1', '2', '3']} | |
1028 | service_data_['vulnerabilities'] = [vuln_data_] | |
1029 | host_data_['services'] = [service_data_] | |
1030 | host_data_['credentials'] = [credential_data] | |
1031 | host_data_['vulnerabilities'] = [vuln_data_] | |
1032 | res = test_client.post( | |
1033 | url, | |
1034 | data=dict(hosts=[host_data_], command=command_data) | |
1035 | ) | |
1036 | assert res.status_code == 201, res.json | |
1037 | assert count(Host, workspace) == 1 | |
1038 | assert count(Service, workspace) == 1 | |
1039 | assert count(Vulnerability, workspace) == 2 | |
1040 | assert count(Command, workspace) == 1 | |
1041 | host = Host.query.filter(Host.workspace == workspace).one() | |
1042 | assert host.ip == "127.0.0.1" | |
1043 | assert host.creator_id == logged_user.id | |
1044 | assert set({hn.name for hn in host.hostnames}) == {"test.com", "test2.org"} | |
1045 | assert len(host.services) == 1 | |
1046 | assert len(host.vulnerabilities) == 1 | |
1047 | assert len(host.services[0].vulnerabilities) == 1 | |
1048 | service = Service.query.filter(Service.workspace == workspace).one() | |
1049 | assert service.creator_id == logged_user.id | |
1050 | credential = Credential.query.filter(Credential.workspace == workspace).one() | |
1051 | assert credential.creator_id == logged_user.id | |
1052 | command = Command.query.filter(Credential.workspace == workspace).one() | |
1053 | assert command.creator_id == logged_user.id | |
1054 | assert res.json["command_id"] == command.id | |
1055 | for vuln in Vulnerability.query.filter(Vulnerability.workspace == workspace): | |
1056 | assert vuln.custom_fields['changes'] == ['1', '2', '3'] | |
1057 | 1023 | |
1058 | 1024 | @pytest.mark.usefixtures('logged_user') |
1059 | 1025 | def test_vuln_web_cannot_have_host_parent(self, session, workspace, test_client, logged_user): |
1068 | 1034 | data=dict(hosts=[host_data_], command=command_data) |
1069 | 1035 | ) |
1070 | 1036 | assert res.status_code == 400 |
1037 | ||
1038 | @pytest.mark.usefixtures('logged_user') | |
1039 | def test_bulk_create_endpoint_without_host_ip(self, session, workspace, test_client): | |
1040 | url = f'/v3/ws/{workspace.name}/bulk_create' | |
1041 | host_data_ = host_data.copy() | |
1042 | host_data_.pop('ip') | |
1043 | res = test_client.post(url, data=dict(hosts=[host_data_], command=command_data.copy())) | |
1044 | assert res.status_code == 400 |
379 | 379 | def test_update_command(self, test_client, session): |
380 | 380 | command = self.factory() |
381 | 381 | session.commit() |
382 | start_date = datetime.datetime.utcnow() | |
382 | 383 | raw_data = { |
383 | 384 | 'command': 'Import Nessus:', |
384 | 385 | 'tool': 'nessus', |
385 | 386 | 'duration': 120, |
386 | 387 | 'hostname': 'mandarina', |
387 | 388 | 'ip': '192.168.20.53', |
388 | 'itime': 1511387720.048548, | |
389 | 'itime': start_date.timestamp(), | |
389 | 390 | 'params': u'/home/lcubo/.faraday/report/airbnb/nessus_report_Remote.nessus', |
390 | 391 | 'user': 'lcubo' |
391 | 392 | } |
395 | 396 | assert res.status_code == 200 |
396 | 397 | updated_command = self.model.query.get(command.id) |
397 | 398 | print(updated_command.end_date) |
398 | assert updated_command.end_date == datetime.datetime.fromtimestamp( | |
399 | 1511387720.048548) + datetime.timedelta(seconds=120) | |
399 | assert updated_command.end_date == updated_command.start_date + datetime.timedelta(seconds=120) | |
400 | 400 | |
401 | 401 | def test_delete_objects_preserve_history(self, session, test_client): |
402 | 402 |
508 | 508 | assert res.status_code == 200 |
509 | 509 | assert res.json['count'] == 0 |
510 | 510 | |
511 | @pytest.mark.usefixtures('ignore_nplusone') | |
511 | 512 | def test_filter_restless_by_invalid_service_port(self, test_client, session, workspace, |
512 | 513 | service_factory, host_factory): |
513 | 514 | services = service_factory.create_batch(10, workspace=workspace, port=25) |
19 | 19 | class TestFileUpload: |
20 | 20 | |
21 | 21 | def test_file_upload(self, test_client, session, csrf_token, logged_user): |
22 | REPORTS_QUEUE.queue.clear() | |
22 | 23 | ws = WorkspaceFactory.create(name="abc") |
23 | 24 | session.add(ws) |
24 | 25 | session.commit() |
47 | 48 | ws_id = ws.id |
48 | 49 | logged_user_id = logged_user.id |
49 | 50 | |
50 | from faraday.server.threads.reports_processor import ReportsManager | |
51 | false_thread = ReportsManager(None) | |
52 | false_thread.process_report(queue_elem[0], queue_elem[1], | |
51 | from faraday.server.threads.reports_processor import process_report | |
52 | process_report(queue_elem[0], queue_elem[1], | |
53 | 53 | queue_elem[2], queue_elem[3], |
54 | 54 | queue_elem[4]) |
55 | 55 | command = Command.query.filter(Command.workspace_id == ws_id).one() |
56 | 56 | assert command |
57 | 57 | assert command.creator_id == logged_user_id |
58 | 58 | assert command.id == res.json["command_id"] |
59 | assert command.end_date | |
60 | 59 | host = Host.query.filter(Host.workspace_id == ws_id).first() |
61 | 60 | assert host |
62 | 61 | assert host.creator_id == logged_user_id |
149 | 149 | return data |
150 | 150 | |
151 | 151 | |
152 | ORDER = [ | |
153 | [{'field': 'status', 'direction': 'desc'}], | |
154 | [{'field': 'severity', 'direction': 'desc'}], | |
155 | [{'field': 'target', 'direction': 'desc'}], | |
156 | [{'field': 'name', 'direction': 'desc'}], | |
157 | [ | |
158 | {'field': 'status', 'direction': 'desc'}, {'field': 'severity', 'direction': 'desc'}, | |
159 | {'field': 'target', 'direction': 'desc'}, {'field': 'name', 'direction': 'desc'} | |
160 | ], | |
161 | ] | |
162 | ||
163 | ||
164 | GROUP = [ | |
165 | [{'field': 'status'}], | |
166 | [{'field': 'severity'}], | |
167 | [{'field': 'target'}], | |
168 | [{'field': 'name'}], | |
169 | [ | |
170 | {'field': 'status'}, {'field': 'severity'}, | |
171 | {'field': 'target'}, {'field': 'name'} | |
172 | ], | |
173 | ] | |
174 | ||
175 | ||
152 | 176 | @pytest.mark.usefixtures('logged_user') |
153 | 177 | class TestListVulnerabilityView(ReadWriteAPITests): |
154 | 178 | model = Vulnerability |
843 | 867 | assert set(vuln['_id'] for vuln in res.json['data']) == expected_ids |
844 | 868 | |
845 | 869 | @pytest.mark.usefixtures('ignore_nplusone') |
846 | def test_filter_restless_by_target__(self, test_client, session, workspace, host_factory, vulnerability_factory): | |
870 | @pytest.mark.parametrize('filter_params', [ | |
871 | { | |
872 | 'test_name': 'filter_by_target', | |
873 | 'filter_field_name': 'target', | |
874 | 'filter_operations': [ | |
875 | { | |
876 | 'filter_operation': 'eq', | |
877 | 'filter_value': '"192.168.0.1"', | |
878 | 'res_status_code': 200, | |
879 | 'count': 10 | |
880 | } | |
881 | ], | |
882 | 'order_operations': ORDER, | |
883 | 'group_operations': GROUP, | |
884 | }, | |
885 | { | |
886 | 'test_name': 'filter_by_target_host_ip', # Habria que sacar esto ya que esta por target :| | |
887 | 'filter_field_name': 'target', | |
888 | 'filter_operations': [ | |
889 | { | |
890 | 'filter_operation': 'eq', | |
891 | 'filter_value': '"192.168.0.1"', | |
892 | 'res_status_code': 200, | |
893 | 'count': 10 | |
894 | } | |
895 | ], | |
896 | 'order_operations': ORDER, | |
897 | 'group_operations': GROUP, | |
898 | }, | |
899 | { | |
900 | 'test_name': 'test_filter_restless_by_service_port', | |
901 | 'filter_field_name': 'service', | |
902 | 'filter_operations': [ | |
903 | { | |
904 | 'filter_operation': 'has', | |
905 | 'filter_value': '{"name": "port", "op": "eq", "val": "8956"}', | |
906 | 'res_status_code': 200, | |
907 | 'count': 8 | |
908 | } | |
909 | ], | |
910 | 'order_operations': ORDER, | |
911 | 'group_operations': GROUP, | |
912 | }, | |
913 | { | |
914 | 'test_name': 'test_filter_restless_by_service_name', | |
915 | 'filter_field_name': 'service', | |
916 | 'filter_operations': [ | |
917 | { | |
918 | 'filter_operation': 'has', | |
919 | 'filter_value': '{"name": "name", "op": "eq", "val": "ssh"}', | |
920 | 'res_status_code': 200, | |
921 | 'count': 1 | |
922 | } | |
923 | ], | |
924 | 'order_operations': ORDER, | |
925 | 'group_operations': GROUP, | |
926 | }, | |
927 | { | |
928 | 'test_name': 'filter_by_name', | |
929 | 'filter_field_name': 'name', | |
930 | 'filter_operations': [ | |
931 | { | |
932 | 'filter_operation': 'eq', | |
933 | 'filter_value': '"test_vuln1"', | |
934 | 'res_status_code': 200, | |
935 | 'count': 1 | |
936 | }, | |
937 | { | |
938 | 'filter_operation': 'like', | |
939 | 'filter_value': '"test_vuln%"', | |
940 | 'res_status_code': 200, | |
941 | 'count': 2 | |
942 | }, | |
943 | { | |
944 | 'filter_operation': 'ilike', | |
945 | 'filter_value': '"%TEST_VULN%"', | |
946 | 'res_status_code': 200, | |
947 | 'count': 2 | |
948 | } | |
949 | ], | |
950 | 'order_operations': ORDER, | |
951 | 'group_operations': GROUP, | |
952 | }, | |
953 | { | |
954 | 'test_name': 'filter_by_severity', | |
955 | 'filter_field_name': 'severity', | |
956 | 'filter_operations': [ | |
957 | { | |
958 | 'filter_operation': 'eq', | |
959 | 'filter_value': '"high"', | |
960 | 'res_status_code': 200, | |
961 | 'count': 1 | |
962 | }, | |
963 | { | |
964 | 'filter_operation': 'eq', | |
965 | 'filter_value': '"medium"', | |
966 | 'res_status_code': 200, | |
967 | 'count': 8 | |
968 | }, | |
969 | { | |
970 | 'filter_operation': 'eq', | |
971 | 'filter_value': '"informational"', | |
972 | 'res_status_code': 200, | |
973 | 'count': 10 | |
974 | } | |
975 | ], | |
976 | 'order_operations': ORDER, | |
977 | 'group_operations': GROUP, | |
978 | }, | |
979 | { | |
980 | 'test_name': 'filter_by_status', | |
981 | 'filter_field_name': 'status', | |
982 | 'filter_operations': [ | |
983 | { | |
984 | 'filter_operation': 'eq', | |
985 | 'filter_value': '"open"', | |
986 | 'res_status_code': 200, | |
987 | 'count': 0 | |
988 | }, | |
989 | { | |
990 | 'filter_operation': 'eq', | |
991 | 'filter_value': '"closed"', | |
992 | 'res_status_code': 200, | |
993 | 'count': 11 | |
994 | }, | |
995 | { | |
996 | 'filter_operation': 'eq', | |
997 | 'filter_value': '"re-opened"', | |
998 | 'res_status_code': 200, | |
999 | 'count': 1 | |
1000 | }, | |
1001 | { | |
1002 | 'filter_operation': 'eq', | |
1003 | 'filter_value': '"risk-accepted"', | |
1004 | 'res_status_code': 200, | |
1005 | 'count': 8 | |
1006 | }, | |
1007 | ], | |
1008 | 'order_operations': ORDER, | |
1009 | 'group_operations': GROUP, | |
1010 | }, | |
1011 | ||
1012 | ]) | |
1013 | def test_filter_restless_react_confirmed(self, test_client, session, workspace, host_factory, vulnerability_web_factory, vulnerability_factory, service_factory, filter_params): | |
847 | 1014 | |
848 | 1015 | Vulnerability.query.delete() |
849 | 1016 | host = host_factory.create(workspace=workspace, ip="192.168.0.2") |
850 | 1017 | host_vulns = vulnerability_factory.create_batch( |
851 | 1, workspace=self.workspace, host=host, service=None) | |
1018 | 1, severity="high", name="test_vuln1", status="closed", workspace=self.workspace, host=host, service=None) | |
852 | 1019 | |
853 | 1020 | host2 = host_factory.create(workspace=workspace, ip="192.168.0.1") |
854 | 1021 | host_vulns2 = vulnerability_factory.create_batch( |
855 | 10, workspace=self.workspace, host=host2, service=None) | |
856 | ||
857 | session.commit() | |
858 | res = test_client.get(urljoin( | |
859 | self.url(), 'filter?q={"filters":[{"name": "target", "op":"eq", "val":"192.168.0.1"}]}' | |
860 | )) | |
861 | ||
862 | assert res.status_code == 200 | |
863 | assert len(res.json['vulnerabilities']) == 10 | |
864 | ||
865 | @pytest.mark.usefixtures('ignore_nplusone') | |
866 | def test_filter_restless_by_target_host_ip(self, test_client, session, workspace, | |
867 | host_factory, vulnerability_factory): | |
868 | ||
869 | Vulnerability.query.delete() | |
870 | host = host_factory.create(workspace=workspace, ip="192.168.0.2") | |
871 | host_vulns = vulnerability_factory.create_batch( | |
872 | 1, workspace=self.workspace, host=host, service=None) | |
873 | ||
874 | host2 = host_factory.create(workspace=workspace, ip="192.168.0.1") | |
875 | host_vulns2 = vulnerability_factory.create_batch( | |
876 | 10, workspace=self.workspace, host=host2, service=None) | |
877 | ||
878 | session.commit() | |
879 | res = test_client.get(urljoin( | |
880 | self.url(), | |
881 | 'filter?q={"filters":[{"name": "target_host_ip", "op":"eq", "val":"192.168.0.2"}]}' | |
882 | )) | |
883 | assert res.status_code == 200 | |
884 | assert len(res.json['vulnerabilities']) == 1 | |
885 | assert res.json['vulnerabilities'][0]['value']['target'] == '192.168.0.2' | |
886 | ||
887 | @pytest.mark.usefixtures('ignore_nplusone') | |
888 | def test_filter_restless_by_service_port(self, test_client, session, workspace, | |
889 | host_factory, vulnerability_factory, | |
890 | vulnerability_web_factory, service_factory): | |
1022 | 10, severity="informational", workspace=self.workspace, host=host2, status="closed", service=None) | |
891 | 1023 | |
892 | 1024 | service = service_factory.create(port=9098, name="ssh", workspace=self.workspace) |
893 | 1025 | vulns = vulnerability_factory.create_batch( |
894 | 1, workspace=self.workspace, service=service, host=None) | |
1026 | 1, name="test_vuln2", severity="low", workspace=self.workspace, status="re-opened", service=service, host=None) | |
895 | 1027 | |
896 | 1028 | service = service_factory.create(port=8956, name="443", workspace=self.workspace) |
897 | 1029 | |
898 | 1030 | vulns_web = vulnerability_web_factory.create_batch( |
899 | 10, workspace=self.workspace, host=None, service=service) | |
900 | ||
901 | session.commit() | |
902 | res = test_client.get(urljoin( | |
903 | self.url(), | |
904 | 'filter?q={"filters":[{"name": "service", "op":"has", "val":{"name": "port", "op":"eq", "val":"8956"}}]}' | |
905 | )) | |
906 | assert res.status_code == 200 | |
907 | assert len(res.json['vulnerabilities']) == 10 | |
908 | assert res.json['count'] == 10 | |
909 | ||
910 | @pytest.mark.usefixtures('ignore_nplusone') | |
911 | def test_filter_restless_by_service_name(self, test_client, session, workspace, | |
912 | host_factory, vulnerability_factory, | |
913 | vulnerability_web_factory, service_factory): | |
914 | ||
915 | service = service_factory.create(port=9098, name="ssh", workspace=self.workspace) | |
916 | vulns = vulnerability_factory.create_batch( | |
917 | 1, workspace=self.workspace, service=service, host=None) | |
918 | ||
919 | service = service_factory.create(port=8956, name="443", workspace=self.workspace) | |
920 | ||
921 | vulns_web = vulnerability_web_factory.create_batch( | |
922 | 10, workspace=self.workspace, host=None, service=service) | |
923 | ||
924 | session.commit() | |
925 | res = test_client.get(urljoin( | |
926 | self.url(), | |
927 | 'filter?q={"filters":[{"name": "service", "op":"has", "val":{"name": "name", "op":"eq", "val":"ssh"}}]}' | |
928 | )) | |
929 | assert res.status_code == 200 | |
930 | assert len(res.json['vulnerabilities']) == 1 | |
931 | assert res.json['count'] == 1 | |
1031 | 8, workspace=self.workspace, host=None, service=service, status="risk-accepted", severity='medium') | |
1032 | ||
1033 | session.commit() | |
1034 | for operation in filter_params['filter_operations']: | |
1035 | ||
1036 | # With no order by | |
1037 | qparams = f'filter?q={{"filters":[' \ | |
1038 | f'{{"name": "{filter_params["filter_field_name"]}", ' \ | |
1039 | f'"op":"{operation["filter_operation"]}",' \ | |
1040 | f'"val": {operation["filter_value"]} }}]}}' | |
1041 | ||
1042 | res = test_client.get(urljoin(self.url(), qparams)) | |
1043 | ||
1044 | assert res.status_code == operation['res_status_code'] | |
1045 | assert len(res.json['vulnerabilities']) == operation['count'] | |
1046 | ||
1047 | for order_ops in filter_params['order_operations']: | |
1048 | orderparams = '"order_by": [' | |
1049 | separator = "" | |
1050 | for order in order_ops: | |
1051 | orderparams = f'{orderparams}{separator}{{"field": "{order["field"]}", "direction": "{order["direction"]}"}}' | |
1052 | separator = ',' | |
1053 | orderparams = f'{orderparams}]' | |
1054 | ||
1055 | qparams = f'filter?q={{"filters":['\ | |
1056 | f'{{"name": "{filter_params["filter_field_name"]}", '\ | |
1057 | f'"op":"{operation["filter_operation"]}",'\ | |
1058 | f'"val": {operation["filter_value"]} }}], {orderparams} }}' | |
1059 | res = test_client.get(urljoin(self.url(), qparams)) | |
1060 | ||
1061 | assert res.status_code == operation['res_status_code'] | |
1062 | assert len(res.json['vulnerabilities']) == operation['count'] | |
1063 | ||
1064 | for group_ops in filter_params['group_operations']: | |
1065 | groupparams = '"group_by": [' | |
1066 | separator = "" | |
1067 | for group in group_ops: | |
1068 | groupparams = f'{groupparams}{separator}{{"field": "{group["field"]}"}}' | |
1069 | separator = ',' | |
1070 | groupparams = f'{groupparams}]' | |
1071 | ||
1072 | qparams = f'filter?q={{"filters":['\ | |
1073 | f'{{"name": "{filter_params["filter_field_name"]}", '\ | |
1074 | f'"op":"{operation["filter_operation"]}",'\ | |
1075 | f'"val": {operation["filter_value"]} }}], {groupparams} }}' | |
1076 | res = test_client.get(urljoin(self.url(), qparams)) | |
1077 | ||
1078 | assert res.status_code == 200 | |
932 | 1079 | |
933 | 1080 | @pytest.mark.usefixtures('mock_envelope_list') |
934 | 1081 | def test_sort_by_method(self, session, test_client, second_workspace, |
2256 | 2403 | {'count': 1, 'name': 'test2', 'description': 'test'}] |
2257 | 2404 | assert [vuln['value'] for vuln in res.json['vulnerabilities']] == expected |
2258 | 2405 | |
2406 | @pytest.mark.skip(reason="Not working. the relation field host__vulnerability_high_generic_count is a function which needs to join Host. From react confirmed that they don't use this field. The cost benefit does not worth it, imho") | |
2259 | 2407 | def test_vuln_restless_sort_by_(self, test_client, session): |
2260 | 2408 | workspace = WorkspaceFactory.create() |
2261 | 2409 | host = HostFactory.create(workspace=workspace) |
3565 | 3713 | session.commit() |
3566 | 3714 | query_filter = { |
3567 | 3715 | "order_by": |
3716 | [{"field": "severity", "direction": "desc"}], | |
3717 | "limit": 10, | |
3718 | "offset": 0 | |
3719 | } | |
3720 | ||
3721 | res = test_client.get( | |
3722 | f'/v3/ws/{workspace.name}/vulns/filter?q={json.dumps(query_filter)}' | |
3723 | ) | |
3724 | assert res.status_code == 200 | |
3725 | expected_order = ['critical', 'high', 'med', 'low'] | |
3726 | ||
3727 | assert expected_order == [vuln['value']['severity'] for vuln in res.json['vulnerabilities']] | |
3728 | ||
3729 | query_filter = { | |
3730 | "order_by": | |
3568 | 3731 | [{"field": "severity", "direction": "asc"}], |
3569 | 3732 | "limit": 10, |
3570 | 3733 | "offset": 0 |
3574 | 3737 | f'/v3/ws/{workspace.name}/vulns/filter?q={json.dumps(query_filter)}' |
3575 | 3738 | ) |
3576 | 3739 | assert res.status_code == 200 |
3577 | expected_order = ['critical', 'high', 'med', 'low'] | |
3740 | expected_order = ['low', 'med', 'high', 'critical'] | |
3578 | 3741 | |
3579 | 3742 | assert expected_order == [vuln['value']['severity'] for vuln in res.json['vulnerabilities']] |
3580 | 3743 |
92 | 92 | ) |
93 | 93 | assert res.status_code == 200 |
94 | 94 | assert len(res.json) == 1 |
95 | ||
96 | assert res.json[0]['stats']['std_vulns'] == 11 | |
97 | assert res.json[0]['stats']['web_vulns'] == 8 | |
98 | assert res.json[0]['stats']['code_vulns'] == 0 | |
99 | assert res.json[0]['active_agents_count'] == 0 | |
100 | ||
95 | 101 | assert res.json[0]['description'] == self.first_object.description |
96 | 102 | assert res.json[0]['stats']['total_vulns'] == 19 |
97 | 103 | assert res.json[0]['stats']['info_vulns'] == 8 |