### Copyright 2014 Pixar ### ### Licensed under the Apache License, Version 2.0 (the "Apache License") ### with the following modification; you may not use this file except in ### compliance with the Apache License and the following modification to it: ### Section 6. Trademarks. is deleted and replaced with: ### ### 6. Trademarks. This License does not grant permission to use the trade ### names, trademarks, service marks, or product names of the Licensor ### and its affiliates, except as required to comply with Section 4(c) of ### the License and to reproduce the content of the NOTICE file. ### ### You may obtain a copy of the Apache License at ### ### http://www.apache.org/licenses/LICENSE-2.0 ### ### Unless required by applicable law or agreed to in writing, software ### distributed under the Apache License with the above modification is ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ### KIND, either express or implied. See the Apache License for the specific ### language governing permissions and limitations under the Apache License. ### ### ### module JSS ###################### ### Module Constants ###################### ###################### ### Module Variables ###################### ###################### ### Module Methods ###################### ##################################### ### Classes ##################################### ### ### A class implementing a JSS Policy. ### ### Like many API objects, the data comes from the API in sections, and ### the items in the :general section are mapped to direct attributes ### of this Class. ### ### ### Policy instances are partially read-only: ### - New policies cannot be created via this class, please use the WebApp. ### - Only a few attributes can be changed and updated via the Policy class: ### - - name ### - - frequency ### - - target_drive ### - - offline ### - - enabled ### - - category ### - - triggers ### - - scope, see {JSS::Scopable} and {JSS::Scopable::Scope} ### - - files and processes ### All other values and sections must be edited via the Web App. ### ### Policies may be deleted via this class ### ### @see JSS::APIObject ### class Policy < JSS::APIObject ##################################### ### Mix-Ins ##################################### include JSS::Updatable include JSS::Scopable include JSS::Uploadable ##################################### ### Class Methods ##################################### ##################################### ### Class Constants ##################################### ### The base for REST resources of this class RSRC_BASE = "policies" ### the hash key used for the JSON list output of all objects in the JSS RSRC_LIST_KEY = :policies ### The hash key used for the JSON object output. ### It's also used in various error messages RSRC_OBJECT_KEY = :policy ### these keys, as well as :id and :name, are present in valid API JSON data for this class VALID_DATA_KEYS = [:scope, :user_interaction, :files_processes ] SECTIONS = [ :general, :maintenance, :account_maintenance, :scripts, :self_service, :package_configuration, :scope, :user_interaction, :reboot, :files_processes, :dock_items, :disk_encryption, :printers ] FREQUENCIES = { :ongoing => "Ongoing", :once_per_computer => "Once per computer", :once_per_user =>"Once per user", :daily => "Once every day", :weekly => "Once every week", :monthly => "Once every month" } RESTART_WHEN = { :if_pkg_requires => "Restart if a package or update requires it", :now => "Restart immediately", :delayed => "Restart", :dont => "Do not restart" } RESTART_DISKS = { :current => "Current Startup Disk", :selected => "Currently Selected Startup Disk (No Bless)", :netboot => "NetBoot", :os_installer => "inPlaceOSUpgradeDirectory" } # Note: any other value in :specify_startup is a path to some other drive to boot from, e.g. /Volumes/Foo ACCOUNT_ACTIONS = { :create => "Create", :change_pw => "Reset", :delete => "Delete", :disable_fv2 => "DisableFileVault" } MGMT_ACCOUNT_ACTIONS = { :no_change => "doNotChange", :change_pw => "specified", :generate_pw => "random", :enable_fv2 => "fileVaultEnable", :disable_fv2 => "fileVaultDisable" } PACKAGE_ACTIONS = { :install => "Install", :remove => "Uninstall", :cache =>"Cache", :install_cache => "Install Cached" } SCRIPT_PRIORITIES = {:pre => "Before", :post => "After"} PRINTER_ACTIIONS = {:map => "install", :unmap => "uninstall"} DOCK_ITEM_ACTIONS = {:add_start => "Add To Beginning", :add_end => "Add To End", :remove => "Remove"} NETWORK_REQUIREMENTS = {:any => "Any", :ethernet => "EtherNet"} TRIGGER_EVENTS = { :startup => :trigger_startup, :login => :trigger_login, :logout => :trigger_logout, :checkin => :trigger_checkin, :network_state => :trigger_network_state_changed , :enrollment => :trigger_enrollment_complete , :custom => :trigger_other } TRIGGER_TYPES = {:event => "EVENT", :user => "USER_INITIATED"} SCOPE_TARGET_KEY = :computers ###################### ### Attributes ###################### ##### General ### This data comes from the :general hash in the raw JSON data ### and correspond to the general section of the Edit Policy window in ### the JSS WebApp. They are general settings for this policy. ### We'll map it to direct attributes. ### @return [String] policy category name attr_reader :category ### @return [String] how often to run the policy on each computer attr_reader :frequency ### @return [String] which drive should the policy target attr_reader :target_drive ### @return [Boolean] should be policy be available offline attr_reader :offline ### @return [Boolean] is the policy enabled? attr_reader :enabled ### @return [String] a string with the site name attr_reader :site ### @return [Hash] ### ### Overrides for various defaults ### ### The hash looks like: !{ :distribution_point => "", :force_afp_smb => false, :netboot_server => "current", :target_drive => "default", :sus => "default"} attr_reader :override_default_settings ### The API has a :network_requirements key in the general section, but ### in the UI its in a subsection called Client Side Limitiations. ### so we'll store it in a hash called client_side_limitations, ### defined below. ### the network_limitations hash of the general section seems to be redundant. ### it contains minimum_network_connection ("Ethernet" or "No Minimum") ### which is also reflected in the general[:network_requirements] ("Ethernet" or "Any") ### it contains network_segments, which are also listed ### in the limitations hash of the scope section ### it contains any_ip_address, which is true or false based on there being ### any network_segment limitations. ### Therefore, we'll ignore it, and use the other places for that data ### The API has a general key ":date_time_limitations" which has this ### this data: ### :activation - Time ### :expiration - Time ### :no_execute_on - An array of short day names as symbols, e.g. [:sun, :mon, :wed, :thu] ### :no_execute_start - Time ### :no_execute_end - Time ### but in the UI, those are set in the Server Side Limitiations and Client Side Limitiations. ### areas, so we'll store them in matching hashes below. ### attr_reader :date_time_limitations ### @return [Hash] ### ### The server-side limitations of this policy. ### ### The keys are :activation and :expiration, both are Times. ### ### the data comes from the API in the date_time_limitations hash of the general ### section, but the UI shows them in the Server Side Limitations area. ### This attribute is just for convience and consistency, and just ### refers to the data in their API locations attr_reader :server_side_limitations ### @return [Hash] ### ### The client-side limitations of this policy. ### ### The keys are: ### - :no_execute_on - An array of short day names as strings, e.g. ["Sun", "Mon", "Tue"] ### - :no_execute_start - Time ### - :no_execute_end - Time ### - :network_connection - String ### The data for the first three comes from the API in the date_time_limitations ### hash of the general section. ### The fourth comes from the network_requirements of the general section of the API, ### but the UI shows them in the Client Side Limitations area. ### ### This attribute is just for convience and consistency, and just ### refers to the data in their API locations attr_reader :client_side_limitations ### @return [String] ### ### Either EVENT or USER_INITIATED ### ### If it's EVENT, then one or more of the members @trigger_events must true. attr_reader :trigger ### @return [Hash] ### ### The triggers that cause this policy to execute on a client when the @trigger is "EVENT" ### ### This is a hash with the following keys. Each comes from the API ### as a key in the :general hash, but they make more sense separated out ### like this. ### - :trigger_startup => Bool ### - :trigger_login => Bool ### - :trigger_logout => Bool ### - :trigger_checkin => Bool ### - :trigger_network_state_changed => Bool ### - :trigger_enrollment_complete => Bool ### - :trigger_other => the String that causes a custom trigger ### ### To edit a value, call ### set_trigger_event(type, new_val) ### where type is one of the keys in TRIGGER_EVENTS and new val is the new value (usually boolean) ### attr_reader :trigger_events ##### client machine maintenence ### These are the computer maint. tasks ### that might be performed by this policy ### All are boolean ### @return [Boolean] client maintenance task attr_reader :verify_startup_disk ### @return [Boolean] client maintenance task attr_reader :permissions_repair ### @return [Boolean] client maintenance task attr_reader :recon ### @return [Boolean] client maintenance task attr_reader :fix_byhost ### @return [Boolean] client maintenance task attr_reader :reset_name ### @return [Boolean] client maintenance task attr_reader :flush_system_cache ### @return [Boolean] client maintenance task attr_reader :install_cached_pkgs ### @return [Boolean] client maintenance task attr_reader :flush_user_cache ### attr_reader :heal # deprecated ### attr_reader :prebinding # deprecated ##### client account maint ### acct related maintenence performed by this policy ### @return [Array<Hash>] ### ### The directory bindings applied ### ### each hash is like: !{:name => "LDAP", :id => 4} attr_reader :directory_bindings ### @return [Hash] the open firmware mode and password attr_reader :open_firmware_efi_password ### @return [Hash] ### ### The management accout changes applied by the policy ### ### The keys are: ### - :action see MGMT_ACCOUNT_ACTIONS ### - :managed_password ### - :managed_password_md5 ### - :managed_password_sha256 ### - :managed_password_length # for random generating pws ### attr_reader :management_account ### @return [Array<Hash>] ### ### Local accts acted-upon by this policy ### ### Keys are: ### - :action => "Create", ### - :hint => "foo bar", ### - :picture => "/path/to/pic.tif", ### - :admin => true, ### - :home => "/Users/chrisltest", ### - :realname => "ChrisTest Lasell", ### - :filevault_enabled => true, ### - :username => "chrisltest", ### - :password_md5 => "3858f62230ac3c915f300c664312c63f", ### - : password => "foobar", ### - :password_sha256=> "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2" ### attr_reader :accounts ### @return [Array<Hash>] ### ### The pkgs handled by this policy ### ### Hash keys are: ### - :action => "Install" ### - :update_autorun => false, ### - :feu => false, ### - :name => "rbgem-json-1.6.5-4.pkg", ### - :id => 1073 ### attr_reader :packages ### @return [Array<Hash>] ### ### The scripts run by this policy ### ### Hash keys are: ### - :name => "chromegetter.sh", ### - :parameter4 => "", ### - :parameter5 => "", ### - :parameter6 => "", ### - :parameter7 => "", ### - :parameter8 => "", ### - :parameter9 => "", ### - :parameter10 => "", ### - :parameter11 => "", ### - :id => 1428, ### - :priority => "After" ### attr_reader :scripts ### @return [Hash] ### ### Self-service-related data for this policy ### ### The hash keys are: ### - :self_service_icon => !{:uri => String,:id => Integer, :filename => String} ### - :use_for_self_service => true, ### - :install_button_text => "Install", ### - :self_service_description => "Descriptive text", ### - :force_users_to_view_description => false ### ### Note: we'll add a ruby-std convenience method below "self_service?" ### which returns the :use_for_self_service key. attr_reader :self_service #### user interaction ### These are extracted from the :user_interaction hash ### in the JSON output, which looks like this: ### :message_start => "", ### :allow_users_to_defer => false, ### :allow_deferral_until_utc => "", ### :message_finish => "" ### ### @return [Boolean] can the user defer the policy? attr_reader :user_may_defer ### @return [Time] when is the user no longer allowed to defer? attr_reader :user_may_defer_until ### @return [String] the message shown the user at policy start attr_reader :user_message_start ### @return [String] the message shown the user at policy end attr_reader :user_message_finish ### @return [Hash] ### ### Reboot options for the policy ### ### The hash keys are: ### - :user_logged_in => "Do not restart", ### - :minutes_until_reboot => 5, ### - :message=> "This computer will restart in 5 minutes. Please save anything you are working on and log out by choosing Log Out from the bottom of the Apple menu.", ### - :startup_disk => "Current Startup Disk", ### - :specify_startup => "", ### - :no_user_logged_in => "Do not restart" ### attr_reader :reboot_options ##### files & processes ### a hash like this: ### {:spotlight_search => "Spotlight This", ### :search_for_process => "SafariProcess", ### :search_by_path => "/this/is/a/path", ### :kill_process => true, ### :delete_file => true, ### :run_command => "/usr/local/pixar/sbin/force-fde-logout --setup", ### :locate_file => "this-is-a-filename", ### :update_locate_database => true} ### ### NOTE, since these items are editable, they have custom getters/setters ### so that the hash isn't directly changable without @need_to_update. ### attr_reader :files_processes ### @return [Array<Hash>] ### ### The dock items handled by this policy ### ### each item hash looks like: !{:name => "Mail", :id => 14, :action => "Add To Beginning"} attr_reader :dock_items ### @return [Hash] ### ### Disk encryption options for this policy ### ### The hash looks like !{:disk_encryption_configuration_id => 3, :action => "apply"} attr_reader :disk_encryption ### @return [Array<Hash>] ### ### The printers handled by this policy ### ### Each Hash looks like: !{:make_default => false, :name => "torlan", :id => 3, :action => "install"} attr_reader :printers ##################################### ### Public Instance Methods ##################################### ### ### @see APIObject#initialize ### def initialize(args = {}) super gen = @init_data[:general] @category = JSS::APIObject.get_name(gen[:category]) @frequency = gen[:frequency] @target_drive = gen[:target_drive] @offline = gen[:offline] @enabled = gen[:enabled] @site = JSS::APIObject.get_name(gen[:site][:name]) @override_default_settings = gen[:override_default_settings] @trigger = gen[:trigger ] @trigger_events = { :trigger_startup => gen[:trigger_startup ], :trigger_login => gen[:trigger_login ], :trigger_logout => gen[:trigger_logout ], :trigger_checkin => gen[:trigger_checkin ], :trigger_network_state_changed => gen[:trigger_network_state_changed ], :trigger_enrollment_complete => gen[:trigger_enrollment_complete ], :trigger_other => gen[:trigger_other ] } dtl = gen[:date_time_limitations] @server_side_limitations = { :activation => JSS.epoch_to_time(dtl[:activation_date_epoch]), :expiration => JSS.epoch_to_time(dtl[:expiration_date_epoch]) } @client_side_limitations = { :no_execute_on => dtl[:no_execute_on], # NOTE- there's a bug in the JSON output, it's been reported to JAMF. :no_execute_start => dtl[:no_execute_start], # String like "1:01 AM" :no_execute_end => dtl[:no_execute_end], # String like "2:02 PM" :network_requirements => gen[:network_requirements] } maint = @init_data[:maintenance] @verify_startup_disk = maint[:verify] @permissions_repair = maint[:permissions] @recon = maint[:recon] @fix_byhost = maint[:byhost] @reset_name = maint[:reset_name] @flush_system_cache = maint[:system_cache] @install_cached_pkgs = maint[:install_all_cached_packages] @flush_user_cache = maint[:user_cache] amaint = @init_data[:account_maintenance] @directory_bindings = amaint[:directory_bindings] @open_firmware_efi_password = amaint[:open_firmware_efi_password] @management_account = amaint[:management_account] @accounts = amaint[:accounts] @packages = @init_data[:package_configuration][:packages] ? @init_data[:package_configuration][:packages] : [] @scripts = @init_data[:scripts] @self_service = @init_data[:self_service] uint = @init_data[:user_interaction] @user_may_defer = uint[:allow_users_to_defer] @user_may_defer_until = JSS.parse_datetime uint[:allow_deferral_until_utc] @user_message_start = uint[:message_start] @user_message_finish = uint[:message_finish] @reboot_options = @init_data[:reboot] @files_processes = @init_data[:files_processes] @dock_items = @init_data[:dock_items] @disk_encryption = @init_data[:disk_encryption] @printers = @init_data[:printers] parse_scope end # init ### ### Change the enabled state of this item ### ### @param new_val[Boolean] the new state. ### ### @return [void] ### def enabled= (new_val) return nil if @enabled == new_val raise JSS::InvalidDataError, "New value must be true or false" unless JSS::TRUE_FALSE.include? new_val @enabled = new_val @need_to_update = true end ### ### Set a new frequency for this policy. ### ### @param freq[Symbol] the desired frequency, must be one of the keys of {FREQUENCIES} ### ### @return [void] ### def frequency= (freq) raise JSS::InvalidDataError, "New frequency must be one of :#{FREQUENCIES.keys.join ", :"}" unless FREQUENCIES.keys.include? freq @frequency = FREQUENCIES[freq] @need_to_update = true end ### ### Set a new target drive for this policy. ### ### @param path_to_drive[String,Pathname] the full path to the target drive, must start with a '/' ### ### @return [void] ### def target_drive= (path_to_drive) raise JSS::InvalidDataError, "Path to target drive must be absolute" unless path_to_drive.to_s.start_with? '/' @target_drive = path_to_drive.to_s @need_to_update = true end ### ### Set whether this policy is available offline. ### ### @param new_val[Boolean] ### ### @return [void] ### def offline= (new_val) raise JSS::InvalidDataError, "New value must be boolean true or false" unless JSS::TRUE_FALSE.include? new_val @offline = new_val @need_to_update = true end ### ### Change the category of this item, arg is a category name ### ### @param new_val[String] the name of the new category ### ### @return [void] ### def category= (new_val = JSS::Category::DEFAULT_CATEGORY) return nil if @category == new_val new_val = nil if new_val == '' new_val ||= JSS::Category::DEFAULT_CATEGORY raise JSS::NoSuchItemError, "No category '#{new_val}' in the JSS" unless JSS::Category.all_names(:refresh).include? new_val @category = new_val @need_to_update = true end ### ### Change a trigger event ### ### @param type[Symbol] the type of trigger, one of the keys of {TRIGGER_EVENTS} ### ### @param new_val[Boolean] whether the type of trigger is active or not. ### ### @return [void] ### def set_trigger_event (type, new_val) raise JSS::InvalidDataError, "Trigger type must be one of #{TRIGGER_EVENTS.keys.join(', ')}" unless TRIGGER_EVENTS.keys.include? type if type == :custom raise JSS::InvalidDataError, "Custom triggers must be Strings" unless new_val.kind_of? String else raise JSS::InvalidDataError, "Non-custom triggers must be true or false" unless JSS::TRUE_FALSE.include? new_val end @trigger_events[TRIGGER_EVENTS[type]] = new_val @need_to_update = true end ### ### @return [String] The unix shell command to run on ths client. ### def run_command ; @files_processes[:run_command] ; end ### ### Set the unix shell command to be run on the client ### ### @param command[String] the unix shell command to be run on the client ### ### @return [void] ### def run_command= (command) raise JSS::InvalidDataError, "Command to run must be a String" unless command.is_a? String @files_processes[:run_command] = command @need_to_update = true end ### ### @return [Boolean] Should we update the database used by the locate command? ### def update_locate_database? ; @files_processes[:update_locate_database] ; end ### ### Set whether or not to update the database used by the locate command. ### ### @param tf[Boolean] whether or not to update the database used by the locate command. ### ### @return [void] ### def update_locate_database= (tf) @files_processes[:update_locate_database] = tf ? true : false @need_to_update = true end ### ### @return [String] The process name to search for on the client ### def search_for_process @files_processes[:search_for_process] end ### ### @return [Boolean] Should the searched-for process be killed if found. ### def kill_process? @files_processes[:kill_process] end ### ### Set the process name to search for, and if it should be killed if found. ### ### Setter methods (which end with =) can't easily take ### multiple arguments, so we instead name them "set_blah_blah" ### rather than "blah_blah=" ### ### @param process[String] the process name to search for ### ### @param kill[Boolean] should be process be killed if found ### ### @return [void] ### def set_search_for_process (process, kill = false) @files_processes[:search_for_process] = process.to_s @files_processes[:kill_process] = kill ? true : false @need_to_update = true end ### ### @return [Pathname] The path to search for ### def search_by_path ; Pathname.new @files_processes[:search_by_path] ; end ### ### @return [Boolean] Should the searched-for path be deleted if found? ### def delete_file? ; @files_processes[:delete_file] ; end ### ### Set the path to search for, a String or Pathname, and whether or not to delete it if found. ### ### Setter methods (which end with =) can't easily take ### multiple arguments, so we instead name them "set_blah_blah" ### rather than "blah_blah=" ### ### @param path[String,Pathname] the path to search for ### ### @param delete[Boolean] should the path be deleted if found ### ### @return [void] ### def set_search_by_path (path, delete = false) raise JSS::InvalidDataError, "Path to search for must be a String or a Pathname" unless path.is_a? String or path.is_a? Pathname @files_processes[:search_by_path] = path.to_s @files_processes[:delete_file] = delete ? true : false @need_to_update = true end ### ### @return [String] The term to search for using spotlight ### def spotlight_search ; @files_processes[:spotlight_search] ; end ### Set the term to seach for using spotlight ### ### @param term[String] the term to seach for using spotlight ### ### @return [void] ### def spotlight_search= (term) raise JSS::InvalidDataError, "Spotlight search term must be a String" unless term.is_a? String @files_processes[:spotlight_search] = term @need_to_update = true end ### ### @return [String] The term to seach for using the locate command ### def locate_file ; @files_processes[:locate_file] ; end ### Set the term to seach for using the locate command ### ### @param term[String] the term to seach for using the locate command ### ### @return [void] ### def locate_file= (term) raise JSS::InvalidDataError, "Term to locate must be a String" unless term.is_a? String @files_processes[:locate_file] = term @need_to_update = true end ### @return [Array] the id's of the packages handled by the policy def package_ids; @packages.map{|p| p[:id]} ; end ### @return [Array] the names of the packages handled by the policy def package_names; @packages.map{|p| p[:name]} ; end ### @return [Array] the id's of the scripts handled by the policy def script_ids; @scripts.map{|p| p[:id]} ; end ### @return [Array] the names of the scripts handled by the policy def script_names; @scripts.map{|p| p[:name]} ; end ### @return [Array] the id's of the directory_bindings handled by the policy def directory_binding_ids; @directory_bindings.map{|p| p[:id]} ; end ### @return [Array] the names of the directory_bindings handled by the policy def directory_binding_names; @directory_bindings.map{|p| p[:name]} ; end ### @return [Array] the id's of the dock_items handled by the policy def dock_item_ids; @dock_items.map{|p| p[:id]} ; end ### @return [Array] the names of the dock_items handled by the policy def dock_item_names; @dock_items.map{|p| p[:name]} ; end ### @return [Array] the id's of the printers handled by the policy def printer_ids; @printers.map{|p| p[:id]} ; end ### @return [Array] the names of the printers handled by the policy def printer_names; @printers.map{|p| p[:name]} ; end ### @return [Boolean] is this policy available in SelfService? def self_service?; @self_service[:use_for_self_service] ; end ### Aliases alias enabled? enabled alias pkgs packages alias command_to_run run_command alias delete_path? delete_file? ##################################### ### Private Instance Methods ##################################### private def rest_xml doc = REXML::Document.new APIConnection::XML_HEADER obj = doc.add_element RSRC_OBJECT_KEY.to_s general = obj.add_element "general" general.add_element('name').text = @name general.add_element('enabled').text = @enabled general.add_element('frequency').text = @frequency general.add_element('target_drive').text = @target_drive general.add_element('offline').text = @offline general.add_element('category').add_element('name').text = @category JSS.hash_to_rexml_array(@trigger_events).each{|t| general << t} obj << @scope.scope_xml files_processes = obj.add_element "files_processes" JSS.hash_to_rexml_array(@files_processes).each{|f| files_processes << f} return doc.to_s end end # class policy end # module