Class: Jamf::Scopable::Scope

Inherits:
Object
  • Object
show all
Defined in:
lib/jamf/api/classic/api_objects/scopable/scope.rb

Overview

This class represents a Scope in the JSS, as can be applied to Scopable objects like Policies, Profiles, etc. Instances of this class are generally used as the value of the @scope attribute of those objects.

Scope data comes from the API as a hash within the overall object data. The main keys of the hash define the included targets of the scope. A sub-hash defines limitations on those targets, and another sub-hash defines explicit exclusions.

This class provides methods for adding, removing, or fully replacing the various items in scope’s realms: targets, limitations, and exclusions.

This class also provides a way to see if a machine will be included in this scope.

Discussion: Users & User Groups in Scopes:

The Classic API has bugs, as well as non-obvious/historical oddness, regarding the use of Users, UserGroups, Directory Service/Local Users, and Directory Service User Groups in scopes. Here’s a discussion of those issues, and how ruby-jss handles them.

Historical Oddness

Because the concept of ‘scope’ existed before Jamf Pro had ‘Users’ and ‘User Groups’ (Jamf::User and Jamf::UserGroup classes in ruby-jss) there is non-obvious inconsistency between the labels for API data, and the labels for that data in the web UI:

Users

What appears in the UI as ‘Users’ are User objects in Jamf pro, which in ruby-jss are Jamf::User instances.

These will appear in the API data as <jss_users> element with <user> sub-elements (XML) or the ‘jss_users’ array (JSON). These are available as Targets or Exclusions.

In this class, they are also referred to as ‘jss_users’

Directory Service/Local Users

When editing a scope in the UI, in Limitations and Exclusions, you can add arbitrary strings that will be matched to the users assigned to machines, or that appear in any of the defined LDAP servers. These scope items are called ‘Directory Service/Local Users’ but used to be called ‘LDAP/Local Users’

In the API data for scopes, these items appear in the <users> element with <user> sub-elements (XML) or ‘users’ array (JSON) of the limitations and exclusions data

In this class, these items ultimately use the same names they have in the API data: ‘users’ but when specifying that you are setting that value, you can use any of these synonyms, plural or singular:

ldap_users, jamf_ldap_users, directory_service_local_users

User Groups

What appears in the UI as ‘User Groups’ are User Group objects in Jamf Pro, both static and smart. In ruby-jss, these are Jamf::UserGroup instances.

They will appear in the API data as <jss_user_groups> element with <user_group> sub-elements (XML) or the ‘jss_user_groups’ array (JSON). These are available as Targets or Exclusions.

In this class they are also referred to as ‘jss_user_groups’

Directory Service User Groups

When editing a scope in the UI, in Limitations and Exclusions, you can look up and add groups from any of the defined LDAP servers. These scope items are called ‘Directory Service User Groups’ but used to be called ‘LDAP User Groups’

In the API data for scopes, these items appear in the <user_groups> element with <user_group> sub-elements (XML) or ‘user_groups’ array (JSON) of the limitations and exclusions data

In this class, these items ultimately use the same names they have in the API data: ‘user_groups’ but when specifying that you are setting that value, you can use any of these synonyms, singular or plural:

ldap_user_groups, directory_service_user_groups

IMPORTANT: API BUG IN POLICY AND PATCH POLICY SCOPES - CAN CAUSE DATA LOSS

When you GET the data for policies and patch policies from the Classic API the scope data returned will NOT include the ‘jss_users’ and ‘jss_user_groups’ data in the targets or the exclusions, even if they are defined in the web UI.

More importantly, if you try to include those in the XML when you PUT a policy back to make a change via the API, you’ll get an error because the API endpoint doesn’t know what <jss_users> or <jss_user_groups> elements are.

Even more importanly, since you cannot include those elements in your PUT body, if they actually exist in the scope, THEY WILL BE ERASED from the actual scope, because they weren’t in the PUT data. This will always happen if you include the <scope> element in your PUT data, even if you didn’t change the scope.

  • How ruby-jss handles this bug:

Fortunately the Classic API, or at least this part of it, doesn’t fully adhere to the REST standards for PUT, and if you don’t include the <scope> element in the XML, the server will just ignore the scope entirely, and nothing will change.

We make use of that here to allow for editing Policies without fear of erasing those parts of the scope. As long as you don’t change anything about the scope, there will be no <scope> element in the XML sent with a PUT, and the scope is safe from harm.

If you DO change the scope of a policy, this bug cannot be avoided, and you’ll delete any “User”/jss_user and “User Groups/jss_user_groups” defined in the targets or exclusions.

By default, if you try to change the scope of a Policy of PatchPolicy, you’ll get a warning about the possibility of losing data when you save.

You can supress those warnings either by supressing all ruby warnings, or by calling Jamf::Scopable::Scope.do_not_warn_about_policy_scope_bugs

IMPORTANT: API BUG IN OSX CONFIG PROFILE SCOPES - CAN CAUSE DATA LOSS

When fetching the data for OSX Configuration Profiles using JSON (which ruby-jss does) and the scope of the profile contains more than one ‘jss_user_groups` as a target, then only the last one will be returned. If you have more than one such group as a target, and use ruby-jss to make changes to the scope, all but the last jss_user_groups used as targets will be removed.

This only appears to affect scope targets, not exclusions, and only for OSX Config Profiles. Other scopable objects that use jss_user_groups in their API data seem to be OK.

This is due to a long-standing API bug regarding how Arrays in XML are incorrectly translated into Hashes of a single Hash when returning the data as JSON - they shoud be Arrays of Hashes in JSON - one hash for each item.

Even though this bug was first reported to jamf in 2009, it still appears in many places throughout the Classic API. ruby-jss works around some of the worst instances of the bug, but such workarounds are complex requiring re-fetching the data in XML and parsing it manually. At the moment there are no plans to do that for this specific scope bug.

By default, if you try to change the scope of an object affected by this bug, you’ll get a warning about the possibility of losing data when you save.

You can supress those warnings either by supressing all ruby warnings, or by calling Jamf::Scopable::Scope.do_not_warn_about_array_hash_scope_bugs

See Also:

Constant Summary collapse

SCOPING_CLASSES =

These are the classes that Scopes can use for defining a scope, keyed by appropriate symbols.

synonyms, including singular/plural forms, are used to allow for more natural language when specifying these scope entities. The key used in the actual API data is usually the plural.

NOTE: user and user_group in Scope data refer to ‘Directory Service/Local User’ and ‘Directory Service User Group’ as labeled in the web-ui. These were formerly labeled as ‘LDAP/Local User’ and ‘LDAP User Group’.

{
  computers: Jamf::Computer,
  computer: Jamf::Computer,

  computer_groups: Jamf::ComputerGroup,
  computer_group: Jamf::ComputerGroup,

  mobile_devices: Jamf::MobileDevice,
  mobile_device: Jamf::MobileDevice,

  mobile_device_groups: Jamf::MobileDeviceGroup,
  mobile_device_group: Jamf::MobileDeviceGroup,

  buildings: Jamf::Building,
  building: Jamf::Building,

  departments: Jamf::Department,
  department: Jamf::Department,

  network_segments: Jamf::NetworkSegment,
  network_segment: Jamf::NetworkSegment,

  ibeacons: Jamf::IBeacon,
  ibeacon: Jamf::IBeacon,

  jss_users: Jamf::User,
  jss_user: Jamf::User,

  jss_user_groups: Jamf::UserGroup,
  jss_user_group: Jamf::UserGroup,

  users: nil,
  user: nil,
  ldap_users: nil,
  ldap_user: nil,
  jamf_ldap_users: nil,
  jamf_ldap_user: nil,
  directory_service_local_users: nil,
  directory_service_local_user: nil,

  user_groups: nil,
  user_group: nil,
  ldap_user_groups: nil,
  ldap_user_group: nil,
  directory_service_user_groups: nil,
  directory_service_user_group: nil
}.freeze
JAMF_DATA_LOSS_BUG_CLASSES =

These classes are affected by the jss_users/jss_user_groups bug.

They do not accept jss_users or jss_user_groups in their targets or exclusions, and editing their scope via the API will always delete those items from the scope if they exist.

See discussion in the Scope class comments.

[
  Jamf::Policy,
  Jamf::PatchPolicy
].freeze
JAMF_DATA_LOSS_BUG_KEYS =

The classes affected by the jss_users/jss_user_groups bug do not include these items in their Target or Exclusion API data, even if the scope has such items defined in the JSS

See discussion in the Scope class comments.

%i[jss_users jss_user_groups].freeze
LDAP_BASED_KEYS =

In the API data for limitations and exclusions ‘users’ is what appears as Directory Service/Local Users in the web UI and ‘user_groups’ appears as ‘Directory Service User Groups’.

Contrasted with ‘jss_users’ and ‘jss_user_groups’ in the API data for targets and exlcusions, which are Jamf::User and Jamf::UserGroup objects.

%i[users user_groups].freeze
LDAP_JAMF_USER_KEYS =

These keys always mean :users

%i[
  user
  users
  ldap_user
  ldap_users
  jamf_ldap_user
  jamf_ldap_users
  directory_service_local_user
  directory_service_local_users
].freeze
LDAP_GROUP_KEYS =

These keys always mean :user_groups

%i[
  user_group
  user_groups
  ldap_user_group
  ldap_user_groups
  directory_service_user_group
  directory_service_user_groups
].freeze
TARGETS_AND_GROUPS =

This hash maps the availble Scope Target keys from SCOPING_CLASSES to their corresponding target group keys from SCOPING_CLASSES.

{ computers: :computer_groups, mobile_devices: :mobile_device_groups }.freeze
ESS =

added to the ends of singular key names if needed, e.g. computer_group => computer_groups

's'.freeze
TARGETS =

These can be part of the base target list of the scope, along with the appropriate target and target group keys

%i[buildings departments jss_users jss_user_groups].freeze
INCLUSIONS =

Backward Compatibility

TARGETS
LIMITATIONS =

These can limit the inclusion list These are the keys that come from the API the :users key from the API is what we call :jamf_ldap_users and the :user_groups key from the API we call :ldap_user_groups See the IMPORTANT discussion above.

%i[
  ibeacons
  network_segments
  users
  user_groups
].freeze
EXCLUSIONS =

any of them can be excluded

TARGETS + LIMITATIONS
DEFAULT_SCOPE =

Here’s a default scope as it might come from the API.

{
  all_computers: false,
  all_mobile_devices: false,
  limitations: {},
  exclusions: {}
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(target_key, raw_scope = nil, container: nil) ⇒ Scope

If raw_scope is empty, a default scope, scoped to no targets, is created, and can be modified as needed.

Parameters:

  • target_key (Symbol)

    the kind of thing we’re scoping, a key from TARGETS_AND_GROUPS

  • raw_scope (Hash) (defaults to: nil)

    the JSON :scope data from an API query that is scopable, e.g. a Policy.

  • container (Jamf::APIObject) (defaults to: nil)

    The scopable object to which this scope belongs, e,g, an instance of Jamf::Policy, Jamf::MobileDeviceApplication, etc.. If not provided, will be set automatically after initialization



447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 447

def initialize(target_key, raw_scope = nil, container: nil)
  raw_scope ||= DEFAULT_SCOPE.dup
  unless TARGETS_AND_GROUPS.key?(target_key)
    raise Jamf::InvalidDataError, "The target class of a Scope must be one of the symbols :#{TARGETS_AND_GROUPS.keys.join(', :')}"
  end

  @should_update = false
  @container = container

  @target_key = target_key
  @target_class = SCOPING_CLASSES[@target_key]
  @group_key = TARGETS_AND_GROUPS[@target_key]
  @group_class = SCOPING_CLASSES[@group_key]

  @target_keys = [@target_key, @group_key] + TARGETS
  @exclusion_keys = [@target_key, @group_key] + EXCLUSIONS

  if JAMF_DATA_LOSS_BUG_CLASSES.include?(@container.class)
    @target_keys -= JAMF_DATA_LOSS_BUG_KEYS
    @exclusion_keys -= JAMF_DATA_LOSS_BUG_KEYS
  end

  parse_targets(raw_scope)
  parse_limitations(raw_scope)
  parse_exclusions(raw_scope)
end

Instance Attribute Details

#all_targetsBoolean Also known as: all_targets?

Does this scope cover all targets?

If this is true, the @targets Hash is ignored, and all targets in the JSS form the base scope.

Returns:

  • (Boolean)


376
377
378
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 376

def all_targets
  @all_targets
end

#containerJamf::APIObject subclass

A reference to the object that contains this Scope

For telling it when a change is made and an update needed and for accessing its api connection

Returns:



357
358
359
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 357

def container
  @container
end

#exclusionsHash{Symbol: Array<Integer, String>} (readonly)

The items in these arrays are the exclusions applied to targets in the @targets .

The arrays of ids are:

  • :computers or :mobile_devices (which are directly excluded)

  • :direct_exclusions - a synonym for :mobile_devices or :computers

  • :computer_groups or :mobile_device_groups (which exclude all of their memebers)

  • :group_exclusions - a synonym for :computer_groups or :mobile_device_groups

  • :departments

  • :buildings

  • :network_segments

  • :jss_users

  • :jss_user_groups

  • :users

  • :user_groups #

Returns:



425
426
427
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 425

def exclusions
  @exclusions
end

#group_classObject (readonly)

what type of target group is this scope for? ComputerGroups or MobileDeviceGroups?



367
368
369
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 367

def group_class
  @group_class
end

#limitationsHash{Symbol: Array<Integer, String>} (readonly)

The items in these arrays are the limitations applied to targets in the @targets .

The arrays of ids are:

  • :network_segments

  • :users

  • :user_groups

  • :ibeacons

Returns:



408
409
410
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 408

def limitations
  @limitations
end

#should_updateBoolean Also known as: should_update?

Returns Have changes been made to the scope, that need to be sent to the server?.

Returns:

  • (Boolean)

    Have changes been made to the scope, that need to be sent to the server?



429
430
431
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 429

def should_update
  @should_update
end

#target_classObject (readonly)

what type of target is this scope for? Computers or MobileDevices?



364
365
366
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 364

def target_class
  @target_class
end

#targetsHash{Symbol: Array<Integer>} (readonly) Also known as: inclusions

The items which form the base scope of included targets

This is the group of targets to which the limitations and exclusions apply. they keys are:

  • :computers or :mobile_devices (which are directly targeted)

  • :direct_targets - a synonym for :mobile_devices or :computers

  • :computer_groups or :mobile_device_groups (which target all of their memebers)

  • :group_targets - a synonym for :computer_groups or :mobile_device_groups

  • :departments

  • :buildings

  • :jss_users

  • :jss_user_groups

and the values are Arrays of names of those things.

Returns:



395
396
397
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 395

def targets
  @targets
end

#unable_to_verify_ldap_entriesBoolean

Returns should we expect a potential 409 Conflict if we can’t connect to LDAP servers for verification?.

Returns:

  • (Boolean)

    should we expect a potential 409 Conflict if we can’t connect to LDAP servers for verification?



361
362
363
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 361

def unable_to_verify_ldap_entries
  @unable_to_verify_ldap_entries
end

Class Method Details

.do_not_warn_about_array_hash_scope_bugsObject

call this to suppress warnings about data loss bug in OSXConfigurationProfile scopes when there are jss_user_groups used as targets



339
340
341
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 339

def self.do_not_warn_about_array_hash_scope_bugs
  @do_not_warn_about_array_hash_scope_bugs = true
end

.do_not_warn_about_array_hash_scope_bugs?Boolean

Has do_not_warn_about_policy_scope_bugs been set?

Returns:

  • (Boolean)


344
345
346
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 344

def self.do_not_warn_about_array_hash_scope_bugs?
  @do_not_warn_about_array_hash_scope_bugs
end

.do_not_warn_about_policy_scope_bugsObject

call this to suppress warnings about data loss bug in Policy and Patch Policy scopes



327
328
329
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 327

def self.do_not_warn_about_policy_scope_bugs
  @do_not_warn_about_policy_scope_bugs = true
end

.do_not_warn_about_policy_scope_bugs?Boolean

Has do_not_warn_about_policy_scope_bugs been set?

Returns:

  • (Boolean)


332
333
334
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 332

def self.do_not_warn_about_policy_scope_bugs?
  @do_not_warn_about_policy_scope_bugs
end

Instance Method Details

#add_exclusion(key, item) ⇒ void

This method returns an undefined value.

Add a single item for exclusions of this scope.

The item name will be checked for existence in the JSS, and an exception raised if the item doesn’t exist.

Examples:

add_exclusion(:network_segments, "foo")

Parameters:

  • key (Symbol)

    the type of item being added to the exclusions, :computer, :building, etc…

  • item (String, integer)

    a valid identifier of the item being added

Raises:



835
836
837
838
839
840
841
842
843
844
845
846
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 835

def add_exclusion(key, item)
  key = pluralize_key(key)
  item_id = validate_item(:exclusion, key, item)
  return if @exclusions[key]&.include?(item_id)

  raise Jamf::AlreadyExistsError, "Can't exclude #{key} scope to '#{item}' because it's already explicitly included." if @targets[key]&.include?(item)

  raise Jamf::AlreadyExistsError, "Can't exclude #{key} '#{item}' because it's already an explicit limitation." if @limitations[key]&.include?(item)

  @exclusions[key] << item_id
  note_pending_changes
end

#add_limitation(key, item) ⇒ void

TODO:

handle ldap user/group lookups

This method returns an undefined value.

Add a single item for limiting this scope.

The item name will be checked for existence in the JSS, and an exception raised if the item doesn’t exist.

Examples:

add_limitation(:network_segments, "foo")

Parameters:

  • key (Symbol)

    the type of item being added, :computer, :building, etc…

  • item (String, integer)

    a valid identifier of the item being added



743
744
745
746
747
748
749
750
751
752
753
754
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 743

def add_limitation(key, item)
  key = pluralize_key(key)
  item_id = validate_item(:limitation, key, item)
  return nil if @limitations[key]&.include?(item_id)

  if @exclusions[key]&.include?(item_id)
    raise Jamf::AlreadyExistsError, "Can't set #{key} limitation for '#{name}' because it's already an explicit exclusion."
  end

  @limitations[key] << item_id
  note_pending_changes
end

#add_target(key, item = nil) ⇒ void Also known as: add_inclusion

This method returns an undefined value.

Add a single item as a target in this scope.

The item name will be checked for existence in the JSS, and an exception

raised if the item doesn't exist.

Examples:

add_target(:computers, "mantis")
add_target(:computer_groups, 2342)

Parameters:

  • key (Symbol)

    the key from #SCOPING_CLASSES for the kind of item being added, :computer, :building, etc… Use :all to scope to all targets (the same as calling #set_all_targets)

  • item (String, integer) (defaults to: nil)

    a valid identifier of the item being added



648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 648

def add_target(key, item = nil)
  if key == :all
    set_all_targets
    return
  end

  key = pluralize_key(key)
  item_id = validate_item(:target, key, item)
  return if @targets[key]&.include?(item_id)

  if @exclusions[key]&.include?(item_id)
    raise Jamf::AlreadyExistsError,
          "Can't set #{key} target to '#{item}' because it's already an explicit exclusion."
  end

  @targets[key] << item_id
  @all_targets = false
  note_pending_changes
end

#in_scope?(machine) ⇒ Boolean

is a given machine is in this scope?

For a parameter you may pass either an instantiated Jamf::MobileDevice or Jamf::Computer, or an identifier for one. If an identifier is passed, it is not instantiated, but an API request is made for just the required subsets of data, thus speeding things up a bit when calling this method many times.

WARNING: For scopes that include Jamf Users and Jamf User Groups as targets or exclusions, this method may return an incorrect value. See the discussion in the documentation for the Scopable::Scope class under ‘IMPORTANT - Users & User Groups in Targets and Exclusions’

NOTE: currently in-range iBeacons are transient, and are not reported to the JSS as inventory data. As such they are ignored in this result. If a scope contains iBeacon limitations or exclusions, it is up to the user to be aware of that when evaluating the meaning of this result.

Parameters:

Returns:

  • (Boolean)


1002
1003
1004
1005
1006
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 1002

def in_scope?(machine)
  machine_data = fetch_machine_data machine

  a_target?(machine_data) && within_limitations?(machine_data) && !excluded?(machine_data)
end

#pretty_print_instance_variablesArray

Remove large or redundant data structures from the instance_variables used to create pretty-print (pp) output.

Returns:

  • (Array)

    the desired instance_variables



948
949
950
951
952
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 948

def pretty_print_instance_variables
  vars = instance_variables.sort
  vars.delete :@container
  vars
end

#remove_exclusion(key, item) ⇒ void

This method returns an undefined value.

Remove a single item for exclusions of this scope

Examples:

remove_exclusion(:network_segments, "foo")

Parameters:

  • key (Symbol)

    the type of item being removed from the excludions, :computer, :building, etc…

  • item (String, integer)

    a valid identifier of the item being removed



859
860
861
862
863
864
865
866
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 859

def remove_exclusion(key, item)
  key = pluralize_key(key)
  item_id = validate_item :exclusion, key, item, error_if_not_found: false
  return unless @exclusions[key]&.include?(item_id)

  @exclusions[key].delete item_id
  note_pending_changes
end

#remove_limitation(key, item) ⇒ void

TODO:

handle ldap user/group lookups

This method returns an undefined value.

Remove a single item for limiting this scope.

Examples:

remove_limitation(:network_segments, "foo")

Parameters:

  • key (Symbol)

    the type of item being removed, :computer, :building, etc…

  • item (String, integer)

    a valid identifier of the item being removed



769
770
771
772
773
774
775
776
777
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 769

def remove_limitation(key, item)
  key = pluralize_key(key)
  item_id = validate_item :limitation, key, item, error_if_not_found: false
  return unless item_id
  return unless @limitations[key]&.include?(item_id)

  @limitations[key].delete item_id
  note_pending_changes
end

#remove_target(key, item) ⇒ void Also known as: remove_inclusion

This method returns an undefined value.

Remove a single item as a target for this scope.

Examples:

remove_target(:computer, "mantis")

Parameters:

  • key (Symbol)

    the key from #SCOPING_CLASSES for the kind of item being removed, :computer, :building, etc…

  • item (String, integer)

    a valid identifier of the item being removed



680
681
682
683
684
685
686
687
688
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 680

def remove_target(key, item)
  key = pluralize_key(key)
  item_id = validate_item :target, key, item, error_if_not_found: false
  return unless item_id
  return unless @targets[key]&.include?(item_id)

  @targets[key].delete item_id
  note_pending_changes
end

#scope_xmlREXML::Element

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return a REXML Element containing the current state of the Scope for adding into the XML of the container.

Returns:

  • (REXML::Element)


874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 874

def scope_xml
  scope = REXML::Element.new 'scope'
  scope.add_element(@all_key.to_s).text = @all_targets

  @target_keys.each do |klass|
    list = @targets[klass]
    list.compact!
    list.delete 0
    list_as_hashes = list.map { |i| { id: i } }

    xml_list = SCOPING_CLASSES[klass].xml_list(list_as_hashes, :id)
    xml_list.name = 'jss_users' if SCOPING_CLASSES[klass] == Jamf::User
    xml_list.name = 'jss_user_groups' if SCOPING_CLASSES[klass] == Jamf::UserGroup
    scope << xml_list
  end

  limitations = scope.add_element('limitations')
  @limitations.each do |klass, list|
    list.compact!
    list.delete 0
    if klass == :users
      users_xml = limitations.add_element 'users'
      list.each do |name|
        user_xml = users_xml.add_element 'user'
        user_xml.add_element('name').text = name
      end
    elsif klass == :user_groups
      user_groups_xml = limitations.add_element 'user_groups'
      list.each do |name|
        user_group_xml = user_groups_xml.add_element 'user_group'
        user_group_xml.add_element('name').text = name
      end
    else
      list_as_hashes = list.map { |i| { id: i } }
      limitations << SCOPING_CLASSES[klass].xml_list(list_as_hashes, :id)
    end
  end

  exclusions = scope.add_element('exclusions')
  @exclusion_keys.each do |klass|
    list = @exclusions[klass]
    list.compact!
    list.delete 0
    if klass == :users
      users_xml = exclusions.add_element 'users'
      list.each do |name|
        user_xml = users_xml.add_element 'user'
        user_xml.add_element('name').text = name
      end
    elsif klass == :user_groups
      user_groups_xml = exclusions.add_element 'user_groups'
      list.each do |name|
        user_group_xml = user_groups_xml.add_element 'user_group'
        user_group_xml.add_element('name').text = name
      end
    else
      list_as_hashes = list.map { |i| { id: i } }

      xml_list = SCOPING_CLASSES[klass].xml_list(list_as_hashes, :id)
      xml_list.name = 'jss_users' if SCOPING_CLASSES[klass] == Jamf::User
      xml_list.name = 'jss_user_groups' if SCOPING_CLASSES[klass] == Jamf::UserGroup
      exclusions << xml_list

    end
  end
  scope
end

#scoped_machinesHash{Integer => String}

Return a hash of id => name for all machines in the target class that are within this scope.

WARNING: This must instantiate all machines in the target class. It will still be slow, at least the first time for each target class. On the upside, the instantiated machines will be cached, so generating this list for other scopes with the same target class will be much much faster. In tests, 1600 Computers took about 7 minutes the first time, but less than 1 second after caching.

See also the warning for #in_scope?

Returns:



970
971
972
973
974
975
976
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 970

def scoped_machines
  scoped_machines = {}
  @target_class.all_objects(:refresh, cnx: container.cnx).each do |machine|
    scoped_machines[machine.id] = machine.name if in_scope? machine
  end
  scoped_machines
end

#set_all_targets(clear = false) ⇒ void Also known as: include_all

This method returns an undefined value.

Set the scope’s targets to all.

By default, the limitations and exclusions remain. If a non-false parameter is provided, they will be removed also.

Parameters:

  • clear (Boolean) (defaults to: false)

    Should the limitations and exclusions be removed also?



551
552
553
554
555
556
557
558
559
560
561
562
563
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 551

def set_all_targets(clear = false)
  @targets = {}
  @target_keys.each { |k| @targets[k] = [] }
  @all_targets = true
  if clear
    @limitations = {}
    LIMITATIONS.each { |k| @limitations[k] = [] }

    @exclusions = {}
    @exclusion_keys.each { |k| @exclusions[k] = [] }
  end
  note_pending_changes
end

#set_exclusions(key, list) ⇒ void Also known as: set_exclusion

This method returns an undefined value.

Replace an exclusion list for this scope

The list must be an Array of names of items of the Class being excluded from the scope Each will be checked for existence in the JSS, and an exception raised if the item doesn’t exist.

Examples:

set_exclusion(:network_segments, ['foo','bar'])

Parameters:

  • key (Symbol)

    the type of item being excluded, :computer, :building, etc…

  • list (Array)

    the identifiers of the items being set

Raises:



793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 793

def set_exclusions(key, list)
  key = pluralize_key(key)
  raise Jamf::InvalidDataError, "List must be an Array of #{key} identifiers, it may be empty." unless list.is_a? Array

  # check the idents
  list_of_ids = list.map do |ident|
    item_id = validate_item(:exclusion, key, ident)
    case key
    when *@target_keys
      if @targets[key]&.include?(item_id)
        msg = "Can't exclude #{key} '#{ident}' because it's already explicitly included."
        raise Jamf::AlreadyExistsError, msg
      end
    when *LIMITATIONS
      if @limitations[key]&.include?(item_id)
        msg = "Can't exclude #{key} '#{ident}' because it's already an explicit limitation."
        raise Jamf::AlreadyExistsError, msg
      end
    end
    item_id
  end # each

  return nil if list_of_ids.sort == @exclusions[key].sort

  @exclusions[key] = list_of_ids
  note_pending_changes
end

#set_limitations(key, list) ⇒ void Also known as: set_limitation

TODO:

handle ldap user group lookups

This method returns an undefined value.

Replace a limitation list for this scope.

The list must be an Array of names of items of the Class represented by the key. Each will be checked for existence in the JSS, and an exception raised if the item doesn’t exist.

Examples:

set_limitation(:network_segments, ['foo',231])

Parameters:

  • key (Symbol)

    the type of items being set as limitations, :network_segments, :users, etc…

  • list (Array)

    the identifiers of the items being set as limitations

Raises:



707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 707

def set_limitations(key, list)
  key = pluralize_key(key)
  raise Jamf::InvalidDataError, "List must be an Array of #{key} identifiers, it may be empty." unless list.is_a? Array

  # check the idents
  list_of_ids = list.map do |ident|
    item_id = validate_item(:limitation, key, ident)
    if @exclusions[key]&.include?(item_id)
      raise Jamf::AlreadyExistsError, "Can't set #{key} limitation for '#{name}' because it's already an explicit exclusion."
    end

    item_id
  end # each

  return nil if list_of_ids.sort == @limitations[key].sort

  @limitations[key] = list_of_ids
  note_pending_changes
end

#set_targets(key, list = nil) ⇒ void Also known as: set_target, set_inclusion, set_inclusions

This method returns an undefined value.

Replace a list of item names for as targets in this scope.

The list must be an Array of names of items of the Class represented by the key. Each will be checked for existence in the JSS, and an exception raised if the item doesn’t exist.

being included, :computer, :building, etc… Use :all to scope to all targets (the same as calling #set_all_targets)

Examples:

set_targets(:computers, ['kimchi','mantis'])

Parameters:

  • key (Symbol)

    the key from #SCOPING_CLASSES for the kind of items

  • list (Array) (defaults to: nil)

    identifiers of the items being added

Raises:



584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 584

def set_targets(key, list = nil)
  if key == :all
    set_all_targets
    return
  end

  key = pluralize_key(key)
  raise Jamf::InvalidDataError, "List must be an Array of #{key} identifiers, it may be empty." unless list.is_a? Array

  # check the idents
  list_of_ids = list.map do |ident|
    item_id = validate_item(:target, key, ident)

    if @exclusions[key]&.include?(item_id)
      raise Jamf::AlreadyExistsError, \
            "Can't set #{key} target to '#{ident}' because it's already an explicit exclusion."
    end

    item_id
  end # each

  return nil if list_of_ids.sort == @targets[key].sort

  @targets[key] = list_of_ids
  @all_targets = false
  note_pending_changes
end

#to_sObject



1009
1010
1011
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 1009

def to_s
  "Scope for #{container.class} id #{container.id}"
end