Skip to content

Commit

Permalink
Merge pull request #451 from deploymenttheory/feat-maintainence_pipel…
Browse files Browse the repository at this point in the history
…ines

Refactor privilege validation logic and update comments for clarity
  • Loading branch information
ShocOne authored Nov 4, 2024
2 parents 4b20fcc + db877d4 commit b5dca10
Showing 1 changed file with 108 additions and 51 deletions.
159 changes: 108 additions & 51 deletions internal/resources/common/jamfprivileges/validate.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
// common/jamfprivileges/validate.go
// This package is used to validate the privileges fields in the Jamf Pro configuration.
// Whilst there is an api to return the api priviledge set, they are not categorised in
// the same way as the Jamf Pro UI. There it's required to export the priviledge set from
// the Jamf Pro UI and use this to validate the fields. There is a JSON file for each of
// the priviledge types that are used to validate the fields and these are exported using
// a recipe from the scripts folder in the provider. Based upon the jamf pro sdk recipes
// implementation.

// The json files are updated when a new version of Jamf Pro is released and the provider
// via a pipeline, which will update the provider and the JSON files. The JSON files are
// stored in the privileges folder in the jamfprivileges package. The JSON files are named
// jss_objects_privileges.json, jss_settings_privileges.json and jss_actions_privileges.json.
// Broken down by jamf pro version.

// The folder name for the privileges folder is 'privileges' and this should be in the same
// directory as the validate.go file. Beneath this folder are the version folders and the JSON
// files for the priviledge types.
// This package is used to validate the privileges fields in the Jamf Pro user accounts
// configuration.

// Whilst there is an api to return a complete api role priviledge set, they are not returned
// in a useful categorised format that's required for comparision for user accounts. Therefore
// there is a seperate maintainence pipeline that exports the priviledge
// sets to json files for user accounts.

// The maintainence pipelines runs once a week and will update the priviledges JSON files
// when there is a new version of Jamf Pro is found. The JSON files are named
// jss_objects_privileges.json, jss_settings_privileges.json and jss_actions_privileges.json
// and the provider maintains 3 jamf pro version permission sets.

// jamfprivileges
// ├── validate.go
// ├── 11.7.1
// │ ├── jss_actions_privileges.json
// │ ├── jss_objects_privileges.json
// │ └── jss_settings_privileges.json
// ├── 11.7.0
// ├── 11.10.1
// │ ├── jss_actions_privileges.json
// │ ├── jss_objects_privileges.json
// │ └── jss_settings_privileges.json
// ├── 11.10.2
// │ ├── jss_actions_privileges.json
// │ ├── jss_objects_privileges.json
// │ └── jss_settings_privileges.json
Expand All @@ -33,6 +32,7 @@ package jamfprivileges
import (
"embed"
"encoding/json"
"errors"
"fmt"
"path"
"sort"
Expand All @@ -48,8 +48,6 @@ const (
NMinus2Version = "11.7.1"
)

var validVersions = []string{LatestVersion, NMinus1Version, NMinus2Version}

// PrivilegeSupport tracks which versions support a privilege
type PrivilegeSupport struct {
Name string
Expand All @@ -59,23 +57,44 @@ type PrivilegeSupport struct {
// loadJSONPrivilegesWithVersion loads privileges from JSON files and tracks which version they're from
func loadJSONPrivilegesWithVersion(filename string) (map[string]PrivilegeSupport, error) {
privilegeMap := make(map[string]PrivilegeSupport)
latestPrivileges := make(map[string]bool)
foundAny := false

latestFilePath := path.Join("privileges", LatestVersion, filename)
data, err := privilegesFS.ReadFile(latestFilePath)
if err != nil {
return nil, fmt.Errorf("error reading latest privileges from %s: %w", latestFilePath, err)
}

var privileges []string
if err := json.Unmarshal(data, &privileges); err != nil {
return nil, fmt.Errorf("error unmarshaling JSON from %s: %w", latestFilePath, err)
}

for _, priv := range privileges {
latestPrivileges[priv] = true
privilegeMap[priv] = PrivilegeSupport{
Name: priv,
SupportedVersion: LatestVersion,
}
}
foundAny = true

for _, version := range validVersions {
for _, version := range []string{NMinus1Version, NMinus2Version} {
filePath := path.Join("privileges", version, filename)
data, err := privilegesFS.ReadFile(filePath)
if err != nil {
continue
}

var privileges []string
if err := json.Unmarshal(data, &privileges); err != nil {
var oldPrivileges []string
if err := json.Unmarshal(data, &oldPrivileges); err != nil {
return nil, fmt.Errorf("error unmarshaling JSON from %s: %w", filePath, err)
}

// Add or update privileges with their version information
for _, priv := range privileges {
// Only store the oldest version where the privilege appears
if _, exists := privilegeMap[priv]; !exists {
// Only store privileges that don't exist in the latest version
for _, priv := range oldPrivileges {
if !latestPrivileges[priv] {
privilegeMap[priv] = PrivilegeSupport{
Name: priv,
SupportedVersion: version,
Expand All @@ -84,7 +103,7 @@ func loadJSONPrivilegesWithVersion(filename string) (map[string]PrivilegeSupport
}
}

if len(privilegeMap) == 0 {
if !foundAny {
return nil, fmt.Errorf("no valid privileges found for any version")
}

Expand All @@ -93,33 +112,71 @@ func loadJSONPrivilegesWithVersion(filename string) (map[string]PrivilegeSupport

// formatPrivilegesError creates a detailed error message with version information
func formatPrivilegesError(invalidValue, key string, privileges map[string]PrivilegeSupport) error {
// Sort privileges by version and then alphabetically
var sortedPrivileges []PrivilegeSupport
for _, p := range privileges {
sortedPrivileges = append(sortedPrivileges, p)
}
sort.Slice(sortedPrivileges, func(i, j int) bool {
if sortedPrivileges[i].SupportedVersion != sortedPrivileges[j].SupportedVersion {
return sortedPrivileges[i].SupportedVersion > sortedPrivileges[j].SupportedVersion
var b strings.Builder

b.WriteString("Invalid value '")
b.WriteString(invalidValue)
b.WriteString("' for ")
b.WriteString(key)
b.WriteString(".\n\n")
b.WriteString("This provider supports Jamf Pro versions ")
b.WriteString(LatestVersion)
b.WriteString(" (Latest), ")
b.WriteString(NMinus1Version)
b.WriteString(" (N-1), and ")
b.WriteString(NMinus2Version)
b.WriteString(" (N-2)\n\n")

// Group privileges by version and sort
versionPrivs := make(map[string][]string)
for _, priv := range privileges {
versionPrivs[priv.SupportedVersion] = append(versionPrivs[priv.SupportedVersion], priv.Name)
}

for version := range versionPrivs {
sort.Strings(versionPrivs[version])
}

b.WriteString("Current privileges (")
b.WriteString(LatestVersion)
b.WriteString("):\n")
for _, priv := range versionPrivs[LatestVersion] {
b.WriteString("- ")
b.WriteString(priv)
b.WriteString("\n")
}

// Check for additional privileges in older versions
hasOlderPrivs := len(versionPrivs[NMinus1Version]) > 0 || len(versionPrivs[NMinus2Version]) > 0
if hasOlderPrivs {
b.WriteString("\nAdditional (deprecated) privileges available in older Jamf Pro versions supported by this provider:\n")

if len(versionPrivs[NMinus1Version]) > 0 {
b.WriteString("\nJamf Pro ")
b.WriteString(NMinus1Version)
b.WriteString(" (N-1):\n")
for _, priv := range versionPrivs[NMinus1Version] {
b.WriteString("- ")
b.WriteString(priv)
b.WriteString("\n")
}
}
return sortedPrivileges[i].Name < sortedPrivileges[j].Name
})

// Build the error message
var sb strings.Builder
sb.WriteString(fmt.Sprintf("Invalid value '%s' for %s.\n\n", invalidValue, key))
sb.WriteString("Valid privileges by version:\n\n")

currentVersion := ""
for _, priv := range sortedPrivileges {
if currentVersion != priv.SupportedVersion {
currentVersion = priv.SupportedVersion
sb.WriteString(fmt.Sprintf("\nJamf Pro %s:\n", currentVersion))

if len(versionPrivs[NMinus2Version]) > 0 {
b.WriteString("\nJamf Pro ")
b.WriteString(NMinus2Version)
b.WriteString(" (N-2):\n")
for _, priv := range versionPrivs[NMinus2Version] {
b.WriteString("- ")
b.WriteString(priv)
b.WriteString("\n")
}
}
sb.WriteString(fmt.Sprintf("- %s\n", priv.Name))

b.WriteString("\nNote: Privileges from older versions may not be available in the latest Jamf Pro version.\n")
}

return fmt.Errorf(sb.String())
return errors.New(b.String())
}

// ValidateJSSObjectsPrivileges ensures that each privilege in the list is valid.
Expand Down

0 comments on commit b5dca10

Please sign in to comment.