Class: JSS::Scopable::Scope
- Defined in:
- lib/jss/api_object/scopable/scope.rb,
lib/jss.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 inclusions, 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.
IMPORTANT: The classic API has bugs regarding the use of Users, UserGroups, LDAP/Local Users, & LDAP User Groups in scopes. Here’s a discussion of those bugs and how ruby-jss handles them.
Targets/Inclusions
- 'Users' can only be JSS::Users - No LDAP
- BUG: They do not appear in API data (XML or JSON) and are
NOT SUPPORTED in ruby-jss.
- You must use the Web UI to work with them in a Scope.
- 'User Groups' can only be JSS::UserGroups - No LDAP
- BUG: They do not appear in API data (XML or JSON) and are
NOT SUPPORTED in ruby-jss.
- You must use the Web UI to work with them in a Scope.
Limitations
- 'LDAP/Local Users' can be any string
- The Web UI accepts any string, even if no matching Local or LDAP user.
- The data shows up in API data in scope=>limitations=>users
by name only (the string provided), no IDs
- 'LDAP User Groups' can only be LDAP groups that actually exist
- The Web UI won't let you add a group that doesn't exist in ldap
- The data shows up in API data in scope=>limitations=>user_groups
by name and LDAP ID (which may be empty)
- The data ALSO shows up in API data in scope=>limit_to_users=>user_groups
by name only, no LDAP IDs. ruby-jss ignores this and looks at
scope=>limitations=>user_groups
Exclusions, combines the behavior of Inclusions & Limitations
- 'Users' can only be JSS::Users - No LDAP
- BUG: They do not appear in API data (XML or JSON) and are
NOT SUPPORTED in ruby-jss.
- You must use the Web UI to work with them in a Scope.
- 'User Groups' can only be JSS::UserGroups - No LDAP
- BUG: They do not appear in API data (XML or JSON) and are
NOT SUPPORTED in ruby-jss.
- You must use the Web UI to work with them in a Scope.
- 'LDAP/Local Users' can be any string
- The Web UI accepts any string, even if no matching Local or LDAP user.
- The data shows up in API data in scope=>exclusions=>users
by name only (the string provided), no IDs
- 'LDAP User Groups' can only be LDAP groups that actually exist
- The Web UI won't let you add a group that doesn't exist in ldap
- The data shows up in API data in scope=>exclusions=>user_groups
by name and LDAP ID (which may be empty)
How ruby-jss handles this:
- Methods #set_targets and #add_target will not accept the keys
:user, :users, :user_group, :user_groups.
- Method #remove_target will ignore them.
- Methods #set_limitations, #add_limitation & #remove_limitation will accept:
- :user, :ldap_user, or :jamf_ldap_user (and their plurals) for working
with 'LDAP/Local Users'. When setting or adding, the provided
string(s) must exist as either a JSS::User or an LDAP user
- :user_group or :ldap_user_group (and their plurals) for working with
'LDAP User Groups'. When setting or adding, the provided string
must exist as a group in LDAP.
- Methods #set_exclusions, #add_exclusion & #remove_exclusion will accept:
- :user, :ldap_user, or :jamf_ldap_user (and their plurals) for working
with 'LDAP/Local Users'. When setting or adding, the provided string(s)
must exist as either a JSS::User or an LDAP user.
- :user_group or :ldap_user_group (and their plurals) for working with
'LDAP User Groups''. When setting or adding, the provided string
must exist as a group in LDAP.
Internally in the Scope instance:
- The limitations and exclusions that match the WebUI's 'LDAP/Local Users'
are in @limitations[:jamf_ldap_users] and @exclusions[:jamf_ldap_users]
- The limitations and exclusions that match the WebUI's 'LDAP User Groups'
are in @limitations[:ldap_user_groups] and @exclusions[:ldap_user_groups]
Constant Summary collapse
- SCOPING_CLASSES =
These are the classes that Scopes can use for defining a scope, keyed by appropriate symbols. NOTE: All the user and group ones don’t actually refer to JSS::User or JSS::UserGroup. See IMPORTANT discussion above.
{ computers: JSS::Computer, computer: JSS::Computer, computer_groups: JSS::ComputerGroup, computer_group: JSS::ComputerGroup, mobile_devices: JSS::MobileDevice, mobile_device: JSS::MobileDevice, mobile_device_groups: JSS::MobileDeviceGroup, mobile_device_group: JSS::MobileDeviceGroup, buildings: JSS::Building, building: JSS::Building, departments: JSS::Department, department: JSS::Department, network_segments: JSS::NetworkSegment, network_segment: JSS::NetworkSegment, ibeacon: JSS::IBeacon, ibeacons: JSS::IBeacon, user: nil, users: nil, ldap_user: nil, ldap_users: nil, jamf_ldap_user: nil, jamf_ldap_users: nil, user_group: nil, user_groups: nil, ldap_user_group: nil, ldap_user_groups: nil }.freeze
- LDAP_JAMF_USER_KEYS =
These keys always mean :jamf_ldap_users
%i[ user users ldap_user ldap_users jamf_ldap_user jamf_ldap_users ].freeze
- LDAP_GROUP_KEYS =
These keys always mean :ldap_user_groups
%i[ user_group user_groups ldap_user_group ldap_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
- INCLUSIONS =
These can be part of the base inclusion list of the scope, along with the appropriate target and target group keys
%i[buildings departments].freeze
- 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 jamf_ldap_users ldap_user_groups ].freeze
- EXCLUSIONS =
any of them can be excluded
INCLUSIONS + LIMITATIONS
- DEFAULT_SCOPE =
Here’s a default scope as it might come from the API.
{ all_computers: true, all_mobile_devices: true, limitations: {}, exclusions: {} }.freeze
Instance Attribute Summary collapse
-
#all_targets ⇒ Boolean
(also: #all_targets?)
readonly
Does this scope cover all targets?.
-
#container ⇒ JSS::APIObject subclass
A reference to the object that contains this Scope.
-
#exclusions ⇒ Hash<Array>
readonly
The items in these arrays are the exclusions applied to targets in the @inclusions .
-
#inclusions ⇒ Hash<Array>
readonly
The items which form the base scope of included targets.
-
#limitations ⇒ Hash<Array>
readonly
The items in these arrays are the limitations applied to targets in the @inclusions .
-
#target_class ⇒ Object
readonly
what type of target is this scope for? Computers or Mobiledevices?.
-
#unable_to_verify_ldap_entries ⇒ Boolean
Should we expect a potential 409 Conflict if we can’t connect to LDAP servers for verification?.
Instance Method Summary collapse
-
#add_exclusion(key, item) ⇒ void
Add a single item for exclusions of this scope.
-
#add_limitation(key, item) ⇒ void
Add a single item for limiting this scope.
-
#add_target(key, item) ⇒ void
(also: #add_inclusion)
Add a single item as a target in this scope.
-
#include_all(clear = false) ⇒ void
Set the scope’s inclusions to all targets.
-
#initialize(target_key, raw_scope = nil) ⇒ Scope
constructor
If raw_scope is empty, a default scope, scoped to all targets, is created, and can be modified as needed.
-
#pretty_print_instance_variables ⇒ Array
Remove the init_data and api object from the instance_variables used to create pretty-print (pp) output.
-
#remove_exclusion(key, item) ⇒ void
Remove a single item for exclusions of this scope.
-
#remove_limitation(key, item) ⇒ void
Remove a single item for limiting this scope.
-
#remove_target(key, item) ⇒ void
(also: #remove_inclusion)
Remove a single item as a target for this scope.
-
#scope_xml ⇒ REXML::Element
private
Return a REXML Element containing the current state of the Scope for adding into the XML of the container.
-
#set_exclusion(key, list) ⇒ void
Replace an exclusion list for this scope.
-
#set_limitation(key, list) ⇒ void
(also: #set_limitations)
Replace a limitation list for this scope.
-
#set_targets(key, list) ⇒ void
(also: #set_target, #set_inclusion, #set_inclusions)
Replace a list of item names for as targets in this scope.
Constructor Details
#initialize(target_key, raw_scope = nil) ⇒ Scope
If raw_scope is empty, a default scope, scoped to all targets, is created, and can be modified as needed.
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 |
# File 'lib/jss/api_object/scopable/scope.rb', line 291 def initialize(target_key, raw_scope = nil) raw_scope ||= DEFAULT_SCOPE.dup unless TARGETS_AND_GROUPS.key?(target_key) raise JSS::InvalidDataError, "The target class of a Scope must be one of the symbols :#{TARGETS_AND_GROUPS.keys.join(', :')}" end @target_key = target_key @target_class = SCOPING_CLASSES[@target_key] @group_key = TARGETS_AND_GROUPS[@target_key] @group_class = SCOPING_CLASSES[@group_key] @inclusion_keys = [@target_key, @group_key] + INCLUSIONS @exclusion_keys = [@target_key, @group_key] + EXCLUSIONS @all_key = "all_#{target_key}".to_sym @all_targets = raw_scope[@all_key] # Everything gets mapped from an Array of Hashes to # an Array of ids @inclusions = {} @inclusion_keys.each do |k| raw_scope[k] ||= [] @inclusions[k] = raw_scope[k].compact.map { |n| n[:id].to_i } end # @inclusion_keys.each do |k| # 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. @limitations = {} if raw_scope[:limitations] LIMITATIONS.each do |k| # :jamf_ldap_users comes from :users in the API data if k == :jamf_ldap_users api_data = raw_scope[:limitations][:users] api_data ||= [] @limitations[k] = api_data.compact.map { |n| n[:name].to_s } # :ldap_user_groups comes from :user_groups in the API data elsif k == :ldap_user_groups api_data = raw_scope[:limitations][:user_groups] api_data ||= [] @limitations[k] = api_data.compact.map { |n| n[:name].to_s } # others handled normally. else api_data = raw_scope[:limitations][k] api_data ||= [] @limitations[k] = api_data.compact.map { |n| n[:id].to_i } end end # LIMITATIONS.each do |k| end # if raw_scope[:limitations] # 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. @exclusions = {} if raw_scope[:exclusions] @exclusion_keys.each do |k| # :jamf_ldap_users comes from :users in the API data if k == :jamf_ldap_users api_data = raw_scope[:exclusions][:users] api_data ||= [] @exclusions[k] = api_data.compact.map { |n| n[:name].to_s } # :ldap_user_groups comes from :user_groups in the API data elsif k == :ldap_user_groups api_data = raw_scope[:exclusions][:user_groups] api_data ||= [] @exclusions[k] = api_data.compact.map { |n| n[:name].to_s } # others handled normally. else api_data = raw_scope[:exclusions][k] api_data ||= [] @exclusions[k] = api_data.compact.map { |n| n[:id].to_i } end # if ...elsif... else end # @exclusion_keys.each end # if raw_scope[:exclusions] @container = nil end |
Instance Attribute Details
#all_targets ⇒ Boolean (readonly) Also known as: all_targets?
Does this scope cover all targets?
If this is true, the @inclusions Hash is ignored, and all targets in the JSS form the base scope.
253 254 255 |
# File 'lib/jss/api_object/scopable/scope.rb', line 253 def all_targets @all_targets end |
#container ⇒ JSS::APIObject subclass
A reference to the object that contains this Scope
For telling it when a change is made and an update needed
223 224 225 |
# File 'lib/jss/api_object/scopable/scope.rb', line 223 def container @container end |
#exclusions ⇒ Hash<Array> (readonly)
The items in these arrays are the exclusions applied to targets in the @inclusions .
The arrays of names are:
-
:targets
-
:target_groups
-
:departments
-
:buildings
-
:network_segments
-
:users
-
:user_groups
279 280 281 |
# File 'lib/jss/api_object/scopable/scope.rb', line 279 def exclusions @exclusions end |
#inclusions ⇒ Hash<Array> (readonly)
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:
-
:targets
-
:target_groups
-
:departments
-
:buildings
and the values are Arrays of names of those things.
244 245 246 |
# File 'lib/jss/api_object/scopable/scope.rb', line 244 def inclusions @inclusions end |
#limitations ⇒ Hash<Array> (readonly)
The items in these arrays are the limitations applied to targets in the @inclusions .
The arrays of names are:
-
:network_segments
-
:users
-
:user_groups
264 265 266 |
# File 'lib/jss/api_object/scopable/scope.rb', line 264 def limitations @limitations end |
#target_class ⇒ Object (readonly)
what type of target is this scope for? Computers or Mobiledevices?
230 231 232 |
# File 'lib/jss/api_object/scopable/scope.rb', line 230 def target_class @target_class end |
#unable_to_verify_ldap_entries ⇒ Boolean
Returns should we expect a potential 409 Conflict if we can’t connect to LDAP servers for verification?.
227 228 229 |
# File 'lib/jss/api_object/scopable/scope.rb', line 227 def unable_to_verify_ldap_entries @unable_to_verify_ldap_entries 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.
632 633 634 635 636 637 638 639 640 641 642 643 |
# File 'lib/jss/api_object/scopable/scope.rb', line 632 def add_exclusion(key, item) key = pluralize_key(key) item_id = validate_item(:exclusion, key, item) return if @exclusions[key]&.include?(item_id) raise JSS::AlreadyExistsError, "Can't exclude #{key} scope to '#{item}' because it's already explicitly included." if @inclusions[key]&.include?(item) raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{item}' because it's already an explicit limitation." if @limitations[key]&.include?(item) @exclusions[key] << item_id @container&.should_update end |
#add_limitation(key, item) ⇒ void
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.
545 546 547 548 549 550 551 552 553 554 555 556 |
# File 'lib/jss/api_object/scopable/scope.rb', line 545 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 JSS::AlreadyExistsError, "Can't set #{key} limitation for '#{name}' because it's already an explicit exclusion." end @limitations[key] << item_id @container&.should_update end |
#add_target(key, item) ⇒ 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.
458 459 460 461 462 463 464 465 466 467 468 |
# File 'lib/jss/api_object/scopable/scope.rb', line 458 def add_target(key, item) key = pluralize_key(key) item_id = validate_item(:target, key, item) return if @inclusions[key]&.include?(item_id) raise JSS::AlreadyExistsError, "Can't set #{key} target to '#{item}' because it's already an explicit exclusion." if @exclusions[key]&.include?(item_id) @inclusions[key] << item_id @all_targets = false @container&.should_update end |
#include_all(clear = false) ⇒ void
This method returns an undefined value.
Set the scope’s inclusions to all targets.
By default, the limitations and exclusions remain. If a non-false parameter is provided, they will be removed also.
384 385 386 387 388 389 390 391 392 393 394 395 396 |
# File 'lib/jss/api_object/scopable/scope.rb', line 384 def include_all(clear = false) @inclusions = {} @inclusion_keys.each { |k| @inclusions[k] = [] } @all_targets = true if clear @limitations = {} LIMITATIONS.each { |k| @limitations[k] = [] } @exclusions = {} @exclusion_keys.each { |k| @exclusions[k] = [] } end @container&.should_update end |
#pretty_print_instance_variables ⇒ Array
Remove the init_data and api object from the instance_variables used to create pretty-print (pp) output.
734 735 736 737 738 |
# File 'lib/jss/api_object/scopable/scope.rb', line 734 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
656 657 658 659 660 661 662 663 |
# File 'lib/jss/api_object/scopable/scope.rb', line 656 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 @container&.should_update end |
#remove_limitation(key, item) ⇒ void
handle ldap user/group lookups
This method returns an undefined value.
Remove a single item for limiting this scope.
571 572 573 574 575 576 577 578 579 |
# File 'lib/jss/api_object/scopable/scope.rb', line 571 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 @container&.should_update 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.
482 483 484 485 486 487 488 489 490 |
# File 'lib/jss/api_object/scopable/scope.rb', line 482 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 @inclusions[key]&.include?(item_id) @inclusions[key].delete item_id @container&.should_update end |
#scope_xml ⇒ REXML::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.
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 |
# File 'lib/jss/api_object/scopable/scope.rb', line 671 def scope_xml scope = REXML::Element.new 'scope' scope.add_element(@all_key.to_s).text = @all_targets @inclusions.each do |klass, list| list.compact! list.delete 0 list_as_hashes = list.map { |i| { id: i } } scope << SCOPING_CLASSES[klass].xml_list(list_as_hashes, :id) end limitations = scope.add_element('limitations') @limitations.each do |klass, list| list.compact! list.delete 0 if klass == :jamf_ldap_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 == :ldap_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') @exclusions.each do |klass, list| list.compact! list.delete 0 if klass == :jamf_ldap_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 == :ldap_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 } } exclusions << SCOPING_CLASSES[klass].xml_list(list_as_hashes, :id) end end scope end |
#set_exclusion(key, list) ⇒ void
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.
595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 |
# File 'lib/jss/api_object/scopable/scope.rb', line 595 def set_exclusion(key, list) key = pluralize_key(key) raise JSS::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 *@inclusion_keys raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{ident}' because it's already explicitly included." if @inclusions[key]&.include?(item_id) when *LIMITATIONS if @limitations[key]&.include?(item_id) raise JSS::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 @container&.should_update end |
#set_limitation(key, list) ⇒ void Also known as: set_limitations
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.
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 |
# File 'lib/jss/api_object/scopable/scope.rb', line 509 def set_limitation(key, list) key = pluralize_key(key) raise JSS::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 JSS::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 @container&.should_update end |
#set_targets(key, list) ⇒ 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…
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 |
# File 'lib/jss/api_object/scopable/scope.rb', line 415 def set_targets(key, list) key = pluralize_key(key) raise JSS::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 JSS::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 == @inclusions[key].sort @inclusions[key] = list @all_targets = false @container&.should_update end |