diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..ef9877f --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +per-file-ignores = tests/*:S101 diff --git a/Makefile b/Makefile index ea45988..1e7af68 100644 --- a/Makefile +++ b/Makefile @@ -76,7 +76,7 @@ install: ## install updated project.toml with flint devenv: ## setup development environment python3 -m venv --prompt honeypot .venv .venv/bin/pip3 install --upgrade pip - .venv/bin/pip3 install flit + .venv/bin/pip3 install "flit>3.2" .venv/bin/flit install --pth-file repo: devenv ## complete project setup with development environment and git repo @@ -86,5 +86,5 @@ repo: devenv ## complete project setup with development environment and git repo git commit -m "import of project template" git remote add origin https://git.cpi.imtek.uni-freiburg.de/CPI/honeypot.git git push -u origin main --no-verify - + .venv/bin/pre-commit install --install-hooks diff --git a/honeypot/__init__.py b/honeypot/__init__.py index 62d6907..989e0bb 100644 --- a/honeypot/__init__.py +++ b/honeypot/__init__.py @@ -6,93 +6,88 @@ A honeypot for wiki scrapers __version__ = "0.0.1" import os -import pickle import re +import pickle # noqa: S403 -from collections import OrderedDict -from pyramid.httpexceptions import HTTPFound -from pyramid.config import Configurator -from pyramid.response import Response from pyramid.view import view_config +from pyramid.config import Configurator +from pyramid.httpexceptions import HTTPFound from pyramid_mailer.message import Message from . import utils class RootResource: - ''' A simple 'catch all' resource ''' + """A simple 'catch all' resource""" moin_config_dir = None moin_wiki_defs = [] admin_emails = [] def __init__(self, request): - ''' initialization ''' + """initialization""" self.request = request def __getitem__(self, key): - ''' no child resource lookup, only one view used''' + """no child resource lookup, only one view used""" return self @classmethod def configure(cls, settings): - ''' parses the moinmoin farmconfig file ''' - cls.moin_config_dir = settings['moin.config_path'] - moin_farmconfig = os.path.join(cls.moin_config_dir, 'farmconfig.py') + """parses the moinmoin farmconfig file""" + cls.moin_config_dir = settings["moin.config_path"] + moin_farmconfig = os.path.join(cls.moin_config_dir, "farmconfig.py") encoding = utils.guess_encoding(moin_farmconfig) - with open(moin_farmconfig, 'r', encoding=encoding) as fh: + with open(moin_farmconfig, "r", encoding=encoding) as fh: cls.moin_wiki_defs = list(utils.extract_wiki_definitions(fh)) - cls.admin_emails = settings['mail.admin_email'].split() - + cls.admin_emails = settings["mail.admin_email"].split() def get_moin_user(self): - ''' returns a name and email address of the current wiki user''' - name, email = '', '' + """returns a name and email address of the current wiki user""" + name, email = "", "" try: moin_data_dir = self._get_wiki_data_dir() moin_session_dir = os.path.join( - moin_data_dir, - 'cache', - '__session__' - ) + moin_data_dir, "cache", "__session__" + ) moin_user_id = self._get_user_id(moin_session_dir) - moin_user_file = os.path.join(moin_data_dir, 'user', moin_user_id) - with open(moin_user_file, 'r') as fh: - for line in fh: - if line.startswith('email='): - email = line.split('=', 1)[1].strip() - if line.startswith('name='): - name = line.split('=', 1)[1].strip() - except: + moin_user_file = os.path.join(moin_data_dir, "user", moin_user_id) + with open(moin_user_file, "r") as fh: + for line in fh: + if line.startswith("email="): + email = line.split("=", 1)[1].strip() + if line.startswith("name="): + name = line.split("=", 1)[1].strip() + except: # noqa: S110, E722 pass return name, email def _get_wiki_data_dir(self): - ''' get the data directory by parsing a wiki config ''' + """get the data directory by parsing a wiki config""" wiki_name = self._get_wiki_name() - wiki_config = os.path.join(self.moin_config_dir, wiki_name + '.py') + wiki_config = os.path.join(self.moin_config_dir, wiki_name + ".py") encoding = utils.guess_encoding(wiki_config) - with open(wiki_config, 'r', encoding=encoding) as fh: + with open(wiki_config, "r", encoding=encoding) as fh: data_dir = utils.extract_data_dir(fh) return data_dir def _get_wiki_name(self): - ''' return the internal wiki name for a url ''' + """return the internal wiki name for a url""" for name, re_url in self.moin_wiki_defs: if re.match(re_url, self.request.url): return name def _get_user_id(self, session_dir): - ''' extract the user id from the session store ''' + """extract the user id from the session store""" session_path = self._get_session_path(session_dir) - with open(session_path, 'rb') as fh: - session_data = pickle.load(fh) - return session_data.get('user.id') + with open(session_path, "rb") as fh: + session_data = pickle.load(fh) # noqa: S301 + return session_data.get("user.id") def _get_session_path(self, session_dir): - ''' get the path to the session store for a given cookie ''' + """get the path to the session store for a given cookie""" for key, value in self.request.cookies.items(): - if key.lower().startswith('moin'): + if key.lower().startswith("moin"): session_path = os.path.join(session_dir, value) if os.path.isfile(session_path): return session_path @@ -101,45 +96,44 @@ class RootResource: @view_config(context=RootResource) def the_view(context, request): - ''' the one and only view for the app ''' + """the one and only view for the app""" name, email = context.get_moin_user() body = [ - 'The Honey Pot Was Accessed', - '--------------------------', - '', - 'This might be an attempt to scrape the whole wiki', - '', - 'wiki user: %s (%s)' % (name, email), - '', - 'requested url: %s' % request.url, - 'request method: %s' % request.method, - 'client ip address: %s' % request.client_addr, - 'remote ip address: %s' % request.remote_addr, - '', - 'headers:' - ] - - headers = [' %s: %s' % (k, v) for k, v in request.headers.items()] + "The Honey Pot Was Accessed", + "--------------------------", + "", + "This might be an attempt to scrape the whole wiki", + "", + "wiki user: %s (%s)" % (name, email), + "", + "requested url: %s" % request.url, + "request method: %s" % request.method, + "client ip address: %s" % request.client_addr, + "remote ip address: %s" % request.remote_addr, + "", + "headers:", + ] + + headers = [" %s: %s" % (k, v) for k, v in request.headers.items()] body.extend(headers) - body = '\n'.join(body) + body = "\n".join(body) message = Message( - subject='[cpi wikis]: HoneyPot Link Was Accessed', - sender=request.registry.settings['mail.default_sender'], + subject="[cpi wikis]: HoneyPot Link Was Accessed", + sender=request.registry.settings["mail.default_sender"], recipients=context.admin_emails, - body=body - ) + body=body, + ) request.mailer.send_immediately(message) - return HTTPFound('https://www.cpi.uni-freiburg.de/') + return HTTPFound("https://www.cpi.uni-freiburg.de/") def main(global_config, **settings): - """ This function returns a Pyramid WSGI application. - """ + """This function returns a Pyramid WSGI application.""" RootResource.configure(settings) config = Configurator(settings=settings) diff --git a/honeypot/utils.py b/honeypot/utils.py index 54289bd..fa03c9d 100644 --- a/honeypot/utils.py +++ b/honeypot/utils.py @@ -2,23 +2,24 @@ from chardet.universaldetector import UniversalDetector def guess_encoding(path): - ''' guess the encoding of a file at a given path ''' + """guess the encoding of a file at a given path""" detector = UniversalDetector() - with open(path, 'rb') as fh: + with open(path, "rb") as fh: for line in fh: detector.feed(line) - if detector.done: break + if detector.done: + break detector.close() - return detector.result['encoding'] + return detector.result["encoding"] def extract_wiki_definitions(file_handle): - ''' extract the wiki definitions from a moinmoin farmconfig file ''' + """extract the wiki definitions from a moinmoin farmconfig file""" for line in file_handle: - if line.startswith('wikis = ['): + if line.startswith("wikis = ["): break for line in file_handle: - if line.startswith(']'): + if line.startswith("]"): break parts = split_wiki_definitions(line) if parts is not None: @@ -26,7 +27,7 @@ def extract_wiki_definitions(file_handle): def split_wiki_definitions(line): - ''' small helper, returns the wiki name and wiki url regex ''' + """small helper, returns the wiki name and wiki url regex""" for quote in ('"', "'"): parts = line.split(quote) if len(parts) == 5: @@ -35,12 +36,11 @@ def split_wiki_definitions(line): def extract_data_dir(fh): - ''' returns the data directory from a single moinmoin wiki config ''' + """returns the data directory from a single moinmoin wiki config""" for line in fh: - parts = line.split('=', 1) + parts = line.split("=", 1) if len(parts) == 2: name, value = parts - if name.strip() == 'data_dir': + if name.strip() == "data_dir": value = value.strip() return value[1:-1] - diff --git a/pyproject.toml b/pyproject.toml index 4149d03..3fcedf6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,14 +4,17 @@ requires = ["flit"] build-backend = "flit.buildapi" -[tool.flit.metadata] -module = "honeypot" -dist-name = "honeypot" -author = "Holger Frey" -author-email = "frey@imtek.de" -home-page = "https://git.cpi.imtek.uni-freiburg.de/CPI/honeypot.git" -description-file = "README.md" -license = "Beerware" +[project] +name = "honeypot" +readme = "README.md" +description = "A honeypot for wiki scrapers" +license = { file = "LICENSE" } +requires-python = ">=3.7" +dynamic = ["version"] + +authors = [ + {name = "Holger Frey", email = "frey@imtek.de"}, +] # see https://pypi.org/classifiers/ classifiers = [ @@ -26,16 +29,18 @@ classifiers = [ "License :: Freely Distributable", ] -requires = [ +dependencies = [ "chardet", "plaster_pastedeploy", "pyramid", "pyramid_mailer", "waitress", ] -requires-python = ">=3.7" -[tool.flit.metadata.requires-extra] +[project.urls] +Source = "https://git.cpi.imtek.uni-freiburg.de/CPI/honeypot.git" + +[project.optional-dependencies] test = [ "pytest >=4.0.0", "pytest-cov", @@ -53,19 +58,8 @@ dev = [ "pre-commit", ] -[tool.black] -line-length = 79 -py37 = true -include = "\.pyi?$" -exclude = """ -/( - \.git - | \.tox - | \.venv - | build - | dist -)/ -""" +[project.entry-points."paste.app_factory"] +main = "honeypot:main" [tool.isort] line_length=79 @@ -73,13 +67,26 @@ multi_line_output=3 length_sort="True" include_trailing_comma="True" + [tool.pytest.ini_options] markers = [ - 'fun: marks tests as functional (deselect with "-m \"not fun\"")', + "fun: marks tests as functional (deselect with '-m \"not fun\"')", ] addopts = [ "--strict-markers", ] -[project.entry-points."paste.app_factory"] -main = "honeypot:main" +[tool.black] +line-length = 79 +target-version = ['py37','py38', 'py39'] +include = '\.pyi?$' +extend-exclude = ''' +# A regex preceded with ^/ will apply only to files and directories +# in the root of the project. +^/.git +^/.tox +^/.venv +^/.build +^/.dist +''' + diff --git a/tests/test_honeypot.py b/tests/test_honeypot.py index ab75af1..93564df 100644 --- a/tests/test_honeypot.py +++ b/tests/test_honeypot.py @@ -25,17 +25,21 @@ import pytest def test_example_unittest(): - """ example unittest + """example unittest will be run by 'make test' and 'make testall' but not 'make coverage' """ + import honeypot # noqa: F401 + assert True @pytest.mark.fun def test_example_functional_test(): - """ example unittest + """example unittest will be by 'make coverage' and 'make testall' but not 'make test' """ + import honeypot # noqa: F401 + assert True