Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions internal/api/fileupload/filters/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package filters

import (
"context"
"encoding/json"
"fmt"
"net/http"

"github.com/google/uuid"
)

// AllowList represents the response structure from the deeproxy filters API.
type AllowList struct {
ConfigFiles []string `json:"configFiles"`
Extensions []string `json:"extensions"`
}

// Client defines the interface for the filters client.
type Client interface {
GetFilters(ctx context.Context, orgID uuid.UUID) (AllowList, error)
}

// DeeproxyClient is the deeproxy implementation of the Client interface.
type DeeproxyClient struct {
httpClient *http.Client
cfg Config
}

// Config contains the configuration for the filters client.
type Config struct {
BaseURL string
IsFedRamp bool
}

var _ Client = (*DeeproxyClient)(nil)

// NewDeeproxyClient creates a new DeeproxyClient with the given configuration and options.
func NewDeeproxyClient(cfg Config, opts ...Opt) *DeeproxyClient {
c := &DeeproxyClient{
cfg: cfg,
httpClient: http.DefaultClient,
}

for _, opt := range opts {
opt(c)
}

return c
}

// GetFilters returns the deeproxy filters in the form of an AllowList.
func (c *DeeproxyClient) GetFilters(ctx context.Context, orgID uuid.UUID) (AllowList, error) {
var allowList AllowList

url := getFilterURL(c.cfg.BaseURL, orgID, c.cfg.IsFedRamp)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
if err != nil {
return allowList, fmt.Errorf("failed to create deeproxy filters request: %w", err)
}

req.Header.Set("snyk-org-name", orgID.String())

resp, err := c.httpClient.Do(req)
if err != nil {
return allowList, fmt.Errorf("error making deeproxy filters request: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode < 200 || resp.StatusCode > 299 {
return allowList, fmt.Errorf("unexpected response code: %s", resp.Status)
}

if err := json.NewDecoder(resp.Body).Decode(&allowList); err != nil {
return allowList, fmt.Errorf("failed to decode deeproxy filters response: %w", err)
}

return allowList, nil
}
77 changes: 77 additions & 0 deletions internal/api/fileupload/filters/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package filters //nolint:testpackage // Testing private utility functions.

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestClients(t *testing.T) {
tests := []struct {
getClient func(t *testing.T, orgID uuid.UUID, expectedAllow AllowList) (Client, func())
clientName string
}{
{
clientName: "deeproxyClient",
getClient: func(t *testing.T, orgID uuid.UUID, expectedAllow AllowList) (Client, func()) {
t.Helper()

s := setupServer(t, orgID, expectedAllow)
cleanup := func() {
s.Close()
}
c := NewDeeproxyClient(Config{BaseURL: s.URL, IsFedRamp: true}, WithHTTPClient(s.Client()))

return c, cleanup
},
},
{
clientName: "fakeClient",
getClient: func(t *testing.T, _ uuid.UUID, expectedAllow AllowList) (Client, func()) {
t.Helper()

c := NewFakeClient(expectedAllow)

return c, func() {}
},
},
}

for _, testData := range tests {
t.Run(testData.clientName+": GetFilters", func(t *testing.T) {
orgID := uuid.New()
expectedAllow := AllowList{
ConfigFiles: []string{"package.json"},
Extensions: []string{".ts", ".js"},
}
client, cleanup := testData.getClient(t, orgID, expectedAllow)
defer cleanup()

allow, err := client.GetFilters(t.Context(), orgID)
require.NoError(t, err)

assert.Equal(t, expectedAllow, allow)
})
}
}

func setupServer(t *testing.T, orgID uuid.UUID, expectedAllow AllowList) *httptest.Server {
t.Helper()
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
expectedURL := getFilterURL("", orgID, true)
assert.Equal(t, expectedURL, r.URL.Path)
assert.Equal(t, orgID.String(), r.Header.Get("snyk-org-name"))
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(expectedAllow); err != nil {
http.Error(w, "failed to encode response", http.StatusInternalServerError)
return
}
}))
ts.Start()
return ts
}
25 changes: 25 additions & 0 deletions internal/api/fileupload/filters/fake_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package filters

import (
"context"

"github.com/google/uuid"
)

type FakeClient struct {
getFilters func(ctx context.Context, orgID uuid.UUID) (AllowList, error)
}

var _ Client = (*FakeClient)(nil)

func NewFakeClient(allowList AllowList) *FakeClient {
return &FakeClient{
getFilters: func(ctx context.Context, orgID uuid.UUID) (AllowList, error) {
return allowList, nil
},
}
}

func (f *FakeClient) GetFilters(ctx context.Context, orgID uuid.UUID) (AllowList, error) {
return f.getFilters(ctx, orgID)
}
13 changes: 13 additions & 0 deletions internal/api/fileupload/filters/opts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package filters

import "net/http"

// Opt is a function that configures an deeproxyClient instance.
type Opt func(*DeeproxyClient)

// WithHTTPClient sets a custom HTTP client for the filters client.
func WithHTTPClient(httpClient *http.Client) Opt {
return func(c *DeeproxyClient) {
c.httpClient = httpClient
}
}
17 changes: 17 additions & 0 deletions internal/api/fileupload/filters/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package filters

import (
"fmt"
"strings"

"github.com/google/uuid"
)

func getFilterURL(baseURL string, orgID uuid.UUID, isFedRamp bool) string {
if isFedRamp {
return fmt.Sprintf("%s/hidden/orgs/%s/code/filters", baseURL, orgID)
}

deeproxyURL := strings.ReplaceAll(baseURL, "api", "deeproxy")
return fmt.Sprintf("%s/filters", deeproxyURL)
}
20 changes: 20 additions & 0 deletions internal/api/fileupload/filters/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package filters //nolint:testpackage // Testing private utility functions.

import (
"testing"

"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)

var orgID = uuid.MustParse("738ef92e-21cc-4a11-8c13-388d89272f4b")

func Test_getBaseUrl_notFedramp(t *testing.T) {
actualURL := getFilterURL("https://api.snyk.io", orgID, false)
assert.Equal(t, "https://deeproxy.snyk.io/filters", actualURL)
}

func Test_getBaseUrl_fedramp(t *testing.T) {
actualURL := getFilterURL("https://api.snyk.io", orgID, true)
assert.Equal(t, "https://api.snyk.io/hidden/orgs/738ef92e-21cc-4a11-8c13-388d89272f4b/code/filters", actualURL)
}
15 changes: 15 additions & 0 deletions internal/api/fileupload/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package fileupload

import (
"sync"

"github.com/puzpuzpuz/xsync"
)

// Filters holds the filtering configuration for file uploads with thread-safe maps.
type Filters struct {
SupportedExtensions *xsync.MapOf[string, bool]
SupportedConfigFiles *xsync.MapOf[string, bool]
Once sync.Once
InitErr error
}
Loading