Skip to content

Commit 0ffa9b2

Browse files
authored
feat: Configuration for sharing inside context configuration (#4615)
Moved sharing configuration from top-level to context-specific settings, following the same pattern as other context-based configurations **Before:** ```yaml sharing: auto_accept_trusted: true contexts: twake: auto_accept_trusted: false ``` **After:** ```yaml contexts: default: sharing: auto_accept_trusted: false twake: sharing: auto_accept_trusted: true ```
2 parents 49bfece + 27fc6cb commit 0ffa9b2

File tree

8 files changed

+142
-107
lines changed

8 files changed

+142
-107
lines changed

cozy.example.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,25 @@ contexts:
530530
- src: /logos/1_partner.png
531531
alt: Partner n°1
532532
type: secondary
533+
# Sharing configuration for cozy-to-cozy sharing trust rules
534+
sharing:
535+
# Auto-accept sharing invitations from trusted domains or contacts(default: false)
536+
auto_accept_trusted: true
537+
# Auto-accept sharing invitations from trusted contacts (default: false)
538+
auto_accept_trusted_contacts: true
539+
# List of trusted domains for automatic sharing acceptance
540+
trusted_domains:
541+
- example.com
542+
- mycompany.twake.app
543+
544+
# Default context - used as fallback when instance context is not specified
545+
# or when a context doesn't have sharing configuration
546+
default:
547+
sharing:
548+
# Conservative defaults for production
549+
auto_accept_trusted: false
550+
auto_accept_trusted_contacts: false
551+
trusted_domains: []
533552

534553
rabbitmq:
535554
enabled: true

model/sharing/oauth.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -473,9 +473,9 @@ func (s *Sharing) SendAnswer(inst *instance.Instance, state string) error {
473473
s.Active = true
474474
s.Initial = s.NbFiles > 0
475475

476-
options := config.GetConfig().Sharing.OptionsForContext(inst.ContextName)
476+
options := config.GetSharingConfig(inst.ContextName)
477477
// Mark the sender's contact as trusted since we accepted their sharing
478-
if *options.AutoAcceptTrustedContacts && len(s.Members) > 0 && s.Members[0].Email != "" {
478+
if options.AutoAcceptTrustedContacts && len(s.Members) > 0 && s.Members[0].Email != "" {
479479
c, err := contact.FindByEmail(inst, s.Members[0].Email)
480480
if err != nil {
481481
// Contact doesn't exist, create it using the standardized method

model/sharing/trust.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ func IsTrustedMember(inst *instance.Instance, member *Member) bool {
1919
if inst == nil || member == nil {
2020
return false
2121
}
22-
options := config.GetConfig().Sharing.OptionsForContext(inst.ContextName)
22+
options := config.GetSharingConfig(inst.ContextName)
2323

24-
if options.AutoAcceptTrusted == nil || !*options.AutoAcceptTrusted {
24+
if !options.AutoAcceptTrusted {
2525
return false
2626
}
2727

@@ -48,7 +48,7 @@ func IsTrustedMember(inst *instance.Instance, member *Member) bool {
4848
}
4949

5050
// Check if this member is a trusted contact
51-
if options.AutoAcceptTrustedContacts == nil || !*options.AutoAcceptTrustedContacts {
51+
if !options.AutoAcceptTrustedContacts {
5252
return false
5353
}
5454
if isTrustedContact(inst, member) {

model/sharing/trust_test.go

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ func TestIsTrustedMember(t *testing.T) {
1818
inst := setup.GetTestInstance()
1919

2020
cfg := config.GetConfig()
21-
prevSharing := cfg.Sharing
22-
cfg.Sharing = config.SharingConfig{
23-
AutoAcceptTrusted: true,
24-
Contexts: map[string]config.SharingContext{},
21+
prevContexts := cfg.Contexts
22+
// Initialize contexts with sharing config
23+
cfg.Contexts = map[string]interface{}{
24+
config.DefaultInstanceContext: map[string]interface{}{
25+
"sharing": map[string]interface{}{
26+
"auto_accept_trusted": true,
27+
},
28+
},
2529
}
26-
t.Cleanup(func() { cfg.Sharing = prevSharing })
30+
t.Cleanup(func() { cfg.Contexts = prevContexts })
2731

2832
inst.Domain = "owner.example.com"
2933

@@ -40,10 +44,14 @@ func TestIsTrustedMember(t *testing.T) {
4044
t.Run("Scenario 1: B2C SaaS (twake.app) - users DON'T trust each other", func(t *testing.T) {
4145
// In B2C SaaS, users like alice.twake.app and bob.twake.app should NOT trust each other
4246
// Configuration: Don't add twake.app to TrustedDomains
43-
cfg.Sharing.Contexts[config.DefaultInstanceContext] = config.SharingContext{
44-
TrustedDomains: []string{}, // Empty - no trust between users
47+
prevDefault := cfg.Contexts[config.DefaultInstanceContext]
48+
cfg.Contexts[config.DefaultInstanceContext] = map[string]interface{}{
49+
"sharing": map[string]interface{}{
50+
"auto_accept_trusted": true,
51+
"trusted_domains": []interface{}{}, // Empty - no trust between users
52+
},
4553
}
46-
t.Cleanup(func() { delete(cfg.Sharing.Contexts, config.DefaultInstanceContext) })
54+
t.Cleanup(func() { cfg.Contexts[config.DefaultInstanceContext] = prevDefault })
4755

4856
inst.Domain = "alice.twake.app"
4957
member := &Member{Email: "bob@twake.app", Instance: "https://bob.twake.app"}
@@ -56,10 +64,14 @@ func TestIsTrustedMember(t *testing.T) {
5664
// In B2B SaaS, users within the same organization should trust each other
5765
// Organization: linagora.twake.app
5866
// Users: alice.linagora.twake.app, bob.linagora.twake.app
59-
cfg.Sharing.Contexts[config.DefaultInstanceContext] = config.SharingContext{
60-
TrustedDomains: []string{"linagora.twake.app"},
67+
prevDefault := cfg.Contexts[config.DefaultInstanceContext]
68+
cfg.Contexts[config.DefaultInstanceContext] = map[string]interface{}{
69+
"sharing": map[string]interface{}{
70+
"auto_accept_trusted": true,
71+
"trusted_domains": []interface{}{"linagora.twake.app"},
72+
},
6173
}
62-
t.Cleanup(func() { delete(cfg.Sharing.Contexts, config.DefaultInstanceContext) })
74+
t.Cleanup(func() { cfg.Contexts[config.DefaultInstanceContext] = prevDefault })
6375

6476
t.Run("users in same org trust each other", func(t *testing.T) {
6577
inst.Domain = "alice.linagora.twake.app"
@@ -88,10 +100,14 @@ func TestIsTrustedMember(t *testing.T) {
88100

89101
t.Run("Scenario 3: On-premise - all users trust each other", func(t *testing.T) {
90102
// On-premise deployment where all users under *.linagora.com trust each other
91-
cfg.Sharing.Contexts[config.DefaultInstanceContext] = config.SharingContext{
92-
TrustedDomains: []string{"linagora.com"},
103+
prevDefault := cfg.Contexts[config.DefaultInstanceContext]
104+
cfg.Contexts[config.DefaultInstanceContext] = map[string]interface{}{
105+
"sharing": map[string]interface{}{
106+
"auto_accept_trusted": true,
107+
"trusted_domains": []interface{}{"linagora.com"},
108+
},
93109
}
94-
t.Cleanup(func() { delete(cfg.Sharing.Contexts, config.DefaultInstanceContext) })
110+
t.Cleanup(func() { cfg.Contexts[config.DefaultInstanceContext] = prevDefault })
95111

96112
t.Run("all users under same domain trust each other", func(t *testing.T) {
97113
inst.Domain = "alice.linagora.com"
@@ -121,11 +137,15 @@ func TestIsTrustedMember(t *testing.T) {
121137
t.Run("Contact-based trust", func(t *testing.T) {
122138
// Configure with NO trusted domains
123139

124-
cfg.Sharing.Contexts[config.DefaultInstanceContext] = config.SharingContext{
125-
TrustedDomains: []string{},
126-
AutoAcceptTrustedContacts: &[]bool{true}[0],
140+
prevDefault := cfg.Contexts[config.DefaultInstanceContext]
141+
cfg.Contexts[config.DefaultInstanceContext] = map[string]interface{}{
142+
"sharing": map[string]interface{}{
143+
"auto_accept_trusted": true,
144+
"trusted_domains": []interface{}{},
145+
"auto_accept_trusted_contacts": true,
146+
},
127147
}
128-
t.Cleanup(func() { delete(cfg.Sharing.Contexts, config.DefaultInstanceContext) })
148+
t.Cleanup(func() { cfg.Contexts[config.DefaultInstanceContext] = prevDefault })
129149

130150
t.Run("untrusted contact from untrusted domain", func(t *testing.T) {
131151
inst.Domain = "alice.example.com"

pkg/config/config/config.go

Lines changed: 36 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/cozy/cozy-stack/pkg/tlsclient"
3434
"github.com/cozy/cozy-stack/pkg/utils"
3535
"github.com/cozy/gomail"
36+
"github.com/mitchellh/mapstructure"
3637
"github.com/redis/go-redis/v9"
3738
"github.com/spf13/viper"
3839
)
@@ -131,8 +132,6 @@ type Config struct {
131132
Notifications Notifications
132133
Flagship Flagship
133134

134-
Sharing SharingConfig
135-
136135
Lock lock.Getter
137136
Limiter *limits.RateLimiter
138137
SessionStorage redis.UniversalClient
@@ -319,47 +318,52 @@ type Flagship struct {
319318
AppleAppIDs []string
320319
}
321320

322-
// SharingConfig contains configuration for cozy-to-cozy sharing trust rules.
321+
// SharingConfig contains sharing trust settings for a specific context.
323322
type SharingConfig struct {
324-
AutoAcceptTrusted bool `mapstructure:"auto_accept_trusted"`
325-
AutoAcceptTrustedContacts bool `mapstructure:"auto_accept_trusted_contacts"`
326-
Contexts map[string]SharingContext `mapstructure:"contexts"`
327-
}
328-
329-
// SharingContext allows overriding sharing trust settings for a specific context.
330-
type SharingContext struct {
331-
AutoAcceptTrusted *bool `mapstructure:"auto_accept_trusted"`
332-
AutoAcceptTrustedContacts *bool `mapstructure:"auto_accept_trusted_contacts"`
323+
AutoAcceptTrusted bool `mapstructure:"auto_accept_trusted"`
324+
AutoAcceptTrustedContacts bool `mapstructure:"auto_accept_trusted_contacts"`
333325
TrustedDomains []string `mapstructure:"trusted_domains"`
334326
}
335327

336-
// OptionsForContext returns the effective sharing configuration for a given context,
337-
// after applying context-specific overrides to the global settings.
338-
func (c SharingConfig) OptionsForContext(contextName string) SharingContext {
339-
// Start with an empty context
340-
var result SharingContext
328+
// GetSharingConfig returns the sharing configuration for a given context.
329+
// It reads from contexts.<contextName>.sharing and falls back to contexts.default.sharing
330+
// if the specific context doesn't have sharing configuration.
331+
func GetSharingConfig(contextName string) SharingConfig {
332+
// Default configuration with safe defaults
333+
defaultConfig := SharingConfig{
334+
AutoAcceptTrusted: false,
335+
AutoAcceptTrustedContacts: false,
336+
TrustedDomains: []string{},
337+
}
341338

342-
// Try to get specific context, fallback to default context, or create empty
343-
if contextName != "" {
344-
if ctx, ok := c.Contexts[contextName]; ok {
345-
result = ctx
346-
} else if defaultCtx, ok := c.Contexts[DefaultInstanceContext]; ok {
347-
result = defaultCtx
348-
}
349-
} else if defaultCtx, ok := c.Contexts[DefaultInstanceContext]; ok {
350-
result = defaultCtx
339+
if config == nil || config.Contexts == nil {
340+
return defaultConfig
351341
}
352342

353-
// Apply global AutoAcceptTrusted if not set in context
354-
if result.AutoAcceptTrusted == nil {
355-
result.AutoAcceptTrusted = &c.AutoAcceptTrusted
343+
// Try to get sharing config from the specific context
344+
if contextName != "" {
345+
if ctxData, ok := config.Contexts[contextName].(map[string]interface{}); ok {
346+
if sharingData, ok := ctxData["sharing"].(map[string]interface{}); ok {
347+
var cfg SharingConfig
348+
if err := mapstructure.Decode(sharingData, &cfg); err == nil {
349+
return cfg
350+
}
351+
}
352+
}
356353
}
357354

358-
if result.AutoAcceptTrustedContacts == nil {
359-
result.AutoAcceptTrustedContacts = &c.AutoAcceptTrustedContacts
355+
// Fall back to default context
356+
if ctxData, ok := config.Contexts[DefaultInstanceContext].(map[string]interface{}); ok {
357+
if sharingData, ok := ctxData["sharing"].(map[string]interface{}); ok {
358+
var cfg SharingConfig
359+
if err := mapstructure.Decode(sharingData, &cfg); err == nil {
360+
return cfg
361+
}
362+
}
360363
}
361364

362-
return result
365+
// Return empty config with safe defaults
366+
return defaultConfig
363367
}
364368

365369
// SMS contains the configuration to send notifications by SMS.
@@ -606,7 +610,6 @@ func applyDefaults(v *viper.Viper) {
606610
v.SetDefault("assets_polling_interval", 2*time.Minute)
607611
v.SetDefault("fs.versioning.max_number_of_versions_to_keep", 20)
608612
v.SetDefault("fs.versioning.min_delay_between_two_versions", 15*time.Minute)
609-
v.SetDefault("sharing.auto_accept_trusted", false)
610613
}
611614

612615
func envMap() map[string]string {
@@ -688,11 +691,6 @@ func UseViper(v *viper.Viper) error {
688691
return err
689692
}
690693

691-
sharingCfg, err := makeSharingConfig(v)
692-
if err != nil {
693-
return err
694-
}
695-
696694
var subdomains SubdomainType
697695
if subs := v.GetString("subdomains"); subs != "" {
698696
switch subs {
@@ -1002,7 +1000,6 @@ func UseViper(v *viper.Viper) error {
10021000
PlayIntegrityVerificationKeys: v.GetStringSlice("flagship.play_integrity_verification_keys"),
10031001
AppleAppIDs: v.GetStringSlice("flagship.apple_app_ids"),
10041002
},
1005-
Sharing: sharingCfg,
10061003
Lock: lock.New(lockRedis),
10071004
SessionStorage: sessionsRedis,
10081005
DownloadStorage: downloadRedis,
@@ -1255,22 +1252,6 @@ func makeSMS(raw map[string]interface{}) map[string]SMS {
12551252
return sms
12561253
}
12571254

1258-
func makeSharingConfig(v *viper.Viper) (SharingConfig, error) {
1259-
var cfg SharingConfig
1260-
1261-
// Use UnmarshalKey to automatically handle type conversions
1262-
if err := v.UnmarshalKey("sharing", &cfg); err != nil {
1263-
return SharingConfig{}, fmt.Errorf("config: failed to unmarshal sharing config: %w", err)
1264-
}
1265-
1266-
// Ensure Contexts map is initialized even if not present in config
1267-
if cfg.Contexts == nil {
1268-
cfg.Contexts = map[string]SharingContext{}
1269-
}
1270-
1271-
return cfg, nil
1272-
}
1273-
12741255
func makeCommonSettings(v *viper.Viper) (map[string]CommonSettings, error) {
12751256
settings := make(map[string]CommonSettings)
12761257

pkg/config/config/config_test.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,18 @@ func TestConfigUnmarshal(t *testing.T) {
125125
},
126126
}, cfg.CommonSettings)
127127

128-
falseVal := false
129-
assert.EqualValues(t, SharingConfig{
130-
AutoAcceptTrusted: true,
131-
Contexts: map[string]SharingContext{
132-
"my-context": {
133-
AutoAcceptTrusted: &falseVal,
134-
TrustedDomains: []string{"context.example"},
135-
},
136-
},
137-
}, cfg.Sharing)
128+
// Test GetSharingConfig for my-context
129+
myContextSharing := GetSharingConfig("my-context")
130+
assert.Equal(t, true, myContextSharing.AutoAcceptTrusted)
131+
assert.Equal(t, []string{"linagora.com"}, myContextSharing.TrustedDomains)
132+
133+
// Test GetSharingConfig for default context
134+
defaultSharing := GetSharingConfig("default")
135+
assert.Equal(t, true, defaultSharing.AutoAcceptTrusted)
136+
137+
// Test GetSharingConfig for non-existent context falls back to default
138+
fallbackSharing := GetSharingConfig("non-existent")
139+
assert.Equal(t, true, fallbackSharing.AutoAcceptTrusted)
138140

139141
// Contexts
140142
assert.EqualValues(t, map[string]interface{}{
@@ -150,6 +152,10 @@ func TestConfigUnmarshal(t *testing.T) {
150152
map[string]interface{}{"home.konnectors.hide-errors": true},
151153
map[string]interface{}{"home_hidden_apps": []interface{}{"foobar"}},
152154
},
155+
"sharing": map[string]interface{}{
156+
"auto_accept_trusted": true,
157+
"trusted_domains": []interface{}{"linagora.com"},
158+
},
153159
"logos": map[string]interface{}{
154160
"coachco2": map[string]interface{}{
155161
"light": []interface{}{
@@ -193,6 +199,11 @@ func TestConfigUnmarshal(t *testing.T) {
193199
},
194200
},
195201
},
202+
"default": map[string]interface{}{
203+
"sharing": map[string]interface{}{
204+
"auto_accept_trusted": true,
205+
},
206+
},
196207
}, cfg.Contexts)
197208

198209
// Authentication

pkg/config/config/testdata/full_config.yaml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,6 @@ move:
9393
konnectors:
9494
cmd: some-cmd
9595

96-
sharing:
97-
auto_accept_trusted: true
98-
contexts:
99-
my-context:
100-
auto_accept_trusted: false
101-
trusted_domains:
102-
- context.example
103-
10496
registries:
10597
default: []
10698
example:
@@ -255,3 +247,11 @@ contexts:
255247
- src: /logos/1_partner.png
256248
alt: Partner n°1
257249
type: secondary
250+
sharing:
251+
auto_accept_trusted: true
252+
trusted_domains:
253+
- linagora.com
254+
255+
default:
256+
sharing:
257+
auto_accept_trusted: true

0 commit comments

Comments
 (0)