Skip to content

Commit

Permalink
2.4.0: Various changes
Browse files Browse the repository at this point in the history
  • Loading branch information
psrok1 committed Apr 16, 2019
1 parent 32a4c21 commit 1e180f6
Show file tree
Hide file tree
Showing 24 changed files with 983 additions and 115 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
.idea
*.pyc

build/
dist/
*egg-info/
.vscode/
venv/
docs/build/
19 changes: 19 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
image: python:3.6
test_style:
script:
- pip3 install flake8
- flake8 . --exclude=venv --max-line-length 120 # imo 120 is way too much

image: python:3.6
test_code:
script:
- pip3 install -r requirements.txt
- pip3 install pytest
- pytest .

image: python:3.6
test_e2e:
script:
- pip3 install -r requirements.txt
- pip3 install pytest
- pytest tests/e2etest_mwdblib.py
19 changes: 19 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = source
BUILDDIR = build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
35 changes: 35 additions & 0 deletions docs/make.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@ECHO OFF

pushd %~dp0

REM Command file for Sphinx documentation

if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build

if "%1" == "" goto help

%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)

%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end

:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%

:end
popd
57 changes: 57 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# http://www.sphinx-doc.org/en/master/config

# -- Path setup --------------------------------------------------------------

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
sys.path.insert(0, os.path.abspath('..'))


# -- Project information -----------------------------------------------------

project = 'mwdblib'
copyright = '2019, CERT Polska'
author = 'CERT Polska'

# The full version, including alpha/beta/rc tags
release = '2.4.0'


# -- General configuration ---------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.viewcode"
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []


# -- Options for HTML output -------------------------------------------------

# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinxdoc'

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
33 changes: 33 additions & 0 deletions docs/source/getexample.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Getting data automatically from Malwarecage
===========================================

Looking for recently uploaded files and retrieving them if file type contains "PE32":

.. code-block:: python
import itertools
import time
from mwdblib import Malwarecage
mwdb = Malwarecage(api_key="<secret>")
def report_new_sample(sample):
print("Found new sample {} ({})".format(sample.name, sample.sha256))
if "PE32" in sample.type:
with open(sample.id, "wb") as f:
f.write(sample.download())
print("[+] PE32 downloaded successfully!")
last_sample = None
while True:
top_sample = next(mwdb.recent_samples()).id
if last_sample is not None:
for sample in itertools.takewhile(lambda s: s.id != last_sample.id,
mwdb.recent_samples()):
report_new_sample(sample)
last_sample = top_sample
# Wait 10 minutes before next try
time.sleep(600)
30 changes: 30 additions & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.. mwdblib documentation master file, created by
sphinx-quickstart on Mon Apr 15 15:48:25 2019.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to mwdblib's documentation!
===================================

.. toctree::
:maxdepth: 2
:caption: API:

mwdblib
mwdbtypes
mwdbsecondary


.. toctree::
:maxdepth: 2
:caption: Usage examples:

getexample


Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
7 changes: 7 additions & 0 deletions docs/source/mwdblib.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Malwarecage core interface
==================================

.. automodule:: mwdblib

.. autoclass:: Malwarecage
:members:
11 changes: 11 additions & 0 deletions docs/source/mwdbsecondary.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Secondary objects
==================================

.. automodule:: mwdblib

.. autoclass:: mwdblib.comment.MalwarecageComment
:members:
.. autoclass:: mwdblib.share.MalwarecageShare
:members:
.. autoclass:: mwdblib.share.MalwarecageShareReason
:members:
13 changes: 13 additions & 0 deletions docs/source/mwdbtypes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Object types
==================================

.. automodule:: mwdblib

.. autoclass:: MalwarecageObject
:members:
.. autoclass:: MalwarecageFile
:members:
.. autoclass:: MalwarecageConfig
:members:
.. autoclass:: MalwarecageBlob
:members:
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from distutils.core import setup

setup(name="mwdblib",
version="2.3.0",
version="2.4.0",
description="malwaredb API bindings for Python",
author="psrok1",
package_dir={'mwdblib': 'src'},
Expand Down
13 changes: 13 additions & 0 deletions src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
from .core import Malwarecage
from .api import MalwarecageAPI
from .file import MalwarecageFile
from .object import MalwarecageObject
from .config import MalwarecageConfig
from .blob import MalwarecageBlob

__all__ = [
'Malwarecage',
'MalwarecageAPI',
'MalwarecageFile',
'MalwarecageObject',
'MalwarecageConfig',
'MalwarecageBlob',
]
51 changes: 46 additions & 5 deletions src/api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import base64
import json
import warnings

try:
from urlparse import urljoin
Expand All @@ -15,16 +16,31 @@


class MalwarecageAPI(object):
def __init__(self, api_url=API_URL, api_key=None):
def __init__(self, api_url=API_URL, api_key=None, verify_ssl=False):
self.api_url = api_url
self.api_key = None
self.session = requests.Session()
self.set_api_key(api_key)
self.username = None
self.password = None
self.verify_ssl = verify_ssl

def set_api_key(self, api_key):
self.api_key = api_key
self.session.headers.update({'Authorization': 'Bearer {}'.format(self.api_key)})

def login(self, username, password):
warnings.warn("Password-authenticated sessions are short lived, so password needs to be stored "
"in MalwarecageAPI object. Ask Malwarecage instance administrator for an API key "
"(send e-mail to [email protected] if you use mwdb.cert.pl)")
result = self.post("auth/login", json={
"login": username,
"password": password
}, noauth=True)
self.username = username
self.password = password
self.set_api_key(result["token"])

def logged_user(self):
if self.api_key is None:
return None
Expand All @@ -41,10 +57,35 @@ def request(self, method, url, noauth=False, raw=False, *args, **kwargs):
# Set method name and request URL
url = urljoin(self.api_url, url)
# Set default kwargs
kwargs["verify"] = False
kwargs["json"] = kwargs.get("json", True)
response = self.session.request(method, url, *args, **kwargs)
response.raise_for_status()
kwargs["verify"] = self.verify_ssl

# If there are both 'form data' and 'json' passed - we need to pack them into multipart/form-data
if "data" in kwargs and "json" in kwargs:
files = kwargs.get("files", {})
files["json"] = (None, json.dumps(kwargs["json"]), "application/json")
del kwargs["json"]

def try_request():
response = self.session.request(method, url, *args, **kwargs)
response.raise_for_status()
return response

try:
response = try_request()
except requests.HTTPError as e:
# If not unauthorized: re-raise
if e.response.status_code != requests.codes.unauthorized:
raise
# Forget api_key
self.api_key = None
# If authenticated using api_key: re-raise
if self.username is None:
raise
# Try to log in
self.login(self.username, self.password)
# Repeat failed request
response = try_request()

return response.json() if not raw else response.content

def get(self, *args, **kwargs):
Expand Down
Loading

0 comments on commit 1e180f6

Please sign in to comment.