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



464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 464

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)


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

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:



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

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:



442
443
444
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 442

def exclusions
  @exclusions
end

#group_classObject (readonly)

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



384
385
386
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 384

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:



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

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?



446
447
448
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 446

def should_update
  @should_update
end

#target_classObject (readonly)

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



381
382
383
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 381

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:



412
413
414
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 412

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?



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

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



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

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)


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

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



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

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)


349
350
351
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 349

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:



850
851
852
853
854
855
856
857
858
859
860
861
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 850

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



760
761
762
763
764
765
766
767
768
769
770
771
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 760

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



665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 665

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)


1017
1018
1019
1020
1021
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 1017

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



963
964
965
966
967
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 963

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



874
875
876
877
878
879
880
881
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 874

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



786
787
788
789
790
791
792
793
794
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 786

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



697
698
699
700
701
702
703
704
705
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 697

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)


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
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 889

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:



985
986
987
988
989
990
991
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 985

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?



568
569
570
571
572
573
574
575
576
577
578
579
580
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 568

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:



810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 810

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.map! do |ident|
    item_id = validate_item(:exclusion, key, ident)
    case key
    when *@target_keys
      if @targets[key] && @exclusions[key].include?(item_id)
        raise Jamf::AlreadyExistsError, "Can't exclude #{key} '#{ident}' because it's already explicitly included."
      end
    when *LIMITATIONS
      if @limitations[key] && @exclusions[key].include?(item_id)
        raise Jamf::AlreadyExistsError, "Can't exclude #{key} '#{ident}' because it's already an explicit limitation."
      end
    end
    item_id
  end # each

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

  @exclusions[key] = list
  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:



724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 724

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.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.sort == @limitations[key].sort

  @limitations[key] = list
  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:



601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 601

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.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.sort == @targets[key].sort

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

#to_sObject



1024
1025
1026
# File 'lib/jamf/api/classic/api_objects/scopable/scope.rb', line 1024

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