1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package atproto
import (
"encoding/json"
"slices"
"strings"
"k8s.io/apimachinery/pkg/api/meta"
"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
"github.com/johnandersen777/socialweb-computer-kcp/internal/kit/conds"
)
func Associate(id *v1alpha1.MarketIdentity, watch *v1alpha1.AtprotoRecordWatch, status *v1alpha1.MarketIdentityStatus) {
declared := uniqueDIDs(id.Spec.ApprovedAssociations)
previous := make([]string, 0, len(status.Associations))
for _, did := range uniqueDIDs(status.Associations) {
if !slices.Contains(declared, did) {
previous = append(previous, did)
}
}
verified, observed := verifiedClaims(id, watch, status)
if !observed {
verified = previous
}
removed := false
for _, did := range previous {
if !slices.Contains(verified, did) {
removed = true
}
}
status.Associations = uniqueDIDs(append(slices.Clone(declared), verified...))
if removed && len(status.Associations) == 0 {
conds.Set(&status.Conditions, id.Generation, v1alpha1.ConditionAssociated, false, ReasonRevoked, "the operator's verified claim record is gone")
return
}
setAssociated(status, id.Generation, id.Spec.Association)
}
func verifiedClaims(id *v1alpha1.MarketIdentity, watch *v1alpha1.AtprotoRecordWatch, status *v1alpha1.MarketIdentityStatus) ([]string, bool) {
if watch == nil || id.Spec.Mode != v1alpha1.MarketIdentityModeLocalRepo || id.Spec.Association == v1alpha1.AssociationSkip {
return nil, false
}
subject := strings.TrimSpace(status.Did)
if subject == "" {
return nil, false
}
if !meta.IsStatusConditionTrue(watch.Status.Conditions, v1alpha1.ConditionVerified) && len(watch.Status.Deleted) == 0 {
return nil, false
}
var out []string
for _, record := range watch.Status.Records {
did := associationClaimDid(record, subject, id.Spec.AssociationService)
if did == "" {
continue
}
if operator := strings.TrimSpace(id.Spec.AssociateWith); strings.HasPrefix(operator, "did:") && did != operator {
continue
}
out = append(out, did)
}
slices.Sort(out)
return slices.Compact(out), true
}
func uniqueDIDs(in []string) []string {
out := make([]string, 0, len(in))
for _, did := range in {
if did = strings.TrimSpace(did); did != "" && !slices.Contains(out, did) {
out = append(out, did)
}
}
return out
}
func associationClaimDid(record v1alpha1.WatchedRecord, subject, service string) string {
var value struct {
Challenge string `json:"challenge"`
KeyID string `json:"keyId"`
Service string `json:"service"`
}
if err := json.Unmarshal(record.Value, &value); err != nil {
return ""
}
challenge := strings.TrimSpace(value.Challenge)
keyID := strings.TrimSpace(value.KeyID)
if strings.TrimSpace(service) == "" || strings.TrimSpace(value.Service) != service {
return ""
}
if strings.HasPrefix(challenge, "did:") && keyID == subject && record.Did == challenge {
return challenge
}
return ""
}
func associatedTrue(status *v1alpha1.MarketIdentityStatus) bool {
return meta.IsStatusConditionTrue(status.Conditions, v1alpha1.ConditionAssociated)
}
func setAssociated(status *v1alpha1.MarketIdentityStatus, generation int64, mode v1alpha1.AssociationMode) {
if refCondition(status, generation) {
return
}
if mode == v1alpha1.AssociationSkip {
conds.Set(&status.Conditions, generation, v1alpha1.ConditionAssociated, true, "AssociationSkipped", "the association is skipped")
return
}
if len(status.Associations) > 0 {
conds.Set(&status.Conditions, generation, v1alpha1.ConditionAssociated, true, "Associated", "the operator association is approved")
return
}
if revoked := meta.FindStatusCondition(status.Conditions, v1alpha1.ConditionAssociated); revoked != nil && revoked.Reason == ReasonRevoked {
conds.Set(&status.Conditions, generation, v1alpha1.ConditionAssociated, false, ReasonRevoked, revoked.Message)
return
}
conds.Set(&status.Conditions, generation, v1alpha1.ConditionAssociated, false, "AwaitingAssociation", "the PDS is ready; the association is pending")
}