diff --git a/internal/resources/common/jamfprivileges/validate.go b/internal/resources/common/jamfprivileges/validate.go index b82cc33f..f4a0f2e0 100644 --- a/internal/resources/common/jamfprivileges/validate.go +++ b/internal/resources/common/jamfprivileges/validate.go @@ -1,21 +1,16 @@ // 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 @@ -23,7 +18,11 @@ // │ ├── 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 @@ -33,6 +32,7 @@ package jamfprivileges import ( "embed" "encoding/json" + "errors" "fmt" "path" "sort" @@ -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 @@ -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, @@ -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") } @@ -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.