From 424dc1c64c2ee28b0ad981c7d5002b9fee38417b Mon Sep 17 00:00:00 2001 From: Nirmal N R Date: Tue, 24 Mar 2026 11:33:11 +0530 Subject: [PATCH] feat: added network ID allowlist in DeDi registry plugin --- .../implementation/dediregistry/README.md | 16 ++++++---- .../implementation/dediregistry/cmd/plugin.go | 12 +++---- .../dediregistry/cmd/plugin_test.go | 17 ++++++++++ .../dediregistry/dediregistry.go | 24 +++++++------- .../dediregistry/dediregistry_test.go | 32 +++++++++++-------- 5 files changed, 62 insertions(+), 39 deletions(-) diff --git a/pkg/plugin/implementation/dediregistry/README.md b/pkg/plugin/implementation/dediregistry/README.md index 0ba4e83..2e51c2c 100644 --- a/pkg/plugin/implementation/dediregistry/README.md +++ b/pkg/plugin/implementation/dediregistry/README.md @@ -18,7 +18,7 @@ registry: config: url: "https://dedi-wrapper.example.com/dedi" registryName: "subscribers.beckn.one" - allowedParentNamespaces: "commerce-network.org,retail-collective.org" + allowedNetworkIDs: "commerce-network/subscriber-references,local-commerce/subscriber-references" timeout: 30 retry_max: 3 retry_wait_min: 1s @@ -31,7 +31,7 @@ registry: |-----------|----------|-------------|---------| | `url` | Yes | DeDi wrapper API base URL (include /dedi path) | - | | `registryName` | Yes | Registry name for lookup path | - | -| `allowedParentNamespaces` | No | Allowlist of parent namespace domains for signature validation | - | +| `allowedNetworkIDs` | No | Allowlist of network membership IDs from `data.network_memberships` for signature validation | - | | `timeout` | No | Request timeout in seconds | Client default | | `retry_max` | No | Maximum number of retry attempts | 4 (library default) | | `retry_wait_min` | No | Minimum wait time between retries (e.g., "1s", "500ms") | 1s (library default) | @@ -39,15 +39,15 @@ registry: ## API Integration -### DeDi Wrapper API Format +### Beckn Registry API Format ``` GET {url}/lookup/{subscriber_id}/{registryName}/{key_id} ``` -**Example**: `https://dedi-wrapper.com/dedi/lookup/bpp.example.com/subscribers.beckn.one/key-1` +**Example**: `https://api.bekcn.io/registry/dedi/lookup/bpp.example.com/subscribers.beckn.one/76EU7K8oC9EQbXPMRL5uw3KbmTxbg3YDXHvm9nVQpK2eGghASnwHzm` ### Authentication -**No authentication required** - DeDi wrapper API is public. +**No authentication required** - Beckn Registry API is public. ### Expected Response Format @@ -64,7 +64,7 @@ GET {url}/lookup/{subscriber_id}/{registryName}/{key_id} "signing_public_key": "384qqkIIpxo71WaJPsWqQNWUDGAFnfnJPxuDmtuBiLo=", "encr_public_key": "test-encr-key" }, - "parent_namespaces": ["commerce-network.org", "local-commerce.org"], + "network_memberships": ["commerce-network/subscriber-references", "local-commerce/subscriber-references"], "created_at": "2025-10-27T11:45:27.963Z", "updated_at": "2025-10-27T11:46:23.563Z" } @@ -94,8 +94,9 @@ modules: registry: id: dediregistry config: - url: "https://dedi-wrapper.example.com/dedi" + url: "https://api.bekcn.io/registry/dedi" registryName: "subscribers.beckn.one" + allowedNetworkIDs: "commerce-network/subscriber-references,local-commerce/subscriber-references" timeout: 30 retry_max: 3 retry_wait_min: 1s @@ -148,6 +149,7 @@ This plugin replaces direct DeDi API integration with the new DeDi Wrapper API f - **Added**: Configurable registryName parameter - **Changed**: POST requests → GET requests - **Updated**: Response structure parsing (`data.details` object) +- **Updated**: Optional allowlist validation now checks `data.network_memberships` - **Added**: New URL path parameter format ## Dependencies diff --git a/pkg/plugin/implementation/dediregistry/cmd/plugin.go b/pkg/plugin/implementation/dediregistry/cmd/plugin.go index 5a0e31f..0ac37a1 100644 --- a/pkg/plugin/implementation/dediregistry/cmd/plugin.go +++ b/pkg/plugin/implementation/dediregistry/cmd/plugin.go @@ -35,8 +35,8 @@ func (d dediRegistryProvider) New(ctx context.Context, config map[string]string) } } - if rawNamespaces, exists := config["allowedParentNamespaces"]; exists && rawNamespaces != "" { - dediConfig.AllowedParentNamespaces = parseAllowedParentNamespaces(rawNamespaces) + if rawNetworkIDs, exists := config["allowedNetworkIDs"]; exists && rawNetworkIDs != "" { + dediConfig.AllowedNetworkIDs = parseAllowedNetworkIDs(rawNetworkIDs) } log.Debugf(ctx, "DeDi Registry config mapped: %+v", dediConfig) @@ -51,17 +51,17 @@ func (d dediRegistryProvider) New(ctx context.Context, config map[string]string) return dediClient, closer, nil } -func parseAllowedParentNamespaces(raw string) []string { +func parseAllowedNetworkIDs(raw string) []string { parts := strings.Split(raw, ",") - namespaces := make([]string, 0, len(parts)) + networkIDs := make([]string, 0, len(parts)) for _, part := range parts { item := strings.TrimSpace(part) if item == "" { continue } - namespaces = append(namespaces, item) + networkIDs = append(networkIDs, item) } - return namespaces + return networkIDs } // Provider is the exported plugin instance diff --git a/pkg/plugin/implementation/dediregistry/cmd/plugin_test.go b/pkg/plugin/implementation/dediregistry/cmd/plugin_test.go index 7043fbd..573f8da 100644 --- a/pkg/plugin/implementation/dediregistry/cmd/plugin_test.go +++ b/pkg/plugin/implementation/dediregistry/cmd/plugin_test.go @@ -90,6 +90,23 @@ func TestDediRegistryProvider_New_InvalidTimeout(t *testing.T) { } } +func TestParseAllowedNetworkIDs(t *testing.T) { + got := parseAllowedNetworkIDs("commerce-network/subscriber-references, retail-network/subscriber-references, ,") + want := []string{ + "commerce-network/subscriber-references", + "retail-network/subscriber-references", + } + + if len(got) != len(want) { + t.Fatalf("expected %d allowed network IDs, got %d", len(want), len(got)) + } + for i := range want { + if got[i] != want[i] { + t.Errorf("expected allowedNetworkIDs[%d] = %q, got %q", i, want[i], got[i]) + } + } +} + func TestDediRegistryProvider_New_NilContext(t *testing.T) { provider := dediRegistryProvider{} diff --git a/pkg/plugin/implementation/dediregistry/dediregistry.go b/pkg/plugin/implementation/dediregistry/dediregistry.go index 352342f..8639f53 100644 --- a/pkg/plugin/implementation/dediregistry/dediregistry.go +++ b/pkg/plugin/implementation/dediregistry/dediregistry.go @@ -15,13 +15,13 @@ import ( // Config holds configuration parameters for the DeDi registry client. type Config struct { - URL string `yaml:"url" json:"url"` - RegistryName string `yaml:"registryName" json:"registryName"` - AllowedParentNamespaces []string `yaml:"allowedParentNamespaces" json:"allowedParentNamespaces"` - Timeout int `yaml:"timeout" json:"timeout"` - RetryMax int `yaml:"retry_max" json:"retry_max"` - RetryWaitMin time.Duration `yaml:"retry_wait_min" json:"retry_wait_min"` - RetryWaitMax time.Duration `yaml:"retry_wait_max" json:"retry_wait_max"` + URL string `yaml:"url" json:"url"` + RegistryName string `yaml:"registryName" json:"registryName"` + AllowedNetworkIDs []string `yaml:"allowedNetworkIDs" json:"allowedNetworkIDs"` + Timeout int `yaml:"timeout" json:"timeout"` + RetryMax int `yaml:"retry_max" json:"retry_max"` + RetryWaitMin time.Duration `yaml:"retry_wait_min" json:"retry_wait_min"` + RetryWaitMax time.Duration `yaml:"retry_wait_max" json:"retry_wait_max"` } // DeDiRegistryClient encapsulates the logic for calling the DeDi registry endpoints. @@ -164,11 +164,11 @@ func (c *DeDiRegistryClient) Lookup(ctx context.Context, req *model.Subscription detailsDomain, _ := details["domain"].(string) detailsSubscriberID, _ := details["subscriber_id"].(string) - // Validate parent namespaces if configured - parentNamespaces := extractStringSlice(data["parent_namespaces"]) - if len(c.config.AllowedParentNamespaces) > 0 { - if len(parentNamespaces) == 0 || !containsAny(parentNamespaces, c.config.AllowedParentNamespaces) { - return nil, fmt.Errorf("registry entry with subscriber_id '%s' does not belong to any configured parent namespaces (registry.config.allowedParentNamespaces)", detailsSubscriberID) + // Validate network memberships if configured. + networkMemberships := extractStringSlice(data["network_memberships"]) + if len(c.config.AllowedNetworkIDs) > 0 { + if len(networkMemberships) == 0 || !containsAny(networkMemberships, c.config.AllowedNetworkIDs) { + return nil, fmt.Errorf("registry entry with subscriber_id '%s' does not belong to any configured network memberships (registry.config.allowedNetworkIDs)", detailsSubscriberID) } } diff --git a/pkg/plugin/implementation/dediregistry/dediregistry_test.go b/pkg/plugin/implementation/dediregistry/dediregistry_test.go index 5e85673..32373b7 100644 --- a/pkg/plugin/implementation/dediregistry/dediregistry_test.go +++ b/pkg/plugin/implementation/dediregistry/dediregistry_test.go @@ -140,9 +140,9 @@ func TestLookup(t *testing.T) { "signing_public_key": "384qqkIIpxo71WaJPsWqQNWUDGAFnfnJPxuDmtuBiLo=", "encr_public_key": "test-encr-key", }, - "parent_namespaces": []string{"commerce-network.org", "local-commerce.org"}, - "created_at": "2025-10-27T11:45:27.963Z", - "updated_at": "2025-10-27T11:46:23.563Z", + "network_memberships": []string{"commerce-network/subscriber-references", "local-commerce/subscriber-references"}, + "created_at": "2025-10-27T11:45:27.963Z", + "updated_at": "2025-10-27T11:46:23.563Z", }, } w.Header().Set("Content-Type", "application/json") @@ -192,7 +192,7 @@ func TestLookup(t *testing.T) { } }) - t.Run("allowed parent namespaces match", func(t *testing.T) { + t.Run("allowed network IDs match", func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { response := map[string]interface{}{ "message": "Record retrieved from registry cache", @@ -204,7 +204,7 @@ func TestLookup(t *testing.T) { "subscriber_id": "dev.np2.com", "signing_public_key": "384qqkIIpxo71WaJPsWqQNWUDGAFnfnJPxuDmtuBiLo=", }, - "parent_namespaces": []string{"commerce-network.org", "local-commerce.org"}, + "network_memberships": []string{"commerce-network/subscriber-references", "local-commerce/subscriber-references"}, }, } w.Header().Set("Content-Type", "application/json") @@ -213,9 +213,9 @@ func TestLookup(t *testing.T) { defer server.Close() config := &Config{ - URL: server.URL + "/dedi", - RegistryName: "subscribers.beckn.one", - AllowedParentNamespaces: []string{"commerce-network.org"}, + URL: server.URL + "/dedi", + RegistryName: "subscribers.beckn.one", + AllowedNetworkIDs: []string{"commerce-network/subscriber-references"}, } client, closer, err := New(ctx, config) @@ -236,7 +236,7 @@ func TestLookup(t *testing.T) { } }) - t.Run("allowed parent namespaces mismatch", func(t *testing.T) { + t.Run("allowed network IDs mismatch", func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { response := map[string]interface{}{ "message": "Record retrieved from registry cache", @@ -248,7 +248,7 @@ func TestLookup(t *testing.T) { "subscriber_id": "dev.np2.com", "signing_public_key": "384qqkIIpxo71WaJPsWqQNWUDGAFnfnJPxuDmtuBiLo=", }, - "parent_namespaces": []string{"local-commerce.org"}, + "network_memberships": []string{"local-commerce/subscriber-references"}, }, } w.Header().Set("Content-Type", "application/json") @@ -257,9 +257,9 @@ func TestLookup(t *testing.T) { defer server.Close() config := &Config{ - URL: server.URL + "/dedi", - RegistryName: "subscribers.beckn.one", - AllowedParentNamespaces: []string{"commerce-network.org"}, + URL: server.URL + "/dedi", + RegistryName: "subscribers.beckn.one", + AllowedNetworkIDs: []string{"commerce-network/subscriber-references"}, } client, closer, err := New(ctx, config) @@ -276,7 +276,11 @@ func TestLookup(t *testing.T) { } _, err = client.Lookup(ctx, req) if err == nil { - t.Error("Expected error for disallowed parent namespaces, got nil") + t.Error("Expected error for disallowed network memberships, got nil") + } + expectedErr := "registry entry with subscriber_id 'dev.np2.com' does not belong to any configured network memberships (registry.config.allowedNetworkIDs)" + if err.Error() != expectedErr { + t.Errorf("Expected error %q, got %q", expectedErr, err.Error()) } })