diff --git a/src/mod/plugins/event_system.go b/src/mod/plugins/event_system.go index 0eb3e7a..de7a8d3 100644 --- a/src/mod/plugins/event_system.go +++ b/src/mod/plugins/event_system.go @@ -109,7 +109,7 @@ func (em *eventManager) Emit(payload zoraxyPlugin.EventPayload) error { // Create the event event := zoraxyPlugin.Event{ Name: eventName, - Timestamp: time.Now(), + Timestamp: time.Now().Unix(), Data: payload, } @@ -176,6 +176,17 @@ func (em *eventManager) dispatchToPlugin(pluginID string, event zoraxyPlugin.Eve defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - em.logger.PrintAndLog("event-system", "Plugin "+pluginID+" returned non-200 status for event: "+resp.Status, nil) + respBody := fmt.Errorf("no response body") + if resp.ContentLength > 0 { + buffer := bytes.NewBuffer(make([]byte, 0, resp.ContentLength)) + _, respErr := buffer.ReadFrom(resp.Body) + if respErr != nil { + respBody = fmt.Errorf("failed to read response body: %v", respErr) + } else { + respBody = fmt.Errorf("response body: %s", buffer.String()) + } + } + + em.logger.PrintAndLog("event-system", fmt.Sprintf("Plugin %s returned non-200 status for event `%s`: %s", pluginID, event.Name, resp.Status), respBody) } } diff --git a/src/mod/plugins/event_system_test.go b/src/mod/plugins/event_system_test.go new file mode 100644 index 0000000..c12f2ab --- /dev/null +++ b/src/mod/plugins/event_system_test.go @@ -0,0 +1,88 @@ +package plugins + +import ( + "encoding/json" + "fmt" + "testing" + "time" + + "imuslab.com/zoraxy/mod/plugins/zoraxy_plugin" +) + +// Test (de)serialization of events +func TestEventDeSerialization(t *testing.T) { + type SerializationTest struct { + name string + event zoraxy_plugin.Event + expectedJson string + } + + timestamp := time.Now().Unix() + + tests := []SerializationTest{ + { + name: "BlacklistedIPBlocked", + event: zoraxy_plugin.Event{ + Name: zoraxy_plugin.EventBlacklistedIPBlocked, + Timestamp: timestamp, + Data: &zoraxy_plugin.BlacklistedIPBlockedEvent{ + IP: "192.168.1.1", + Comment: "Test comment", + RequestedURL: "http://example.com", + Hostname: "example.com", + UserAgent: "TestUserAgent", + 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"}}`, + }, + { + name: "BlacklistToggled", + event: zoraxy_plugin.Event{ + Name: zoraxy_plugin.EventBlacklistToggled, + Timestamp: timestamp, + Data: &zoraxy_plugin.BlacklistToggledEvent{ + RuleID: "rule123", + Enabled: true, + }, + }, + expectedJson: `{"name":"blacklistToggled","timestamp":` + fmt.Sprintf("%d", timestamp) + `,"data":{"rule_id":"rule123","enabled":true}}`, + }, + { + name: "AccessRuleCreated", + event: zoraxy_plugin.Event{ + Name: zoraxy_plugin.EventAccessRuleCreated, + Timestamp: timestamp, + Data: &zoraxy_plugin.AccessRuleCreatedEvent{ + ID: "rule456", + Name: "New Access Rule", + Desc: "A dummy access rule", + BlacklistEnabled: true, + 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}}`, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Serialize the event + jsonData, err := json.Marshal(test.event) + if err != nil { + t.Fatalf("Failed to serialize event: %v", err) + } + + // Compare the serialized JSON with the expected JSON + if string(jsonData) != test.expectedJson { + t.Fatalf("Unexpected JSON output.\nGot: %s\nWant: %s", jsonData, test.expectedJson) + } + + // Deserialize the JSON back into an event + var deserializedEvent zoraxy_plugin.Event + if err := zoraxy_plugin.ParseEvent(jsonData, &deserializedEvent); err != nil { + t.Fatalf("Failed to parse event: %v", err) + } + }) + } +} diff --git a/src/mod/plugins/lifecycle.go b/src/mod/plugins/lifecycle.go index e88ab39..50a3c05 100644 --- a/src/mod/plugins/lifecycle.go +++ b/src/mod/plugins/lifecycle.go @@ -157,9 +157,9 @@ func (m *Manager) StartPlugin(pluginID string) error { eventType := zoraxyPlugin.EventName(eventName) err := EventSystem.Subscribe(thisPlugin.Spec.ID, eventType) if err != nil { - m.Log("Failed to subscribe plugin "+thisPlugin.Spec.Name+" to event "+eventName, err) + m.Log("Failed to subscribe plugin "+thisPlugin.Spec.Name+" to event "+string(eventName), err) } else { - m.Log("Subscribed plugin "+thisPlugin.Spec.Name+" to event "+eventName, nil) + m.Log("Subscribed plugin "+thisPlugin.Spec.Name+" to event "+string(eventName), nil) } } } diff --git a/src/mod/plugins/zoraxy_plugin/event.go b/src/mod/plugins/zoraxy_plugin/event.go index 31bfde7..238be69 100644 --- a/src/mod/plugins/zoraxy_plugin/event.go +++ b/src/mod/plugins/zoraxy_plugin/event.go @@ -1,7 +1,8 @@ package zoraxy_plugin import ( - "time" + "encoding/json" + "fmt" ) // EventName represents the type of event @@ -15,8 +16,8 @@ type EventPayload interface { // Event represents a system event type Event struct { - Name EventName `json:"type"` - Timestamp time.Time `json:"timestamp"` + Name EventName `json:"name"` + Timestamp int64 `json:"timestamp"` // Unix timestamp Data EventPayload `json:"data"` } @@ -59,7 +60,7 @@ func (e *BlacklistToggledEvent) GetName() EventName { type AccessRuleCreatedEvent struct { ID string `json:"id"` Name string `json:"name"` - Desc string `json:"description"` + Desc string `json:"desc"` BlacklistEnabled bool `json:"blacklist_enabled"` WhitelistEnabled bool `json:"whitelist_enabled"` } @@ -67,3 +68,44 @@ type AccessRuleCreatedEvent struct { 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: + var payload BlacklistedIPBlockedEvent + if err := json.Unmarshal(jsonData, &payload); err != nil { + return err + } + event.Data = &payload + case EventBlacklistToggled: + var payload BlacklistToggledEvent + if err := json.Unmarshal(jsonData, &payload); err != nil { + return err + } + event.Data = &payload + case EventAccessRuleCreated: + var payload AccessRuleCreatedEvent + if err := json.Unmarshal(jsonData, &payload); err != nil { + return err + } + event.Data = &payload + default: + return fmt.Errorf("unknown event: %s, %v", temp.Name, jsonData) + } + return nil +} diff --git a/src/mod/plugins/zoraxy_plugin/zoraxy_plugin.go b/src/mod/plugins/zoraxy_plugin/zoraxy_plugin.go index 761b313..08efe3c 100644 --- a/src/mod/plugins/zoraxy_plugin/zoraxy_plugin.go +++ b/src/mod/plugins/zoraxy_plugin/zoraxy_plugin.go @@ -102,8 +102,8 @@ type IntroSpect struct { UIPath string `json:"ui_path"` //UI path of your plugin (e.g. /ui), will proxy the whole subpath tree to Zoraxy Web UI as plugin UI /* Subscriptions Settings */ - SubscriptionPath string `json:"subscription_path"` //Subscription event path of your plugin (e.g. /notifyme), a POST request with SubscriptionEvent as body will be sent to this path when the event is triggered - SubscriptionsEvents map[string]string `json:"subscriptions_events"` //Subscriptions events of your plugin, paired with comments describing how the event is used, see Zoraxy documentation for more details + SubscriptionPath string `json:"subscription_path"` //Subscription event path of your plugin (e.g. /notifyme), a POST request with SubscriptionEvent as body will be sent to this path when the event is triggered + SubscriptionsEvents map[EventName]string `json:"subscriptions_events"` //Subscriptions events of your plugin, paired with comments describing how the event is used, see Zoraxy documentation for more details /* API Access Control */ PermittedAPIEndpoints []PermittedAPIEndpoint `json:"permitted_api_endpoints"` //List of API endpoints this plugin can access, and a description of why the plugin needs to access this endpoint