Merge pull request #810 from AnthonyMichaelTDM/eventpayload-interface-improvements

feat(event system): Flesh out EventPayload interface
This commit is contained in:
Anthony Rubick
2025-09-08 17:54:52 -05:00
committed by GitHub
15 changed files with 750 additions and 132 deletions

View File

@@ -10,6 +10,8 @@
# jemmy1794 maintains the stream proxy module
/src/mod/streamproxy @jemmy1794
# AnthonyMichaelTDM maintains the plugins module
# AnthonyMichaelTDM maintains the plugin and event systems
/src/mod/plugins @AnthonyMichaelTDM
/example/plugins @AnthonyMichaelTDM
/src/**/plugin_*.go @AnthonyMichaelTDM
/src/mod/eventsystem @AnthonyMichaelTDM

View File

@@ -12,12 +12,16 @@ type EventName string
type EventPayload interface {
// GetName returns the event type
GetName() EventName
// Returns the "source" of the event, that is, the component or plugin that emitted the event
GetEventSource() string
}
// Event represents a system event
type Event struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"` // Unix timestamp
UUID string `json:"uuid"` // UUID for the event
Data EventPayload `json:"data"`
}
@@ -28,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed
)
@@ -36,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true,
EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed
// NOTE: Keep up-to-date with event names specified above
}
@@ -59,6 +67,10 @@ func (e *BlacklistedIPBlockedEvent) GetName() EventName {
return EventBlacklistedIPBlocked
}
func (e *BlacklistedIPBlockedEvent) GetEventSource() string {
return "proxy-access"
}
// BlacklistToggledEvent represents an event when the blacklist is disabled for an access rule
type BlacklistToggledEvent struct {
RuleID string `json:"rule_id"`
@@ -69,6 +81,10 @@ func (e *BlacklistToggledEvent) GetName() EventName {
return EventBlacklistToggled
}
func (e *BlacklistToggledEvent) GetEventSource() string {
return "accesslist-api"
}
// AccessRuleCreatedEvent represents an event when a new access ruleset is created
type AccessRuleCreatedEvent struct {
ID string `json:"id"`
@@ -82,12 +98,31 @@ func (e *AccessRuleCreatedEvent) GetName() EventName {
return EventAccessRuleCreated
}
func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api"
}
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data
var temp struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"`
UUID string `json:"uuid"`
}
if err := json.Unmarshal(jsonData, &temp); err != nil {
return err
@@ -96,6 +131,7 @@ func ParseEvent(jsonData []byte, event *Event) error {
// Set the event name and timestamp
event.Name = temp.Name
event.Timestamp = temp.Timestamp
event.UUID = temp.UUID
// Now, based on the event type, unmarshal the specific payload
switch temp.Name {
@@ -126,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err
}
event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
}

View File

@@ -4,6 +4,11 @@
echo "Copying zoraxy_plugin to all mods"
for dir in ./*; do
if [ -d "$dir" ]; then
# remove existing zoraxy_plugin module, if it exists
if [ -d "${dir}/mod/zoraxy_plugin" ]; then
rm -r $dir/mod/zoraxy_plugin
fi
# copy over updated module
cp -r ../../src/mod/plugins/zoraxy_plugin "$dir/mod"
fi
done

View File

@@ -12,12 +12,16 @@ type EventName string
type EventPayload interface {
// GetName returns the event type
GetName() EventName
// Returns the "source" of the event, that is, the component or plugin that emitted the event
GetEventSource() string
}
// Event represents a system event
type Event struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"` // Unix timestamp
UUID string `json:"uuid"` // UUID for the event
Data EventPayload `json:"data"`
}
@@ -28,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed
)
@@ -36,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true,
EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed
// NOTE: Keep up-to-date with event names specified above
}
@@ -59,6 +67,10 @@ func (e *BlacklistedIPBlockedEvent) GetName() EventName {
return EventBlacklistedIPBlocked
}
func (e *BlacklistedIPBlockedEvent) GetEventSource() string {
return "proxy-access"
}
// BlacklistToggledEvent represents an event when the blacklist is disabled for an access rule
type BlacklistToggledEvent struct {
RuleID string `json:"rule_id"`
@@ -69,6 +81,10 @@ func (e *BlacklistToggledEvent) GetName() EventName {
return EventBlacklistToggled
}
func (e *BlacklistToggledEvent) GetEventSource() string {
return "accesslist-api"
}
// AccessRuleCreatedEvent represents an event when a new access ruleset is created
type AccessRuleCreatedEvent struct {
ID string `json:"id"`
@@ -82,12 +98,31 @@ func (e *AccessRuleCreatedEvent) GetName() EventName {
return EventAccessRuleCreated
}
func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api"
}
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data
var temp struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"`
UUID string `json:"uuid"`
}
if err := json.Unmarshal(jsonData, &temp); err != nil {
return err
@@ -96,6 +131,7 @@ func ParseEvent(jsonData []byte, event *Event) error {
// Set the event name and timestamp
event.Name = temp.Name
event.Timestamp = temp.Timestamp
event.UUID = temp.UUID
// Now, based on the event type, unmarshal the specific payload
switch temp.Name {
@@ -126,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err
}
event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
}

View File

@@ -12,12 +12,16 @@ type EventName string
type EventPayload interface {
// GetName returns the event type
GetName() EventName
// Returns the "source" of the event, that is, the component or plugin that emitted the event
GetEventSource() string
}
// Event represents a system event
type Event struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"` // Unix timestamp
UUID string `json:"uuid"` // UUID for the event
Data EventPayload `json:"data"`
}
@@ -28,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed
)
@@ -36,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true,
EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed
// NOTE: Keep up-to-date with event names specified above
}
@@ -59,6 +67,10 @@ func (e *BlacklistedIPBlockedEvent) GetName() EventName {
return EventBlacklistedIPBlocked
}
func (e *BlacklistedIPBlockedEvent) GetEventSource() string {
return "proxy-access"
}
// BlacklistToggledEvent represents an event when the blacklist is disabled for an access rule
type BlacklistToggledEvent struct {
RuleID string `json:"rule_id"`
@@ -69,6 +81,10 @@ func (e *BlacklistToggledEvent) GetName() EventName {
return EventBlacklistToggled
}
func (e *BlacklistToggledEvent) GetEventSource() string {
return "accesslist-api"
}
// AccessRuleCreatedEvent represents an event when a new access ruleset is created
type AccessRuleCreatedEvent struct {
ID string `json:"id"`
@@ -82,12 +98,31 @@ func (e *AccessRuleCreatedEvent) GetName() EventName {
return EventAccessRuleCreated
}
func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api"
}
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data
var temp struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"`
UUID string `json:"uuid"`
}
if err := json.Unmarshal(jsonData, &temp); err != nil {
return err
@@ -96,6 +131,7 @@ func ParseEvent(jsonData []byte, event *Event) error {
// Set the event name and timestamp
event.Name = temp.Name
event.Timestamp = temp.Timestamp
event.UUID = temp.UUID
// Now, based on the event type, unmarshal the specific payload
switch temp.Name {
@@ -126,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err
}
event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
}

View File

@@ -17,7 +17,7 @@ const (
)
var (
EventLog = make([]plugin.Event, 0) // A slice to store events
EventLog = make([]events.Event, 0) // A slice to store events
EventLogMutex = &sync.Mutex{} // Mutex to protect access to the event log
)
@@ -58,7 +58,7 @@ func main() {
})
http.HandleFunc(EVENT_PATH, func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
var event plugin.Event
var event events.Event
// read the request body
if r.Body == nil || r.ContentLength == 0 {
@@ -74,7 +74,7 @@ func main() {
}
// parse the event from the request body
if err := plugin.ParseEvent(buffer.Bytes(), &event); err != nil {
if err := events.ParseEvent(buffer.Bytes(), &event); err != nil {
http.Error(w, fmt.Sprintf("Failed to parse event: %v", err), http.StatusBadRequest)
return
}

View File

@@ -1,120 +0,0 @@
package zoraxy_plugin
import (
"encoding/json"
"fmt"
)
// EventName represents the type of event
type EventName string
// EventPayload interface for all event payloads
type EventPayload interface {
// GetName returns the event type
GetName() EventName
}
// Event represents a system event
type Event struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"` // Unix timestamp
Data EventPayload `json:"data"`
}
const (
// EventBlacklistedIPBlocked is emitted when a blacklisted IP is blocked
EventBlacklistedIPBlocked EventName = "blacklistedIpBlocked"
// EventBlacklistToggled is emitted when the blacklist is toggled for an access rule
EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated"
// Add more event types as needed
)
// BlacklistedIPBlockedEvent represents an event when a blacklisted IP is blocked
type BlacklistedIPBlockedEvent struct {
IP string `json:"ip"`
Comment string `json:"comment"`
RequestedURL string `json:"requested_url"`
Hostname string `json:"hostname"`
UserAgent string `json:"user_agent"`
Method string `json:"method"`
}
func (e *BlacklistedIPBlockedEvent) GetName() EventName {
return EventBlacklistedIPBlocked
}
// BlacklistToggledEvent represents an event when the blacklist is disabled for an access rule
type BlacklistToggledEvent struct {
RuleID string `json:"rule_id"`
Enabled bool `json:"enabled"` // Whether the blacklist is enabled or disabled
}
func (e *BlacklistToggledEvent) GetName() EventName {
return EventBlacklistToggled
}
// AccessRuleCreatedEvent represents an event when a new access ruleset is created
type AccessRuleCreatedEvent struct {
ID string `json:"id"`
Name string `json:"name"`
Desc string `json:"desc"`
BlacklistEnabled bool `json:"blacklist_enabled"`
WhitelistEnabled bool `json:"whitelist_enabled"`
}
func (e *AccessRuleCreatedEvent) GetName() EventName {
return EventAccessRuleCreated
}
// ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data
var temp struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"`
}
if err := json.Unmarshal(jsonData, &temp); err != nil {
return err
}
// Set the event name and timestamp
event.Name = temp.Name
event.Timestamp = temp.Timestamp
// Now, based on the event type, unmarshal the specific payload
switch temp.Name {
case EventBlacklistedIPBlocked:
type tempData struct {
Data BlacklistedIPBlockedEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
case EventBlacklistToggled:
type tempData struct {
Data BlacklistToggledEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
case EventAccessRuleCreated:
type tempData struct {
Data AccessRuleCreatedEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
}
return nil
}

View File

@@ -12,12 +12,16 @@ type EventName string
type EventPayload interface {
// GetName returns the event type
GetName() EventName
// Returns the "source" of the event, that is, the component or plugin that emitted the event
GetEventSource() string
}
// Event represents a system event
type Event struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"` // Unix timestamp
UUID string `json:"uuid"` // UUID for the event
Data EventPayload `json:"data"`
}
@@ -28,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed
)
@@ -36,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true,
EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed
// NOTE: Keep up-to-date with event names specified above
}
@@ -59,6 +67,10 @@ func (e *BlacklistedIPBlockedEvent) GetName() EventName {
return EventBlacklistedIPBlocked
}
func (e *BlacklistedIPBlockedEvent) GetEventSource() string {
return "proxy-access"
}
// BlacklistToggledEvent represents an event when the blacklist is disabled for an access rule
type BlacklistToggledEvent struct {
RuleID string `json:"rule_id"`
@@ -69,6 +81,10 @@ func (e *BlacklistToggledEvent) GetName() EventName {
return EventBlacklistToggled
}
func (e *BlacklistToggledEvent) GetEventSource() string {
return "accesslist-api"
}
// AccessRuleCreatedEvent represents an event when a new access ruleset is created
type AccessRuleCreatedEvent struct {
ID string `json:"id"`
@@ -82,12 +98,31 @@ func (e *AccessRuleCreatedEvent) GetName() EventName {
return EventAccessRuleCreated
}
func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api"
}
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data
var temp struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"`
UUID string `json:"uuid"`
}
if err := json.Unmarshal(jsonData, &temp); err != nil {
return err
@@ -96,6 +131,7 @@ func ParseEvent(jsonData []byte, event *Event) error {
// Set the event name and timestamp
event.Name = temp.Name
event.Timestamp = temp.Timestamp
event.UUID = temp.UUID
// Now, based on the event type, unmarshal the specific payload
switch temp.Name {
@@ -126,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err
}
event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
}

View File

@@ -12,12 +12,16 @@ type EventName string
type EventPayload interface {
// GetName returns the event type
GetName() EventName
// Returns the "source" of the event, that is, the component or plugin that emitted the event
GetEventSource() string
}
// Event represents a system event
type Event struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"` // Unix timestamp
UUID string `json:"uuid"` // UUID for the event
Data EventPayload `json:"data"`
}
@@ -28,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed
)
@@ -36,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true,
EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed
// NOTE: Keep up-to-date with event names specified above
}
@@ -59,6 +67,10 @@ func (e *BlacklistedIPBlockedEvent) GetName() EventName {
return EventBlacklistedIPBlocked
}
func (e *BlacklistedIPBlockedEvent) GetEventSource() string {
return "proxy-access"
}
// BlacklistToggledEvent represents an event when the blacklist is disabled for an access rule
type BlacklistToggledEvent struct {
RuleID string `json:"rule_id"`
@@ -69,6 +81,10 @@ func (e *BlacklistToggledEvent) GetName() EventName {
return EventBlacklistToggled
}
func (e *BlacklistToggledEvent) GetEventSource() string {
return "accesslist-api"
}
// AccessRuleCreatedEvent represents an event when a new access ruleset is created
type AccessRuleCreatedEvent struct {
ID string `json:"id"`
@@ -82,12 +98,31 @@ func (e *AccessRuleCreatedEvent) GetName() EventName {
return EventAccessRuleCreated
}
func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api"
}
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data
var temp struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"`
UUID string `json:"uuid"`
}
if err := json.Unmarshal(jsonData, &temp); err != nil {
return err
@@ -96,6 +131,7 @@ func ParseEvent(jsonData []byte, event *Event) error {
// Set the event name and timestamp
event.Name = temp.Name
event.Timestamp = temp.Timestamp
event.UUID = temp.UUID
// Now, based on the event type, unmarshal the specific payload
switch temp.Name {
@@ -126,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err
}
event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
}

View File

@@ -12,12 +12,16 @@ type EventName string
type EventPayload interface {
// GetName returns the event type
GetName() EventName
// Returns the "source" of the event, that is, the component or plugin that emitted the event
GetEventSource() string
}
// Event represents a system event
type Event struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"` // Unix timestamp
UUID string `json:"uuid"` // UUID for the event
Data EventPayload `json:"data"`
}
@@ -28,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed
)
@@ -36,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true,
EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed
// NOTE: Keep up-to-date with event names specified above
}
@@ -59,6 +67,10 @@ func (e *BlacklistedIPBlockedEvent) GetName() EventName {
return EventBlacklistedIPBlocked
}
func (e *BlacklistedIPBlockedEvent) GetEventSource() string {
return "proxy-access"
}
// BlacklistToggledEvent represents an event when the blacklist is disabled for an access rule
type BlacklistToggledEvent struct {
RuleID string `json:"rule_id"`
@@ -69,6 +81,10 @@ func (e *BlacklistToggledEvent) GetName() EventName {
return EventBlacklistToggled
}
func (e *BlacklistToggledEvent) GetEventSource() string {
return "accesslist-api"
}
// AccessRuleCreatedEvent represents an event when a new access ruleset is created
type AccessRuleCreatedEvent struct {
ID string `json:"id"`
@@ -82,12 +98,31 @@ func (e *AccessRuleCreatedEvent) GetName() EventName {
return EventAccessRuleCreated
}
func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api"
}
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data
var temp struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"`
UUID string `json:"uuid"`
}
if err := json.Unmarshal(jsonData, &temp); err != nil {
return err
@@ -96,6 +131,7 @@ func ParseEvent(jsonData []byte, event *Event) error {
// Set the event name and timestamp
event.Name = temp.Name
event.Timestamp = temp.Timestamp
event.UUID = temp.UUID
// Now, based on the event type, unmarshal the specific payload
switch temp.Name {
@@ -126,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err
}
event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
}

View File

@@ -12,12 +12,16 @@ type EventName string
type EventPayload interface {
// GetName returns the event type
GetName() EventName
// Returns the "source" of the event, that is, the component or plugin that emitted the event
GetEventSource() string
}
// Event represents a system event
type Event struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"` // Unix timestamp
UUID string `json:"uuid"` // UUID for the event
Data EventPayload `json:"data"`
}
@@ -28,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed
)
@@ -36,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true,
EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed
// NOTE: Keep up-to-date with event names specified above
}
@@ -59,6 +67,10 @@ func (e *BlacklistedIPBlockedEvent) GetName() EventName {
return EventBlacklistedIPBlocked
}
func (e *BlacklistedIPBlockedEvent) GetEventSource() string {
return "proxy-access"
}
// BlacklistToggledEvent represents an event when the blacklist is disabled for an access rule
type BlacklistToggledEvent struct {
RuleID string `json:"rule_id"`
@@ -69,6 +81,10 @@ func (e *BlacklistToggledEvent) GetName() EventName {
return EventBlacklistToggled
}
func (e *BlacklistToggledEvent) GetEventSource() string {
return "accesslist-api"
}
// AccessRuleCreatedEvent represents an event when a new access ruleset is created
type AccessRuleCreatedEvent struct {
ID string `json:"id"`
@@ -82,12 +98,31 @@ func (e *AccessRuleCreatedEvent) GetName() EventName {
return EventAccessRuleCreated
}
func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api"
}
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data
var temp struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"`
UUID string `json:"uuid"`
}
if err := json.Unmarshal(jsonData, &temp); err != nil {
return err
@@ -96,6 +131,7 @@ func ParseEvent(jsonData []byte, event *Event) error {
// Set the event name and timestamp
event.Name = temp.Name
event.Timestamp = temp.Timestamp
event.UUID = temp.UUID
// Now, based on the event type, unmarshal the specific payload
switch temp.Name {
@@ -126,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err
}
event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
}

View File

@@ -12,12 +12,16 @@ type EventName string
type EventPayload interface {
// GetName returns the event type
GetName() EventName
// Returns the "source" of the event, that is, the component or plugin that emitted the event
GetEventSource() string
}
// Event represents a system event
type Event struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"` // Unix timestamp
UUID string `json:"uuid"` // UUID for the event
Data EventPayload `json:"data"`
}
@@ -28,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed
)
@@ -36,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true,
EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed
// NOTE: Keep up-to-date with event names specified above
}
@@ -59,6 +67,10 @@ func (e *BlacklistedIPBlockedEvent) GetName() EventName {
return EventBlacklistedIPBlocked
}
func (e *BlacklistedIPBlockedEvent) GetEventSource() string {
return "proxy-access"
}
// BlacklistToggledEvent represents an event when the blacklist is disabled for an access rule
type BlacklistToggledEvent struct {
RuleID string `json:"rule_id"`
@@ -69,6 +81,10 @@ func (e *BlacklistToggledEvent) GetName() EventName {
return EventBlacklistToggled
}
func (e *BlacklistToggledEvent) GetEventSource() string {
return "accesslist-api"
}
// AccessRuleCreatedEvent represents an event when a new access ruleset is created
type AccessRuleCreatedEvent struct {
ID string `json:"id"`
@@ -82,12 +98,31 @@ func (e *AccessRuleCreatedEvent) GetName() EventName {
return EventAccessRuleCreated
}
func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api"
}
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data
var temp struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"`
UUID string `json:"uuid"`
}
if err := json.Unmarshal(jsonData, &temp); err != nil {
return err
@@ -96,6 +131,7 @@ func ParseEvent(jsonData []byte, event *Event) error {
// Set the event name and timestamp
event.Name = temp.Name
event.Timestamp = temp.Timestamp
event.UUID = temp.UUID
// Now, based on the event type, unmarshal the specific payload
switch temp.Name {
@@ -126,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err
}
event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
}

View File

@@ -4,8 +4,8 @@ import (
"sync"
"time"
"github.com/google/uuid"
"imuslab.com/zoraxy/mod/info/logger"
// "imuslab.com/zoraxy/mod/plugins"
"imuslab.com/zoraxy/mod/plugins/zoraxy_plugin/events"
)
@@ -86,8 +86,39 @@ func (em *eventManager) UnregisterSubscriber(listenerID ListenerID) error {
return nil
}
// EmitToSubscribersAnd dispatches an event to the specific listeners in addition to the events subscribers.
//
// The primary use-case of this function is for plugin-to-plugin communication
func (em *eventManager) EmitToSubscribersAnd(listenerIDs []ListenerID, payload events.EventPayload) {
eventName := payload.GetName()
if len(listenerIDs) == 0 {
return // No listeners specified
}
// Create the event
event := events.Event{
Name: eventName,
Timestamp: time.Now().Unix(),
UUID: uuid.New().String(),
Data: payload,
}
// Dispatch to all specified listeners asynchronously
em.emitTo(listenerIDs, event)
// Also emit to all subscribers of the event as usual
em.mutex.RLock()
defer em.mutex.RUnlock()
subscribers, exists := em.subscriptions[eventName]
if !exists || len(subscribers) == 0 {
return // No subscribers
}
em.emitTo(subscribers, event)
}
// Emit dispatches an event to all subscribed listeners
func (em *eventManager) Emit(payload events.EventPayload) error {
func (em *eventManager) Emit(payload events.EventPayload) {
eventName := payload.GetName()
em.mutex.RLock()
@@ -95,22 +126,38 @@ func (em *eventManager) Emit(payload events.EventPayload) error {
subscribers, exists := em.subscriptions[eventName]
if !exists || len(subscribers) == 0 {
return nil // No subscribers
return // No subscribers
}
// Create the event
event := events.Event{
Name: eventName,
Timestamp: time.Now().Unix(),
UUID: uuid.New().String(),
Data: payload,
}
// Dispatch to all subscribers asynchronously
for _, listenerID := range subscribers {
em.emitTo(subscribers, event)
}
// Dispatch event to all specified listeners asynchronously
func (em *eventManager) emitTo(listenerIDs []ListenerID, event events.Event) {
if len(listenerIDs) == 0 {
return
}
// Dispatch to all specified listeners asynchronously
em.mutex.RLock()
defer em.mutex.RUnlock()
listenersToUnregister := []ListenerID{}
for _, listenerID := range listenerIDs {
listener, exists := em.subscribers[listenerID]
if !exists {
em.logger.PrintAndLog("event-system", "Failed to get listener for event dispatch, removing "+string(listenerID)+" from subscriptions", nil)
// Mark for removal
listenersToUnregister = append(listenersToUnregister, listenerID)
continue
}
@@ -121,5 +168,10 @@ func (em *eventManager) Emit(payload events.EventPayload) error {
}(listener)
}
return nil
// Unregister any listeners that no longer exist, asynchronously
go func() {
for _, id := range listenersToUnregister {
em.UnregisterSubscriber(id)
}
}()
}

View File

@@ -6,6 +6,8 @@ import (
"testing"
"time"
"github.com/google/uuid"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/plugins/zoraxy_plugin/events"
)
@@ -18,6 +20,7 @@ func TestEventDeSerialization(t *testing.T) {
}
timestamp := time.Now().Unix()
uuid := uuid.New().String()
tests := []SerializationTest{
{
@@ -25,6 +28,7 @@ func TestEventDeSerialization(t *testing.T) {
event: events.Event{
Name: events.EventBlacklistedIPBlocked,
Timestamp: timestamp,
UUID: uuid,
Data: &events.BlacklistedIPBlockedEvent{
IP: "192.168.1.1",
Comment: "Test comment",
@@ -34,25 +38,27 @@ func TestEventDeSerialization(t *testing.T) {
Method: "GET",
},
},
expectedJson: `{"name":"blacklistedIpBlocked","timestamp":` + fmt.Sprintf("%d", timestamp) + `,"data":{"ip":"192.168.1.1","comment":"Test comment","requested_url":"http://example.com","hostname":"example.com","user_agent":"TestUserAgent","method":"GET"}}`,
expectedJson: `{"name":"blacklistedIpBlocked","timestamp":` + fmt.Sprintf("%d", timestamp) + `,"uuid":"` + uuid + `","data":{"ip":"192.168.1.1","comment":"Test comment","requested_url":"http://example.com","hostname":"example.com","user_agent":"TestUserAgent","method":"GET"}}`,
},
{
name: "BlacklistToggled",
event: events.Event{
Name: events.EventBlacklistToggled,
Timestamp: timestamp,
UUID: uuid,
Data: &events.BlacklistToggledEvent{
RuleID: "rule123",
Enabled: true,
},
},
expectedJson: `{"name":"blacklistToggled","timestamp":` + fmt.Sprintf("%d", timestamp) + `,"data":{"rule_id":"rule123","enabled":true}}`,
expectedJson: `{"name":"blacklistToggled","timestamp":` + fmt.Sprintf("%d", timestamp) + `,"uuid":"` + uuid + `","data":{"rule_id":"rule123","enabled":true}}`,
},
{
name: "AccessRuleCreated",
event: events.Event{
Name: events.EventAccessRuleCreated,
Timestamp: timestamp,
UUID: uuid,
Data: &events.AccessRuleCreatedEvent{
ID: "rule456",
Name: "New Access Rule",
@@ -61,7 +67,7 @@ func TestEventDeSerialization(t *testing.T) {
WhitelistEnabled: false,
},
},
expectedJson: `{"name":"accessRuleCreated","timestamp":` + fmt.Sprintf("%d", timestamp) + `,"data":{"id":"rule456","name":"New Access Rule","desc":"A dummy access rule","blacklist_enabled":true,"whitelist_enabled":false}}`,
expectedJson: `{"name":"accessRuleCreated","timestamp":` + fmt.Sprintf("%d", timestamp) + `,"uuid":"` + uuid + `","data":{"id":"rule456","name":"New Access Rule","desc":"A dummy access rule","blacklist_enabled":true,"whitelist_enabled":false}}`,
},
}
@@ -111,3 +117,271 @@ func TestEventDeSerialization(t *testing.T) {
})
}
}
type TestListener struct {
id ListenerID
receivedEvents chan events.Event
}
func (tl *TestListener) GetID() ListenerID {
return tl.id
}
func (tl *TestListener) Notify(event events.Event) error {
tl.receivedEvents <- event
return nil
}
func TestEventEmission(t *testing.T) {
logger, err := logger.NewFmtLogger()
if err != nil {
t.Fatalf("Failed to create logger: %v", err)
}
em := eventManager{
subscriptions: make(map[events.EventName][]ListenerID),
subscribers: make(map[ListenerID]Listener),
logger: logger,
}
// Create a test listener
listenerID := ListenerID("testListener")
testListener := &TestListener{
id: listenerID,
receivedEvents: make(chan events.Event, 10),
}
// Register the listener for BlacklistedIPBlocked events
if err := em.RegisterSubscriberToEvent(testListener, events.EventBlacklistedIPBlocked); err != nil {
t.Fatalf("Failed to register subscriber: %v", err)
}
// Emit a BlacklistedIPBlocked event
testEvent := &events.BlacklistedIPBlockedEvent{
IP: "192.168.1.1",
Comment: "Malicious activity detected",
RequestedURL: "http://mysite.com/admin",
Hostname: "mysite.com",
UserAgent: "BadBot/1.0",
Method: "GET",
}
em.Emit(testEvent)
// Verify that the listener received the event
select {
case receivedEvent := <-testListener.receivedEvents:
if receivedEvent.Name != events.EventBlacklistedIPBlocked {
t.Fatalf("Unexpected event received by listener.\nGot: %+v\nWant: %+v", receivedEvent, testEvent)
}
case <-time.After(1 * time.Second):
t.Fatal("Timeout waiting for event to be received by listener")
}
// Unregister the listener
if err := em.UnregisterSubscriber(listenerID); err != nil {
t.Fatalf("Failed to unregister subscriber: %v", err)
}
}
func TestEventEmissionToSpecificListener(t *testing.T) {
logger, err := logger.NewFmtLogger()
if err != nil {
t.Fatalf("Failed to create logger: %v", err)
}
em := eventManager{
subscriptions: make(map[events.EventName][]ListenerID),
subscribers: make(map[ListenerID]Listener),
logger: logger,
}
// Create a few test listeners
// listener1 and listener2 are plugins that will send events to each other
// moderator is a plugin that is subscribed to EventCustom, so it will receive all custom events
// otherListener is neither subscribed to EventCustom nor a recipient of any custom event, so it should not receive any events
listener1ID := ListenerID("pluginA")
listener1 := &TestListener{
id: listener1ID,
receivedEvents: make(chan events.Event, 10),
}
em.RegisterSubscriberToEvent(listener1, events.EventAccessRuleCreated) // We need to register to some event to be a valid subscriber
listener2ID := ListenerID("pluginB")
listener2 := &TestListener{
id: listener2ID,
receivedEvents: make(chan events.Event, 10),
}
em.RegisterSubscriberToEvent(listener2, events.EventAccessRuleCreated) // We need to register to some event to be a valid subscriber
moderatorID := ListenerID("moderator")
moderator := &TestListener{
id: moderatorID,
receivedEvents: make(chan events.Event, 10),
}
em.RegisterSubscriberToEvent(moderator, events.EventCustom)
otherListenerID := ListenerID("pluginD")
otherListener := &TestListener{
id: otherListenerID,
receivedEvents: make(chan events.Event, 10),
}
em.RegisterSubscriberToEvent(otherListener, events.EventAccessRuleCreated) // We need to register to some event to be a valid subscriber
// Send a custom event from listener1 to listener2
customEvent := &events.CustomEvent{
SourcePlugin: "pluginA",
Recipients: []string{"pluginB"},
Payload: map[string]interface{}{
"message": "Hello from pluginA",
},
}
em.EmitToSubscribersAnd([]ListenerID{listener2ID}, customEvent)
// Verify that listener2 received the event
select {
case receivedEvent := <-listener2.receivedEvents:
if receivedEvent.Name != events.EventCustom {
t.Fatalf("Unexpected event received by listener2.\nGot: %+v\nWant: %+v", receivedEvent, customEvent)
}
case <-time.After(1 * time.Second):
t.Fatal("Timeout waiting for event to be received by listener2")
}
// Verify that listener1 did not receive the event
select {
case <-listener1.receivedEvents:
t.Fatal("Listener1 should not have received any events")
case <-time.After(100 * time.Millisecond):
// No event received, as expected
}
// send a custom event from listener2 to listener1
customEvent2 := &events.CustomEvent{
SourcePlugin: "pluginB",
Recipients: []string{"pluginA"},
Payload: map[string]interface{}{
"message": "Hello from pluginB",
},
}
em.EmitToSubscribersAnd([]ListenerID{listener1ID}, customEvent2)
// Verify that listener1 received the event
select {
case receivedEvent := <-listener1.receivedEvents:
if receivedEvent.Name != events.EventCustom {
t.Fatalf("Unexpected event received by listener1.\nGot: %+v\nWant: %+v", receivedEvent, customEvent2)
}
case <-time.After(1 * time.Second):
t.Fatal("Timeout waiting for event to be received by listener1")
}
// Verify that listener2 did not receive any new event
select {
case <-listener2.receivedEvents:
t.Fatal("Listener2 should not have received any new events")
case <-time.After(100 * time.Millisecond):
// No event received, as expected
}
// ensure the moderator received both events
expectedMessagesSeen := map[string]bool{
"Hello from pluginA": false,
"Hello from pluginB": false,
}
for range expectedMessagesSeen {
select {
case receivedEvent := <-moderator.receivedEvents:
if receivedEvent.Name != events.EventCustom {
t.Fatalf("Unexpected event received by moderator.\nGot: %+v\nWant: %+v", receivedEvent, customEvent)
}
data, ok := receivedEvent.Data.(*events.CustomEvent)
if !ok {
t.Fatalf("Unexpected data type in event received by moderator.\nGot: %T\nWant: *events.CustomEvent", receivedEvent.Data)
}
message, ok := data.Payload["message"].(string)
if !ok {
t.Fatalf("Unexpected payload format in event received by moderator.\nGot: %+v\nWant: map with 'message' key", data.Payload)
}
if alreadySeen, exists := expectedMessagesSeen[message]; exists {
if alreadySeen {
t.Fatalf("Duplicate message in event received by moderator: %s", message)
}
expectedMessagesSeen[message] = true
} else {
t.Fatalf("Unexpected message in event received by moderator: %s", message)
}
case <-time.After(1 * time.Second):
t.Fatal("Timeout waiting for event to be received by moderator")
}
}
for msg, seen := range expectedMessagesSeen {
if !seen {
t.Fatalf("Moderator did not receive expected message: %s", msg)
}
}
// Ensure that the events were not seen by the other listener
select {
case <-otherListener.receivedEvents:
t.Fatal("otherListener should not have received any events")
case <-time.After(100 * time.Millisecond):
// No event received, as expected
}
}
func TestEmissionToNonExistentListener(t *testing.T) {
// Create a test listener and register it
listenerID := ListenerID("testListener")
testListener := &TestListener{
id: listenerID,
receivedEvents: make(chan events.Event, 10),
}
// Create event manager with the test listener marked as subscribed to BlacklistToggledEvents,
// but not actually registered
logger, err := logger.NewFmtLogger()
if err != nil {
t.Fatalf("Failed to create logger: %v", err)
}
em := eventManager{
subscriptions: map[events.EventName][]ListenerID{
events.EventBlacklistToggled: {listenerID},
},
subscribers: make(map[ListenerID]Listener),
logger: logger,
}
// Emit a BlacklistToggled event
testEvent := &events.BlacklistToggledEvent{
RuleID: "rule123",
Enabled: false,
}
eventEmitted := make(chan struct{})
go func() {
em.Emit(testEvent)
time.Sleep(10 * time.Millisecond) // Give some time for the emission to process
close(eventEmitted)
}()
// Wait for the event emission to complete
select {
case <-eventEmitted:
case <-time.After(1 * time.Second):
t.Fatal("Timeout waiting for event emission to complete, likely due to deadlock")
}
// check if the listener is still tracked by the event manager
em.mutex.RLock()
_, isRegistered := em.subscribers[listenerID]
subscribers := em.subscriptions[events.EventBlacklistToggled]
em.mutex.RUnlock()
if len(subscribers) != 0 {
t.Fatal("Listener should have been removed from subscriptions after failed dispatch")
}
if isRegistered {
t.Fatal("Listener was somehow registered")
}
// Since the listener was unregistered, it should not receive any events
select {
case <-testListener.receivedEvents:
t.Fatal("Listener should not have received any events after being unregistered")
case <-time.After(100 * time.Millisecond):
// No event received, as expected
}
}

View File

@@ -12,12 +12,16 @@ type EventName string
type EventPayload interface {
// GetName returns the event type
GetName() EventName
// Returns the "source" of the event, that is, the component or plugin that emitted the event
GetEventSource() string
}
// Event represents a system event
type Event struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"` // Unix timestamp
UUID string `json:"uuid"` // UUID for the event
Data EventPayload `json:"data"`
}
@@ -28,6 +32,9 @@ const (
EventBlacklistToggled EventName = "blacklistToggled"
// EventAccessRuleCreated is emitted when a new access ruleset is created
EventAccessRuleCreated EventName = "accessRuleCreated"
// A custom event emitted by a plugin, with the intention of being broadcast
// to the designated recipient(s)
EventCustom EventName = "customEvent"
// Add more event types as needed
)
@@ -36,6 +43,7 @@ var validEventNames = map[EventName]bool{
EventBlacklistedIPBlocked: true,
EventBlacklistToggled: true,
EventAccessRuleCreated: true,
EventCustom: true,
// Add more event types as needed
// NOTE: Keep up-to-date with event names specified above
}
@@ -59,6 +67,10 @@ func (e *BlacklistedIPBlockedEvent) GetName() EventName {
return EventBlacklistedIPBlocked
}
func (e *BlacklistedIPBlockedEvent) GetEventSource() string {
return "proxy-access"
}
// BlacklistToggledEvent represents an event when the blacklist is disabled for an access rule
type BlacklistToggledEvent struct {
RuleID string `json:"rule_id"`
@@ -69,6 +81,10 @@ func (e *BlacklistToggledEvent) GetName() EventName {
return EventBlacklistToggled
}
func (e *BlacklistToggledEvent) GetEventSource() string {
return "accesslist-api"
}
// AccessRuleCreatedEvent represents an event when a new access ruleset is created
type AccessRuleCreatedEvent struct {
ID string `json:"id"`
@@ -82,12 +98,31 @@ func (e *AccessRuleCreatedEvent) GetName() EventName {
return EventAccessRuleCreated
}
func (e *AccessRuleCreatedEvent) GetEventSource() string {
return "accesslist-api"
}
type CustomEvent struct {
SourcePlugin string `json:"source_plugin"`
Recipients []string `json:"recipients"`
Payload map[string]any `json:"payload"`
}
func (e *CustomEvent) GetName() EventName {
return EventCustom
}
func (e *CustomEvent) GetEventSource() string {
return e.SourcePlugin
}
// ParseEvent parses a JSON byte slice into an Event struct
func ParseEvent(jsonData []byte, event *Event) error {
// First, determine the event type, and parse shared fields, from the JSON data
var temp struct {
Name EventName `json:"name"`
Timestamp int64 `json:"timestamp"`
UUID string `json:"uuid"`
}
if err := json.Unmarshal(jsonData, &temp); err != nil {
return err
@@ -96,6 +131,7 @@ func ParseEvent(jsonData []byte, event *Event) error {
// Set the event name and timestamp
event.Name = temp.Name
event.Timestamp = temp.Timestamp
event.UUID = temp.UUID
// Now, based on the event type, unmarshal the specific payload
switch temp.Name {
@@ -126,6 +162,15 @@ func ParseEvent(jsonData []byte, event *Event) error {
return err
}
event.Data = &payload.Data
case EventCustom:
type tempData struct {
Data CustomEvent `json:"data"`
}
var payload tempData
if err := json.Unmarshal(jsonData, &payload); err != nil {
return err
}
event.Data = &payload.Data
default:
return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData)
}