0038295bbc
- Load IdPs and supported instance-setting groups as runtime overlays from /etc/secrets. - Reject API mutations of deployment-managed resources and serialize authentication safety checks across database drivers. - Preserve upgrade compatibility, demo SSO policy, stable IdP ordering, and driver-specific transaction retries.
498 lines
21 KiB
Go
498 lines
21 KiB
Go
package store_test
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
"github.com/usememos/memos/internal/profile"
|
|
storepb "github.com/usememos/memos/proto/gen/store"
|
|
"github.com/usememos/memos/store"
|
|
"github.com/usememos/memos/store/db/sqlite"
|
|
)
|
|
|
|
func TestLoadDeploymentConfigurationPublishesRuntimeOnlyIdentityProvider(t *testing.T) {
|
|
ctx := context.Background()
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
dir := t.TempDir()
|
|
writeDeploymentIdentityProvider(t, filepath.Join(dir, "memos-idp-primary.json"), "primary-sso", "File SSO", "file-secret")
|
|
|
|
require.NoError(t, stores.LoadDeploymentConfigurationDir(ctx, dir))
|
|
|
|
effective, err := stores.GetIdentityProvider(ctx, &store.FindIdentityProvider{UID: ptr("primary-sso")})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, effective)
|
|
assert.Zero(t, effective.Id)
|
|
assert.Equal(t, "File SSO", effective.Name)
|
|
assert.Equal(t, "file-secret", effective.Config.GetOauth2Config().ClientSecret)
|
|
assert.True(t, stores.IsIdentityProviderDeploymentConfigured("primary-sso"))
|
|
|
|
stored, err := stores.GetStoredIdentityProvider(ctx, &store.FindIdentityProvider{UID: ptr("primary-sso")})
|
|
require.NoError(t, err)
|
|
assert.Nil(t, stored)
|
|
}
|
|
|
|
func TestLoadDeploymentConfigurationShadowsWithoutChangingStoredResources(t *testing.T) {
|
|
ctx := context.Background()
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
stored, err := stores.CreateIdentityProvider(ctx, deploymentIdentityProvider("primary-sso", "Stored SSO", "stored-secret"))
|
|
require.NoError(t, err)
|
|
storedID := stored.Id
|
|
_, err = stores.UpsertInstanceSetting(ctx, &storepb.InstanceSetting{
|
|
Key: storepb.InstanceSettingKey_GENERAL,
|
|
Value: &storepb.InstanceSetting_GeneralSetting{GeneralSetting: &storepb.InstanceGeneralSetting{
|
|
WeekStartDayOffset: 1,
|
|
}},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
dir := t.TempDir()
|
|
writeDeploymentIdentityProvider(t, filepath.Join(dir, "memos-idp-primary.json"), "primary-sso", "File SSO", "file-secret")
|
|
writeDeploymentGeneralSetting(t, filepath.Join(dir, "memos-instance-setting-general.json"), 4, true)
|
|
require.NoError(t, stores.LoadDeploymentConfigurationDir(ctx, dir))
|
|
|
|
effectiveProvider, err := stores.GetIdentityProvider(ctx, &store.FindIdentityProvider{UID: ptr("primary-sso")})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "File SSO", effectiveProvider.Name)
|
|
storedProvider, err := stores.GetStoredIdentityProvider(ctx, &store.FindIdentityProvider{UID: ptr("primary-sso")})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, storedID, storedProvider.Id)
|
|
assert.Equal(t, "Stored SSO", storedProvider.Name)
|
|
|
|
effectiveGeneral, err := stores.GetInstanceGeneralSetting(ctx)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int32(4), effectiveGeneral.WeekStartDayOffset)
|
|
assert.True(t, effectiveGeneral.DisallowPasswordAuth)
|
|
rawGeneral, err := stores.GetStoredInstanceSetting(ctx, &store.FindInstanceSetting{Name: storepb.InstanceSettingKey_GENERAL.String()})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, rawGeneral)
|
|
assert.Equal(t, int32(1), rawGeneral.GetGeneralSetting().WeekStartDayOffset)
|
|
}
|
|
|
|
func TestLoadDeploymentConfigurationReturnsDefensiveClones(t *testing.T) {
|
|
ctx := context.Background()
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
dir := t.TempDir()
|
|
writeDeploymentIdentityProvider(t, filepath.Join(dir, "memos-idp-primary.json"), "primary-sso", "File SSO", "file-secret")
|
|
writeDeploymentGeneralSetting(t, filepath.Join(dir, "memos-instance-setting-general.json"), 2, false)
|
|
require.NoError(t, stores.LoadDeploymentConfigurationDir(ctx, dir))
|
|
|
|
provider, err := stores.GetIdentityProvider(ctx, &store.FindIdentityProvider{UID: ptr("primary-sso")})
|
|
require.NoError(t, err)
|
|
provider.Name = "Mutated"
|
|
provider.Config.GetOauth2Config().ClientSecret = "mutated-secret"
|
|
providerAgain, err := stores.GetIdentityProvider(ctx, &store.FindIdentityProvider{UID: ptr("primary-sso")})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "File SSO", providerAgain.Name)
|
|
assert.Equal(t, "file-secret", providerAgain.Config.GetOauth2Config().ClientSecret)
|
|
|
|
general, err := stores.GetInstanceGeneralSetting(ctx)
|
|
require.NoError(t, err)
|
|
general.WeekStartDayOffset = 6
|
|
generalAgain, err := stores.GetInstanceGeneralSetting(ctx)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int32(2), generalAgain.WeekStartDayOffset)
|
|
}
|
|
|
|
func TestLoadDeploymentConfigurationPublishesAtomically(t *testing.T) {
|
|
ctx := context.Background()
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
validDir := t.TempDir()
|
|
writeDeploymentIdentityProvider(t, filepath.Join(validDir, "memos-idp-primary.json"), "primary-sso", "File SSO", "file-secret")
|
|
require.NoError(t, stores.LoadDeploymentConfigurationDir(ctx, validDir))
|
|
|
|
invalidDir := t.TempDir()
|
|
writeDeploymentIdentityProvider(t, filepath.Join(invalidDir, "memos-idp-primary.json"), "primary-sso", "Changed SSO", "changed-secret")
|
|
require.NoError(t, os.WriteFile(filepath.Join(invalidDir, "memos-instance-setting-invalid.json"), []byte(`{"key":"GENERAL","unknown":true}`), 0600))
|
|
err := stores.LoadDeploymentConfigurationDir(ctx, invalidDir)
|
|
require.Error(t, err)
|
|
assert.ErrorContains(t, err, `unknown field "unknown"`)
|
|
|
|
provider, getErr := stores.GetIdentityProvider(ctx, &store.FindIdentityProvider{UID: ptr("primary-sso")})
|
|
require.NoError(t, getErr)
|
|
assert.Equal(t, "File SSO", provider.Name)
|
|
}
|
|
|
|
func TestLoadDeploymentConfigurationRejectsDuplicateResources(t *testing.T) {
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
dir := t.TempDir()
|
|
writeDeploymentIdentityProvider(t, filepath.Join(dir, "memos-idp-first.json"), "primary-sso", "First", "first-secret")
|
|
writeDeploymentIdentityProvider(t, filepath.Join(dir, "memos-idp-second.json"), "primary-sso", "Second", "second-secret")
|
|
|
|
err := stores.LoadDeploymentConfigurationDir(context.Background(), dir)
|
|
require.Error(t, err)
|
|
assert.ErrorContains(t, err, `identity provider UID "primary-sso" is declared by both`)
|
|
}
|
|
|
|
func TestLoadDeploymentConfigurationValidatesAffectedAuthState(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
t.Run("managed GENERAL cannot disable regular password auth without SSO", func(t *testing.T) {
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
dir := t.TempDir()
|
|
writeDeploymentGeneralSetting(t, filepath.Join(dir, "memos-instance-setting-general.json"), 0, true)
|
|
err := stores.LoadDeploymentConfigurationDir(ctx, dir)
|
|
require.Error(t, err)
|
|
assert.ErrorContains(t, err, "has no effective identity provider")
|
|
})
|
|
|
|
t.Run("unrelated file does not reject unmanaged legacy state", func(t *testing.T) {
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
_, err := stores.UpsertInstanceSetting(ctx, &storepb.InstanceSetting{
|
|
Key: storepb.InstanceSettingKey_GENERAL,
|
|
Value: &storepb.InstanceSetting_GeneralSetting{GeneralSetting: &storepb.InstanceGeneralSetting{
|
|
DisallowPasswordAuth: true,
|
|
}},
|
|
})
|
|
require.NoError(t, err)
|
|
dir := t.TempDir()
|
|
require.NoError(t, os.WriteFile(filepath.Join(dir, "memos-instance-setting-storage.json"), []byte(`{
|
|
"key": "STORAGE",
|
|
"storageSetting": {"storageType": "LOCAL"}
|
|
}`), 0600))
|
|
require.NoError(t, stores.LoadDeploymentConfigurationDir(ctx, dir))
|
|
})
|
|
}
|
|
|
|
func TestLoadDeploymentConfigurationIgnoresUnrelatedFilesAndMissingDirectory(t *testing.T) {
|
|
ctx := context.Background()
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
dir := t.TempDir()
|
|
require.NoError(t, os.WriteFile(filepath.Join(dir, ".env"), []byte("DATABASE_PASSWORD=secret"), 0600))
|
|
require.NoError(t, stores.LoadDeploymentConfigurationDir(ctx, dir))
|
|
require.NoError(t, stores.LoadDeploymentConfigurationDir(ctx, filepath.Join(t.TempDir(), "missing")))
|
|
}
|
|
|
|
func TestLoadDeploymentConfigurationSupportsLegacyIdentityProviderFilenames(t *testing.T) {
|
|
ctx := context.Background()
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
dir := t.TempDir()
|
|
writeDeploymentIdentityProvider(t, filepath.Join(dir, "memos-idp-Primary_SSO.json"), "primary-sso", "Primary", "secret")
|
|
|
|
require.NoError(t, stores.LoadDeploymentConfigurationDir(ctx, dir))
|
|
assert.True(t, stores.IsIdentityProviderDeploymentConfigured("primary-sso"))
|
|
|
|
invalidDir := t.TempDir()
|
|
require.NoError(t, os.WriteFile(filepath.Join(invalidDir, "memos-idp-Bad.json"), []byte("not-json"), 0600))
|
|
require.Error(t, stores.LoadDeploymentConfigurationDir(ctx, invalidDir))
|
|
}
|
|
|
|
func TestLoadDeploymentConfigurationAcceptsSaturdayWeekStart(t *testing.T) {
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
dir := t.TempDir()
|
|
writeDeploymentGeneralSetting(t, filepath.Join(dir, "memos-instance-setting-general.json"), -1, false)
|
|
require.NoError(t, stores.LoadDeploymentConfigurationDir(context.Background(), dir))
|
|
|
|
general, err := stores.GetInstanceGeneralSetting(context.Background())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int32(-1), general.WeekStartDayOffset)
|
|
}
|
|
|
|
func TestLoadDeploymentConfigurationAcceptsRegularFileSymlink(t *testing.T) {
|
|
ctx := context.Background()
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
dir := t.TempDir()
|
|
target := filepath.Join(t.TempDir(), "provider.json")
|
|
writeDeploymentIdentityProvider(t, target, "primary-sso", "Primary", "secret")
|
|
require.NoError(t, os.Symlink(target, filepath.Join(dir, "memos-idp-primary.json")))
|
|
require.NoError(t, stores.LoadDeploymentConfigurationDir(ctx, dir))
|
|
assert.True(t, stores.IsIdentityProviderDeploymentConfigured("primary-sso"))
|
|
}
|
|
|
|
func TestLoadDeploymentConfigurationSupportsEveryProvisionableSettingGroup(t *testing.T) {
|
|
ctx := context.Background()
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
dir := t.TempDir()
|
|
settings := map[string]*storepb.InstanceSetting{
|
|
"memos-instance-setting-general.json": {
|
|
Key: storepb.InstanceSettingKey_GENERAL,
|
|
Value: &storepb.InstanceSetting_GeneralSetting{GeneralSetting: &storepb.InstanceGeneralSetting{WeekStartDayOffset: 1}},
|
|
},
|
|
"memos-instance-setting-storage.json": {
|
|
Key: storepb.InstanceSettingKey_STORAGE,
|
|
Value: &storepb.InstanceSetting_StorageSetting{StorageSetting: &storepb.InstanceStorageSetting{StorageType: storepb.InstanceStorageSetting_LOCAL}},
|
|
},
|
|
"memos-instance-setting-memo-related.json": {
|
|
Key: storepb.InstanceSettingKey_MEMO_RELATED,
|
|
Value: &storepb.InstanceSetting_MemoRelatedSetting{MemoRelatedSetting: &storepb.InstanceMemoRelatedSetting{
|
|
ContentLengthLimit: store.DefaultContentLengthLimit,
|
|
Reactions: []string{"👍"},
|
|
}},
|
|
},
|
|
"memos-instance-setting-notification.json": {
|
|
Key: storepb.InstanceSettingKey_NOTIFICATION,
|
|
Value: &storepb.InstanceSetting_NotificationSetting{NotificationSetting: &storepb.InstanceNotificationSetting{}},
|
|
},
|
|
"memos-instance-setting-ai.json": {
|
|
Key: storepb.InstanceSettingKey_AI,
|
|
Value: &storepb.InstanceSetting_AiSetting{AiSetting: &storepb.InstanceAISetting{Providers: []*storepb.AIProviderConfig{
|
|
{Id: "primary", Title: "Primary", Type: storepb.AIProviderType_OPENAI, ApiKey: "ai-secret"},
|
|
}}},
|
|
},
|
|
}
|
|
for filename, setting := range settings {
|
|
writeDeploymentMessage(t, filepath.Join(dir, filename), setting)
|
|
}
|
|
require.NoError(t, stores.LoadDeploymentConfigurationDir(ctx, dir))
|
|
for _, setting := range settings {
|
|
assert.True(t, stores.IsInstanceSettingDeploymentConfigured(setting.Key))
|
|
}
|
|
ai, err := stores.GetInstanceAISetting(ctx)
|
|
require.NoError(t, err)
|
|
require.Len(t, ai.Providers, 1)
|
|
assert.Equal(t, "https://api.openai.com/v1", ai.Providers[0].Endpoint)
|
|
}
|
|
|
|
func TestLoadDeploymentConfigurationRejectsInvalidSettingResources(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
content string
|
|
errorString string
|
|
}{
|
|
{name: "BASIC", content: `{"key":"BASIC","basicSetting":{}}`, errorString: "cannot be deployment configured"},
|
|
{name: "TAGS", content: `{"key":"TAGS","tagsSetting":{}}`, errorString: "cannot be deployment configured"},
|
|
{name: "mismatched oneof", content: `{"key":"GENERAL","storageSetting":{}}`, errorString: "generalSetting must be populated"},
|
|
{name: "invalid week start", content: `{"key":"GENERAL","generalSetting":{"weekStartDayOffset":-2}}`, errorString: "must be between -1 and 6"},
|
|
{name: "unknown field", content: `{"key":"GENERAL","generalSetting":{},"typo":true}`, errorString: `unknown field "typo"`},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
dir := t.TempDir()
|
|
require.NoError(t, os.WriteFile(filepath.Join(dir, "memos-instance-setting-invalid.json"), []byte(test.content), 0600))
|
|
err := stores.LoadDeploymentConfigurationDir(context.Background(), dir)
|
|
require.Error(t, err)
|
|
assert.ErrorContains(t, err, test.errorString)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadDeploymentConfigurationBoundsFilesAndRedactsDecodeErrors(t *testing.T) {
|
|
t.Run("oversized file", func(t *testing.T) {
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
dir := t.TempDir()
|
|
require.NoError(t, os.WriteFile(filepath.Join(dir, "memos-idp-oversized.json"), []byte(strings.Repeat("x", (1<<20)+1)), 0600))
|
|
err := stores.LoadDeploymentConfigurationDir(context.Background(), dir)
|
|
require.Error(t, err)
|
|
assert.ErrorContains(t, err, "exceeds 1048576 bytes")
|
|
})
|
|
|
|
t.Run("secret value is not included in a type error", func(t *testing.T) {
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
dir := t.TempDir()
|
|
content := `{
|
|
"uid":"primary-sso",
|
|
"name":"Primary",
|
|
"type":"OAUTH2",
|
|
"config":{"oauth2Config":{"clientSecret":{"value":"must-not-appear"}}}
|
|
}`
|
|
require.NoError(t, os.WriteFile(filepath.Join(dir, "memos-idp-primary.json"), []byte(content), 0600))
|
|
err := stores.LoadDeploymentConfigurationDir(context.Background(), dir)
|
|
require.Error(t, err)
|
|
assert.NotContains(t, err.Error(), "must-not-appear")
|
|
})
|
|
}
|
|
|
|
func TestAuthenticationConfigurationMutationsAreSafe(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
t.Run("rejects disabling regular password auth without an IdP", func(t *testing.T) {
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
_, err := stores.UpsertInstanceGeneralSettingSafely(ctx, &storepb.InstanceSetting{
|
|
Key: storepb.InstanceSettingKey_GENERAL,
|
|
Value: &storepb.InstanceSetting_GeneralSetting{GeneralSetting: &storepb.InstanceGeneralSetting{
|
|
DisallowPasswordAuth: true,
|
|
}},
|
|
})
|
|
require.ErrorIs(t, err, store.ErrUnsafeAuthenticationConfiguration)
|
|
stored, getErr := stores.GetStoredInstanceSetting(ctx, &store.FindInstanceSetting{Name: storepb.InstanceSettingKey_GENERAL.String()})
|
|
require.NoError(t, getErr)
|
|
assert.Nil(t, stored)
|
|
})
|
|
|
|
t.Run("rejects deleting the last effective IdP", func(t *testing.T) {
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
provider, err := stores.CreateIdentityProvider(ctx, deploymentIdentityProvider("primary-sso", "Primary", "secret"))
|
|
require.NoError(t, err)
|
|
_, err = stores.UpsertInstanceGeneralSettingSafely(ctx, &storepb.InstanceSetting{
|
|
Key: storepb.InstanceSettingKey_GENERAL,
|
|
Value: &storepb.InstanceSetting_GeneralSetting{GeneralSetting: &storepb.InstanceGeneralSetting{
|
|
DisallowPasswordAuth: true,
|
|
}},
|
|
})
|
|
require.NoError(t, err)
|
|
err = stores.DeleteIdentityProviderSafely(ctx, &store.DeleteIdentityProvider{ID: provider.Id})
|
|
require.ErrorIs(t, err, store.ErrUnsafeAuthenticationConfiguration)
|
|
stored, getErr := stores.GetStoredIdentityProvider(ctx, &store.FindIdentityProvider{ID: &provider.Id})
|
|
require.NoError(t, getErr)
|
|
assert.NotNil(t, stored)
|
|
})
|
|
|
|
t.Run("file-backed IdP satisfies the invariant", func(t *testing.T) {
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
dir := t.TempDir()
|
|
writeDeploymentIdentityProvider(t, filepath.Join(dir, "memos-idp-primary.json"), "primary-sso", "Primary", "secret")
|
|
require.NoError(t, stores.LoadDeploymentConfigurationDir(ctx, dir))
|
|
_, err := stores.UpsertInstanceGeneralSettingSafely(ctx, &storepb.InstanceSetting{
|
|
Key: storepb.InstanceSettingKey_GENERAL,
|
|
Value: &storepb.InstanceSetting_GeneralSetting{GeneralSetting: &storepb.InstanceGeneralSetting{
|
|
DisallowPasswordAuth: true,
|
|
}},
|
|
})
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("allows unrelated edits to an existing unsafe GENERAL state", func(t *testing.T) {
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
_, err := stores.UpsertInstanceSetting(ctx, &storepb.InstanceSetting{
|
|
Key: storepb.InstanceSettingKey_GENERAL,
|
|
Value: &storepb.InstanceSetting_GeneralSetting{GeneralSetting: &storepb.InstanceGeneralSetting{
|
|
DisallowPasswordAuth: true,
|
|
}},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = stores.UpsertInstanceGeneralSettingSafely(ctx, &storepb.InstanceSetting{
|
|
Key: storepb.InstanceSettingKey_GENERAL,
|
|
Value: &storepb.InstanceSetting_GeneralSetting{GeneralSetting: &storepb.InstanceGeneralSetting{
|
|
DisallowPasswordAuth: true,
|
|
WeekStartDayOffset: 1,
|
|
}},
|
|
})
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("rejects making the stored fallback unsafe under deployment GENERAL", func(t *testing.T) {
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
dir := t.TempDir()
|
|
writeDeploymentGeneralSetting(t, filepath.Join(dir, "memos-instance-setting-general.json"), 0, false)
|
|
require.NoError(t, stores.LoadDeploymentConfigurationDir(ctx, dir))
|
|
|
|
_, err := stores.UpsertInstanceGeneralSettingSafely(ctx, &storepb.InstanceSetting{
|
|
Key: storepb.InstanceSettingKey_GENERAL,
|
|
Value: &storepb.InstanceSetting_GeneralSetting{GeneralSetting: &storepb.InstanceGeneralSetting{
|
|
DisallowPasswordAuth: true,
|
|
}},
|
|
})
|
|
require.ErrorIs(t, err, store.ErrUnsafeAuthenticationConfiguration)
|
|
})
|
|
}
|
|
|
|
func TestListIdentityProvidersPreservesStoredOrder(t *testing.T) {
|
|
ctx := context.Background()
|
|
stores := newDeploymentConfigurationTestStore(t)
|
|
_, err := stores.CreateIdentityProvider(ctx, deploymentIdentityProvider("zeta-sso", "Stored Zeta", "stored-secret"))
|
|
require.NoError(t, err)
|
|
_, err = stores.CreateIdentityProvider(ctx, deploymentIdentityProvider("alpha-test", "Stored Alpha", "stored-secret"))
|
|
require.NoError(t, err)
|
|
|
|
dir := t.TempDir()
|
|
writeDeploymentIdentityProvider(t, filepath.Join(dir, "memos-idp-zeta.json"), "zeta-sso", "File Zeta", "file-secret")
|
|
writeDeploymentIdentityProvider(t, filepath.Join(dir, "memos-idp-beta.json"), "beta-file", "File Beta", "file-secret")
|
|
require.NoError(t, stores.LoadDeploymentConfigurationDir(ctx, dir))
|
|
|
|
providers, err := stores.ListIdentityProviders(ctx, &store.FindIdentityProvider{})
|
|
require.NoError(t, err)
|
|
require.Len(t, providers, 3)
|
|
assert.Equal(t, []string{"zeta-sso", "alpha-test", "beta-file"}, []string{providers[0].Uid, providers[1].Uid, providers[2].Uid})
|
|
assert.Equal(t, "File Zeta", providers[0].Name)
|
|
}
|
|
|
|
func newDeploymentConfigurationTestStore(t *testing.T) *store.Store {
|
|
t.Helper()
|
|
p := &profile.Profile{
|
|
Data: t.TempDir(),
|
|
Driver: "sqlite",
|
|
DSN: filepath.Join(t.TempDir(), "deployment.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 deploymentIdentityProvider(uid, title, secret string) *storepb.IdentityProvider {
|
|
return &storepb.IdentityProvider{
|
|
Uid: uid,
|
|
Name: title,
|
|
Type: storepb.IdentityProvider_OAUTH2,
|
|
Config: &storepb.IdentityProviderConfig{Config: &storepb.IdentityProviderConfig_Oauth2Config{Oauth2Config: &storepb.OAuth2Config{
|
|
ClientId: "client-id",
|
|
ClientSecret: secret,
|
|
AuthUrl: "https://example.com/authorize",
|
|
TokenUrl: "https://example.com/token",
|
|
UserInfoUrl: "https://example.com/userinfo",
|
|
Scopes: []string{"profile", "email"},
|
|
FieldMapping: &storepb.FieldMapping{Identifier: "sub"},
|
|
}}},
|
|
}
|
|
}
|
|
|
|
func writeDeploymentIdentityProvider(t *testing.T, path, uid, title, secret string) {
|
|
t.Helper()
|
|
content := `{
|
|
"uid": "` + uid + `",
|
|
"name": "` + title + `",
|
|
"type": "OAUTH2",
|
|
"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"}
|
|
}
|
|
}
|
|
}`
|
|
require.False(t, strings.Contains(uid+title+secret, `"`))
|
|
require.NoError(t, os.WriteFile(path, []byte(content), 0600))
|
|
}
|
|
|
|
func writeDeploymentGeneralSetting(t *testing.T, path string, weekStart int32, disallowPasswordAuth bool) {
|
|
t.Helper()
|
|
content := `{
|
|
"key": "GENERAL",
|
|
"generalSetting": {
|
|
"weekStartDayOffset": ` + assertInt32(weekStart) + `,
|
|
"disallowPasswordAuth": ` + assertBool(disallowPasswordAuth) + `
|
|
}
|
|
}`
|
|
require.NoError(t, os.WriteFile(path, []byte(content), 0600))
|
|
}
|
|
|
|
func writeDeploymentMessage(t *testing.T, path string, message proto.Message) {
|
|
t.Helper()
|
|
content, err := (protojson.MarshalOptions{Indent: " "}).Marshal(message)
|
|
require.NoError(t, err)
|
|
require.NoError(t, os.WriteFile(path, content, 0600))
|
|
}
|
|
|
|
func assertInt32(value int32) string {
|
|
return strconv.FormatInt(int64(value), 10)
|
|
}
|
|
|
|
func assertBool(value bool) string {
|
|
if value {
|
|
return "true"
|
|
}
|
|
return "false"
|
|
}
|
|
|
|
func ptr[T any](value T) *T {
|
|
return &value
|
|
}
|