Files
Victor Lyuboslavsky 98060b08a6 Add Windows managed local account server flow (#48721) (#49924)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #48721 

Part 2 of https://github.com/fleetdm/fleet/issues/43488

# Checklist for submitter

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.

## Testing

- [x] Added/updated automated tests

- [x] QA'd all new/changed functionality manually

## Database migrations

- [x] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).

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

* **New Features**
* Windows devices can now create and securely escrow managed local
account passwords during enrollment.
* Added Windows managed local account status and password availability
to host details.
  * Device-reported setup errors are surfaced with helpful details.
* Account creation is automatically requested when supported by the
device, plan, and configuration.
* **Bug Fixes**
  * Windows accounts are excluded from password rotation workflows.
  * Re-enrollment correctly triggers account creation when needed.
  * Passwords remain available when settings change after enrollment.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-03 09:03:27 -05:00

80 lines
2.4 KiB
Go

package str
import (
"strconv"
"strings"
"unicode/utf8"
)
// MaxErrorResponseBytes is the maximum number of bytes captured from a remote
// error response body before the string is truncated.
const MaxErrorResponseBytes = 512 * 1024
// TruncateErrorResponse caps s at MaxErrorResponseBytes bytes. When the string
// is longer it is cut at a valid UTF-8 boundary and " [truncated]" is appended.
func TruncateErrorResponse(s string) string {
if len(s) <= MaxErrorResponseBytes {
return s
}
cut := s[:MaxErrorResponseBytes]
// Step back from the cut point to ensure we end on a valid rune boundary.
for !utf8.ValidString(cut) {
cut = cut[:len(cut)-1]
}
return cut + " [truncated]"
}
// TruncateRunes returns s shortened to at most maxRunes characters, preserving the start of the string. Use it before
// storing device- or user-supplied text in a column: utf8mb4 VARCHAR(N) in MySQL counts characters (runes), not bytes,
// so slicing on runes both matches the column constraint and cannot cut a multi-byte character in half and produce invalid UTF-8.
func TruncateRunes(s string, maxRunes int) string {
if len(s) <= maxRunes {
// Fast path: a string of at most maxRunes bytes cannot exceed maxRunes characters.
return s
}
if utf8.RuneCountInString(s) <= maxRunes {
return s
}
return string([]rune(s)[:maxRunes])
}
func SplitAndTrim(s string, delimiter string, removeEmpty bool) []string {
parts := strings.Split(s, delimiter)
cleaned := make([]string, 0, len(parts))
for _, part := range parts {
part = strings.TrimSpace(part)
if !removeEmpty || part != "" {
cleaned = append(cleaned, part)
}
}
return cleaned
}
// ParseUintList parses a comma-separated string of unsigned integers, trimming
// whitespace around each value and silently skipping values that cannot be
// parsed. Returns nil for an empty input.
func ParseUintList(s string) []uint {
if s == "" {
return nil
}
parts := strings.Split(s, ",")
result := make([]uint, 0, len(parts))
for _, p := range parts {
p = strings.TrimSpace(p)
if v, err := strconv.ParseUint(p, 10, 0); err == nil {
result = append(result, uint(v))
}
}
return result
}
// ParseStringList parses a comma-separated string into a slice of strings,
// trimming whitespace and dropping empty values. Returns nil for an empty
// input.
func ParseStringList(s string) []string {
if s == "" {
return nil
}
return SplitAndTrim(s, ",", true)
}