From 03f02d52d1e04ce30cabdffa4563eee79fe0c83a Mon Sep 17 00:00:00 2001 From: Christian Schwartz Date: Sat, 3 Nov 2018 10:12:40 +0100 Subject: [PATCH 01/22] Correctly parses Templates without Attachments. Fixes #13 (#14) --- gophish/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gophish/models.py b/gophish/models.py index 87b2297..6e2649f 100644 --- a/gophish/models.py +++ b/gophish/models.py @@ -340,7 +340,7 @@ def parse(cls, json): for key, val in json.items(): if key == 'modified_date': setattr(template, key, parse_date(val)) - elif key == 'attachments': + elif key == 'attachments' and val: attachments = [ Attachment.parse(attachment) for attachment in val ] From 38046ca68119885f42b48f6276a82859cd3949bc Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Sun, 4 Nov 2018 19:56:56 -0600 Subject: [PATCH 02/22] Bumped version to 0.1.6 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 7a7cac6..db9aab8 100644 --- a/setup.py +++ b/setup.py @@ -3,13 +3,13 @@ setup( name='gophish', packages=['gophish', 'gophish.api'], - version='0.1.5', + version='0.1.6', description='Python API Client for Gophish', author='Jordan Wright', author_email='python@getgophish.com', url='https://github.com/gophish/api-client-python', license='MIT', - download_url='https://github.com/gophish/api-client-python/tarball/0.1.3', + download_url='https://github.com/gophish/api-client-python/tarball/0.1.6', keywords=['gophish'], classifiers=[ 'Development Status :: 3 - Alpha', From 6780eb65e3823244197caa56dab6686ef6219faf Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Mon, 5 Nov 2018 14:50:22 -0600 Subject: [PATCH 03/22] Raising errors instead of returning them as objects. Bumped version to 0.2.0 --- gophish/api/api.py | 8 ++++---- gophish/models.py | 20 +++++++++++++++++--- setup.py | 4 ++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/gophish/api/api.py b/gophish/api/api.py index b266b7e..db88cad 100644 --- a/gophish/api/api.py +++ b/gophish/api/api.py @@ -58,7 +58,7 @@ def get(self, response = self.api.execute("GET", endpoint) if not response.ok: - return Error.parse(response.json()) + raise Error.parse(response.json()) if resource_id or single_resource: return resource_cls.parse(response.json()) @@ -76,7 +76,7 @@ def post(self, resource): "POST", self.endpoint, json=(resource.as_dict())) if not response.ok: - return Error.parse(response.json()) + raise Error.parse(response.json()) return self._cls.parse(response.json()) @@ -95,7 +95,7 @@ def put(self, resource): response = self.api.execute("PUT", endpoint, json=resource.as_json()) if not response.ok: - return Error.parse(response.json()) + raise Error.parse(response.json()) return self._cls.parse(response.json()) @@ -111,6 +111,6 @@ def delete(self, resource_id): response = self.api.execute("DELETE", endpoint) if not response.ok: - return Error.parse(response.json()) + raise Error.parse(response.json()) return self._cls.parse(response.json()) diff --git a/gophish/models.py b/gophish/models.py index 6e2649f..adf283e 100644 --- a/gophish/models.py +++ b/gophish/models.py @@ -17,16 +17,21 @@ def __init__(self): def as_dict(self): """ Returns a dict representation of the resource """ result = {} + print(self._valid_properties) for key in self._valid_properties: val = getattr(self, key) if isinstance(val, datetime): val = val.isoformat() # Parse custom classes - elif val and not isinstance(val, (int, float, str, list, dict)): + elif val and not isinstance(val, + (int, float, str, list, dict, bool)): val = val.as_dict() # Parse lists of objects elif isinstance(val, list): val = [e.as_dict() for e in val] + # If it's a boolean, add it regardless of the value + elif isinstance(val, bool): + result[key] = val # Add it if it's not None if val: @@ -388,8 +393,17 @@ def parse(cls, json): return attachment -class Error(Model): - _valid_properties = {'message', 'success', 'data'} +class Error(Exception, Model): + _valid_properties = {'message': None, 'success': None, 'data': None} + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def __str__(self): + return self.message + + def __repr__(self): + return _json.dumps(self.as_dict()) @classmethod def parse(cls, json): diff --git a/setup.py b/setup.py index db9aab8..88cc071 100644 --- a/setup.py +++ b/setup.py @@ -3,13 +3,13 @@ setup( name='gophish', packages=['gophish', 'gophish.api'], - version='0.1.6', + version='0.2.0', description='Python API Client for Gophish', author='Jordan Wright', author_email='python@getgophish.com', url='https://github.com/gophish/api-client-python', license='MIT', - download_url='https://github.com/gophish/api-client-python/tarball/0.1.6', + download_url='https://github.com/gophish/api-client-python/tarball/0.2.0', keywords=['gophish'], classifiers=[ 'Development Status :: 3 - Alpha', From 67707513e8c25c2a0cd291a139d79dae8b73c8bc Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Mon, 5 Nov 2018 14:58:14 -0600 Subject: [PATCH 04/22] Removed print statement - bumped version to 0.2.1 --- gophish/models.py | 1 - setup.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/gophish/models.py b/gophish/models.py index adf283e..e5eadd3 100644 --- a/gophish/models.py +++ b/gophish/models.py @@ -17,7 +17,6 @@ def __init__(self): def as_dict(self): """ Returns a dict representation of the resource """ result = {} - print(self._valid_properties) for key in self._valid_properties: val = getattr(self, key) if isinstance(val, datetime): diff --git a/setup.py b/setup.py index 88cc071..b701c2e 100644 --- a/setup.py +++ b/setup.py @@ -3,13 +3,13 @@ setup( name='gophish', packages=['gophish', 'gophish.api'], - version='0.2.0', + version='0.2.1', description='Python API Client for Gophish', author='Jordan Wright', author_email='python@getgophish.com', url='https://github.com/gophish/api-client-python', license='MIT', - download_url='https://github.com/gophish/api-client-python/tarball/0.2.0', + download_url='https://github.com/gophish/api-client-python/tarball/0.2.1', keywords=['gophish'], classifiers=[ 'Development Status :: 3 - Alpha', From 890aee69f1fb801f77461dc00526927bee02ee8f Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Tue, 6 Nov 2018 15:03:03 -0600 Subject: [PATCH 05/22] Added send_by_date parameter. Bumped version to 0.2.2 --- gophish/models.py | 3 ++- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gophish/models.py b/gophish/models.py index e5eadd3..487027d 100644 --- a/gophish/models.py +++ b/gophish/models.py @@ -49,6 +49,7 @@ class Campaign(Model): 'name': None, 'created_date': datetime.now(tzlocal()), 'launch_date': datetime.now(tzlocal()), + 'send_by_date': None, 'completed_date': None, 'template': None, 'page': None, @@ -58,7 +59,6 @@ class Campaign(Model): 'smtp': None, 'url': None, 'groups': [], - 'profile': None } def __init__(self, **kwargs): @@ -118,6 +118,7 @@ class CampaignSummary(Model): 'name': None, 'status': None, 'created_date': None, + 'send_by_date': None, 'launch_date': None, 'completed_date': None, 'stats': None diff --git a/setup.py b/setup.py index b701c2e..2e6c3b2 100644 --- a/setup.py +++ b/setup.py @@ -3,13 +3,13 @@ setup( name='gophish', packages=['gophish', 'gophish.api'], - version='0.2.1', + version='0.2.2', description='Python API Client for Gophish', author='Jordan Wright', author_email='python@getgophish.com', url='https://github.com/gophish/api-client-python', license='MIT', - download_url='https://github.com/gophish/api-client-python/tarball/0.2.1', + download_url='https://github.com/gophish/api-client-python/tarball/0.2.2', keywords=['gophish'], classifiers=[ 'Development Status :: 3 - Alpha', From 05eed67a7f961a79b5622dc48439e7196f4460b6 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Tue, 6 Nov 2018 15:04:08 -0600 Subject: [PATCH 06/22] Bumped requests dependency version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f3261e4..a1d8a8e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,5 +2,5 @@ appdirs==1.4.0 packaging==16.8 pyparsing==2.1.10 python-dateutil==2.6.0 -requests==2.12.5 +requests>=2.20.0 six==1.10.0 From 378dc26ea191cbed68ec1d33fd301ee219d30c36 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Thu, 20 Dec 2018 22:37:09 -0600 Subject: [PATCH 07/22] Fixing syntax error for PUT requests. Updating the way URLs are built to be more reliable --- gophish/api/api.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/gophish/api/api.py b/gophish/api/api.py index db88cad..757290e 100644 --- a/gophish/api/api.py +++ b/gophish/api/api.py @@ -24,6 +24,19 @@ def __init__(self, api, endpoint=None, cls=None): self.endpoint = endpoint self._cls = cls + def _build_url(self, *parts): + """Builds a path to an API resource by joining the individual parts + with a slash (/). + + This is used instead of urljoin since we're given relative URL parts + which need to be chained together. + + Returns: + str -- The parts joined with a slash + """ + + return '/'.join(str(part).rstrip('/') for part in parts) + def get(self, resource_id=None, resource_action=None, @@ -51,10 +64,10 @@ def get(self, resource_cls = self._cls if resource_id: - endpoint = '{}/{}'.format(endpoint, resource_id) + endpoint = self._build_url(endpoint, resource_id) if resource_action: - endpoint = '{}/{}'.format(endpoint, resource_action) + endpoint = self._build_url(endpoint, resource_action) response = self.api.execute("GET", endpoint) if not response.ok: @@ -90,9 +103,9 @@ def put(self, resource): endpoint = self.endpoint if resource.id: - endpoint = '{}/{}'.format(endpoint, resource.id) + endpoint = self._build_url(endpoint, resource.id) - response = self.api.execute("PUT", endpoint, json=resource.as_json()) + response = self.api.execute("PUT", endpoint, json=resource.as_dict()) if not response.ok: raise Error.parse(response.json()) From 825bea12e16e1e1fcf36ab6649252e321d8860d2 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Sat, 29 Dec 2018 23:29:58 -0600 Subject: [PATCH 08/22] Added SMTP auth parameters and SMTP headers. Fixed PUT page/:id. Bumped version to 0.2.4. --- gophish/api/pages.py | 2 +- gophish/models.py | 19 +++++++++++++++---- setup.py | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/gophish/api/pages.py b/gophish/api/pages.py index 46ec3fb..0f69157 100644 --- a/gophish/api/pages.py +++ b/gophish/api/pages.py @@ -19,7 +19,7 @@ def post(self, page): def put(self, page): """ Edits a page """ - return super(API, self).put(put) + return super(API, self).put(page) def delete(self, page_id): """ Deletes a page by ID """ diff --git a/gophish/models.py b/gophish/models.py index 487027d..c0f3d8e 100644 --- a/gophish/models.py +++ b/gophish/models.py @@ -14,6 +14,10 @@ class Model(object): def __init__(self): self._valid_properties = {} + @classmethod + def _is_builtin(cls, obj): + return isinstance(obj, (int, float, str, list, dict, bool)) + def as_dict(self): """ Returns a dict representation of the resource """ result = {} @@ -22,12 +26,16 @@ def as_dict(self): if isinstance(val, datetime): val = val.isoformat() # Parse custom classes - elif val and not isinstance(val, - (int, float, str, list, dict, bool)): + elif val and not Model._is_builtin(val): val = val.as_dict() # Parse lists of objects elif isinstance(val, list): - val = [e.as_dict() for e in val] + # We only want to call as_dict in the case where the item + # isn't a builtin type. + for i in range(len(val)): + if Model._is_builtin(val[i]): + continue + val[i] = val[i].as_dict() # If it's a boolean, add it regardless of the value elif isinstance(val, bool): result[key] = val @@ -304,9 +312,12 @@ class SMTP(Model): 'interface_type': 'SMTP', 'name': None, 'host': None, + 'username': None, + 'password': None, 'from_address': None, 'ignore_cert_errors': False, - 'modified_date': datetime.now(tzlocal()) + 'modified_date': datetime.now(tzlocal()), + 'headers': [] } def __init__(self, **kwargs): diff --git a/setup.py b/setup.py index 2e6c3b2..7762ba8 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='gophish', packages=['gophish', 'gophish.api'], - version='0.2.2', + version='0.2.4', description='Python API Client for Gophish', author='Jordan Wright', author_email='python@getgophish.com', From 28a7790f19e13c92ef0fb7bde8cd89389df5c155 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Sun, 30 Dec 2018 12:32:25 -0600 Subject: [PATCH 09/22] Changed client to use Authorization header instead of GET parameter. --- gophish/client.py | 5 ++++- setup.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/gophish/client.py b/gophish/client.py index 586c6b9..61e0562 100644 --- a/gophish/client.py +++ b/gophish/client.py @@ -19,7 +19,10 @@ def execute(self, method, path, **kwargs): url = "{}{}".format(self.host, path) kwargs.update(self._client_kwargs) response = requests.request( - method, url, params={"api_key": self.api_key}, **kwargs) + method, + url, + headers={"Authorization": "Bearer {}".format(self.api_key)}, + **kwargs) return response diff --git a/setup.py b/setup.py index 7762ba8..1666fc0 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='gophish', packages=['gophish', 'gophish.api'], - version='0.2.4', + version='0.2.5', description='Python API Client for Gophish', author='Jordan Wright', author_email='python@getgophish.com', From b91511257dee32bfe04accee9b7975ffd8412943 Mon Sep 17 00:00:00 2001 From: quelsan <52572277+quelsan@users.noreply.github.com> Date: Sat, 20 Jul 2019 02:49:41 +0200 Subject: [PATCH 10/22] Fixed issue #17 (#18) The API client was appending an extra forward slash infront of all request paths, which caused the CSRF protection to trigger on state changing request. This commit fixes the issue. Related issue in Gophish: https://github.com/gophish/gophish/issues/1506 --- gophish/api/campaigns.py | 2 +- gophish/api/groups.py | 2 +- gophish/api/pages.py | 2 +- gophish/api/smtp.py | 2 +- gophish/api/templates.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gophish/api/campaigns.py b/gophish/api/campaigns.py index 2401647..3827ec8 100644 --- a/gophish/api/campaigns.py +++ b/gophish/api/campaigns.py @@ -4,7 +4,7 @@ class API(APIEndpoint): - def __init__(self, api, endpoint='/api/campaigns/'): + def __init__(self, api, endpoint='api/campaigns/'): """ Creates a new instance of the campaigns API """ super(API, self).__init__(api, endpoint=endpoint, cls=Campaign) diff --git a/gophish/api/groups.py b/gophish/api/groups.py index 63ecf76..fb3fea9 100644 --- a/gophish/api/groups.py +++ b/gophish/api/groups.py @@ -3,7 +3,7 @@ class API(APIEndpoint): - def __init__(self, api, endpoint='/api/groups/'): + def __init__(self, api, endpoint='api/groups/'): super(API, self).__init__(api, endpoint=endpoint, cls=Group) def get(self, group_id=None): diff --git a/gophish/api/pages.py b/gophish/api/pages.py index 0f69157..00e3bad 100644 --- a/gophish/api/pages.py +++ b/gophish/api/pages.py @@ -3,7 +3,7 @@ class API(APIEndpoint): - def __init__(self, api, endpoint='/api/pages/'): + def __init__(self, api, endpoint='api/pages/'): super(API, self).__init__(api, endpoint=endpoint, cls=Page) def get(self, page_id=None): diff --git a/gophish/api/smtp.py b/gophish/api/smtp.py index 13cdf67..2abf509 100644 --- a/gophish/api/smtp.py +++ b/gophish/api/smtp.py @@ -3,7 +3,7 @@ class API(APIEndpoint): - def __init__(self, api, endpoint='/api/smtp/'): + def __init__(self, api, endpoint='api/smtp/'): super(API, self).__init__(api, endpoint=endpoint, cls=SMTP) def get(self, smtp_id=None): diff --git a/gophish/api/templates.py b/gophish/api/templates.py index 6acd355..a2a4863 100644 --- a/gophish/api/templates.py +++ b/gophish/api/templates.py @@ -3,7 +3,7 @@ class API(APIEndpoint): - def __init__(self, api, endpoint='/api/templates/'): + def __init__(self, api, endpoint='api/templates/'): super(API, self).__init__(api, endpoint=endpoint, cls=Template) def get(self, template_id=None): From 9a86be5908c8759f4e9cf8fdf29e4b9203753cbf Mon Sep 17 00:00:00 2001 From: Mark Feldhousen Date: Tue, 30 Jul 2019 23:13:43 -0400 Subject: [PATCH 11/22] Include required dependencies in setup.py (#16) * Move package dependencies into setup.py install_requires * Add dev requirements * Remove requirements files per @jordan-wright's request. --- requirements.txt | 6 ------ setup.py | 43 ++++++++++++++++++++++++++----------------- 2 files changed, 26 insertions(+), 23 deletions(-) delete mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index a1d8a8e..0000000 --- a/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -appdirs==1.4.0 -packaging==16.8 -pyparsing==2.1.10 -python-dateutil==2.6.0 -requests>=2.20.0 -six==1.10.0 diff --git a/setup.py b/setup.py index 1666fc0..90580be 100644 --- a/setup.py +++ b/setup.py @@ -1,23 +1,32 @@ +"""This is the setup module for the Python Gophish API client.""" from setuptools import setup setup( - name='gophish', - packages=['gophish', 'gophish.api'], - version='0.2.5', - description='Python API Client for Gophish', - author='Jordan Wright', - author_email='python@getgophish.com', - url='https://github.com/gophish/api-client-python', - license='MIT', - download_url='https://github.com/gophish/api-client-python/tarball/0.2.2', - keywords=['gophish'], + name="gophish", + packages=["gophish", "gophish.api"], + version="0.2.5", + description="Python API Client for Gophish", + author="Jordan Wright", + author_email="python@getgophish.com", + url="https://github.com/gophish/api-client-python", + license="MIT", + download_url="https://github.com/gophish/api-client-python/tarball/0.2.2", + keywords=["gophish"], classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Natural Language :: English", + "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.3", + ], + install_requires=[ + "appdirs==1.4.0", + "packaging==16.8", + "pyparsing==2.1.10", + "python-dateutil==2.6.0", + "requests>=2.20.0", + "six==1.10.0", ], ) From 416d24b7147fbba8852f7cca5d24b6b361d3dde3 Mon Sep 17 00:00:00 2001 From: quelsan <52572277+quelsan@users.noreply.github.com> Date: Fri, 2 Aug 2019 03:07:22 +0200 Subject: [PATCH 12/22] Added validation of "host" attribute (#20) Modified the init functionality to add a trailing forward slash to the host attribute if none is provided, which is required. This commit complements https://github.com/gophish/api-client-python/pull/18 , which caused an issue if a trailing slash was missing. --- gophish/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gophish/client.py b/gophish/client.py index 61e0562..a945a24 100644 --- a/gophish/client.py +++ b/gophish/client.py @@ -10,7 +10,10 @@ class GophishClient(object): def __init__(self, api_key, host=DEFAULT_URL, **kwargs): self.api_key = api_key - self.host = host + if host.endswith('/'): + self.host = host + else: + self.host = host + '/' self._client_kwargs = kwargs def execute(self, method, path, **kwargs): From 356196006b71a98e7d50793ecf7caa3260b5b92d Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Sun, 2 Feb 2020 11:50:33 -0600 Subject: [PATCH 13/22] Create pythonpublish.yml Initial commit of automatic Python package release management via GitHub Actions. --- .github/workflows/pythonpublish.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/pythonpublish.yml diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml new file mode 100644 index 0000000..b143a53 --- /dev/null +++ b/.github/workflows/pythonpublish.yml @@ -0,0 +1,26 @@ +name: Upload Python Package + +on: + release: + types: [created] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* From 69f00cae3e7a3d165481153b16f94564690bfd02 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Thu, 1 Aug 2019 20:13:57 -0500 Subject: [PATCH 14/22] Bumping version to 0.3.0 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 90580be..2f4d452 100644 --- a/setup.py +++ b/setup.py @@ -4,13 +4,13 @@ setup( name="gophish", packages=["gophish", "gophish.api"], - version="0.2.5", + version="0.3.0", description="Python API Client for Gophish", author="Jordan Wright", author_email="python@getgophish.com", url="https://github.com/gophish/api-client-python", license="MIT", - download_url="https://github.com/gophish/api-client-python/tarball/0.2.2", + download_url="https://github.com/gophish/api-client-python/tarball/0.3.0", keywords=["gophish"], classifiers=[ "Development Status :: 3 - Alpha", From f5c4cccec7c9fd4005e72f53f654b384442da494 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Sun, 2 Feb 2020 20:25:46 -0600 Subject: [PATCH 15/22] Adding webhook support --- gophish/api/api.py | 57 +++++++++++++++++++++++++---------------- gophish/api/webhooks.py | 54 ++++++++++++++++++++++++++++++++++++++ gophish/client.py | 4 +-- gophish/models.py | 22 ++++++++++++++++ 4 files changed, 113 insertions(+), 24 deletions(-) create mode 100644 gophish/api/webhooks.py diff --git a/gophish/api/api.py b/gophish/api/api.py index 757290e..3c10074 100644 --- a/gophish/api/api.py +++ b/gophish/api/api.py @@ -11,7 +11,6 @@ class APIEndpoint(object): Represents an API endpoint for Gophish, containing common patterns for CRUD operations. """ - def __init__(self, api, endpoint=None, cls=None): """ Creates an instance of the APIEndpoint class. @@ -37,6 +36,33 @@ def _build_url(self, *parts): return '/'.join(str(part).rstrip('/') for part in parts) + def request(self, + method, + resource_id=None, + resource_action=None, + resource_cls=None, + single_resource=False): + + endpoint = self.endpoint + + if not resource_cls: + resource_cls = self._cls + + if resource_id: + endpoint = self._build_url(endpoint, resource_id) + + if resource_action: + endpoint = self._build_url(endpoint, resource_action) + + response = self.api.execute(method, endpoint) + if not response.ok: + raise Error.parse(response.json()) + + if resource_id or single_resource: + return resource_cls.parse(response.json()) + + return [resource_cls.parse(resource) for resource in response.json()] + def get(self, resource_id=None, resource_action=None, @@ -58,25 +84,11 @@ def get(self, One or more instances of cls parsed from the returned JSON """ - endpoint = self.endpoint - - if not resource_cls: - resource_cls = self._cls - - if resource_id: - endpoint = self._build_url(endpoint, resource_id) - - if resource_action: - endpoint = self._build_url(endpoint, resource_action) - - response = self.api.execute("GET", endpoint) - if not response.ok: - raise Error.parse(response.json()) - - if resource_id or single_resource: - return resource_cls.parse(response.json()) - - return [resource_cls.parse(resource) for resource in response.json()] + return self.request("GET", + resource_id=resource_id, + resource_action=resource_action, + resource_cls=resource_cls, + single_resource=single_resource) def post(self, resource): """ Creates a new instance of the resource. @@ -85,8 +97,9 @@ def post(self, resource): resource - gophish.models.Model - The resource instance """ - response = self.api.execute( - "POST", self.endpoint, json=(resource.as_dict())) + response = self.api.execute("POST", + self.endpoint, + json=(resource.as_dict())) if not response.ok: raise Error.parse(response.json()) diff --git a/gophish/api/webhooks.py b/gophish/api/webhooks.py new file mode 100644 index 0000000..84c067d --- /dev/null +++ b/gophish/api/webhooks.py @@ -0,0 +1,54 @@ +from gophish.models import Webhook +from gophish.api import APIEndpoint + + +class API(APIEndpoint): + def __init__(self, api, endpoint='api/webhooks/'): + super(API, self).__init__(api, endpoint=endpoint, cls=Webhook) + + def get(self, webhook_id=None): + """Gets one or more webhooks + + Keyword Arguments: + webhook_id {int} -- The ID of the Webhook (optional, default: {None}) + """ + + return super(API, self).get(resource_id=webhook_id) + + def post(self, webhook): + """Creates a new webhook + + Arguments: + webhook {gophish.models.Webhook} -- The webhook to create + """ + + return super(API, self).post(webhook) + + def put(self, webhook): + """Edits a webhook + + Arguments: + webhook {gophish.models.Webhook} -- The updated webhook details + """ + + return super(API, self).put(webhook) + + def delete(self, webhook_id): + """Deletes a webhook by ID + + Arguments: + webhook_id {int} -- The ID of the webhook to delete + """ + return super(API, self).delete(webhook_id) + + def validate(self, webhook_id): + """Sends a validation payload to the webhook specified by the given ID + + Arguments: + webhook_id {int} -- The ID of the webhook to validate + """ + return self.request("POST", + resource_id=webhook_id, + resource_action='validate', + resource_cls=Webhook, + single_resource=True) diff --git a/gophish/client.py b/gophish/client.py index a945a24..4baffb3 100644 --- a/gophish/client.py +++ b/gophish/client.py @@ -1,13 +1,12 @@ import requests -from gophish.api import (campaigns, groups, pages, smtp, templates) +from gophish.api import (campaigns, groups, pages, smtp, templates, webhooks) DEFAULT_URL = 'http://localhost:3333' class GophishClient(object): """ A standard HTTP REST client used by Gophish """ - def __init__(self, api_key, host=DEFAULT_URL, **kwargs): self.api_key = api_key if host.endswith('/'): @@ -41,3 +40,4 @@ def __init__(self, self.pages = pages.API(self.client) self.smtp = smtp.API(self.client) self.templates = templates.API(self.client) + self.webhooks = webhooks.API(self.client) diff --git a/gophish/models.py b/gophish/models.py index c0f3d8e..2241dc0 100644 --- a/gophish/models.py +++ b/gophish/models.py @@ -404,6 +404,28 @@ def parse(cls, json): return attachment +class Webhook(Model): + _valid_properties = { + 'id': None, + 'name': None, + 'url': None, + 'secret': None, + 'is_active': None + } + + def __init__(self, **kwargs): + for key, default in Webhook._valid_properties.items(): + setattr(self, key, kwargs.get(key, default)) + + @classmethod + def parse(cls, json): + webhook = cls() + for key, val in json.items(): + if key in cls._valid_properties: + setattr(webhook, key, val) + return webhook + + class Error(Exception, Model): _valid_properties = {'message': None, 'success': None, 'data': None} From cd2b18f0a0a798d7d0dc9fe046ae53c998c6fdea Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Sun, 2 Feb 2020 21:22:45 -0600 Subject: [PATCH 16/22] Added IMAP support --- gophish/api/api.py | 3 ++- gophish/api/imap.py | 34 ++++++++++++++++++++++++++++++++ gophish/client.py | 4 +++- gophish/models.py | 47 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 gophish/api/imap.py diff --git a/gophish/api/api.py b/gophish/api/api.py index 3c10074..3dbea14 100644 --- a/gophish/api/api.py +++ b/gophish/api/api.py @@ -38,6 +38,7 @@ def _build_url(self, *parts): def request(self, method, + body=None, resource_id=None, resource_action=None, resource_cls=None, @@ -54,7 +55,7 @@ def request(self, if resource_action: endpoint = self._build_url(endpoint, resource_action) - response = self.api.execute(method, endpoint) + response = self.api.execute(method, endpoint, json=body) if not response.ok: raise Error.parse(response.json()) diff --git a/gophish/api/imap.py b/gophish/api/imap.py new file mode 100644 index 0000000..a7a8e08 --- /dev/null +++ b/gophish/api/imap.py @@ -0,0 +1,34 @@ +from gophish.models import IMAP, Success +from gophish.api import APIEndpoint + + +class API(APIEndpoint): + def __init__(self, api, endpoint='api/imap/'): + super(API, self).__init__(api, endpoint=endpoint, cls=IMAP) + + def get(self): + """Gets the configured IMAP settings + """ + + return super(API, self).get() + + def post(self, imap): + """Updates the IMAP settings + + Arguments: + imap {gophish.models.IMAP} -- The IMAP settings to configure + """ + + return super(API, self).post(imap) + + def validate(self, imap): + """Sends a validation payload to the webhook specified by the given ID + + Arguments: + webhook_id {int} -- The ID of the webhook to validate + """ + return self.request("POST", + body=imap.as_dict(), + resource_action='validate', + resource_cls=Success, + single_resource=True) diff --git a/gophish/client.py b/gophish/client.py index 4baffb3..944ab68 100644 --- a/gophish/client.py +++ b/gophish/client.py @@ -1,6 +1,7 @@ import requests -from gophish.api import (campaigns, groups, pages, smtp, templates, webhooks) +from gophish.api import (campaigns, groups, imap, pages, smtp, templates, + webhooks) DEFAULT_URL = 'http://localhost:3333' @@ -37,6 +38,7 @@ def __init__(self, self.client = client(api_key, host=host, **kwargs) self.campaigns = campaigns.API(self.client) self.groups = groups.API(self.client) + self.imap = imap.API(self.client) self.pages = pages.API(self.client) self.smtp = smtp.API(self.client) self.templates = templates.API(self.client) diff --git a/gophish/models.py b/gophish/models.py index 2241dc0..6e7c3f0 100644 --- a/gophish/models.py +++ b/gophish/models.py @@ -426,6 +426,53 @@ def parse(cls, json): return webhook +class IMAP(Model): + _valid_properties = { + 'enabled': None, + 'host': None, + 'port': None, + 'username': None, + 'password': None, + 'tls': None, + 'folder': None, + 'restrict_domain': None, + 'delete_reported_campaign_email': None, + 'last_login': None, + 'modified_date': None, + 'imap_freq': None + } + + def __init__(self, **kwargs): + for key, default in IMAP._valid_properties.items(): + setattr(self, key, kwargs.get(key, default)) + + @classmethod + def parse(cls, json): + imap = cls() + for key, val in json.items(): + if key in cls._valid_properties: + setattr(imap, key, val) + return imap + + +class Success(Exception, Model): + _valid_properties = {'message': None, 'success': None, 'data': None} + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def __str__(self): + return self.message + + @classmethod + def parse(cls, json): + success = cls() + for key, val in json.items(): + if key in cls._valid_properties: + setattr(success, key, val) + return success + + class Error(Exception, Model): _valid_properties = {'message': None, 'success': None, 'data': None} From 0978ef5f9533c0d47305c96ab52d361b74108fc5 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Sun, 2 Feb 2020 21:33:42 -0600 Subject: [PATCH 17/22] Bumped version to 0.4.0 --- LICENSE | 2 +- setup.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index e561eb0..75560dc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 gophish +Copyright (c) 2020 gophish Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/setup.py b/setup.py index 2f4d452..ac1b5d7 100644 --- a/setup.py +++ b/setup.py @@ -4,13 +4,13 @@ setup( name="gophish", packages=["gophish", "gophish.api"], - version="0.3.0", + version="0.4.0", description="Python API Client for Gophish", author="Jordan Wright", author_email="python@getgophish.com", url="https://github.com/gophish/api-client-python", license="MIT", - download_url="https://github.com/gophish/api-client-python/tarball/0.3.0", + download_url="https://github.com/gophish/api-client-python/tarball/0.4.0", keywords=["gophish"], classifiers=[ "Development Status :: 3 - Alpha", From 1ddcc746c37b4638eed76bc76ecf202740d26518 Mon Sep 17 00:00:00 2001 From: Hitesh Patel <35237459+HiteshPatel0101@users.noreply.github.com> Date: Mon, 24 Aug 2020 21:13:26 +0530 Subject: [PATCH 18/22] Update README.md (#28) ## Full Documentation Link was broken, removed gitbook URL and added proper link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a0ca2e..e19d735 100644 --- a/README.md +++ b/README.md @@ -29,4 +29,4 @@ Now you're ready to start using the API! ## Full Documentation -You can find the full Python client documentation [here.](https://gophish.gitbooks.io/python-api-client/content/) +You can find the full Python client documentation [here.](https://docs.getgophish.com/python-api-client/) From f02819cac8d23a1aa24eef72e9ac04ad7bec6036 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Thu, 17 Sep 2020 20:56:01 -0500 Subject: [PATCH 19/22] Updated dependencies --- setup.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index ac1b5d7..4b8aacc 100644 --- a/setup.py +++ b/setup.py @@ -22,11 +22,15 @@ "Programming Language :: Python :: 3.3", ], install_requires=[ - "appdirs==1.4.0", - "packaging==16.8", - "pyparsing==2.1.10", - "python-dateutil==2.6.0", - "requests>=2.20.0", - "six==1.10.0", + "appdirs==1.4.4", + "certifi==2020.6.20", + "chardet==3.0.4", + "idna==2.10", + "packaging==20.4", + "pyparsing==2.4.7", + "python-dateutil==2.8.1", + "requests==2.24.0", + "six==1.15.0", + "urllib3==1.25.10" ], ) From b5d82330d88072021a8f54f9fb8818beee986beb Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Thu, 17 Sep 2020 20:56:12 -0500 Subject: [PATCH 20/22] Changed default host to use HTTPS --- gophish/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gophish/client.py b/gophish/client.py index 944ab68..64d9c9f 100644 --- a/gophish/client.py +++ b/gophish/client.py @@ -3,7 +3,7 @@ from gophish.api import (campaigns, groups, imap, pages, smtp, templates, webhooks) -DEFAULT_URL = 'http://localhost:3333' +DEFAULT_URL = 'https://localhost:3333' class GophishClient(object): From 1a8a6be31d43925e8cec903c4834869951b68ea6 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Thu, 17 Sep 2020 21:05:47 -0500 Subject: [PATCH 21/22] Bumping version to 0.5.1 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 4b8aacc..9fc8a07 100644 --- a/setup.py +++ b/setup.py @@ -4,13 +4,13 @@ setup( name="gophish", packages=["gophish", "gophish.api"], - version="0.4.0", + version="0.5.1", description="Python API Client for Gophish", author="Jordan Wright", author_email="python@getgophish.com", url="https://github.com/gophish/api-client-python", license="MIT", - download_url="https://github.com/gophish/api-client-python/tarball/0.4.0", + download_url="https://github.com/gophish/api-client-python/tarball/0.5.1", keywords=["gophish"], classifiers=[ "Development Status :: 3 - Alpha", From a4094b866293ae25c5425981d9e7fb405a7beebe Mon Sep 17 00:00:00 2001 From: Glenn Wilkinson Date: Wed, 30 Nov 2022 12:06:38 +0000 Subject: [PATCH 22/22] Added email_reported to models Stat --- gophish/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gophish/models.py b/gophish/models.py index 6e7c3f0..f1a5044 100644 --- a/gophish/models.py +++ b/gophish/models.py @@ -156,6 +156,7 @@ class Stat(Model): 'opened': None, 'clicked': None, 'submitted_data': None, + 'email_reported': None, 'error': None }