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
230 changes: 230 additions & 0 deletions internal/api/handlers/v0/publish_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,134 @@ func TestPublishEndpoint(t *testing.T) {
setupRegistryService: func(_ service.RegistryService) {},
expectedStatus: http.StatusOK,
},
{
name: "invalid server name - multiple slashes (two slashes)",
requestBody: apiv0.ServerJSON{
Name: "com.example/server/path",
Description: "Server with multiple slashes in name",
Version: "1.0.0",
Repository: model.Repository{
URL: "https://github.com/example/test-server",
Source: "github",
ID: "example/test-server",
},
},
tokenClaims: &auth.JWTClaims{
AuthMethod: auth.MethodNone,
Permissions: []auth.Permission{
{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
},
},
setupRegistryService: func(_ service.RegistryService) {},
expectedStatus: http.StatusBadRequest,
expectedError: "server name cannot contain multiple slashes",
},
{
name: "invalid server name - multiple slashes (three slashes)",
requestBody: apiv0.ServerJSON{
Name: "org.company/dept/team/project",
Description: "Server with three slashes in name",
Version: "1.0.0",
},
tokenClaims: &auth.JWTClaims{
AuthMethod: auth.MethodNone,
Permissions: []auth.Permission{
{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
},
},
setupRegistryService: func(_ service.RegistryService) {},
expectedStatus: http.StatusBadRequest,
expectedError: "server name cannot contain multiple slashes",
},
{
name: "invalid server name - consecutive slashes",
requestBody: apiv0.ServerJSON{
Name: "com.example//double-slash",
Description: "Server with consecutive slashes",
Version: "1.0.0",
},
tokenClaims: &auth.JWTClaims{
AuthMethod: auth.MethodNone,
Permissions: []auth.Permission{
{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
},
},
setupRegistryService: func(_ service.RegistryService) {},
expectedStatus: http.StatusBadRequest,
expectedError: "server name cannot contain multiple slashes",
},
{
name: "invalid server name - URL-like path",
requestBody: apiv0.ServerJSON{
Name: "com.example/servers/v1/api",
Description: "Server with URL-like path structure",
Version: "1.0.0",
},
tokenClaims: &auth.JWTClaims{
AuthMethod: auth.MethodNone,
Permissions: []auth.Permission{
{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
},
},
setupRegistryService: func(_ service.RegistryService) {},
expectedStatus: http.StatusBadRequest,
expectedError: "server name cannot contain multiple slashes",
},
{
name: "invalid server name - many slashes",
requestBody: apiv0.ServerJSON{
Name: "a/b/c/d/e/f",
Description: "Server with many slashes",
Version: "1.0.0",
},
tokenClaims: &auth.JWTClaims{
AuthMethod: auth.MethodNone,
Permissions: []auth.Permission{
{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
},
},
setupRegistryService: func(_ service.RegistryService) {},
expectedStatus: http.StatusBadRequest,
expectedError: "server name cannot contain multiple slashes",
},
{
name: "invalid server name - with packages and remotes",
requestBody: apiv0.ServerJSON{
Name: "com.example/test/server/v2",
Description: "Complex server with invalid name",
Version: "2.0.0",
Repository: model.Repository{
URL: "https://github.com/example/test-server",
Source: "github",
ID: "example/test-server",
},
Packages: []model.Package{
{
RegistryType: model.RegistryTypeNPM,
Identifier: "test-package",
Version: "2.0.0",
Transport: model.Transport{
Type: model.TransportTypeStdio,
},
},
},
Remotes: []model.Transport{
{
Type: model.TransportTypeStreamableHTTP,
URL: "https://example.com/api",
},
},
},
tokenClaims: &auth.JWTClaims{
AuthMethod: auth.MethodNone,
Permissions: []auth.Permission{
{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
},
},
setupRegistryService: func(_ service.RegistryService) {},
expectedStatus: http.StatusBadRequest,
expectedError: "server name cannot contain multiple slashes",
},
}

for _, tc := range testCases {
Expand Down Expand Up @@ -279,3 +407,105 @@ func TestPublishEndpoint(t *testing.T) {
})
}
}

// TestPublishEndpoint_MultipleSlashesEdgeCases tests additional edge cases for multi-slash validation
func TestPublishEndpoint_MultipleSlashesEdgeCases(t *testing.T) {
testSeed := make([]byte, ed25519.SeedSize)
_, err := rand.Read(testSeed)
require.NoError(t, err)
testConfig := &config.Config{
JWTPrivateKey: hex.EncodeToString(testSeed),
EnableRegistryValidation: false,
}

testCases := []struct {
name string
serverName string
expectedStatus int
description string
}{
{
name: "valid - single slash",
serverName: "com.example/server",
expectedStatus: http.StatusOK,
description: "Valid server name with single slash should succeed",
},
{
name: "invalid - trailing slash after valid name",
serverName: "com.example/server/",
expectedStatus: http.StatusBadRequest,
description: "Trailing slash creates multiple slashes",
},
{
name: "invalid - leading and middle slash",
serverName: "/com.example/server",
expectedStatus: http.StatusBadRequest,
description: "Leading slash with middle slash",
},
{
name: "invalid - file system style path",
serverName: "usr/local/bin/server",
expectedStatus: http.StatusBadRequest,
description: "File system style paths should be rejected",
},
{
name: "invalid - version-like suffix",
serverName: "com.example/server/v1.0.0",
expectedStatus: http.StatusBadRequest,
description: "Version suffixes with slash should be rejected",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create registry service
registryService := service.NewRegistryService(database.NewMemoryDB(), testConfig)

// Create a new ServeMux and Huma API
mux := http.NewServeMux()
api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0"))

// Register the endpoint
v0.RegisterPublishEndpoint(api, registryService, testConfig)

// Create request body
requestBody := apiv0.ServerJSON{
Name: tc.serverName,
Description: "Test server",
Version: "1.0.0",
}

bodyBytes, err := json.Marshal(requestBody)
require.NoError(t, err)

// Create request
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "/v0/publish", bytes.NewBuffer(bodyBytes))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/json")

// Set auth header with permissions
tokenClaims := auth.JWTClaims{
AuthMethod: auth.MethodNone,
Permissions: []auth.Permission{
{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
},
}
token, err := generateTestJWTToken(testConfig, tokenClaims)
require.NoError(t, err)
req.Header.Set("Authorization", "Bearer "+token)

// Perform request
rr := httptest.NewRecorder()
mux.ServeHTTP(rr, req)

// Assertions
assert.Equal(t, tc.expectedStatus, rr.Code,
"%s: expected status %d, got %d", tc.description, tc.expectedStatus, rr.Code)

if tc.expectedStatus == http.StatusBadRequest {
assert.Contains(t, rr.Body.String(), "server name cannot contain multiple slashes",
"%s: should contain specific error message", tc.description)
}
})
}
}
5 changes: 4 additions & 1 deletion internal/validators/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ var (
ErrInvalidNamedArgumentName = errors.New("invalid named argument name format")
ErrArgumentValueStartsWithName = errors.New("argument value cannot start with the argument name")
ErrArgumentDefaultStartsWithName = errors.New("argument default cannot start with the argument name")

// Server name validation errors
ErrMultipleSlashesInServerName = errors.New("server name cannot contain multiple slashes")
)

// RepositorySource represents valid repository sources
Expand All @@ -33,4 +36,4 @@ type RepositorySource string
const (
SourceGitHub RepositorySource = "github"
SourceGitLab RepositorySource = "gitlab"
)
)
8 changes: 7 additions & 1 deletion internal/validators/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,12 @@ func parseServerName(serverJSON apiv0.ServerJSON) (string, error) {
return "", fmt.Errorf("server name must be in format 'dns-namespace/name' (e.g., 'com.example.api/server')")
}

// Check for multiple slashes - reject if found
slashCount := strings.Count(name, "/")
if slashCount > 1 {
return "", ErrMultipleSlashesInServerName
}

parts := strings.SplitN(name, "/", 2)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return "", fmt.Errorf("server name must be in format 'dns-namespace/name' with non-empty namespace and name parts")
Expand Down Expand Up @@ -504,4 +510,4 @@ func isValidHostForDomain(hostname, publisherDomain string) bool {
}

return false
}
}
Loading
Loading