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 0742b94
Show file tree
Hide file tree
Showing 18 changed files with 473 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")
19 changes: 19 additions & 0 deletions guillotina_audit/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from guillotina import configure
from guillotina.api.service import Service
from guillotina.component import query_utility
from guillotina_audit.interfaces import IAuditUtility
from guillotina.interfaces import IContainer


@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
3 changes: 3 additions & 0 deletions guillotina_audit/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from guillotina import configure

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


@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"
]
47 changes: 47 additions & 0 deletions guillotina_audit/tests/fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from guillotina import testing
from guillotina.tests.fixtures import _update_from_pytest_markers

import os
import pytest
import json


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
73 changes: 73 additions & 0 deletions guillotina_audit/tests/test_audit_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
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 0742b94

Please sign in to comment.