* feat(access-token): add ephemeral access-token resource Signed-off-by: Mauritz Uphoff <mauritz.uphoff@stackit.cloud>
253 lines
6.4 KiB
Go
253 lines
6.4 KiB
Go
package access_token
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
_ "embed"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stackitcloud/stackit-sdk-go/core/clients"
|
|
"github.com/stackitcloud/stackit-sdk-go/core/config"
|
|
)
|
|
|
|
//go:embed testdata/service_account.json
|
|
var testServiceAccountKey string
|
|
|
|
func startMockTokenServer() *httptest.Server {
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
resp := clients.TokenResponseBody{
|
|
AccessToken: "mock_access_token",
|
|
RefreshToken: "mock_refresh_token",
|
|
TokenType: "Bearer",
|
|
ExpiresIn: int(time.Now().Add(time.Hour).Unix()),
|
|
Scope: "mock_scope",
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(resp)
|
|
})
|
|
return httptest.NewServer(handler)
|
|
}
|
|
|
|
func generatePrivateKey() (string, error) {
|
|
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
privateKeyPEM := &pem.Block{
|
|
Type: "RSA PRIVATE KEY",
|
|
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
|
}
|
|
return string(pem.EncodeToMemory(privateKeyPEM)), nil
|
|
}
|
|
|
|
func writeTempPEMFile(t *testing.T, pemContent string) string {
|
|
t.Helper()
|
|
|
|
tmpFile, err := os.CreateTemp("", "stackit_test_private_key_*.pem")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := tmpFile.WriteString(pemContent); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := tmpFile.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
_ = os.Remove(tmpFile.Name())
|
|
})
|
|
|
|
return tmpFile.Name()
|
|
}
|
|
|
|
func TestGetAccessToken(t *testing.T) {
|
|
mockServer := startMockTokenServer()
|
|
t.Cleanup(mockServer.Close)
|
|
|
|
privateKey, err := generatePrivateKey()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
tests := []struct {
|
|
description string
|
|
setupEnv func()
|
|
cleanupEnv func()
|
|
cfgFactory func() *config.Configuration
|
|
expectError bool
|
|
expected string
|
|
}{
|
|
{
|
|
description: "should return token when service account key passed by value",
|
|
cfgFactory: func() *config.Configuration {
|
|
return &config.Configuration{
|
|
ServiceAccountKey: testServiceAccountKey,
|
|
PrivateKey: privateKey,
|
|
TokenCustomUrl: mockServer.URL,
|
|
}
|
|
},
|
|
expectError: false,
|
|
expected: "mock_access_token",
|
|
},
|
|
{
|
|
description: "should return token when service account key is loaded from file path",
|
|
cfgFactory: func() *config.Configuration {
|
|
return &config.Configuration{
|
|
ServiceAccountKeyPath: "testdata/service_account.json",
|
|
PrivateKey: privateKey,
|
|
TokenCustomUrl: mockServer.URL,
|
|
}
|
|
},
|
|
expectError: false,
|
|
expected: "mock_access_token",
|
|
},
|
|
{
|
|
description: "should fail when private key is invalid",
|
|
cfgFactory: func() *config.Configuration {
|
|
return &config.Configuration{
|
|
ServiceAccountKey: "invalid-json",
|
|
PrivateKey: "invalid-PEM",
|
|
TokenCustomUrl: mockServer.URL,
|
|
}
|
|
},
|
|
expectError: true,
|
|
expected: "",
|
|
},
|
|
{
|
|
description: "should return token when service account key is set via env",
|
|
setupEnv: func() {
|
|
_ = os.Setenv("STACKIT_SERVICE_ACCOUNT_KEY", testServiceAccountKey)
|
|
},
|
|
cleanupEnv: func() {
|
|
_ = os.Unsetenv("STACKIT_SERVICE_ACCOUNT_KEY")
|
|
},
|
|
cfgFactory: func() *config.Configuration {
|
|
return &config.Configuration{
|
|
PrivateKey: privateKey,
|
|
TokenCustomUrl: mockServer.URL,
|
|
}
|
|
},
|
|
expectError: false,
|
|
expected: "mock_access_token",
|
|
},
|
|
{
|
|
description: "should return token when service account key path is set via env",
|
|
setupEnv: func() {
|
|
_ = os.Setenv("STACKIT_SERVICE_ACCOUNT_KEY_PATH", "testdata/service_account.json")
|
|
},
|
|
cleanupEnv: func() {
|
|
_ = os.Unsetenv("STACKIT_SERVICE_ACCOUNT_KEY_PATH")
|
|
},
|
|
cfgFactory: func() *config.Configuration {
|
|
return &config.Configuration{
|
|
PrivateKey: privateKey,
|
|
TokenCustomUrl: mockServer.URL,
|
|
}
|
|
},
|
|
expectError: false,
|
|
expected: "mock_access_token",
|
|
},
|
|
{
|
|
description: "should return token when private key is set via env",
|
|
setupEnv: func() {
|
|
_ = os.Setenv("STACKIT_PRIVATE_KEY", privateKey)
|
|
},
|
|
cleanupEnv: func() {
|
|
_ = os.Unsetenv("STACKIT_PRIVATE_KEY")
|
|
},
|
|
cfgFactory: func() *config.Configuration {
|
|
return &config.Configuration{
|
|
ServiceAccountKey: testServiceAccountKey,
|
|
TokenCustomUrl: mockServer.URL,
|
|
}
|
|
},
|
|
expectError: false,
|
|
expected: "mock_access_token",
|
|
},
|
|
{
|
|
description: "should return token when private key path is set via env",
|
|
setupEnv: func() {
|
|
// Write temp file and set env
|
|
tmpFile := writeTempPEMFile(t, privateKey)
|
|
_ = os.Setenv("STACKIT_PRIVATE_KEY_PATH", tmpFile)
|
|
},
|
|
cleanupEnv: func() {
|
|
_ = os.Unsetenv("STACKIT_PRIVATE_KEY_PATH")
|
|
},
|
|
cfgFactory: func() *config.Configuration {
|
|
return &config.Configuration{
|
|
ServiceAccountKey: testServiceAccountKey,
|
|
TokenCustomUrl: mockServer.URL,
|
|
}
|
|
},
|
|
expectError: false,
|
|
expected: "mock_access_token",
|
|
},
|
|
{
|
|
description: "should fail when no service account key or private key is set",
|
|
cfgFactory: func() *config.Configuration {
|
|
return &config.Configuration{
|
|
TokenCustomUrl: mockServer.URL,
|
|
}
|
|
},
|
|
expectError: true,
|
|
expected: "",
|
|
},
|
|
{
|
|
description: "should fail when no service account key or private key is set via env",
|
|
setupEnv: func() {
|
|
_ = os.Unsetenv("STACKIT_SERVICE_ACCOUNT_KEY")
|
|
_ = os.Unsetenv("STACKIT_SERVICE_ACCOUNT_KEY_PATH")
|
|
_ = os.Unsetenv("STACKIT_PRIVATE_KEY")
|
|
_ = os.Unsetenv("STACKIT_PRIVATE_KEY_PATH")
|
|
},
|
|
cleanupEnv: func() {
|
|
// Restore original environment variables
|
|
},
|
|
cfgFactory: func() *config.Configuration {
|
|
return &config.Configuration{
|
|
TokenCustomUrl: mockServer.URL,
|
|
}
|
|
},
|
|
expectError: true,
|
|
expected: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.description, func(t *testing.T) {
|
|
if tt.setupEnv != nil {
|
|
tt.setupEnv()
|
|
}
|
|
if tt.cleanupEnv != nil {
|
|
defer tt.cleanupEnv()
|
|
}
|
|
|
|
cfg := tt.cfgFactory()
|
|
|
|
token, err := getAccessToken(cfg)
|
|
if tt.expectError {
|
|
if err == nil {
|
|
t.Errorf("expected error but got none for test case '%s'", tt.description)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Errorf("did not expect error but got: %v for test case '%s'", err, tt.description)
|
|
}
|
|
if token != tt.expected {
|
|
t.Errorf("expected token '%s', got '%s' for test case '%s'", tt.expected, token, tt.description)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|