Skip to content

Commit

Permalink
initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
nilbacardit26 committed Nov 15, 2023
1 parent 9c9c822 commit d698487
Show file tree
Hide file tree
Showing 18 changed files with 498 additions and 1 deletion.
50 changes: 50 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,53 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/


# -*- mode: gitignore; -*-
*~
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*

# Org-mode
.org-id-locations
*_archive

# flymake-mode
*_flymake.*

# eshell files
/eshell/history
/eshell/lastdir

# elpa packages
/elpa/

# reftex files
*.rel

# AUCTeX auto folder
/auto/

# cask packages
.cask/
dist/

# Flycheck
flycheck_*.el

# server auth directory
/server/

# projectiles files
.projectile

# directory configuration
.dir-locals.el

# network security
/network-security.data
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
1.0.0 (2023-11-15)
------------------

- Initial release
[nilbacardit26]
1 change: 0 additions & 1 deletion README.md

This file was deleted.

Empty file added README.rst
Empty file.
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.1
Empty file added guillotina_audit/CHANGELOG.rst
Empty file.
20 changes: 20 additions & 0 deletions guillotina_audit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from guillotina import configure


app_settings = {
"load_utilities": {
"audit": {
"provides": "guillotina_audit.interfaces.IAuditUtility",
"factory": "guillotina_audit.utility.AuditUtility",
"settings": {"index_name": "audit"},
}
}
}


def includeme(root, settings):
configure.scan("guillotina_audit.install")
configure.scan("guillotina_audit.utility")
configure.scan("guillotina_audit.subscriber")
configure.scan("guillotina_audit.api")
configure.scan("guillotina_audit.permissions")
21 changes: 21 additions & 0 deletions guillotina_audit/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from guillotina import configure
from guillotina.api.service import Service
from guillotina.component import query_utility
from guillotina.interfaces import IContainer
from guillotina_audit.interfaces import IAuditUtility


@configure.service(
context=IContainer,
method="GET",
permission="audit.AccessContent",
name="@audit",
summary="Get the audit entry logs",
responses={
"200": {"description": "Get the audit entry logs", "schema": {"properties": {}}}
},
)
class AuditGET(Service):
async def __call__(self):
audit_utility = query_utility(IAuditUtility)
return await audit_utility.query_audit(self.request.query)
17 changes: 17 additions & 0 deletions guillotina_audit/install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
from guillotina import configure
from guillotina.addons import Addon
from guillotina.component import query_utility
from guillotina_audit.interfaces import IAuditUtility


@configure.addon(name="audit", title="Guillotina Audit using ES")
class ImageAddon(Addon):
@classmethod
async def install(cls, container, request):
audit_utility = query_utility(IAuditUtility)
await audit_utility.create_index()

@classmethod
async def uninstall(cls, container, request):
pass
5 changes: 5 additions & 0 deletions guillotina_audit/interfaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from guillotina.async_util import IAsyncUtility


class IAuditUtility(IAsyncUtility):
pass
4 changes: 4 additions & 0 deletions guillotina_audit/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from guillotina import configure


configure.grant(role="guillotina.Manager", permission="audit.AccessContent")
31 changes: 31 additions & 0 deletions guillotina_audit/subscriber.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from guillotina import configure
from guillotina.component import query_utility
from guillotina.interfaces import IObjectAddedEvent
from guillotina.interfaces import IObjectModifiedEvent
from guillotina.interfaces import IObjectRemovedEvent
from guillotina.interfaces import IResource
from guillotina_audit.interfaces import IAuditUtility


@configure.subscriber(
for_=(IResource, IObjectAddedEvent), priority=1001
) # after indexing
async def audit_object_added(obj, event):
audit = query_utility(IAuditUtility)
audit.log_entry(obj, event)


@configure.subscriber(
for_=(IResource, IObjectModifiedEvent), priority=1001
) # after indexing
async def audit_object_modified(obj, event):
audit = query_utility(IAuditUtility)
audit.log_entry(obj, event)


@configure.subscriber(
for_=(IResource, IObjectRemovedEvent), priority=1001
) # after indexing
async def audit_object_removed(obj, event):
audit = query_utility(IAuditUtility)
audit.log_entry(obj, event)
24 changes: 24 additions & 0 deletions guillotina_audit/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from pytest_docker_fixtures import images


image_version = "7.8.0"

images.configure(
"elasticsearch",
"docker.elastic.co/elasticsearch/elasticsearch",
image_version,
max_wait_s=90,
env={
"xpack.security.enabled": None, # unset
"discovery.type": "single-node",
"http.host": "0.0.0.0",
"transport.host": "127.0.0.1",
},
)


pytest_plugins = [
"pytest_docker_fixtures",
"guillotina.tests.fixtures",
"guillotina_audit.tests.fixtures",
]
48 changes: 48 additions & 0 deletions guillotina_audit/tests/fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from guillotina import testing
from guillotina.tests.fixtures import _update_from_pytest_markers

import json
import os
import pytest


ELASTICSEARCH = os.environ.get("ELASTICSEARCH", "True")

annotations = {"elasticsearch": {"host": "localhost:9200"}}


def base_settings_configurator(settings):
if "applications" not in settings:
settings["applications"] = []
settings["applications"].append("guillotina")
settings["applications"].append("guillotina_audit")

settings["audit"] = {
"connection_settings": {
"hosts": [f"{annotations['elasticsearch']['host']}"]
} # noqa
}


testing.configure_with(base_settings_configurator)


@pytest.fixture(scope="function")
def elasticsearch_fixture(es):
settings = testing.get_settings()
host, port = es
settings["audit"]["connection_settings"]["hosts"] = [f"{host}:{port}"]
settings = _update_from_pytest_markers(settings, None)
testing.configure_with(base_settings_configurator)
annotations["elasticsearch"]["host"] = f"{host}:{port}"
testing.configure_with(base_settings_configurator)
yield host, port


@pytest.fixture(scope="function")
async def guillotina_es(elasticsearch_fixture, guillotina):
response, status = await guillotina(
"POST", "/db/", data=json.dumps({"@type": "Container", "id": "guillotina"})
)
assert status == 200
yield guillotina
81 changes: 81 additions & 0 deletions guillotina_audit/tests/test_audit_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from datetime import datetime
from datetime import timedelta
from guillotina.component import query_utility
from guillotina_audit.interfaces import IAuditUtility

import asyncio
import json
import pytest


pytestmark = pytest.mark.asyncio


async def test_audit_basic(guillotina_es):
response, status = await guillotina_es(
"POST", "/db/guillotina/@addons", data=json.dumps({"id": "audit"})
)
assert status == 200
audit_utility = query_utility(IAuditUtility)
# Let's check the index has been created
resp = await audit_utility.async_es.indices.get_alias()
assert "audit" in resp
resp = await audit_utility.async_es.indices.get_mapping(index="audit")
assert "path" in resp["audit"]["mappings"]["properties"]
response, status = await guillotina_es(
"POST", "/db/guillotina/", data=json.dumps({"@type": "Item", "id": "foo_item"})
)
assert status == 201
await asyncio.sleep(2)
resp, status = await guillotina_es("GET", "/db/guillotina/@audit")
assert status == 200
assert len(resp["hits"]["hits"]) == 2
assert resp["hits"]["hits"][0]["_source"]["action"] == "added"
assert resp["hits"]["hits"][0]["_source"]["type_name"] == "Container"
assert resp["hits"]["hits"][0]["_source"]["creator"] == "root"

assert resp["hits"]["hits"][1]["_source"]["action"] == "added"
assert resp["hits"]["hits"][1]["_source"]["type_name"] == "Item"
assert resp["hits"]["hits"][1]["_source"]["creator"] == "root"

response, status = await guillotina_es("DELETE", "/db/guillotina/foo_item")
await asyncio.sleep(2)
resp, status = await guillotina_es("GET", "/db/guillotina/@audit")
assert status == 200
assert len(resp["hits"]["hits"]) == 3
resp, status = await guillotina_es("GET", "/db/guillotina/@audit?action=removed")
assert status == 200
assert len(resp["hits"]["hits"]) == 1
resp, status = await guillotina_es(
"GET", "/db/guillotina/@audit?action=removed&type_name=Item"
)
assert status == 200
assert len(resp["hits"]["hits"]) == 1
resp, status = await guillotina_es(
"GET", "/db/guillotina/@audit?action=added&type_name=Item"
)
assert status == 200
assert len(resp["hits"]["hits"]) == 1
assert resp["hits"]["hits"][0]["_source"]["type_name"] == "Item"
resp, status = await guillotina_es(
"GET", "/db/guillotina/@audit?action=added&type_name=Container"
)
assert status == 200
assert len(resp["hits"]["hits"]) == 1
assert resp["hits"]["hits"][0]["_source"]["type_name"] == "Container"
creation_date = resp["hits"]["hits"][0]["_source"]["creation_date"]
datetime_obj = datetime.strptime(creation_date, "%Y-%m-%dT%H:%M:%S.%f%z")
new_creation_date = datetime_obj - timedelta(seconds=1)
new_creation_date = new_creation_date.strftime("%Y-%m-%dT%H:%M:%S.%f%z")
resp, status = await guillotina_es(
"GET",
f"/db/guillotina/@audit?action=added&type_name=Container&creation_date__gte={new_creation_date}",
) # noqa
assert status == 200
assert len(resp["hits"]["hits"]) == 1
resp, status = await guillotina_es(
"GET",
f"/db/guillotina/@audit?action=added&type_name=Container&creation_date__lte={new_creation_date}",
) # noqa
assert len(resp["hits"]["hits"]) == 0
assert status == 200
Loading

0 comments on commit d698487

Please sign in to comment.