feat: complete unified plugin admin v1.1.0
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -31,6 +32,75 @@ var (
|
||||
marketplaceSHA256Pattern = regexp.MustCompile(`^[a-f0-9]{64}$`)
|
||||
)
|
||||
|
||||
// compareMarketplaceVersions implements the semver subset accepted by the
|
||||
// manifest validator. It is used only for marketplace installs, where a
|
||||
// catalog entry must never silently downgrade an already registered plugin.
|
||||
func compareMarketplaceVersions(left, right string) int {
|
||||
parse := func(value string) ([3]int, []string) {
|
||||
value = strings.TrimPrefix(strings.TrimSpace(value), "v")
|
||||
parts := strings.SplitN(value, "-", 2)
|
||||
core := strings.Split(parts[0], ".")
|
||||
var numbers [3]int
|
||||
for i := 0; i < len(numbers) && i < len(core); i++ {
|
||||
numbers[i], _ = strconv.Atoi(core[i])
|
||||
}
|
||||
if len(parts) == 1 {
|
||||
return numbers, nil
|
||||
}
|
||||
return numbers, strings.Split(parts[1], ".")
|
||||
}
|
||||
leftCore, leftPre := parse(left)
|
||||
rightCore, rightPre := parse(right)
|
||||
for i := range leftCore {
|
||||
if leftCore[i] < rightCore[i] {
|
||||
return -1
|
||||
}
|
||||
if leftCore[i] > rightCore[i] {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
if len(leftPre) == 0 && len(rightPre) == 0 {
|
||||
return 0
|
||||
}
|
||||
if len(leftPre) == 0 {
|
||||
return 1
|
||||
}
|
||||
if len(rightPre) == 0 {
|
||||
return -1
|
||||
}
|
||||
for i := 0; i < len(leftPre) && i < len(rightPre); i++ {
|
||||
l, r := leftPre[i], rightPre[i]
|
||||
if l == r {
|
||||
continue
|
||||
}
|
||||
ln, le := strconv.Atoi(l)
|
||||
rn, re := strconv.Atoi(r)
|
||||
if le == nil && re == nil {
|
||||
if ln < rn {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
if le == nil {
|
||||
return -1
|
||||
}
|
||||
if re == nil {
|
||||
return 1
|
||||
}
|
||||
if l < r {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
if len(leftPre) < len(rightPre) {
|
||||
return -1
|
||||
}
|
||||
if len(leftPre) > len(rightPre) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// marketplaceEntry is deliberately metadata-only. The browser never receives
|
||||
// the archive URL; downloads are performed by this server after catalog and
|
||||
// transport policy validation.
|
||||
@@ -63,6 +133,7 @@ type marketplaceService struct {
|
||||
remoteURL *url.URL
|
||||
allowedHosts map[string]struct{}
|
||||
allowLoopback bool
|
||||
indexSHA256 string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
@@ -238,6 +309,12 @@ func (m marketplaceService) loadIndex(ctx context.Context) (marketplaceIndex, ma
|
||||
return marketplaceIndex{}, origin, errors.New("marketplace index exceeds size limit")
|
||||
}
|
||||
}
|
||||
if expected := strings.TrimSpace(m.indexSHA256); expected != "" {
|
||||
sum := sha256.Sum256(raw)
|
||||
if !strings.EqualFold(hex.EncodeToString(sum[:]), expected) {
|
||||
return marketplaceIndex{}, origin, errors.New("marketplace index sha256 does not match the configured pin")
|
||||
}
|
||||
}
|
||||
var index marketplaceIndex
|
||||
decoder := json.NewDecoder(bytes.NewReader(raw))
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
Reference in New Issue
Block a user