From 424dc1c64c2ee28b0ad981c7d5002b9fee38417b Mon Sep 17 00:00:00 2001 From: Nirmal N R Date: Tue, 24 Mar 2026 11:33:11 +0530 Subject: [PATCH 1/8] 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()) } }) From c7b15aa1a22651c6a6d82dfe487ed72969c51030 Mon Sep 17 00:00:00 2001 From: Nirmal N R Date: Wed, 25 Mar 2026 14:03:10 +0530 Subject: [PATCH 2/8] fix(docs): updated docs to show network membership as domain of parent --- pkg/plugin/implementation/dediregistry/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/plugin/implementation/dediregistry/README.md b/pkg/plugin/implementation/dediregistry/README.md index 2e51c2c..34a90d1 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" - allowedNetworkIDs: "commerce-network/subscriber-references,local-commerce/subscriber-references" + allowedNetworkIDs: "commerce-network.org/subscriber-references,local-commerce.org/subscriber-references" timeout: 30 retry_max: 3 retry_wait_min: 1s @@ -64,7 +64,7 @@ GET {url}/lookup/{subscriber_id}/{registryName}/{key_id} "signing_public_key": "384qqkIIpxo71WaJPsWqQNWUDGAFnfnJPxuDmtuBiLo=", "encr_public_key": "test-encr-key" }, - "network_memberships": ["commerce-network/subscriber-references", "local-commerce/subscriber-references"], + "network_memberships": ["commerce-network.org/subscriber-references", "local-commerce.com/subscriber-references"], "created_at": "2025-10-27T11:45:27.963Z", "updated_at": "2025-10-27T11:46:23.563Z" } @@ -96,7 +96,7 @@ modules: config: url: "https://api.bekcn.io/registry/dedi" registryName: "subscribers.beckn.one" - allowedNetworkIDs: "commerce-network/subscriber-references,local-commerce/subscriber-references" + allowedNetworkIDs: "commerce-network.org/subscriber-references,local-commerce.com/subscriber-references" timeout: 30 retry_max: 3 retry_wait_min: 1s From d81c49efb294ff46a82ebbe52d4d14349a94c82f Mon Sep 17 00:00:00 2001 From: Nirmal N R Date: Wed, 25 Mar 2026 14:06:19 +0530 Subject: [PATCH 3/8] chore: better examples in dedi registry plugin docs --- pkg/plugin/implementation/dediregistry/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/plugin/implementation/dediregistry/README.md b/pkg/plugin/implementation/dediregistry/README.md index 34a90d1..06cadd9 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" - allowedNetworkIDs: "commerce-network.org/subscriber-references,local-commerce.org/subscriber-references" + allowedNetworkIDs: "commerce-network.org/prod,local-commerce.org/production" timeout: 30 retry_max: 3 retry_wait_min: 1s @@ -64,7 +64,7 @@ GET {url}/lookup/{subscriber_id}/{registryName}/{key_id} "signing_public_key": "384qqkIIpxo71WaJPsWqQNWUDGAFnfnJPxuDmtuBiLo=", "encr_public_key": "test-encr-key" }, - "network_memberships": ["commerce-network.org/subscriber-references", "local-commerce.com/subscriber-references"], + "network_memberships": ["commerce-network.org/prod", "local-commerce.com/production"], "created_at": "2025-10-27T11:45:27.963Z", "updated_at": "2025-10-27T11:46:23.563Z" } @@ -96,7 +96,7 @@ modules: config: url: "https://api.bekcn.io/registry/dedi" registryName: "subscribers.beckn.one" - allowedNetworkIDs: "commerce-network.org/subscriber-references,local-commerce.com/subscriber-references" + allowedNetworkIDs: "commerce-network.org/prod,local-commerce.com/production" timeout: 30 retry_max: 3 retry_wait_min: 1s From 67bbac77286607da77b47fc18b9e109c2afcc6bf Mon Sep 17 00:00:00 2001 From: Nirmal N R Date: Fri, 27 Mar 2026 10:58:49 +0530 Subject: [PATCH 4/8] chore: updated error message --- pkg/plugin/implementation/dediregistry/dediregistry.go | 2 +- pkg/plugin/implementation/dediregistry/dediregistry_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/plugin/implementation/dediregistry/dediregistry.go b/pkg/plugin/implementation/dediregistry/dediregistry.go index 8639f53..1cecb2b 100644 --- a/pkg/plugin/implementation/dediregistry/dediregistry.go +++ b/pkg/plugin/implementation/dediregistry/dediregistry.go @@ -168,7 +168,7 @@ func (c *DeDiRegistryClient) Lookup(ctx context.Context, req *model.Subscription 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) + return nil, fmt.Errorf("registry entry with subscriber_id '%s' does not belong to any configured networks (registry.config.allowedNetworkIDs)", detailsSubscriberID) } } diff --git a/pkg/plugin/implementation/dediregistry/dediregistry_test.go b/pkg/plugin/implementation/dediregistry/dediregistry_test.go index 32373b7..cfccc89 100644 --- a/pkg/plugin/implementation/dediregistry/dediregistry_test.go +++ b/pkg/plugin/implementation/dediregistry/dediregistry_test.go @@ -278,7 +278,7 @@ func TestLookup(t *testing.T) { if err == 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)" + expectedErr := "registry entry with subscriber_id 'dev.np2.com' does not belong to any configured networks (registry.config.allowedNetworkIDs)" if err.Error() != expectedErr { t.Errorf("Expected error %q, got %q", expectedErr, err.Error()) } From 1be114188d904dd0d1fe5f3ca7c260eadf8bb9ae Mon Sep 17 00:00:00 2001 From: Nirmal N R Date: Fri, 27 Mar 2026 11:15:29 +0530 Subject: [PATCH 5/8] chore: update registry URL in docs --- pkg/plugin/implementation/dediregistry/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/plugin/implementation/dediregistry/README.md b/pkg/plugin/implementation/dediregistry/README.md index 06cadd9..880cddf 100644 --- a/pkg/plugin/implementation/dediregistry/README.md +++ b/pkg/plugin/implementation/dediregistry/README.md @@ -16,7 +16,7 @@ The DeDi Registry plugin implements the `RegistryLookup` interface to retrieve p registry: id: dediregistry config: - url: "https://dedi-wrapper.example.com/dedi" + url: "https://fabric.nfh.global/registry/dedi" registryName: "subscribers.beckn.one" allowedNetworkIDs: "commerce-network.org/prod,local-commerce.org/production" timeout: 30 @@ -94,7 +94,7 @@ modules: registry: id: dediregistry config: - url: "https://api.bekcn.io/registry/dedi" + url: "https://fabric.nfh.global/registry/dedi" registryName: "subscribers.beckn.one" allowedNetworkIDs: "commerce-network.org/prod,local-commerce.com/production" timeout: 30 From d2d211031ba4f8f79b199944bb7363fdc3cc71de Mon Sep 17 00:00:00 2001 From: Nirmal N R Date: Wed, 1 Apr 2026 18:47:05 +0530 Subject: [PATCH 6/8] fix: update docs and emit deprecation error for old config key --- .../implementation/dediregistry/README.md | 7 +- .../implementation/dediregistry/cmd/plugin.go | 21 +++++- .../dediregistry/cmd/plugin_test.go | 65 ++++++++++++++++++- .../dediregistry/dediregistry_test.go | 8 +-- 4 files changed, 89 insertions(+), 12 deletions(-) diff --git a/pkg/plugin/implementation/dediregistry/README.md b/pkg/plugin/implementation/dediregistry/README.md index 880cddf..6e0a0cd 100644 --- a/pkg/plugin/implementation/dediregistry/README.md +++ b/pkg/plugin/implementation/dediregistry/README.md @@ -44,7 +44,7 @@ registry: GET {url}/lookup/{subscriber_id}/{registryName}/{key_id} ``` -**Example**: `https://api.bekcn.io/registry/dedi/lookup/bpp.example.com/subscribers.beckn.one/76EU7K8oC9EQbXPMRL5uw3KbmTxbg3YDXHvm9nVQpK2eGghASnwHzm` +**Example**: `https://api.beckn.io/registry/dedi/lookup/bpp.example.com/subscribers.beckn.one/76EU7K8oC9EQbXPMRL5uw3KbmTxbg3YDXHvm9nVQpK2eGghASnwHzm` ### Authentication **No authentication required** - Beckn Registry API is public. @@ -64,7 +64,7 @@ GET {url}/lookup/{subscriber_id}/{registryName}/{key_id} "signing_public_key": "384qqkIIpxo71WaJPsWqQNWUDGAFnfnJPxuDmtuBiLo=", "encr_public_key": "test-encr-key" }, - "network_memberships": ["commerce-network.org/prod", "local-commerce.com/production"], + "network_memberships": ["commerce-network.org/prod", "local-commerce.org/production"], "created_at": "2025-10-27T11:45:27.963Z", "updated_at": "2025-10-27T11:46:23.563Z" } @@ -96,7 +96,7 @@ modules: config: url: "https://fabric.nfh.global/registry/dedi" registryName: "subscribers.beckn.one" - allowedNetworkIDs: "commerce-network.org/prod,local-commerce.com/production" + allowedNetworkIDs: "commerce-network.org/prod,local-commerce.org/production" timeout: 30 retry_max: 3 retry_wait_min: 1s @@ -150,6 +150,7 @@ This plugin replaces direct DeDi API integration with the new DeDi Wrapper API f - **Changed**: POST requests → GET requests - **Updated**: Response structure parsing (`data.details` object) - **Updated**: Optional allowlist validation now checks `data.network_memberships` +- **Deprecated**: `allowedParentNamespaces` config key in favor of `allowedNetworkIDs` (plugin now errors until the config is updated to full network membership IDs) - **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 0ac37a1..3714fb1 100644 --- a/pkg/plugin/implementation/dediregistry/cmd/plugin.go +++ b/pkg/plugin/implementation/dediregistry/cmd/plugin.go @@ -3,6 +3,7 @@ package main import ( "context" "errors" + "fmt" "strconv" "strings" @@ -35,9 +36,11 @@ func (d dediRegistryProvider) New(ctx context.Context, config map[string]string) } } - if rawNetworkIDs, exists := config["allowedNetworkIDs"]; exists && rawNetworkIDs != "" { - dediConfig.AllowedNetworkIDs = parseAllowedNetworkIDs(rawNetworkIDs) + allowedNetworkIDs, err := resolveAllowedNetworkIDs(ctx, config) + if err != nil { + return nil, nil, err } + dediConfig.AllowedNetworkIDs = allowedNetworkIDs log.Debugf(ctx, "DeDi Registry config mapped: %+v", dediConfig) @@ -64,5 +67,19 @@ func parseAllowedNetworkIDs(raw string) []string { return networkIDs } +func resolveAllowedNetworkIDs(ctx context.Context, config map[string]string) ([]string, error) { + if rawParentNamespaces, exists := config["allowedParentNamespaces"]; exists && rawParentNamespaces != "" { + if _, hasAllowedNetworkIDs := config["allowedNetworkIDs"]; !hasAllowedNetworkIDs { + return nil, fmt.Errorf("config key 'allowedParentNamespaces' is no longer supported; use 'allowedNetworkIDs' with full network IDs") + } + } + + if rawNetworkIDs, exists := config["allowedNetworkIDs"]; exists && rawNetworkIDs != "" { + return parseAllowedNetworkIDs(rawNetworkIDs), nil + } + + return nil, nil +} + // Provider is the exported plugin instance var Provider = dediRegistryProvider{} diff --git a/pkg/plugin/implementation/dediregistry/cmd/plugin_test.go b/pkg/plugin/implementation/dediregistry/cmd/plugin_test.go index 573f8da..083d30f 100644 --- a/pkg/plugin/implementation/dediregistry/cmd/plugin_test.go +++ b/pkg/plugin/implementation/dediregistry/cmd/plugin_test.go @@ -91,10 +91,53 @@ func TestDediRegistryProvider_New_InvalidTimeout(t *testing.T) { } func TestParseAllowedNetworkIDs(t *testing.T) { - got := parseAllowedNetworkIDs("commerce-network/subscriber-references, retail-network/subscriber-references, ,") + got := parseAllowedNetworkIDs("commerce-network.org/prod, local-commerce.org/production, ,") want := []string{ - "commerce-network/subscriber-references", - "retail-network/subscriber-references", + "commerce-network.org/prod", + "local-commerce.org/production", + } + + 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] to preserve input order as %q, got %q", i, want[i], got[i]) + } + } +} + +func TestResolveAllowedNetworkIDs_DeprecatedAllowedParentNamespacesErrorsWithoutAllowedNetworkIDs(t *testing.T) { + ctx := context.Background() + config := map[string]string{ + "allowedParentNamespaces": "commerce-network.org/prod, local-commerce.org/production", + } + + got, err := resolveAllowedNetworkIDs(ctx, config) + if err == nil { + t.Fatal("expected error when only allowedParentNamespaces is configured") + } + if got != nil { + t.Fatalf("expected nil allowed network IDs on error, got %#v", got) + } +} + +func TestResolveAllowedNetworkIDs_AllowedNetworkIDsTakesPrecedence(t *testing.T) { + ctx := context.Background() + config := map[string]string{ + "url": "https://test.com/dedi", + "registryName": "subscribers.beckn.one", + "allowedParentNamespaces": "deprecated-network.org/legacy", + "allowedNetworkIDs": "commerce-network.org/prod, local-commerce.org/production", + } + + got, err := resolveAllowedNetworkIDs(ctx, config) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + want := []string{ + "commerce-network.org/prod", + "local-commerce.org/production", } if len(got) != len(want) { @@ -107,6 +150,22 @@ func TestParseAllowedNetworkIDs(t *testing.T) { } } +func TestDediRegistryProvider_New_DeprecatedAllowedParentNamespacesErrorsWithoutAllowedNetworkIDs(t *testing.T) { + ctx := context.Background() + provider := dediRegistryProvider{} + + config := map[string]string{ + "url": "https://test.com/dedi", + "registryName": "subscribers.beckn.one", + "allowedParentNamespaces": "commerce-network.org", + } + + _, _, err := provider.New(ctx, config) + if err == nil { + t.Fatal("expected New() to error when only allowedParentNamespaces is configured") + } +} + func TestDediRegistryProvider_New_NilContext(t *testing.T) { provider := dediRegistryProvider{} diff --git a/pkg/plugin/implementation/dediregistry/dediregistry_test.go b/pkg/plugin/implementation/dediregistry/dediregistry_test.go index cfccc89..4f6a0cf 100644 --- a/pkg/plugin/implementation/dediregistry/dediregistry_test.go +++ b/pkg/plugin/implementation/dediregistry/dediregistry_test.go @@ -140,7 +140,7 @@ func TestLookup(t *testing.T) { "signing_public_key": "384qqkIIpxo71WaJPsWqQNWUDGAFnfnJPxuDmtuBiLo=", "encr_public_key": "test-encr-key", }, - "network_memberships": []string{"commerce-network/subscriber-references", "local-commerce/subscriber-references"}, + "network_memberships": []string{"commerce-network.org/prod", "local-commerce.org/production"}, "created_at": "2025-10-27T11:45:27.963Z", "updated_at": "2025-10-27T11:46:23.563Z", }, @@ -204,7 +204,7 @@ func TestLookup(t *testing.T) { "subscriber_id": "dev.np2.com", "signing_public_key": "384qqkIIpxo71WaJPsWqQNWUDGAFnfnJPxuDmtuBiLo=", }, - "network_memberships": []string{"commerce-network/subscriber-references", "local-commerce/subscriber-references"}, + "network_memberships": []string{"commerce-network.org/prod", "local-commerce.org/production"}, }, } w.Header().Set("Content-Type", "application/json") @@ -215,7 +215,7 @@ func TestLookup(t *testing.T) { config := &Config{ URL: server.URL + "/dedi", RegistryName: "subscribers.beckn.one", - AllowedNetworkIDs: []string{"commerce-network/subscriber-references"}, + AllowedNetworkIDs: []string{"commerce-network.org/prod"}, } client, closer, err := New(ctx, config) @@ -248,7 +248,7 @@ func TestLookup(t *testing.T) { "subscriber_id": "dev.np2.com", "signing_public_key": "384qqkIIpxo71WaJPsWqQNWUDGAFnfnJPxuDmtuBiLo=", }, - "network_memberships": []string{"local-commerce/subscriber-references"}, + "network_memberships": []string{"local-commerce.org/production"}, }, } w.Header().Set("Content-Type", "application/json") From db330663bde2b088437106906c50c67fa9d98e12 Mon Sep 17 00:00:00 2001 From: Nirmal N R Date: Wed, 1 Apr 2026 18:54:31 +0530 Subject: [PATCH 7/8] feat: enhance network membership validation and add tests for extractStringSlice function --- .../dediregistry/dediregistry.go | 7 +- .../dediregistry/dediregistry_test.go | 72 +++++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/pkg/plugin/implementation/dediregistry/dediregistry.go b/pkg/plugin/implementation/dediregistry/dediregistry.go index 1cecb2b..787e53a 100644 --- a/pkg/plugin/implementation/dediregistry/dediregistry.go +++ b/pkg/plugin/implementation/dediregistry/dediregistry.go @@ -165,7 +165,7 @@ func (c *DeDiRegistryClient) Lookup(ctx context.Context, req *model.Subscription detailsSubscriberID, _ := details["subscriber_id"].(string) // Validate network memberships if configured. - networkMemberships := extractStringSlice(data["network_memberships"]) + networkMemberships := extractStringSlice(ctx, "network_memberships", 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 networks (registry.config.allowedNetworkIDs)", detailsSubscriberID) @@ -210,7 +210,7 @@ func parseTime(timeStr string) time.Time { return parsedTime } -func extractStringSlice(value interface{}) []string { +func extractStringSlice(ctx context.Context, fieldName string, value interface{}) []string { if value == nil { return nil } @@ -219,9 +219,10 @@ func extractStringSlice(value interface{}) []string { return v case []interface{}: out := make([]string, 0, len(v)) - for _, item := range v { + for i, item := range v { str, ok := item.(string) if !ok { + log.Warnf(ctx, "Ignoring invalid %s entry at index %d during registry lookup: expected a string network ID, got %T. This entry will not be considered for allowlist validation.", fieldName, i, item) continue } if str != "" { diff --git a/pkg/plugin/implementation/dediregistry/dediregistry_test.go b/pkg/plugin/implementation/dediregistry/dediregistry_test.go index 4f6a0cf..6633298 100644 --- a/pkg/plugin/implementation/dediregistry/dediregistry_test.go +++ b/pkg/plugin/implementation/dediregistry/dediregistry_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "net/http" "net/http/httptest" + "reflect" "testing" "time" @@ -111,6 +112,33 @@ func TestNew(t *testing.T) { }) } +func TestExtractStringSlice(t *testing.T) { + ctx := context.Background() + + t.Run("returns strings from []string", func(t *testing.T) { + got := extractStringSlice(ctx, "network_memberships", []string{"commerce-network.org/prod", "local-commerce.org/production"}) + want := []string{"commerce-network.org/prod", "local-commerce.org/production"} + if !reflect.DeepEqual(got, want) { + t.Fatalf("expected %v, got %v", want, got) + } + }) + + t.Run("filters non-string entries from []interface{}", func(t *testing.T) { + got := extractStringSlice(ctx, "network_memberships", []interface{}{"commerce-network.org/prod", 42, true, "", "local-commerce.org/production"}) + want := []string{"commerce-network.org/prod", "local-commerce.org/production"} + if !reflect.DeepEqual(got, want) { + t.Fatalf("expected %v, got %v", want, got) + } + }) + + t.Run("returns nil for unsupported type", func(t *testing.T) { + got := extractStringSlice(ctx, "network_memberships", "commerce-network.org/prod") + if got != nil { + t.Fatalf("expected nil, got %v", got) + } + }) +} + func TestLookup(t *testing.T) { ctx := context.Background() @@ -284,6 +312,50 @@ func TestLookup(t *testing.T) { } }) + t.Run("allowed network IDs match with mixed network membership types", 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", + "data": map[string]interface{}{ + "details": map[string]interface{}{ + "url": "http://dev.np2.com/beckn/bap", + "type": "BAP", + "domain": "energy", + "subscriber_id": "dev.np2.com", + "signing_public_key": "384qqkIIpxo71WaJPsWqQNWUDGAFnfnJPxuDmtuBiLo=", + }, + "network_memberships": []interface{}{123, "commerce-network.org/prod", map[string]interface{}{"invalid": true}}, + }, + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + })) + defer server.Close() + + config := &Config{ + URL: server.URL + "/dedi", + RegistryName: "subscribers.beckn.one", + AllowedNetworkIDs: []string{"commerce-network.org/prod"}, + } + + client, closer, err := New(ctx, config) + if err != nil { + t.Fatalf("New() error = %v", err) + } + defer closer() + + req := &model.Subscription{ + Subscriber: model.Subscriber{ + SubscriberID: "dev.np2.com", + }, + KeyID: "test-key-id", + } + _, err = client.Lookup(ctx, req) + if err != nil { + t.Errorf("Lookup() error = %v", err) + } + }) + // Test empty subscriber ID t.Run("empty subscriber ID", func(t *testing.T) { config := &Config{ From 8997b0e80227e46bc5e4c519caadfc924d1edc45 Mon Sep 17 00:00:00 2001 From: Nirmal N R Date: Thu, 2 Apr 2026 10:54:38 +0530 Subject: [PATCH 8/8] refactor: remove unused ctx from resolveAllowedNetworkIDs function and related tests --- pkg/plugin/implementation/dediregistry/cmd/plugin.go | 4 ++-- pkg/plugin/implementation/dediregistry/cmd/plugin_test.go | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg/plugin/implementation/dediregistry/cmd/plugin.go b/pkg/plugin/implementation/dediregistry/cmd/plugin.go index 3714fb1..a70e3a6 100644 --- a/pkg/plugin/implementation/dediregistry/cmd/plugin.go +++ b/pkg/plugin/implementation/dediregistry/cmd/plugin.go @@ -36,7 +36,7 @@ func (d dediRegistryProvider) New(ctx context.Context, config map[string]string) } } - allowedNetworkIDs, err := resolveAllowedNetworkIDs(ctx, config) + allowedNetworkIDs, err := resolveAllowedNetworkIDs(config) if err != nil { return nil, nil, err } @@ -67,7 +67,7 @@ func parseAllowedNetworkIDs(raw string) []string { return networkIDs } -func resolveAllowedNetworkIDs(ctx context.Context, config map[string]string) ([]string, error) { +func resolveAllowedNetworkIDs(config map[string]string) ([]string, error) { if rawParentNamespaces, exists := config["allowedParentNamespaces"]; exists && rawParentNamespaces != "" { if _, hasAllowedNetworkIDs := config["allowedNetworkIDs"]; !hasAllowedNetworkIDs { return nil, fmt.Errorf("config key 'allowedParentNamespaces' is no longer supported; use 'allowedNetworkIDs' with full network IDs") diff --git a/pkg/plugin/implementation/dediregistry/cmd/plugin_test.go b/pkg/plugin/implementation/dediregistry/cmd/plugin_test.go index 083d30f..4dafe8b 100644 --- a/pkg/plugin/implementation/dediregistry/cmd/plugin_test.go +++ b/pkg/plugin/implementation/dediregistry/cmd/plugin_test.go @@ -108,12 +108,11 @@ func TestParseAllowedNetworkIDs(t *testing.T) { } func TestResolveAllowedNetworkIDs_DeprecatedAllowedParentNamespacesErrorsWithoutAllowedNetworkIDs(t *testing.T) { - ctx := context.Background() config := map[string]string{ "allowedParentNamespaces": "commerce-network.org/prod, local-commerce.org/production", } - got, err := resolveAllowedNetworkIDs(ctx, config) + got, err := resolveAllowedNetworkIDs(config) if err == nil { t.Fatal("expected error when only allowedParentNamespaces is configured") } @@ -123,7 +122,6 @@ func TestResolveAllowedNetworkIDs_DeprecatedAllowedParentNamespacesErrorsWithout } func TestResolveAllowedNetworkIDs_AllowedNetworkIDsTakesPrecedence(t *testing.T) { - ctx := context.Background() config := map[string]string{ "url": "https://test.com/dedi", "registryName": "subscribers.beckn.one", @@ -131,7 +129,7 @@ func TestResolveAllowedNetworkIDs_AllowedNetworkIDsTakesPrecedence(t *testing.T) "allowedNetworkIDs": "commerce-network.org/prod, local-commerce.org/production", } - got, err := resolveAllowedNetworkIDs(ctx, config) + got, err := resolveAllowedNetworkIDs(config) if err != nil { t.Fatalf("expected no error, got %v", err) }