diff --git a/cmd/neofs-rest-gw/integration_test.go b/cmd/neofs-rest-gw/integration_test.go index 4cd04ee..c09c1c0 100644 --- a/cmd/neofs-rest-gw/integration_test.go +++ b/cmd/neofs-rest-gw/integration_test.go @@ -60,7 +60,7 @@ const ( // tests configuration. useWalletConnect = false - useLocalEnvironment = true + useLocalEnvironment = false ) func TestIntegration(t *testing.T) { @@ -123,6 +123,7 @@ func runTests(ctx context.Context, t *testing.T, key *keys.PrivateKey, version s t.Run("rest delete object "+version, func(t *testing.T) { restObjectDelete(ctx, t, clientPool, cnrID) }) t.Run("rest search objects "+version, func(t *testing.T) { restObjectsSearch(ctx, t, clientPool, cnrID) }) + t.Run("rest put container invalid "+version, func(t *testing.T) { restContainerPutInvalid(ctx, t) }) t.Run("rest put container "+version, func(t *testing.T) { restContainerPut(ctx, t, clientPool) }) t.Run("rest get container "+version, func(t *testing.T) { restContainerGet(ctx, t, clientPool, cnrID) }) t.Run("rest delete container "+version, func(t *testing.T) { restContainerDelete(ctx, t, clientPool) }) @@ -813,6 +814,36 @@ func signTokenWalletConnect(t *testing.T, key *keys.PrivateKey, data []byte) *ha } } +func restContainerPutInvalid(ctx context.Context, t *testing.T) { + bearer := &models.Bearer{ + Container: &models.Rule{ + Verb: models.NewVerb(models.VerbPUT), + }, + } + + httpClient := &http.Client{Timeout: 30 * time.Second} + bearerTokens := makeAuthTokenRequest(ctx, t, []*models.Bearer{bearer}, httpClient) + bearerToken := bearerTokens[0] + + reqURL, err := url.Parse(testHost + "/v1/containers") + require.NoError(t, err) + query := reqURL.Query() + query.Add("name-scope-global", "true") + query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect)) + reqURL.RawQuery = query.Encode() + + body, err := json.Marshal(&operations.PutContainerBody{ContainerName: "nameWithCapitalLetters"}) + require.NoError(t, err) + request, err := http.NewRequest(http.MethodPut, reqURL.String(), bytes.NewReader(body)) + require.NoError(t, err) + prepareCommonHeaders(request.Header, bearerToken) + + resp := &models.ErrorResponse{} + doRequest(t, httpClient, request, http.StatusBadRequest, resp) + require.Equal(t, int64(0), resp.Code) + require.Equal(t, models.ErrorTypeGW, *resp.Type) +} + func restContainerPut(ctx context.Context, t *testing.T, clientPool *pool.Pool) { bearer := &models.Bearer{ Container: &models.Rule{ diff --git a/handlers/container_test.go b/handlers/container_test.go new file mode 100644 index 0000000..5ebe0e7 --- /dev/null +++ b/handlers/container_test.go @@ -0,0 +1,37 @@ +package handlers + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCheckContainerName(t *testing.T) { + name64 := "container-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + name256 := "container-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + for _, tc := range []struct { + name string + valid bool + }{ + {name: "container", valid: true}, + {name: "container-name", valid: true}, + {name: "container.name", valid: true}, + {name: "container2", valid: true}, + {name: "2container.name", valid: true}, + {name: "containerName", valid: false}, + {name: "-container", valid: false}, + {name: "container-", valid: false}, + {name: "container name", valid: false}, + {name: "c", valid: false}, + {name: name64 + ".name", valid: false}, + {name: name256, valid: false}, + } { + err := checkNNSContainerName(tc.name) + if tc.valid { + require.NoError(t, err) + } else { + require.Error(t, err) + } + } +} diff --git a/handlers/containers.go b/handlers/containers.go index 47d3f75..ba10e03 100644 --- a/handlers/containers.go +++ b/handlers/containers.go @@ -341,8 +341,8 @@ func createContainer(ctx context.Context, p *pool.Pool, stoken *session.Token, p cnr.SetSessionToken(stoken) if *params.NameScopeGlobal { // we don't check for nil because there is default false value - if request.ContainerName == "" { - return nil, fmt.Errorf("container name must not be empty to be registered in NNS") + if err = checkNNSContainerName(request.ContainerName); err != nil { + return nil, fmt.Errorf("invalid container name: %w", err) } container.SetNativeName(cnr, request.ContainerName) } @@ -352,12 +352,43 @@ func createContainer(ctx context.Context, p *pool.Pool, stoken *session.Token, p cnrID, err := p.PutContainer(ctx, prm) if err != nil { - return nil, fmt.Errorf("could put object to neofs: %w", err) + return nil, fmt.Errorf("put container: %w", err) } return cnrID, nil } +func checkNNSContainerName(name string) error { + length := len(name) + if length < 3 || 255 < length { + return fmt.Errorf("invalid length: %d", length) + } + fragments := strings.Split(name, ".") + + for _, fragment := range fragments { + fLength := len(fragment) + if fLength < 1 || 63 < fLength { + return fmt.Errorf("invalid fragment length: %d", fLength) + } + + if !isAlNum(fragment[0]) || !isAlNum(fragment[fLength-1]) { + return fmt.Errorf("invalid fragment: '%s'", fragment) + } + + for i := 1; i < fLength-1; i++ { + if fragment[i] != '-' && !isAlNum(fragment[i]) { + return fmt.Errorf("invalid fragment: '%s'", fragment) + } + } + } + + return nil +} + +func isAlNum(c uint8) bool { + return c >= 'a' && c <= 'z' || c >= '0' && c <= '9' +} + func prepareSessionToken(bt *BearerToken, isWalletConnect bool) (*session.Token, error) { data, err := base64.StdEncoding.DecodeString(bt.Token) if err != nil {