fix: shared renamed to pkg, formatting changes

This commit is contained in:
rupinder-syngh
2025-03-20 11:33:20 +05:30
parent ed046d9ce4
commit a606d385f4
16 changed files with 14 additions and 40 deletions

View File

@@ -1,16 +0,0 @@
package definition
import "context"
// Publisher defines the general publisher interface for messaging plugins.
type Publisher interface {
// Publish sends a message (as a byte slice) using the underlying messaging system.
Publish(ctx context.Context, msg []byte) error
Close() error // Important for releasing resources
}
type PublisherProvider interface {
// New initializes a new publisher instance with the given configuration
New(ctx context.Context, config map[string]string) (Publisher, error)
}

View File

@@ -1,22 +0,0 @@
package definition
import "context"
// Verifier defines the method for verifying signatures.
type Verifier interface {
// Verify checks the validity of the signature for the given body.
Verify(ctx context.Context, body []byte, header []byte, publicKeyBase64 string) (bool, error)
Close() error // Close for releasing resources
}
// VerifierProvider initializes a new Verifier instance with the given config.
type VerifierProvider interface {
// New creates a new Verifier instance based on the provided config.
New(ctx context.Context, config map[string]string) (Verifier, func() error, error)
}
// PublicKeyManager is the interface for key management plugin.
type PublicKeyManager interface {
// PublicKey retrieves the public key for the given subscriberID and keyID.
PublicKey(ctx context.Context, subscriberID string, keyID string) (string, error)
}

View File

@@ -1,24 +0,0 @@
package definition
import "context"
// Signer defines the method for signing.
type Signer interface {
// Sign generates a signature for the given body and privateKeyBase64.
// The signature is created with the given timestamps: createdAt (signature creation time)
// and expiresAt (signature expiration time).
Sign(ctx context.Context, body []byte, privateKeyBase64 string, createdAt, expiresAt int64) (string, error)
Close() error // Close for releasing resources
}
// SignerProvider initializes a new signer instance with the given config.
type SignerProvider interface {
// New creates a new signer instance based on the provided config.
New(ctx context.Context, config map[string]string) (Signer, func() error, error)
}
// PrivateKeyManager is the interface for key management plugin.
type PrivateKeyManager interface {
// PrivateKey retrieves the private key for the given subscriberID and keyID.
PrivateKey(ctx context.Context, subscriberID string, keyID string) (string, error)
}

View File

@@ -1,51 +0,0 @@
package main
import (
"context"
"fmt"
"github.com/beckn/beckn-onix/shared/plugin/definition"
"github.com/beckn/beckn-onix/shared/plugin/implementation/publisher"
)
// Returns error if required fields are missing.
func validateConfig(config map[string]string) (*publisher.Config, error) {
if config == nil {
return nil, fmt.Errorf("config cannot be nil")
}
project, ok := config["project"]
if !ok || project == "" {
return nil, fmt.Errorf("project ID is required")
}
topic, ok := config["topic"]
if !ok || topic == "" {
return nil, fmt.Errorf("topic ID is required")
}
return &publisher.Config{
ProjectID: project,
TopicID: topic,
}, nil
}
// PublisherProvider implements the definition.PublisherProvider interface.
type PublisherProvider struct{}
// New creates a new Publisher instance.
func (p PublisherProvider) New(ctx context.Context, config map[string]string) (definition.Publisher, error) {
if ctx == nil {
return nil, fmt.Errorf("context cannot be nil")
}
cfg, err := validateConfig(config)
if err != nil {
return nil, fmt.Errorf("invalid config: %w", err)
}
return publisher.New(ctx, cfg)
}
// Provider is the exported symbol that the plugin manager will look for.
var Provider definition.PublisherProvider = PublisherProvider{}

View File

@@ -1,74 +0,0 @@
package main
import (
"context"
"testing"
"github.com/beckn/beckn-onix/shared/plugin/definition"
)
// MockPublisher is a mock implementation of the definition.Publisher interface for testing.
type MockPublisher struct{}
func (m *MockPublisher) Publish(ctx context.Context, msg []byte) error {
return nil
}
func (m *MockPublisher) Close() error {
return nil
}
// TestValidateConfig tests the validateConfig function.
func TestValidateConfig(t *testing.T) {
tests := []struct {
name string
config map[string]string
wantErr bool
}{
{"Valid config", map[string]string{"project": "test-project", "topic": "test-topic"}, false},
{"Nil config", nil, true},
{"Missing project", map[string]string{"topic": "test-topic"}, true},
{"Missing topic", map[string]string{"project": "test-project"}, true},
{"Empty project", map[string]string{"project": "", "topic": "test-topic"}, true},
{"Empty topic", map[string]string{"project": "test-project", "topic": ""}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := validateConfig(tt.config)
if (err != nil) != tt.wantErr {
t.Errorf("validateConfig() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
// TestPublisherProviderNew tests the New method of PublisherProvider.
func TestPublisherProviderNew(t *testing.T) {
tests := []struct {
name string
ctx context.Context
config map[string]string
wantErr bool
}{
{"Nil context", nil, map[string]string{"project": "test-project", "topic": "test-topic"}, true},
{"Invalid config", context.Background(), map[string]string{"project": "", "topic": "test-topic"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
provider := PublisherProvider{}
_, err := provider.New(tt.ctx, tt.config)
if (err != nil) != tt.wantErr {
t.Errorf("PublisherProvider.New() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
// TestProviderImplementation verifies that the Provider is correctly initialized.
func TestProviderImplementation(t *testing.T) {
if _, ok := interface{}(Provider).(definition.PublisherProvider); !ok {
t.Errorf("Provider does not implement definition.PublisherProvider")
}
}

View File

@@ -1,95 +0,0 @@
package publisher
import (
"context"
"errors"
"fmt"
"log"
"strings"
"cloud.google.com/go/pubsub"
"google.golang.org/api/option"
)
// Config holds the Pub/Sub configuration.
type Config struct {
ProjectID string
TopicID string
}
// Publisher is a concrete implementation of a Google Cloud Pub/Sub publisher.
type Publisher struct {
client *pubsub.Client
topic *pubsub.Topic
config *Config
}
var (
ErrProjectMissing = errors.New("missing required field 'Project'")
ErrTopicMissing = errors.New("missing required field 'Topic'")
ErrEmptyConfig = errors.New("empty config")
)
func validate(cfg *Config) error {
if cfg == nil {
return ErrEmptyConfig
}
if strings.TrimSpace(cfg.ProjectID) == "" {
return ErrProjectMissing
}
if strings.TrimSpace(cfg.TopicID) == "" {
return ErrTopicMissing
}
return nil
}
// New initializes a new Publisher instance.
// It creates a real pubsub.Client and then calls NewWithClient.
func New(ctx context.Context, config *Config, opts ...option.ClientOption) (*Publisher, error) {
if err := validate(config); err != nil {
return nil, err
}
client, err := pubsub.NewClient(ctx, config.ProjectID, opts...)
if err != nil {
return nil, fmt.Errorf("failed to create pubsub client: %w", err)
}
topic := client.Topic(config.TopicID)
exists, err := topic.Exists(ctx)
if err != nil {
_ = client.Close()
return nil, fmt.Errorf("failed to check topic existence: %w", err)
}
if !exists {
_ = client.Close()
return nil, fmt.Errorf("topic %s does not exist", config.TopicID)
}
return &Publisher{
client: client,
topic: topic,
config: config,
}, nil
}
// Publish sends a message to Google Cloud Pub/Sub.
func (p *Publisher) Publish(ctx context.Context, msg []byte) error {
pubsubMsg := &pubsub.Message{
Data: msg,
}
result := p.topic.Publish(ctx, pubsubMsg)
id, err := result.Get(ctx)
if err != nil {
return fmt.Errorf("failed to publish message: %w", err)
}
log.Printf("Published message with ID: %s\n", id)
return nil
}
// Close closes the underlying Pub/Sub client.
func (p *Publisher) Close() error {
return p.client.Close()
}

View File

@@ -1,123 +0,0 @@
package publisher
import (
"context"
"errors"
"testing"
"cloud.google.com/go/pubsub"
)
// Helper to create a test publisher directly
func createTestPublisher() *Publisher {
client, _ := pubsub.NewClient(context.Background(), "test-project")
topic := client.Topic("test-topic")
return &Publisher{
client: client,
topic: topic,
config: &Config{ProjectID: "test-project", TopicID: "test-topic"},
}
}
// TestValidate tests the validate function
func TestValidate(t *testing.T) {
tests := []struct {
name string
config *Config
wantErr error
}{
{
name: "Valid config",
config: &Config{ProjectID: "test-project", TopicID: "test-topic"},
wantErr: nil,
},
{
name: "Nil config",
config: nil,
wantErr: ErrEmptyConfig,
},
{
name: "Empty project ID",
config: &Config{ProjectID: "", TopicID: "test-topic"},
wantErr: ErrProjectMissing,
},
{
name: "Whitespace project ID",
config: &Config{ProjectID: " ", TopicID: "test-topic"},
wantErr: ErrProjectMissing,
},
{
name: "Empty topic ID",
config: &Config{ProjectID: "test-project", TopicID: ""},
wantErr: ErrTopicMissing,
},
{
name: "Whitespace topic ID",
config: &Config{ProjectID: "test-project", TopicID: " "},
wantErr: ErrTopicMissing,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validate(tt.config)
if !errors.Is(err, tt.wantErr) {
t.Errorf("validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
// TestNew tests the New function with validation errors only
// We can't easily test the pubsub client creation parts without complex mocks
func TestNew(t *testing.T) {
tests := []struct {
name string
ctx context.Context
config *Config
wantErr bool
}{
{
// Should fail validation
name: "Empty project ID",
ctx: context.Background(),
config: &Config{ProjectID: "", TopicID: "test-topic"},
wantErr: true,
},
{
// Should fail validation
name: "Empty topic ID",
ctx: context.Background(),
config: &Config{ProjectID: "test-project", TopicID: ""},
wantErr: true,
},
{
// Should fail due to nil context
name: "Nil context",
ctx: nil,
config: &Config{ProjectID: "test-project", TopicID: "test-topic"},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := New(tt.ctx, tt.config)
if (err != nil) != tt.wantErr {
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
// TestPublish tests the behavior of the Publisher.Publish method
// Since we can't easily mock the pubsub client and topic, we'll skip the actual test
func TestPublish(t *testing.T) {
t.Skip("Requires real pubsub client or complex mocking")
}
// TestClose tests the behavior of the Publisher.Close method
// Since we can't easily mock the pubsub client, we'll skip the actual test
func TestClose(t *testing.T) {
t.Skip("Requires real pubsub client or complex mocking")
}

View File

@@ -1,26 +0,0 @@
package main
import (
"context"
"errors"
"github.com/beckn/beckn-onix/shared/plugin/definition"
plugin "github.com/beckn/beckn-onix/shared/plugin/definition"
verifier "github.com/beckn/beckn-onix/shared/plugin/implementation/signVerifier"
)
// VerifierProvider provides instances of Verifier.
type VerifierProvider struct{}
// New initializes a new Verifier instance.
func (vp VerifierProvider) New(ctx context.Context, config map[string]string) (plugin.Verifier, func() error, error) {
if ctx == nil {
return nil, nil, errors.New("context cannot be nil")
}
return verifier.New(ctx, &verifier.Config{})
}
// Provider is the exported symbol that the plugin manager will look for.
var Provider definition.VerifierProvider = VerifierProvider{}

View File

@@ -1,89 +0,0 @@
package main
import (
"context"
"testing"
)
// TestVerifierProviderSuccess tests successful creation of a verifier.
func TestVerifierProviderSuccess(t *testing.T) {
provider := VerifierProvider{}
tests := []struct {
name string
ctx context.Context
config map[string]string
}{
{
name: "Successful creation",
ctx: context.Background(),
config: map[string]string{},
},
{
name: "Nil context",
ctx: context.TODO(),
config: map[string]string{},
},
{
name: "Empty config",
ctx: context.Background(),
config: map[string]string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
verifier, close, err := provider.New(tt.ctx, tt.config)
if err != nil {
t.Fatalf("Expected no error, but got: %v", err)
}
if verifier == nil {
t.Fatal("Expected verifier instance to be non-nil")
}
if close != nil {
if err := close(); err != nil {
t.Fatalf("Test %q failed: cleanup function returned an error: %v", tt.name, err)
}
}
})
}
}
// TestVerifierProviderFailure tests cases where verifier creation should fail.
func TestVerifierProviderFailure(t *testing.T) {
provider := VerifierProvider{}
tests := []struct {
name string
ctx context.Context
config map[string]string
wantErr bool
}{
{
name: "Nil context failure",
ctx: nil,
config: map[string]string{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
verifierInstance, close, err := provider.New(tt.ctx, tt.config)
if (err != nil) != tt.wantErr {
t.Fatalf("Expected error: %v, but got: %v", tt.wantErr, err)
}
if verifierInstance != nil {
t.Fatal("Expected verifier instance to be nil")
}
if close != nil {
if err := close(); err != nil {
t.Fatalf("Test %q failed: cleanup function returned an error: %v", tt.name, err)
}
}
})
}
}

View File

@@ -1,120 +0,0 @@
package verifier
import (
"context"
"crypto/ed25519"
"encoding/base64"
"fmt"
"strconv"
"strings"
"time"
"golang.org/x/crypto/blake2b"
)
// Config struct for Verifier.
type Config struct {
}
// Verifier implements the Verifier interface.
type Verifier struct {
config *Config
}
// New creates a new Verifier instance.
func New(ctx context.Context, config *Config) (*Verifier, func() error, error) {
v := &Verifier{config: config}
return v, v.Close, nil
}
// Verify checks the signature for the given payload and public key.
func (v *Verifier) Verify(ctx context.Context, body []byte, header []byte, publicKeyBase64 string) (bool, error) {
createdTimestamp, expiredTimestamp, signature, err := parseAuthHeader(string(header))
if err != nil {
// TODO: Return appropriate error code when Error Code Handling Module is ready
return false, fmt.Errorf("error parsing header: %w", err)
}
signatureBytes, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
// TODO: Return appropriate error code when Error Code Handling Module is ready
return false, fmt.Errorf("error decoding signature: %w", err)
}
currentTime := time.Now().Unix()
if createdTimestamp > currentTime || currentTime > expiredTimestamp {
// TODO: Return appropriate error code when Error Code Handling Module is ready
return false, fmt.Errorf("signature is expired or not yet valid")
}
createdTime := time.Unix(createdTimestamp, 0)
expiredTime := time.Unix(expiredTimestamp, 0)
signingString := hash(body, createdTime.Unix(), expiredTime.Unix())
decodedPublicKey, err := base64.StdEncoding.DecodeString(publicKeyBase64)
if err != nil {
// TODO: Return appropriate error code when Error Code Handling Module is ready
return false, fmt.Errorf("error decoding public key: %w", err)
}
if !ed25519.Verify(ed25519.PublicKey(decodedPublicKey), []byte(signingString), signatureBytes) {
// TODO: Return appropriate error code when Error Code Handling Module is ready
return false, fmt.Errorf("signature verification failed")
}
return true, nil
}
// parseAuthHeader extracts signature values from the Authorization header.
func parseAuthHeader(header string) (int64, int64, string, error) {
header = strings.TrimPrefix(header, "Signature ")
parts := strings.Split(header, ",")
signatureMap := make(map[string]string)
for _, part := range parts {
keyValue := strings.SplitN(strings.TrimSpace(part), "=", 2)
if len(keyValue) == 2 {
key := strings.TrimSpace(keyValue[0])
value := strings.Trim(keyValue[1], "\"")
signatureMap[key] = value
}
}
createdTimestamp, err := strconv.ParseInt(signatureMap["created"], 10, 64)
if err != nil {
// TODO: Return appropriate error code when Error Code Handling Module is ready
return 0, 0, "", fmt.Errorf("invalid created timestamp: %w", err)
}
expiredTimestamp, err := strconv.ParseInt(signatureMap["expires"], 10, 64)
if err != nil {
// TODO: Return appropriate error code when Error Code Handling Module is ready
return 0, 0, "", fmt.Errorf("invalid expires timestamp: %w", err)
}
signature := signatureMap["signature"]
if signature == "" {
// TODO: Return appropriate error code when Error Code Handling Module is ready
return 0, 0, "", fmt.Errorf("signature missing in header")
}
return createdTimestamp, expiredTimestamp, signature, nil
}
// hash constructs a signing string for verification.
func hash(payload []byte, createdTimestamp, expiredTimestamp int64) string {
hasher, _ := blake2b.New512(nil)
hasher.Write(payload)
hashSum := hasher.Sum(nil)
digestB64 := base64.StdEncoding.EncodeToString(hashSum)
return fmt.Sprintf("(created): %d\n(expires): %d\ndigest: BLAKE-512=%s", createdTimestamp, expiredTimestamp, digestB64)
}
// Close releases resources (mock implementation returning nil).
func (v *Verifier) Close() error {
return nil
}

View File

@@ -1,153 +0,0 @@
package verifier
import (
"context"
"crypto/ed25519"
"encoding/base64"
"strconv"
"testing"
"time"
)
// generateTestKeyPair generates a new ED25519 key pair for testing.
func generateTestKeyPair() (string, string) {
publicKey, privateKey, _ := ed25519.GenerateKey(nil)
return base64.StdEncoding.EncodeToString(privateKey), base64.StdEncoding.EncodeToString(publicKey)
}
// signTestData creates a valid signature for test cases.
func signTestData(privateKeyBase64 string, body []byte, createdAt, expiresAt int64) string {
privateKeyBytes, _ := base64.StdEncoding.DecodeString(privateKeyBase64)
privateKey := ed25519.PrivateKey(privateKeyBytes)
signingString := hash(body, createdAt, expiresAt)
signature := ed25519.Sign(privateKey, []byte(signingString))
return base64.StdEncoding.EncodeToString(signature)
}
// TestVerifySuccessCases tests all valid signature verification cases.
func TestVerifySuccess(t *testing.T) {
privateKeyBase64, publicKeyBase64 := generateTestKeyPair()
tests := []struct {
name string
body []byte
createdAt int64
expiresAt int64
}{
{
name: "Valid Signature",
body: []byte("Test Payload"),
createdAt: time.Now().Unix(),
expiresAt: time.Now().Unix() + 3600,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
signature := signTestData(privateKeyBase64, tt.body, tt.createdAt, tt.expiresAt)
header := `Signature created="` + strconv.FormatInt(tt.createdAt, 10) +
`", expires="` + strconv.FormatInt(tt.expiresAt, 10) +
`", signature="` + signature + `"`
verifier, close, _ := New(context.Background(), &Config{})
valid, err := verifier.Verify(context.Background(), tt.body, []byte(header), publicKeyBase64)
if err != nil {
t.Fatalf("Expected no error, but got: %v", err)
}
if !valid {
t.Fatal("Expected signature verification to succeed")
}
if close != nil {
if err := close(); err != nil {
t.Fatalf("Test %q failed: cleanup function returned an error: %v", tt.name, err)
}
}
})
}
}
// TestVerifyFailureCases tests all invalid signature verification cases.
func TestVerifyFailure(t *testing.T) {
privateKeyBase64, publicKeyBase64 := generateTestKeyPair()
_, wrongPublicKeyBase64 := generateTestKeyPair()
tests := []struct {
name string
body []byte
header string
pubKey string
}{
{
name: "Missing Authorization Header",
body: []byte("Test Payload"),
header: "",
pubKey: publicKeyBase64,
},
{
name: "Malformed Header",
body: []byte("Test Payload"),
header: `InvalidSignature created="wrong"`,
pubKey: publicKeyBase64,
},
{
name: "Invalid Base64 Signature",
body: []byte("Test Payload"),
header: `Signature created="` + strconv.FormatInt(time.Now().Unix(), 10) +
`", expires="` + strconv.FormatInt(time.Now().Unix()+3600, 10) +
`", signature="!!INVALIDBASE64!!"`,
pubKey: publicKeyBase64,
},
{
name: "Expired Signature",
body: []byte("Test Payload"),
header: `Signature created="` + strconv.FormatInt(time.Now().Unix()-7200, 10) +
`", expires="` + strconv.FormatInt(time.Now().Unix()-3600, 10) +
`", signature="` + signTestData(privateKeyBase64, []byte("Test Payload"), time.Now().Unix()-7200, time.Now().Unix()-3600) + `"`,
pubKey: publicKeyBase64,
},
{
name: "Invalid Public Key",
body: []byte("Test Payload"),
header: `Signature created="` + strconv.FormatInt(time.Now().Unix(), 10) +
`", expires="` + strconv.FormatInt(time.Now().Unix()+3600, 10) +
`", signature="` + signTestData(privateKeyBase64, []byte("Test Payload"), time.Now().Unix(), time.Now().Unix()+3600) + `"`,
pubKey: wrongPublicKeyBase64,
},
{
name: "Invalid Expires Timestamp",
body: []byte("Test Payload"),
header: `Signature created="` + strconv.FormatInt(time.Now().Unix(), 10) +
`", expires="invalid_timestamp"`,
pubKey: publicKeyBase64,
},
{
name: "Signature Missing in Headers",
body: []byte("Test Payload"),
header: `Signature created="` + strconv.FormatInt(time.Now().Unix(), 10) +
`", expires="` + strconv.FormatInt(time.Now().Unix()+3600, 10) + `"`,
pubKey: publicKeyBase64,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
verifier, close, _ := New(context.Background(), &Config{})
valid, err := verifier.Verify(context.Background(), tt.body, []byte(tt.header), tt.pubKey)
if err == nil {
t.Fatal("Expected an error but got none")
}
if valid {
t.Fatal("Expected verification to fail")
}
if close != nil {
if err := close(); err != nil {
t.Fatalf("Test %q failed: cleanup function returned an error: %v", tt.name, err)
}
}
})
}
}

View File

@@ -1,24 +0,0 @@
package main
import (
"context"
"errors"
"github.com/beckn/beckn-onix/shared/plugin/definition"
"github.com/beckn/beckn-onix/shared/plugin/implementation/signer"
)
// SignerProvider implements the definition.SignerProvider interface.
type SignerProvider struct{}
// New creates a new Signer instance using the provided configuration.
func (p SignerProvider) New(ctx context.Context, config map[string]string) (definition.Signer, func() error, error) {
if ctx == nil {
return nil, nil, errors.New("context cannot be nil")
}
return signer.New(ctx, &signer.Config{})
}
// Provider is the exported symbol that the plugin manager will look for.
var Provider definition.SignerProvider = SignerProvider{}

View File

@@ -1,101 +0,0 @@
package main
import (
"context"
"testing"
)
// TestSignerProviderSuccess verifies successful scenarios for SignerProvider.
func TestSignerProviderSuccess(t *testing.T) {
provider := SignerProvider{}
successTests := []struct {
name string
ctx context.Context
config map[string]string
}{
{
name: "Valid Config",
ctx: context.Background(),
config: map[string]string{},
},
{
name: "Unexpected Config Key",
ctx: context.Background(),
config: map[string]string{"unexpected_key": "some_value"},
},
{
name: "Empty Config",
ctx: context.Background(),
config: map[string]string{},
},
{
name: "Config with empty TTL",
ctx: context.Background(),
config: map[string]string{"ttl": ""},
},
{
name: "Config with negative TTL",
ctx: context.Background(),
config: map[string]string{"ttl": "-100"},
},
{
name: "Config with non-numeric TTL",
ctx: context.Background(),
config: map[string]string{"ttl": "not_a_number"},
},
}
for _, tt := range successTests {
t.Run(tt.name, func(t *testing.T) {
signer, close, err := provider.New(tt.ctx, tt.config)
if err != nil {
t.Fatalf("Test %q failed: expected no error, but got: %v", tt.name, err)
}
if signer == nil {
t.Fatalf("Test %q failed: signer instance should not be nil", tt.name)
}
if close != nil {
if err := close(); err != nil {
t.Fatalf("Cleanup function returned an error: %v", err)
}
}
})
}
}
// TestSignerProviderFailure verifies failure scenarios for SignerProvider.
func TestSignerProviderFailure(t *testing.T) {
provider := SignerProvider{}
failureTests := []struct {
name string
ctx context.Context
config map[string]string
wantErr bool
}{
{
name: "Nil Context",
ctx: nil,
config: map[string]string{},
wantErr: true,
},
}
for _, tt := range failureTests {
t.Run(tt.name, func(t *testing.T) {
signerInstance, close, err := provider.New(tt.ctx, tt.config)
if (err != nil) != tt.wantErr {
t.Fatalf("Test %q failed: expected error: %v, got: %v", tt.name, tt.wantErr, err)
}
if signerInstance != nil {
t.Fatalf("Test %q failed: expected signer instance to be nil", tt.name)
}
if close != nil {
t.Fatalf("Test %q failed: expected cleanup function to be nil", tt.name)
}
})
}
}

View File

@@ -1,77 +0,0 @@
package signer
import (
"context"
"crypto/ed25519"
"encoding/base64"
"errors"
"fmt"
"golang.org/x/crypto/blake2b"
)
// Config holds the configuration for the signing process.
type Config struct {
}
// Signer implements the Signer interface and handles the signing process.
type Signer struct {
config *Config
}
// New creates a new Signer instance with the given configuration.
func New(ctx context.Context, config *Config) (*Signer, func() error, error) {
s := &Signer{config: config}
return s, s.Close, nil
}
// hash generates a signing string using BLAKE-512 hashing.
func hash(payload []byte, createdAt, expiresAt int64) (string, error) {
hasher, _ := blake2b.New512(nil)
_, err := hasher.Write(payload)
if err != nil {
return "", fmt.Errorf("failed to hash payload: %w", err)
}
hashSum := hasher.Sum(nil)
digestB64 := base64.StdEncoding.EncodeToString(hashSum)
return fmt.Sprintf("(created): %d\n(expires): %d\ndigest: BLAKE-512=%s", createdAt, expiresAt, digestB64), nil
}
// generateSignature signs the given signing string using the provided private key.
func generateSignature(signingString []byte, privateKeyBase64 string) ([]byte, error) {
privateKeyBytes, err := base64.StdEncoding.DecodeString(privateKeyBase64)
if err != nil {
return nil, fmt.Errorf("error decoding private key: %w", err)
}
if len(privateKeyBytes) != ed25519.PrivateKeySize {
return nil, errors.New("invalid private key length")
}
privateKey := ed25519.PrivateKey(privateKeyBytes)
return ed25519.Sign(privateKey, signingString), nil
}
// Sign generates a digital signature for the provided payload.
func (s *Signer) Sign(ctx context.Context, body []byte, privateKeyBase64 string, createdAt, expiresAt int64) (string, error) {
signingString, err := hash(body, createdAt, expiresAt)
if err != nil {
return "", err
}
signature, err := generateSignature([]byte(signingString), privateKeyBase64)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(signature), nil
}
// Close releases resources (mock implementation returning nil).
func (s *Signer) Close() error {
return nil
}

View File

@@ -1,104 +0,0 @@
package signer
import (
"context"
"crypto/ed25519"
"encoding/base64"
"strings"
"testing"
"time"
)
// generateTestKeys generates a test private and public key pair in base64 encoding.
func generateTestKeys() (string, string) {
publicKey, privateKey, _ := ed25519.GenerateKey(nil)
return base64.StdEncoding.EncodeToString(privateKey), base64.StdEncoding.EncodeToString(publicKey)
}
// TestSignSuccess tests the Sign method with valid inputs to ensure it produces a valid signature.
func TestSignSuccess(t *testing.T) {
privateKey, _ := generateTestKeys()
config := Config{}
signer, close, _ := New(context.Background(), &config)
successTests := []struct {
name string
payload []byte
privateKey string
createdAt int64
expiresAt int64
}{
{
name: "Valid Signing",
payload: []byte("test payload"),
privateKey: privateKey,
createdAt: time.Now().Unix(),
expiresAt: time.Now().Unix() + 3600,
},
}
for _, tt := range successTests {
t.Run(tt.name, func(t *testing.T) {
signature, err := signer.Sign(context.Background(), tt.payload, tt.privateKey, tt.createdAt, tt.expiresAt)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(signature) == 0 {
t.Errorf("expected a non-empty signature, but got empty")
}
if close != nil {
if err := close(); err != nil {
t.Fatalf("Cleanup function returned an error: %v", err)
}
}
})
}
}
// TestSignFailure tests the Sign method with invalid inputs to ensure proper error handling.
func TestSignFailure(t *testing.T) {
config := Config{}
signer, close, _ := New(context.Background(), &config)
failureTests := []struct {
name string
payload []byte
privateKey string
createdAt int64
expiresAt int64
expectErrString string
}{
{
name: "Invalid Private Key",
payload: []byte("test payload"),
privateKey: "invalid_key",
createdAt: time.Now().Unix(),
expiresAt: time.Now().Unix() + 3600,
expectErrString: "error decoding private key",
},
{
name: "Short Private Key",
payload: []byte("test payload"),
privateKey: base64.StdEncoding.EncodeToString([]byte("short_key")),
createdAt: time.Now().Unix(),
expiresAt: time.Now().Unix() + 3600,
expectErrString: "invalid private key length",
},
}
for _, tt := range failureTests {
t.Run(tt.name, func(t *testing.T) {
_, err := signer.Sign(context.Background(), tt.payload, tt.privateKey, tt.createdAt, tt.expiresAt)
if err == nil {
t.Errorf("expected error but got none")
} else if !strings.Contains(err.Error(), tt.expectErrString) {
t.Errorf("expected error message to contain %q, got %v", tt.expectErrString, err)
}
if close != nil {
if err := close(); err != nil {
t.Fatalf("Cleanup function returned an error: %v", err)
}
}
})
}
}

View File

@@ -1,129 +0,0 @@
package plugin
import (
"context"
"fmt"
"path/filepath"
"plugin"
"strings"
"github.com/beckn/beckn-onix/shared/plugin/definition"
)
// Config represents the plugin manager configuration.
type Config struct {
Root string `yaml:"root"`
Signer PluginConfig `yaml:"signer"`
Verifier PluginConfig `yaml:"verifier"`
Publisher PluginConfig `yaml:"publisher"`
}
// PluginConfig represents configuration details for a plugin.
type PluginConfig struct {
ID string `yaml:"id"`
Config map[string]string `yaml:"config"`
}
// Manager handles dynamic plugin loading and management.
type Manager struct {
sp definition.SignerProvider
vp definition.VerifierProvider
pb definition.PublisherProvider
cfg *Config
}
// NewManager initializes a new Manager with the given configuration file.
func NewManager(ctx context.Context, cfg *Config) (*Manager, error) {
if cfg == nil {
return nil, fmt.Errorf("configuration cannot be nil")
}
// Load signer plugin
sp, err := provider[definition.SignerProvider](cfg.Root, cfg.Signer.ID)
if err != nil {
return nil, fmt.Errorf("failed to load signer plugin: %w", err)
}
// Load publisher plugin
pb, err := provider[definition.PublisherProvider](cfg.Root, cfg.Publisher.ID)
if err != nil {
return nil, fmt.Errorf("failed to load publisher plugin: %w", err)
}
// Load verifier plugin
vp, err := provider[definition.VerifierProvider](cfg.Root, cfg.Verifier.ID)
if err != nil {
return nil, fmt.Errorf("failed to load Verifier plugin: %w", err)
}
return &Manager{sp: sp, vp: vp, pb: pb, cfg: cfg}, nil
}
// provider loads a plugin dynamically and retrieves its provider instance.
func provider[T any](root, id string) (T, error) {
var zero T
if len(strings.TrimSpace(id)) == 0 {
return zero, nil
}
p, err := plugin.Open(pluginPath(root, id))
if err != nil {
return zero, fmt.Errorf("failed to open plugin %s: %w", id, err)
}
symbol, err := p.Lookup("Provider")
if err != nil {
return zero, fmt.Errorf("failed to find Provider symbol in plugin %s: %w", id, err)
}
prov, ok := symbol.(*T)
if !ok {
return zero, fmt.Errorf("failed to cast Provider for %s", id)
}
return *prov, nil
}
// pluginPath constructs the path to the plugin shared object file.
func pluginPath(root, id string) string {
return filepath.Join(root, id+".so")
}
// Signer retrieves the signing plugin instance.
func (m *Manager) Signer(ctx context.Context) (definition.Signer, func() error, error) {
if m.sp == nil {
return nil, nil, fmt.Errorf("signing plugin provider not loaded")
}
signer, close, err := m.sp.New(ctx, m.cfg.Signer.Config)
if err != nil {
return nil, nil, fmt.Errorf("failed to initialize signer: %w", err)
}
return signer, close, nil
}
// Verifier retrieves the verification plugin instance.
func (m *Manager) Verifier(ctx context.Context) (definition.Verifier, func() error, error) {
if m.vp == nil {
return nil, nil, fmt.Errorf("Verifier plugin provider not loaded")
}
Verifier, close, err := m.vp.New(ctx, m.cfg.Verifier.Config)
if err != nil {
return nil, nil, fmt.Errorf("failed to initialize Verifier: %w", err)
}
return Verifier, close, nil
}
// Publisher retrieves the publisher plugin instance.
func (m *Manager) Publisher(ctx context.Context) (definition.Publisher, error) {
if m.pb == nil {
return nil, fmt.Errorf("publisher plugin provider not loaded")
}
publisher, err := m.pb.New(ctx, m.cfg.Publisher.Config)
if err != nil {
return nil, fmt.Errorf("failed to initialize publisher: %w", err)
}
return publisher, nil
}