Ctrl K
ComunidadGuías…

tests/saml_reauth_test.go

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

Archivo original ↗Preguntar a KiBot
package main

import (
	"context"
	"net/http"
	"net/url"
	"testing"
	"time"

	"github.com/go-resty/resty/v2"
	"github.com/shellhub-io/shellhub/tests/environment"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

const (
	samlIdentityProvider = "http://idp.e2e.test"
	samlRelayTokenTTL    = 5 * time.Minute
)

// TestEnterpriseSAMLReauth covers the relay token behind a SAML user's re-authentication for the
// web terminal: the server issues it in the URL that sends the user to the identity provider, keeps
// it for five minutes, and refuses it once it expires. The provider takes the HTTP-POST binding, so
// the server checks the token when it builds the form posted to the provider, and no provider has
// to answer.
func TestEnterpriseSAMLReauth(t *testing.T) {
	t.Parallel()

	compose := newEnterpriseEnvironment(t, context.Background(), environment.New(t, run))
	enableSAMLPostBinding(t, compose)

	t.Run("a relay token lasts five minutes", func(t *testing.T) {
		token := issueSAMLRelayToken(t, compose)

		left := compose.CacheEntryTTL(t, samlRelayTokenCacheKey(token))
		assert.Greater(t, left, samlRelayTokenTTL-time.Minute)
		assert.LessOrEqual(t, left, samlRelayTokenTTL)

		assert.Equal(t, http.StatusOK, requestSAMLReauth(t, compose, token).StatusCode())
	})

	t.Run("an expired relay token is refused", func(t *testing.T) {
		token := issueSAMLRelayToken(t, compose)

		compose.ExpireCacheEntryIn(t, samlRelayTokenCacheKey(token), 0)

		assert.Equal(t, http.StatusForbidden, requestSAMLReauth(t, compose, token).StatusCode())
	})
}

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

	resp, err := compose.R(t.Context()).
		SetBody(map[string]any{
			"enable": true,
			"idp": map[string]any{
				"entity_id":   samlIdentityProvider,
				"certificate": samlIdentityProviderCertificate(t),
				"binding":     map[string]string{"post": samlIdentityProvider + "/sso/post"},
			},
			"sp": map[string]bool{"sign_requests": false},
		}).
		Put("/admin/api/authentication/saml")
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, resp.StatusCode(), resp.String())
}

func samlIdentityProviderCertificate(t *testing.T) string {
	t.Helper()

	certificate, err := environment.SelfSignedCertificate("idp.e2e.test")
	require.NoError(t, err)

	return string(certificate.CertificatePEM)
}

func samlRelayTokenCacheKey(token string) string {
	return "saml-stepup/" + token
}

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

	reauth := struct {
		URL string `json:"url"`
	}{}

	resp, err := compose.R(t.Context()).
		SetQueryParams(map[string]string{"fingerprint": "SHA256:e2e", "approval_code": "E2E"}).
		SetResult(&reauth).
		Get("/api/user/saml/reauth")
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, resp.StatusCode(), resp.String())

	parsed, err := url.Parse(reauth.URL)
	require.NoError(t, err)

	token := parsed.Query().Get("relay_state")
	require.NotEmpty(t, token, "the re-authentication URL %s carries no relay token", reauth.URL)

	return token
}

func requestSAMLReauth(t *testing.T, compose *environment.DockerCompose, token string) *resty.Response {
	t.Helper()

	resp, err := compose.Anonymous(t.Context()).
		SetQueryParam("relay_state", token).
		Get("/api/user/saml/request")
	require.NoError(t, err)

	return resp
}