4cddfcf2f3
Backend Tests / Tests (other) (push) Failing after 1s
Backend Tests / Static Checks (push) Failing after 2s
Backend Tests / Tests (internal) (push) Failing after 2s
Backend Tests / Tests (server) (push) Failing after 1s
Backend Tests / Tests (store) (push) Failing after 1s
Build Canary Image / build-frontend (push) Failing after 1s
Frontend Tests / Lint (push) Failing after 1s
Build Canary Image / build-push (linux/amd64) (push) Has been skipped
Build Canary Image / build-push (linux/arm64) (push) Has been skipped
Build Canary Image / merge (push) Has been skipped
Frontend Tests / Build (push) Failing after 1s
Release Please / release-please (push) Failing after 0s
Proto Linter / Lint Protos (push) Failing after 2s
181 lines
6.6 KiB
Go
181 lines
6.6 KiB
Go
package store_test
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/usememos/memos/internal/profile"
|
|
"github.com/usememos/memos/store"
|
|
"github.com/usememos/memos/store/db/sqlite"
|
|
)
|
|
|
|
func TestApplyIdentityProviderBootstrapCreatesAndUpdatesProvider(t *testing.T) {
|
|
ctx := context.Background()
|
|
stores := newIdentityProviderBootstrapTestStore(t)
|
|
bootstrapDir := t.TempDir()
|
|
bootstrapPath := filepath.Join(bootstrapDir, "memos-idp-primary.json")
|
|
|
|
writeIdentityProviderBootstrap(t, bootstrapPath, "Initial SSO", "initial-secret")
|
|
require.NoError(t, stores.ApplyIdentityProviderBootstrapDir(ctx, bootstrapDir))
|
|
|
|
provider, err := stores.GetIdentityProvider(ctx, &store.FindIdentityProvider{UID: ptr("primary-sso")})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, provider)
|
|
assert.Equal(t, "Initial SSO", provider.Name)
|
|
assert.Equal(t, "initial-secret", provider.Config.GetOauth2Config().ClientSecret)
|
|
providerID := provider.Id
|
|
|
|
writeIdentityProviderBootstrap(t, bootstrapPath, "Updated SSO", "rotated-secret")
|
|
require.NoError(t, stores.ApplyIdentityProviderBootstrapDir(ctx, bootstrapDir))
|
|
|
|
provider, err = stores.GetIdentityProvider(ctx, &store.FindIdentityProvider{UID: ptr("primary-sso")})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, provider)
|
|
assert.Equal(t, providerID, provider.Id)
|
|
assert.Equal(t, "Updated SSO", provider.Name)
|
|
assert.Equal(t, "rotated-secret", provider.Config.GetOauth2Config().ClientSecret)
|
|
}
|
|
|
|
func TestApplyIdentityProviderBootstrapValidatesEntireFileBeforeWriting(t *testing.T) {
|
|
ctx := context.Background()
|
|
stores := newIdentityProviderBootstrapTestStore(t)
|
|
bootstrapDir := t.TempDir()
|
|
writeIdentityProviderBootstrap(t, filepath.Join(bootstrapDir, "memos-idp-a-valid.json"), "Valid Provider", "must-not-appear-in-errors")
|
|
invalidContent := `{
|
|
"uid": "invalid/provider",
|
|
"name": "Invalid Provider",
|
|
"type": "OAUTH2",
|
|
"config": {
|
|
"oauth2Config": {
|
|
"clientId": "client-id",
|
|
"clientSecret": "another-secret",
|
|
"authUrl": "https://example.com/authorize",
|
|
"tokenUrl": "https://example.com/token",
|
|
"userInfoUrl": "https://example.com/userinfo",
|
|
"scopes": ["profile"],
|
|
"fieldMapping": {"identifier": "sub"}
|
|
}
|
|
}
|
|
}`
|
|
require.NoError(t, os.WriteFile(filepath.Join(bootstrapDir, "memos-idp-z-invalid.json"), []byte(invalidContent), 0600))
|
|
|
|
err := stores.ApplyIdentityProviderBootstrapDir(ctx, bootstrapDir)
|
|
require.Error(t, err)
|
|
assert.ErrorContains(t, err, "identityProviders[1].uid is invalid")
|
|
assert.NotContains(t, err.Error(), "must-not-appear-in-errors")
|
|
assert.NotContains(t, err.Error(), "another-secret")
|
|
|
|
providers, err := stores.ListIdentityProviders(ctx, &store.FindIdentityProvider{})
|
|
require.NoError(t, err)
|
|
assert.Empty(t, providers)
|
|
}
|
|
|
|
func TestApplyIdentityProviderBootstrapRejectsUnknownFields(t *testing.T) {
|
|
stores := newIdentityProviderBootstrapTestStore(t)
|
|
bootstrapDir := t.TempDir()
|
|
bootstrapPath := filepath.Join(bootstrapDir, "memos-idp-invalid.json")
|
|
require.NoError(t, os.WriteFile(bootstrapPath, []byte(`{"uid":"primary-sso","unexpected":true}`), 0600))
|
|
|
|
err := stores.ApplyIdentityProviderBootstrapDir(context.Background(), bootstrapDir)
|
|
require.Error(t, err)
|
|
assert.ErrorContains(t, err, `unknown field "unexpected"`)
|
|
}
|
|
|
|
func TestApplyIdentityProviderBootstrapDirRejectsDuplicateUIDsAcrossFiles(t *testing.T) {
|
|
stores := newIdentityProviderBootstrapTestStore(t)
|
|
bootstrapDir := t.TempDir()
|
|
writeIdentityProviderBootstrap(t, filepath.Join(bootstrapDir, "memos-idp-first.json"), "First Provider", "first-secret")
|
|
writeIdentityProviderBootstrap(t, filepath.Join(bootstrapDir, "memos-idp-second.json"), "Second Provider", "second-secret")
|
|
|
|
err := stores.ApplyIdentityProviderBootstrapDir(context.Background(), bootstrapDir)
|
|
require.Error(t, err)
|
|
assert.ErrorContains(t, err, `uid duplicates "primary-sso"`)
|
|
|
|
providers, listErr := stores.ListIdentityProviders(context.Background(), &store.FindIdentityProvider{})
|
|
require.NoError(t, listErr)
|
|
assert.Empty(t, providers)
|
|
}
|
|
|
|
func TestApplyIdentityProviderBootstrapDirIgnoresUnrelatedSecretFiles(t *testing.T) {
|
|
stores := newIdentityProviderBootstrapTestStore(t)
|
|
bootstrapDir := t.TempDir()
|
|
require.NoError(t, os.WriteFile(filepath.Join(bootstrapDir, ".env"), []byte("DATABASE_PASSWORD=secret"), 0600))
|
|
writeIdentityProviderBootstrap(t, filepath.Join(bootstrapDir, "memos-idp-primary.json"), "Primary SSO", "oauth-secret")
|
|
|
|
require.NoError(t, stores.ApplyIdentityProviderBootstrapDir(context.Background(), bootstrapDir))
|
|
provider, err := stores.GetIdentityProvider(context.Background(), &store.FindIdentityProvider{UID: ptr("primary-sso")})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, provider)
|
|
}
|
|
|
|
func TestApplyIdentityProviderBootstrapDirIgnoresDirectoryWithoutMatchingFiles(t *testing.T) {
|
|
stores := newIdentityProviderBootstrapTestStore(t)
|
|
bootstrapDir := t.TempDir()
|
|
require.NoError(t, os.WriteFile(filepath.Join(bootstrapDir, "database-password"), []byte("secret"), 0600))
|
|
|
|
require.NoError(t, stores.ApplyIdentityProviderBootstrapDir(context.Background(), bootstrapDir))
|
|
}
|
|
|
|
func TestApplyIdentityProviderBootstrapDirIgnoresMissingDirectory(t *testing.T) {
|
|
stores := newIdentityProviderBootstrapTestStore(t)
|
|
bootstrapDir := filepath.Join(t.TempDir(), "missing")
|
|
|
|
require.NoError(t, stores.ApplyIdentityProviderBootstrapDir(context.Background(), bootstrapDir))
|
|
}
|
|
|
|
func newIdentityProviderBootstrapTestStore(t *testing.T) *store.Store {
|
|
t.Helper()
|
|
p := &profile.Profile{
|
|
Data: t.TempDir(),
|
|
Driver: "sqlite",
|
|
DSN: filepath.Join(t.TempDir(), "bootstrap.db"),
|
|
}
|
|
driver, err := sqlite.NewDB(p)
|
|
require.NoError(t, err)
|
|
stores := store.New(driver, p)
|
|
require.NoError(t, stores.Migrate(context.Background()))
|
|
t.Cleanup(func() {
|
|
require.NoError(t, stores.Close())
|
|
})
|
|
return stores
|
|
}
|
|
|
|
func writeIdentityProviderBootstrap(t *testing.T, path, title, secret string) {
|
|
t.Helper()
|
|
content := `{
|
|
"uid": "primary-sso",
|
|
"name": "` + title + `",
|
|
"type": "OAUTH2",
|
|
"identifierFilter": "",
|
|
"config": {
|
|
"oauth2Config": {
|
|
"clientId": "client-id",
|
|
"clientSecret": "` + secret + `",
|
|
"authUrl": "https://example.com/authorize",
|
|
"tokenUrl": "https://example.com/token",
|
|
"userInfoUrl": "https://example.com/userinfo",
|
|
"scopes": ["profile", "email"],
|
|
"fieldMapping": {
|
|
"identifier": "sub",
|
|
"displayName": "name",
|
|
"email": "email",
|
|
"avatarUrl": "picture"
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
require.False(t, strings.Contains(title, `"`))
|
|
require.False(t, strings.Contains(secret, `"`))
|
|
require.NoError(t, os.WriteFile(path, []byte(content), 0600))
|
|
}
|
|
|
|
func ptr[T any](value T) *T {
|
|
return &value
|
|
}
|