Skip to content

Commit 80af5d0

Browse files
feat: extend tenant storage with tenant.yaml file (#45)
* feat: extend tenant storage with tenant.yaml file Add a top-level tenant.yaml to tenant storage, mirroring server.yaml in server storage. On write, the tenant-level fields (name, url, jwks, metadata, settings, styling) are serialized via the system Tenant model, which strips the collections persisted as separate files (pools, schemas, mfa_methods, themes, servers). The file is optional on read: a missing tenant.yaml yields an empty base map without error, preserving backwards compatibility with existing on-disk configs. Also bumps acp-client-go and aligns the otp_settings pool test with the updated model. * test: cover tenant settings message redaction in tenant.yaml round-trip Extend the tenant-level configuration test case with settings.message_redaction (address/content) to verify the redaction policy serializes to tenant.yaml and round-trips back through read.
1 parent c9af2a7 commit 80af5d0

4 files changed

Lines changed: 68 additions & 3 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.24.0
44

55
require (
66
github.com/Masterminds/sprig/v3 v3.2.3
7-
github.com/cloudentity/acp-client-go v0.0.0-20260410101459-b244b5048247
7+
github.com/cloudentity/acp-client-go v0.0.0-20260527095100-008ff5049411
88
github.com/corvus-ch/zbase32 v1.0.0
99
github.com/go-json-experiment/json v0.0.0-20240524174822-2d9f40f7385b
1010
github.com/go-openapi/strfmt v0.24.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj
77
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
88
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
99
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
10-
github.com/cloudentity/acp-client-go v0.0.0-20260410101459-b244b5048247 h1:TjlzXba4WIAE4INjq+bhKhl8yhS4iXGwB7leztdAKIA=
11-
github.com/cloudentity/acp-client-go v0.0.0-20260410101459-b244b5048247/go.mod h1:vfVPwSn1JfwCOheMlqGmF1NPGio88S8jdiXOzQYjCeY=
10+
github.com/cloudentity/acp-client-go v0.0.0-20260527095100-008ff5049411 h1:oty7npFWtmLAegF5gYXQAAsY2DOZ7WxcCZVVqBSeIwE=
11+
github.com/cloudentity/acp-client-go v0.0.0-20260527095100-008ff5049411/go.mod h1:Hr2WHHXmp+DC4B2oprhgP47yl/dsCAyj9HcG6P3z4m0=
1212
github.com/corvus-ch/zbase32 v1.0.0 h1:pDV0qZ1g+HYA8P0PbULsgUg/tZue1FIjsZ7r7h4nZeU=
1313
github.com/corvus-ch/zbase32 v1.0.0/go.mod h1:A7KLRecF1tysURyoqiJBvMJFmt/ccqkRdDTLjlQeVsU=
1414
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=

internal/cac/storage/tenant_storage.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package storage
22

33
import (
44
"context"
5+
"encoding/json"
56
"github.com/cloudentity/acp-client-go/clients/hub/models"
7+
smodels "github.com/cloudentity/acp-client-go/clients/system/models"
68
"github.com/cloudentity/cac/internal/cac/api"
79
"github.com/cloudentity/cac/internal/cac/utils"
810
"path/filepath"
@@ -31,6 +33,10 @@ func (t *TenantStorage) Write(ctx context.Context, data models.Rfc7396PatchOpera
3133
return err
3234
}
3335

36+
if err = t.storeTenant(path, model); err != nil {
37+
return err
38+
}
39+
3440
if err = writeFiles(model.Pools,
3541
filepath.Join(path, "pools"),
3642
func(id string, it models.TreePool) string { return it.Name }); err != nil {
@@ -194,6 +200,25 @@ func (t *TenantStorage) Read(ctx context.Context, opts ...api.SourceOpt) (models
194200

195201
var _ Storage = &TenantStorage{}
196202

203+
func (t *TenantStorage) storeTenant(path string, data *models.TreeTenant) error {
204+
var (
205+
tenant smodels.Tenant
206+
bts []byte
207+
err error
208+
)
209+
210+
// serialize the tenant data into system/models to remove the dependencies which are stored in separate files
211+
if bts, err = json.Marshal(data); err != nil {
212+
return err
213+
}
214+
215+
if err = json.Unmarshal(bts, &tenant); err != nil {
216+
return err
217+
}
218+
219+
return writeFile(tenant, filepath.Join(path, "tenant"))
220+
}
221+
197222
func storeTemplates(templates models.TreeTemplates, path string) error {
198223
for id, template := range templates {
199224
var (

internal/cac/storage/tenant_storage_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ identifier_case_insensitive: false
168168
mfa_session_ttl: 0s
169169
name: Idp-datamigration-pool
170170
otp_settings:
171+
require_user_confirmation_before_send: false
171172
verify_address:
172173
length: 6
173174
ttl: 5m0s
@@ -180,6 +181,45 @@ webauthn_settings:
180181
- https://www.sit2.example.com`, string(bts))
181182
},
182183
},
184+
{
185+
desc: "tenant level configuration",
186+
data: &models.TreeTenant{
187+
Name: "Default",
188+
URL: "https://example.com/default",
189+
Metadata: models.TenantMetadata{
190+
"owner": "platform-team",
191+
},
192+
Settings: &models.TenantSettings{
193+
MessageRedaction: &models.RedactionPolicy{
194+
Address: "obfuscate",
195+
Content: "retain",
196+
},
197+
},
198+
MfaMethods: models.TreeMFAMethods{
199+
"sms": models.TreeMFAMethod{
200+
Enabled: true,
201+
Mechanism: "sms",
202+
},
203+
},
204+
},
205+
files: []string{
206+
"tenant.yaml",
207+
"mfa_methods/sms.yaml",
208+
},
209+
assert: func(t *testing.T, path string, bts []byte) {
210+
switch path {
211+
case "tenant.yaml":
212+
require.YAMLEq(t, `name: Default
213+
url: https://example.com/default
214+
metadata:
215+
owner: platform-team
216+
settings:
217+
message_redaction:
218+
address: obfuscate
219+
content: retain`, string(bts))
220+
}
221+
},
222+
},
183223
{
184224
desc: "themes and templates",
185225
data: &models.TreeTenant{

0 commit comments

Comments
 (0)