Skip to content

Commit

Permalink
Added EventStreamSourcePluginPoint
Browse files Browse the repository at this point in the history
Updated plugin docs to cover the UI extension points.
  • Loading branch information
neonbunny committed Nov 20, 2024
1 parent f4af373 commit 1c00cfc
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 52 deletions.
65 changes: 52 additions & 13 deletions docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

SteppingStones provides a number of extension points which can be used to add custom functionality to the project.
These are known as PluginPoints by the [plugin framework](https://pypi.org/project/django-plugins-bihealth/)
(Docs: https://django-plugins.readthedocs.io/en/latest/). Examples can be found within the various `*-reports` apps
included with the Stepping Stones project.
([Docs](https://django-plugins.readthedocs.io/en/latest/)). Examples can be found within the various `*-reports`
Django Apps included with the Stepping Stones project.

## Getting Started

To create a new plugin, first use the standard Django tools to create a new Django app:
To create a new plugin, first use the standard Django tools to create a new Django App:

```shell
python manage.py startapp my_plugin_name
Expand All @@ -20,20 +20,54 @@ Add the new Django App to the `INSTALLED_APPS` list in `stepping_stones\settings
Run the Django plugins command to add the newly defined plugins to the database so Stepping Stones can find it:

```shell
python manage.py startapp my_plugin_name
python manage.py syncplugins
```

## Plugin Points
## PluginPoints

### UI Extensions

### Reporting
Adds menu options to pages in Stepping Stones to tie the plugin into the existing functionality. Useful for
bespoke reporting formats or plugin config pages.

#### EventReportingPluginPoint

A plugin point for a class which generates reports based on events in the main SteppingStones event database.
A plugin point which will add a link under the "Reporting" button on the Events page. Useful for a class which
generates reports based on events in the main SteppingStones event database.

#### CredentialReportingPluginPoint

A plugin point for a class which generates reports based on credentials in the main Stepping Stones credential database.
A plugin point which will add a link under a "Reporting" button on the Credentials page. Useful for a class which
generates reports based on credentials in the main Stepping Stones credential database.

#### EventStreamSourcePluginPoint

A plugin point which will add a link under a "Sources" button on the EventStream page. Useful for allowing users to
graphically configuring services that can populate the EventStream database.

##### Member Variables:

title
: Required : a human-readable, short name for the plugin displayed on the plugin management pages.

name
: Required : a slug used for building plugin URLs

category
: Required : The name of the section title in the drop down menu to add the link under. Can define a new category or
use an existing one

icon_class
: Required : The FontAwesome CSS classes to use for the icon associated with the link, e.g. "fas fa-list-ul"

view_class
: Required : The Django class-based view to use as the entry point to the Plugin. The class should extend the
PermissionRequiredMixin as the permissions are checked to ensure it is worth adding the link to the UI.

urls
: Required : A list similar to the `urlpatterns` found in `urls.py`, specifically for this plugin. The first entry in
list will be used for the link added to the Stepping Stones UI, additional entries can then be used to build mutli-page
plugins.

### Background Tasks

Expand All @@ -43,21 +77,26 @@ Used to start a background thread when SteppingStones starts. Task runs under th

##### Member Variables:

title
: Required : a human-readable, short name for the plugin displayed on the plugin management pages.

name
: Required : a slug used for building plugin URLs

delay_seconds
: The number of seconds after SteppingStones starts to wait before running the initial background task. 30 (default)
: Optional : The number of seconds after Stepping Stones starts to wait before running the initial background task. 30 (default)
means wait 30 seconds for SteppingStones to fully start before attempting to start the task.

repeat_seconds
: The number of seconds that should pass between invocations. 0 (default) means only run once.
: Optional : The number of seconds that should pass between invocations. 0 (default) means only run once.

replace_existing_tasks
: Boolean - True (default) means evict any previously scheduled tasks and only honour the delay/repeat values from now.
: Optional : a boolean value, True (default) means evict any previously scheduled tasks and only honour the delay/repeat values from now.

schedule_function
: A function pointer to the task to start. The function must:
: Required : A function pointer to the task to start. The function must:

* Be defined outside any classes (because the cls/self state can not be serialised)
* Be defined inside a file named `tasks.py` in the root of the plugin's Django application (to ensure the background task runner can find & import the function)
* Use the `background_task.background` decorator from [Django 4 Background Tasks](https://django4-background-tasks.readthedocs.io/en/latest/)


20 changes: 18 additions & 2 deletions event_tracker/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class EventReportingPluginPoint(PluginPoint):
view_class = None

def entry_point_name(self):
return f'{self.name}-entry-point'
return self.urls[0].name

def is_access_permitted(self, user):
if self.view_class:
Expand All @@ -24,7 +24,7 @@ class CredentialReportingPluginPoint(PluginPoint):
view_class = None

def entry_point_name(self):
return f'{self.name}-entry-point'
return self.urls[0].name

def is_access_permitted(self, user):
if self.view_class:
Expand All @@ -39,3 +39,19 @@ class BackgroundTaskPluginPoint(PluginPoint):
repeat_seconds = 0
replace_existing_tasks = True
schedule_function = None


class EventStreamSourcePluginPoint(PluginPoint):
category = "Undefined Category"
icon_class = "fa-solid fa-notdef"
view_class = None

def entry_point_name(self):
return self.urls[0].name

def is_access_permitted(self, user):
if self.view_class:
perms = self.view_class().get_permission_required()
return user.has_perms(perms)
else:
return False
90 changes: 55 additions & 35 deletions event_tracker/templates/event_tracker/eventstream_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -78,45 +78,65 @@
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
{% if perms.event_tracker.add_eventstream %}
<div class="btn-group" role="group" id="page-controls">
<a href="{% url 'event_tracker:eventstream-upload' %}" class="btn btn-success"><i class="fa-solid fa-house-flood-water"></i> Upload EventStream</a>

<div class="dropdown">
<button class="btn btn-outline-info dropdown-toggle" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-info-circle"></i> EventStream Spec.
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
<li><a class="dropdown-item" href="{% static 'eventstream/eventstream-example.json' %}">Example File</a></li>
<li><a class="dropdown-item" href="{% static 'eventstream/eventstream.schema.json' %}">Schema</a></li>
<li><a class="dropdown-item" href="{% static 'eventstream/schema-doc.html' %}">Schema Doc</a></li>
</ul>
<div id="page-controls">
{% if perms.event_tracker.add_eventstream %}
<div class="btn-group" role="group">
<a href="{% url 'event_tracker:eventstream-upload' %}" class="btn btn-success"><i class="fa-solid fa-house-flood-water"></i> Upload EventStream</a>

<div class="dropdown">
<button class="btn btn-outline-info dropdown-toggle" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-info-circle"></i> EventStream Spec.
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
<li><a class="dropdown-item" href="{% static 'eventstream/eventstream-example.json' %}">Example File</a></li>
<li><a class="dropdown-item" href="{% static 'eventstream/eventstream.schema.json' %}">Schema</a></li>
<li><a class="dropdown-item" href="{% static 'eventstream/schema-doc.html' %}">Schema Doc</a></li>
</ul>
</div>
</div>
</div>
{% endif %}
<div class="mt-2">
<table class="table table-sm table-striped">
<thead>
<tr>
<th scope="col">Timestamp</th>
<th scope="col">Source</th>
<th scope="col">Target</th>
<th scope="col">Description</th>
<th scope="col">MITRE</th>
<th scope="col">Additional Data</th>
<th></th>
</tr>
</thead>
<tbody>

</tbody>
</table>
{% if source_plugins %}
<div class="btn-group" role="group">
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-file-signature"></i> Sources
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
{% for plugin in source_plugins %}
{% ifchanged plugin.category %}
{% if not forloop.first %}<li><hr class="dropdown-divider"></li>{% endif %}
<li><h6 class="dropdown-header">{{ plugin.category }}</h6></li>
{% endifchanged %}
<li><a href="{% url plugin.entry_point_name %}" class="dropdown-item"><i class="{{ plugin.icon_class }}"></i> {{ plugin.title }}</a></li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
</div>
<div class="mt-2">
<table class="table table-sm table-striped">
<thead>
<tr>
<th scope="col">Timestamp</th>
<th scope="col">Source</th>
<th scope="col">Target</th>
<th scope="col">Description</th>
<th scope="col">MITRE</th>
<th scope="col">Additional Data</th>
<th></th>
</tr>
</thead>
<tbody>

</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>

<script nonce="{{request.csp_nonce}}">
<script nonce="{{request.csp_nonce}}">
$(function () {
$('[data-toggle="tooltip"]').tooltip();
$.fn.dataTable.moment('{% datetime_format_moment %}');
Expand Down Expand Up @@ -188,6 +208,6 @@
drawCallback: tableDrawCallback
} )
})
</script>
</script>
{% endblock bootstrap5_content %}
{% endblock body %}
12 changes: 11 additions & 1 deletion event_tracker/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@

from dal import autocomplete

from .plugins import EventReportingPluginPoint
from .plugins import EventReportingPluginPoint, EventStreamSourcePluginPoint
from .signals import cs_beacon_to_context, cs_beaconlog_to_file, notify_webhook_new_beacon, cs_listener_to_context, \
get_driver_for
from .templatetags.custom_tags import render_ts_local
Expand Down Expand Up @@ -1006,6 +1006,16 @@ class EventStreamListView(PermissionRequiredMixin, TemplateView):
permission_required = 'event_tracker.view_eventstream'
template_name = "event_tracker/eventstream_list.html"

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)

if EventStreamSourcePluginPoint.get_plugins_qs().filter(status=ENABLED).exists():
context['source_plugins'] = []
for plugin in EventStreamSourcePluginPoint.get_plugins():
if plugin.is_access_permitted(self.request.user):
context['source_plugins'].append(plugin)

return context

class EventStreamListJSON(PermissionRequiredMixin, FilterableDatatableView):
permission_required = 'event_tracker.view_eventstream'
Expand Down
4 changes: 3 additions & 1 deletion stepping_stones/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
from djangoplugins.utils import include_plugins

from event_tracker.models import Task
from event_tracker.plugins import EventReportingPluginPoint, CredentialReportingPluginPoint
from event_tracker.plugins import EventReportingPluginPoint, CredentialReportingPluginPoint, \
EventStreamSourcePluginPoint


def root_view(request):
Expand All @@ -38,4 +39,5 @@ def root_view(request):
path('taggit/', include('taggit_bulk.urls')),
path('plugins/events-reports/', include_plugins(EventReportingPluginPoint)),
path('plugins/cred-reports/', include_plugins(CredentialReportingPluginPoint)),
path('plugins/eventstream-sources/', include_plugins(EventStreamSourcePluginPoint)),
]

0 comments on commit 1c00cfc

Please sign in to comment.