fleet/server/vulnerabilities/macoffice/release_note.go
Konstantin Sykulev 7a1e469ac0
Microsoft office FMA version from release notes (#30686)
Fixes #30082

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
- [x] Added/updated automated tests
- [x] Manual QA for all new/changed functionality

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Microsoft Office applications for Mac now display a simplified short
version identifier, improving consistency with inventory systems.
* Added support for version transformation during ingestion of Homebrew
apps using external reference functions.

* **Bug Fixes**
* Enhanced uninstall process for Microsoft Word on Mac to remove a
broader set of user data and configuration files.

* **Tests**
* Added tests to ensure correct extraction of build numbers and short
version formats from Microsoft Office release notes.

* **Documentation**
* Updated changelog to reflect the addition of short version identifiers
for Microsoft Office Fleet maintained apps.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Ian Littman <iansltx@gmail.com>
2025-07-23 12:07:18 -05:00

135 lines
3.6 KiB
Go

package macoffice
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"time"
"github.com/fleetdm/fleet/v4/server/vulnerabilities/io"
"github.com/fleetdm/fleet/v4/server/vulnerabilities/utils"
)
type ProductType int
const (
WholeSuite ProductType = iota
Outlook
Excel
PowerPoint
Word
OneNote
)
type SecurityUpdate struct {
Product ProductType
Vulnerability string
}
// ReleaseNote contains information about an Office release including security patches.
type ReleaseNote struct {
Date time.Time
Version string // Ths includes the Build ex: 16.69 (Build 23010700)
SecurityUpdates []SecurityUpdate
}
func (or *ReleaseNote) AddSecurityUpdate(pt ProductType, vuln string) {
or.SecurityUpdates = append(or.SecurityUpdates, SecurityUpdate{
Product: pt,
Vulnerability: vuln,
})
}
// Valid returns true if this release note can be used for vulnerability processing. Some release
// notes don't have a release version nor security updates
func (or *ReleaseNote) Valid() bool {
return len(or.Version) != 0 && len(or.SecurityUpdates) != 0
}
// CmpVersion compares the release note version against 'otherVer' returning:
// -1 if rel. note version < other version
// 0 if rel. note version == other version
// 1 if rel. note version > other version
func (or *ReleaseNote) CmpVersion(otherVer string) int {
relVersion := or.Version
matches := VersionPattern.FindStringSubmatch(or.Version)
if len(matches) >= 2 {
relVersion = matches[1]
}
return utils.Rpmvercmp(relVersion, otherVer)
}
// CollectVulnerabilities collect all unique vulnerabilities that were patched in this release by matching
// their product type.
func (or *ReleaseNote) CollectVulnerabilities(product ProductType) []string {
var vulns []string
collected := make(map[string]struct{})
for _, su := range or.SecurityUpdates {
if su.Product == WholeSuite || su.Product == product {
collected[su.Vulnerability] = struct{}{}
}
}
for k := range collected {
vulns = append(vulns, k)
}
return vulns
}
// OfficeProductFromBundleId looks at the provided 'bundleId' and tries to match the Office Product.
// If no match is found, false is returned as the second return value.
func OfficeProductFromBundleId(bundleId string) (ProductType, bool) {
b := strings.ToLower(bundleId)
switch {
case strings.HasPrefix(b, "com.microsoft.powerpoint"):
return PowerPoint, true
case strings.HasPrefix(b, "com.microsoft.word"):
return Word, true
case strings.HasPrefix(b, "com.microsoft.excel"):
return Excel, true
case strings.HasPrefix(b, "com.microsoft.onenote"):
return OneNote, true
case strings.HasPrefix(b, "com.microsoft.outlook"):
return Outlook, true
}
return WholeSuite, false
}
// BuildNumber returns the build number from the release note version.
// "16.69 (Build 23010700)" would return "23010700"
func (or *ReleaseNote) BuildNumber() string {
matches := BuildNumberPattern.FindStringSubmatch(or.Version)
if len(matches) >= 2 {
return matches[1]
}
return ""
}
// ShortVersionFormat returns the version without the build number.
// "16.69 (Build 23010700)" would return "16.69".
// If the version cannot be extracted, an empty string is returned.
func (or *ReleaseNote) ShortVersionFormat() string {
matches := VersionPattern.FindStringSubmatch(or.Version)
if len(matches) >= 2 {
return matches[1]
}
return ""
}
type ReleaseNotes []ReleaseNote
func (rn ReleaseNotes) Serialize(d time.Time, dir string) error {
payload, err := json.Marshal(rn)
if err != nil {
return err
}
fileName := io.MacOfficeRelNotesFileName(d)
filePath := filepath.Join(dir, fileName)
return os.WriteFile(filePath, payload, 0o644)
}