Ctrl K
ComunidadGuías…

tests/request_binding_test.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package main

import (
	"net/http"
	"testing"

	"github.com/go-resty/resty/v2"
	"github.com/shellhub-io/shellhub/pkg/api/authorizer"
	"github.com/shellhub-io/shellhub/pkg/api/requests"
	"github.com/shellhub-io/shellhub/pkg/models"
	"github.com/shellhub-io/shellhub/tests/environment"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

const (
	secondUsername  = "other"
	secondEmail     = "other@shellhub.io"
	secondPassword  = "other-password"
	secondNamespace = "otherspace"
	secondTenant    = "11111111-1111-4111-8111-111111111111"
)

func seedTwoNamespaces(t *testing.T, compose *environment.DockerCompose) {
	t.Helper()

	compose.NewUser(t, ShellHubUsername, ShellHubEmail, ShellHubPassword)
	compose.NewNamespace(t, ShellHubUsername, ShellHubNamespaceName, ShellHubNamespace)
	compose.NewUser(t, secondUsername, secondEmail, secondPassword)
	compose.NewNamespace(t, secondUsername, secondNamespace, secondTenant)
}

func TestRequestFieldsKeepTheirDeclaredSource(t *testing.T) {
	t.Parallel()

	compose := environment.New(t, run).Up(t.Context())
	t.Cleanup(compose.Down)

	seedTwoNamespaces(t, compose)

	caller := compose.AuthUser(t, ShellHubUsername, ShellHubPassword)
	other := compose.AuthUser(t, secondUsername, secondPassword)

	compose.JWT(caller.Token)
	key := compose.CreateAPIKey(t, &requests.CreateAPIKey{
		Name:      "automation",
		ExpiresAt: -1,
		OptRole:   authorizer.RoleAdministrator,
	})

	compose.JWT(other.Token)
	before := compose.GetNamespace(t, secondTenant)

	client := resty.New().SetBaseURL(compose.BaseURL()).SetAllowGetMethodPayload(true)
	asOwner := func(t *testing.T) *resty.Request {
		t.Helper()

		return client.R().SetContext(t.Context()).SetAuthToken(caller.Token)
	}
	asAPIKey := func(t *testing.T) *resty.Request {
		t.Helper()

		return client.R().SetContext(t.Context()).SetHeader("X-API-Key", key.Key)
	}

	cases := []struct {
		description string
		request     func(*testing.T) *resty.Request
		method      string
		path        string
		body        map[string]any
		status      int
	}{
		{
			description: "PUT /api/namespaces/:tenant reads the tenant from the path",
			request:     asOwner,
			method:      http.MethodPut,
			path:        "/api/namespaces/" + ShellHubNamespace,
			body:        map[string]any{"Tenant": secondTenant, "name": "renamed", "settings": map[string]any{"session_record": false}},
			status:      http.StatusUnprocessableEntity,
		},
		{
			description: "PUT /api/users/security/:tenant reads the tenant from the path",
			request:     asOwner,
			method:      http.MethodPut,
			path:        "/api/users/security/" + ShellHubNamespace,
			body:        map[string]any{"Tenant": secondTenant, "session_record": false},
			status:      http.StatusUnprocessableEntity,
		},
		{
			description: "GET /api/namespaces/:tenant/members reads the tenant from the path",
			request:     asOwner,
			method:      http.MethodGet,
			path:        "/api/namespaces/" + ShellHubNamespace + "/members",
			body:        map[string]any{"TenantID": secondTenant},
			status:      http.StatusUnprocessableEntity,
		},
		{
			description: "GET /api/namespaces/:tenant reads the tenant from the path for an API key",
			request:     asAPIKey,
			method:      http.MethodGet,
			path:        "/api/namespaces/" + ShellHubNamespace,
			body:        map[string]any{"Tenant": secondTenant},
			status:      http.StatusUnprocessableEntity,
		},
		{
			description: "POST /api/namespaces/:tenant/members reads the user from the identity",
			request:     asAPIKey,
			method:      http.MethodPost,
			path:        "/api/namespaces/" + ShellHubNamespace + "/members",
			body:        map[string]any{"UserID": other.ID, "email": "member@shellhub.io", "role": "administrator"},
			status:      http.StatusUnprocessableEntity,
		},
		{
			description: "GET /api/namespaces/:tenant/invitations reads the user from the identity",
			request:     asAPIKey,
			method:      http.MethodGet,
			path:        "/api/namespaces/" + ShellHubNamespace + "/invitations",
			body:        map[string]any{"UserID": other.ID},
			status:      http.StatusUnprocessableEntity,
		},
		{
			description: "POST /api/namespaces/:tenant/members needs a user an API key does not have",
			request:     asAPIKey,
			method:      http.MethodPost,
			path:        "/api/namespaces/" + ShellHubNamespace + "/members",
			body:        map[string]any{"email": "member@shellhub.io", "role": "administrator"},
			status:      http.StatusBadRequest,
		},
		{
			description: "DELETE /api/namespaces/:tenant reads the tenant from the path",
			request:     asOwner,
			method:      http.MethodDelete,
			path:        "/api/namespaces/" + ShellHubNamespace,
			body:        map[string]any{"Tenant": secondTenant},
			status:      http.StatusUnprocessableEntity,
		},
	}

	for _, tc := range cases {
		t.Run(tc.description, func(t *testing.T) {
			resp, err := tc.request(t).SetBody(tc.body).Execute(tc.method, tc.path)
			require.NoError(t, err)

			assert.Equal(t, tc.status, resp.StatusCode(), resp.String())
			assert.NotContains(t, resp.String(), secondNamespace)
		})
	}

	compose.JWT(other.Token)
	after := compose.GetNamespace(t, secondTenant)
	assert.Equal(t, before.Name, after.Name)
	assert.Equal(t, before.Settings, after.Settings)

	compose.JWT(caller.Token)
	assert.Equal(t, ShellHubNamespaceName, compose.GetNamespace(t, ShellHubNamespace).Name)
}

func TestMemberRoutesActOnTheTokenNamespace(t *testing.T) {
	t.Parallel()

	compose := environment.New(t, run).Up(t.Context())
	t.Cleanup(compose.Down)

	seedTwoNamespaces(t, compose)
	compose.NewMember(t, ShellHubUsername, secondNamespace, string(authorizer.RoleAdministrator))

	login := compose.AuthUser(t, ShellHubUsername, ShellHubPassword)

	scoped := new(models.UserAuthResponse)
	resp, err := compose.Anonymous(t.Context()).SetAuthToken(login.Token).SetResult(scoped).Get("/api/auth/token/" + ShellHubNamespace)
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, resp.StatusCode(), resp.String())
	require.NotNil(t, scoped.Tenant)
	require.Equal(t, ShellHubNamespace, *scoped.Tenant)

	invite := map[string]any{"email": "member@shellhub.io", "role": "observer"}

	for _, path := range []string{
		"/api/namespaces/" + secondTenant + "/members",
		"/api/namespaces/" + secondTenant + "/invitations/links",
	} {
		t.Run("POST "+path, func(t *testing.T) {
			resp, err := compose.Anonymous(t.Context()).SetAuthToken(scoped.Token).SetBody(invite).Post(path)
			require.NoError(t, err)
			assert.Equal(t, http.StatusForbidden, resp.StatusCode(), resp.String())
		})
	}

	compose.JWT(compose.AuthUser(t, secondUsername, secondPassword).Token)
	resp, err = compose.R(t.Context()).Get("/api/namespaces/" + secondTenant + "/invitations")
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, resp.StatusCode(), resp.String())
	assert.NotContains(t, resp.String(), "member@shellhub.io")
}