pylookyloo 1.24.0__tar.gz → 1.25.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pylookyloo might be problematic. Click here for more details.
- {pylookyloo-1.24.0 → pylookyloo-1.25.0}/PKG-INFO +2 -2
- {pylookyloo-1.24.0 → pylookyloo-1.25.0}/pylookyloo/api.py +123 -3
- {pylookyloo-1.24.0 → pylookyloo-1.25.0}/pyproject.toml +5 -5
- {pylookyloo-1.24.0 → pylookyloo-1.25.0}/LICENSE +0 -0
- {pylookyloo-1.24.0 → pylookyloo-1.25.0}/README.md +0 -0
- {pylookyloo-1.24.0 → pylookyloo-1.25.0}/pylookyloo/__init__.py +0 -0
- {pylookyloo-1.24.0 → pylookyloo-1.25.0}/pylookyloo/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pylookyloo
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.25.0
|
|
4
4
|
Summary: Python CLI and module for Lookyloo
|
|
5
5
|
Home-page: https://github.com/lookyloo/PyLookyloo
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -25,7 +25,7 @@ Classifier: Topic :: Security
|
|
|
25
25
|
Provides-Extra: docs
|
|
26
26
|
Requires-Dist: Sphinx (<7.2) ; (python_version < "3.9") and (extra == "docs")
|
|
27
27
|
Requires-Dist: Sphinx (>=7.2,<8.0) ; (python_version >= "3.9") and (extra == "docs")
|
|
28
|
-
Requires-Dist: requests (>=2.
|
|
28
|
+
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
|
29
29
|
Project-URL: Documentation, https://pylookyloo.readthedocs.io/en/latest/
|
|
30
30
|
Project-URL: Repository, https://github.com/lookyloo/PyLookyloo
|
|
31
31
|
Description-Content-Type: text/markdown
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import os
|
|
5
6
|
import base64
|
|
6
7
|
import warnings
|
|
7
8
|
|
|
9
|
+
from datetime import datetime
|
|
8
10
|
from importlib.metadata import version
|
|
9
11
|
from io import BytesIO, StringIO
|
|
10
|
-
from typing import Any, TypedDict, overload
|
|
12
|
+
from typing import Any, TypedDict, overload, Literal
|
|
11
13
|
from urllib.parse import urljoin, urlparse
|
|
12
14
|
from pathlib import PurePosixPath, Path
|
|
13
15
|
|
|
@@ -283,6 +285,14 @@ class Lookyloo():
|
|
|
283
285
|
else:
|
|
284
286
|
raise AuthError('Unable to initialize API key')
|
|
285
287
|
|
|
288
|
+
def get_user_config(self) -> dict[str, Any] | None:
|
|
289
|
+
'''Get the configuration enforced by the server for the current user (requires an authenticated user, use init_apikey first)
|
|
290
|
+
'''
|
|
291
|
+
if not self.apikey:
|
|
292
|
+
raise AuthError('You need to initialize the apikey to use this method (see init_apikey)')
|
|
293
|
+
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', 'get_user_config'))))
|
|
294
|
+
return r.json()
|
|
295
|
+
|
|
286
296
|
def misp_export(self, tree_uuid: str) -> dict[str, Any]:
|
|
287
297
|
'''Export the capture in MISP format'''
|
|
288
298
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', tree_uuid, 'misp_export'))))
|
|
@@ -433,13 +443,22 @@ class Lookyloo():
|
|
|
433
443
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', 'stats'))))
|
|
434
444
|
return r.json()
|
|
435
445
|
|
|
436
|
-
|
|
446
|
+
@overload
|
|
447
|
+
def get_takedown_information(self, capture_uuid: str, filter_contacts: Literal[True]) -> list[str]:
|
|
448
|
+
...
|
|
449
|
+
|
|
450
|
+
@overload
|
|
451
|
+
def get_takedown_information(self, capture_uuid: str, filter_contacts: Literal[False]=False) -> list[dict[str, Any]]:
|
|
452
|
+
...
|
|
453
|
+
|
|
454
|
+
def get_takedown_information(self, capture_uuid: str, filter_contacts: bool=False) -> list[dict[str, Any]] | list[str]:
|
|
437
455
|
'''Returns information required to request a takedown for a capture
|
|
438
456
|
|
|
439
457
|
:param capture_uuid: UUID of the capture
|
|
458
|
+
:param filter_contacts: If True, will only return the contact emails and filter out the invalid ones.
|
|
440
459
|
'''
|
|
441
460
|
r = self.session.post(urljoin(self.root_url, str(PurePosixPath('json', 'takedown'))),
|
|
442
|
-
json={'capture_uuid': capture_uuid})
|
|
461
|
+
json={'capture_uuid': capture_uuid, 'filter': filter_contacts})
|
|
443
462
|
return r.json()
|
|
444
463
|
|
|
445
464
|
def compare_captures(self, capture_left: str, capture_right: str, /, *, compare_settings: CompareSettings | None=None) -> dict[str, Any]:
|
|
@@ -464,8 +483,109 @@ class Lookyloo():
|
|
|
464
483
|
return r.json()
|
|
465
484
|
|
|
466
485
|
def send_mail(self, tree_uuid: str, email: str = '', comment: str | None = None) -> bool | dict[str, Any]:
|
|
486
|
+
'''Reports a capture by sending an email to the investigation team
|
|
487
|
+
|
|
488
|
+
:param tree_uuid: UUID of the capture
|
|
489
|
+
:param email: Email of the reporter, used by the analyst to get in touch
|
|
490
|
+
:param comment: Description of the URL, will be given to the analyst
|
|
491
|
+
'''
|
|
467
492
|
to_send = {'email': email}
|
|
468
493
|
if comment:
|
|
469
494
|
to_send['comment'] = comment
|
|
470
495
|
r = self.session.post(urljoin(self.root_url, str(PurePosixPath('json', tree_uuid, 'report'))), json=to_send)
|
|
471
496
|
return r.json()
|
|
497
|
+
|
|
498
|
+
def get_recent_captures(self, timestamp: str | datetime | float | None=None) -> list[str]:
|
|
499
|
+
'''Gets the uuids of the most recent captures
|
|
500
|
+
|
|
501
|
+
:param timestamp: Timestamp of the capture
|
|
502
|
+
'''
|
|
503
|
+
if not timestamp:
|
|
504
|
+
url = urljoin(self.root_url, str(PurePosixPath('json', 'recent_captures')))
|
|
505
|
+
else:
|
|
506
|
+
if isinstance(timestamp, datetime):
|
|
507
|
+
timestamp = timestamp.timestamp()
|
|
508
|
+
url = urljoin(self.root_url, str(PurePosixPath('json', 'recent_captures', str(timestamp))))
|
|
509
|
+
r = self.session.get(url)
|
|
510
|
+
return r.json()
|
|
511
|
+
|
|
512
|
+
@overload
|
|
513
|
+
def upload_capture(self, *, quiet: Literal[True],
|
|
514
|
+
listing: bool = False,
|
|
515
|
+
full_capture: Path | BytesIO | str | None = None,
|
|
516
|
+
har: Path | BytesIO | str | None = None,
|
|
517
|
+
html: Path | BytesIO | str | None = None,
|
|
518
|
+
last_redirected_url: str | None = None,
|
|
519
|
+
screenshot: Path | BytesIO | str | None = None) -> str:
|
|
520
|
+
...
|
|
521
|
+
|
|
522
|
+
@overload
|
|
523
|
+
def upload_capture(self, *, quiet: Literal[False]=False,
|
|
524
|
+
listing: bool = False,
|
|
525
|
+
full_capture: Path | BytesIO | str | None = None,
|
|
526
|
+
har: Path | BytesIO | str | None = None,
|
|
527
|
+
html: Path | BytesIO | str | None = None,
|
|
528
|
+
last_redirected_url: str | None = None,
|
|
529
|
+
screenshot: Path | BytesIO | str | None = None) -> tuple[str, dict[str, str]]:
|
|
530
|
+
...
|
|
531
|
+
|
|
532
|
+
def upload_capture(self, *, quiet: bool = False,
|
|
533
|
+
listing: bool = False,
|
|
534
|
+
full_capture: Path | BytesIO | str | None = None,
|
|
535
|
+
har: Path | BytesIO | str | None = None,
|
|
536
|
+
html: Path | BytesIO | str | None = None,
|
|
537
|
+
last_redirected_url: str | None = None,
|
|
538
|
+
screenshot: Path | BytesIO | str | None = None) -> str | tuple[str, dict[str, str]]:
|
|
539
|
+
'''Upload a capture via har-file and others
|
|
540
|
+
|
|
541
|
+
:param quiet: Returns the UUID only, instead of the the UUID and the potential error / warning messages
|
|
542
|
+
:param listing: if true the capture should be public, else private - overwritten if the full_capture is given and it contains no_index
|
|
543
|
+
:param full_capture: path to the capture made by another instance
|
|
544
|
+
:param har: Harfile of the capture
|
|
545
|
+
:param html: rendered HTML of the capture
|
|
546
|
+
:param last_redirected_url: The landing page of the capture
|
|
547
|
+
:param screenshot: Screenshot of the capture
|
|
548
|
+
'''
|
|
549
|
+
def encode_document(document: Path | BytesIO | str) -> str:
|
|
550
|
+
if isinstance(document, str):
|
|
551
|
+
if not os.path.exists(document):
|
|
552
|
+
raise FileNotFoundError(f'{document} does not exist')
|
|
553
|
+
document = Path(document)
|
|
554
|
+
if isinstance(document, Path):
|
|
555
|
+
with document.open('rb') as f:
|
|
556
|
+
document = BytesIO(f.read())
|
|
557
|
+
return base64.b64encode(document.getvalue()).decode()
|
|
558
|
+
|
|
559
|
+
to_send: dict[str, Any] = {'listing': listing}
|
|
560
|
+
|
|
561
|
+
if full_capture:
|
|
562
|
+
b64_full_capture = encode_document(full_capture)
|
|
563
|
+
to_send['full_capture'] = b64_full_capture
|
|
564
|
+
elif har:
|
|
565
|
+
b64_har = encode_document(har)
|
|
566
|
+
to_send['har_file'] = b64_har
|
|
567
|
+
|
|
568
|
+
if html:
|
|
569
|
+
b64_html = encode_document(html)
|
|
570
|
+
to_send['html_file'] = b64_html
|
|
571
|
+
|
|
572
|
+
if last_redirected_url:
|
|
573
|
+
to_send['landing_page'] = last_redirected_url
|
|
574
|
+
|
|
575
|
+
if screenshot:
|
|
576
|
+
b64_screenshot = encode_document(screenshot)
|
|
577
|
+
to_send['screenshot_file'] = b64_screenshot
|
|
578
|
+
else:
|
|
579
|
+
raise PyLookylooError("Full capture or at least har-file are required")
|
|
580
|
+
|
|
581
|
+
r = self.session.post(urljoin(self.root_url, str(PurePosixPath('json', 'upload'))), json=to_send)
|
|
582
|
+
r.raise_for_status()
|
|
583
|
+
json_response = r.json()
|
|
584
|
+
uuid = json_response['uuid']
|
|
585
|
+
messages = json_response['messages']
|
|
586
|
+
|
|
587
|
+
if not uuid:
|
|
588
|
+
raise PyLookylooError('Unable to get UUID from lookyloo instance.')
|
|
589
|
+
if quiet:
|
|
590
|
+
return uuid
|
|
591
|
+
return uuid, messages
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "pylookyloo"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.25.0"
|
|
4
4
|
description = "Python CLI and module for Lookyloo"
|
|
5
5
|
authors = ["Raphaël Vinot <raphael.vinot@circl.lu>"]
|
|
6
6
|
license = "BSD-3-Clause"
|
|
@@ -31,21 +31,21 @@ lookyloo = 'pylookyloo:main'
|
|
|
31
31
|
|
|
32
32
|
[tool.poetry.dependencies]
|
|
33
33
|
python = "^3.8"
|
|
34
|
-
requests = "^2.
|
|
34
|
+
requests = "^2.32.3"
|
|
35
35
|
Sphinx = [
|
|
36
36
|
{version = "<7.2", python = "<3.9", optional = true},
|
|
37
37
|
{version = "^7.2", python = ">=3.9", optional = true}
|
|
38
38
|
]
|
|
39
39
|
|
|
40
40
|
[tool.poetry.group.dev.dependencies]
|
|
41
|
-
mypy = "^1.
|
|
42
|
-
types-requests = "^2.
|
|
41
|
+
mypy = "^1.10.1"
|
|
42
|
+
types-requests = "^2.32.0.20240622"
|
|
43
43
|
ipython = [
|
|
44
44
|
{version = "<8.13.0", python = "<3.9"},
|
|
45
45
|
{version = "^8.18.0", python = ">=3.9"},
|
|
46
46
|
{version = "^8.19.0", python = ">=3.10"}
|
|
47
47
|
]
|
|
48
|
-
pytest = "^8.
|
|
48
|
+
pytest = "^8.2.2"
|
|
49
49
|
|
|
50
50
|
[tool.poetry.extras]
|
|
51
51
|
docs = ["Sphinx"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|