Class: JSS::Scopable::Scope

Inherits:
Object show all
Defined in:
lib/jss/api_object/scopable/scope.rb,
lib/jss.rb

Overview

TODO:

Implement simple LDAP queries using the defined LDAPServers to confirm the existance of users or groups used in limitations and exclusions. As things are now if you add invalid user or group names, you’ll get a 409 conflict error when you try to save your changes to the JSS.

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 parts of the scope’s inclusions, limitations, and exclusions.

See Also:

Constant Summary collapse

SCOPING_CLASSES =

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

{
  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,
  users: JSS::User,
  user: JSS::User,
  user_groups: JSS::UserGroup,
  user_group: JSS::UserGroup
}.freeze
LDAP_USER_KEYS =

Some things get checked in LDAP as well as the JSS

%i[user users].freeze
LDAP_GROUP_KEYS =
%i[user_groups user_group].freeze
CHECK_LDAP_KEYS =
LDAP_USER_KEYS + LDAP_GROUP_KEYS
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
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

%i[network_segments users 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

Instance Method Summary collapse

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.

Parameters:

  • target_key (Symbol)

    the kind of thing we’re scopeing, one of TARGETS_AND_GROUPS

  • raw_scope (Hash) (defaults to: nil)

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

Raises:



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/jss/api_object/scopable/scope.rb', line 184

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

  @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 names (or an empty array)
  # since names are all that really matter when submitting the scope.
  @inclusions = {}
  @inclusion_keys.each { |k| @inclusions[k] = raw_scope[k] ? raw_scope[k].map { |n| n[:name] } : [] }

  @limitations = {}
  if raw_scope[:limitations]
    LIMITATIONS.each { |k| @limitations[k] = raw_scope[:limitations][k] ? raw_scope[:limitations][k].map { |n| n[:name] } : [] }
  end

  @exclusions = {}
  if raw_scope[:exclusions]
    @exclusion_keys.each { |k| @exclusions[k] = raw_scope[:exclusions][k] ? raw_scope[:exclusions][k].map { |n| n[:name] } : [] }
  end

  @container = nil
end

Instance Attribute Details

#all_targetsBoolean (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.

Returns:

  • (Boolean)


146
147
148
# File 'lib/jss/api_object/scopable/scope.rb', line 146

def all_targets
  @all_targets
end

#containerJSS::APIObject subclass

A reference to the object that contains this Scope

For telling it when a change is made and an update needed

Returns:



116
117
118
# File 'lib/jss/api_object/scopable/scope.rb', line 116

def container
  @container
end

#exclusionsHash<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

Returns:



172
173
174
# File 'lib/jss/api_object/scopable/scope.rb', line 172

def exclusions
  @exclusions
end

#inclusionsHash<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.

Returns:



137
138
139
# File 'lib/jss/api_object/scopable/scope.rb', line 137

def inclusions
  @inclusions
end

#limitationsHash<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

Returns:



157
158
159
# File 'lib/jss/api_object/scopable/scope.rb', line 157

def limitations
  @limitations
end

#target_classObject (readonly)

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



123
124
125
# File 'lib/jss/api_object/scopable/scope.rb', line 123

def target_class
  @target_class
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?



120
121
122
# File 'lib/jss/api_object/scopable/scope.rb', line 120

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.

Examples:

add_exclusion(:network_segments, "foo")

Parameters:

  • key (Symbol)

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

  • item (String)

    the name of the item being added

Raises:



475
476
477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/jss/api_object/scopable/scope.rb', line 475

def add_exclusion(key, item)
  raise JSS::InvalidDataError, "Exclusion key must be one of :#{@exclusion_keys.join(', :')}" unless @exclusion_keys.include? key
  raise JSS::InvalidDataError, "Item must be a #{key} name." unless item.is_a? String

  return nil if @exclusions[key] && @exclusions[key].include?(item)

  # check the name
  raise JSS::NoSuchItemError, "No existing #{key} with name '#{item}'" unless check_name key, item
  raise JSS::AlreadyExistsError, "Can't exclude #{key} scope to '#{item}' because it's already explicitly included." if @inclusions[key] && @inclusions[key].include?(item)
  raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{item}' because it's already an explicit limitation." if @limitations[key] && @limitations[key].include?(item)

  @exclusions[key] << item
  @container.should_update if @container
end

#add_inclusion(key, item) ⇒ void

This method returns an undefined value.

Add a single item for this inclusion 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_inclusion(:computer, "mantis")

Parameters:

  • key (Symbol)

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

  • item (String)

    the name of the item being added

Raises:



293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/jss/api_object/scopable/scope.rb', line 293

def add_inclusion(key, item)
  raise JSS::InvalidDataError, "Inclusion key must be one of :#{@inclusion_keys.join(', :')}" unless @inclusion_keys.include? key
  raise JSS::InvalidDataError, "Item must be a #{key} name." unless item.is_a? String

  return nil if @inclusions[key] && @inclusions[key].include?(item)

  # check the name
  raise JSS::NoSuchItemError, "No existing #{key} with name '#{item}'" unless check_name key, item
  raise JSS::AlreadyExistsError, "Can't set #{key} scope to '#{item}' because it's already an explicit exclusion." if @exclusions[key] && @exclusions[key].include?(item)

  @inclusions[key] << item
  @all_targets = false
  @container.should_update if @container
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)

    the name of the item being added

Raises:



385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/jss/api_object/scopable/scope.rb', line 385

def add_limitation(key, item)
  raise JSS::InvalidDataError, "Limitation key must be one of :#{LIMITATIONS.join(', :')}" unless LIMITATIONS.include? key
  raise JSS::InvalidDataError, "Item must be a #{key} name." unless item.is_a? String

  return nil if @limitations[key] && @limitations[key].include?(item)

  # check the name
  raise JSS::NoSuchItemError, "No existing #{key} with name '#{item}'" unless check_name key, item
  raise JSS::AlreadyExistsError, "Can't set #{key} limitation for '#{name}' because it's already an explicit exclusion." if @exclusions[key] && @exclusions[key].include?(item)

  @limitations[key] << item
  @container.should_update if @container
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.

Parameters:

  • clear (Boolean) (defaults to: false)

    Should the limitations and exclusions be removed also?



226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/jss/api_object/scopable/scope.rb', line 226

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 if @container
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)

    the name of the item being removed

Raises:



501
502
503
504
505
506
507
508
509
# File 'lib/jss/api_object/scopable/scope.rb', line 501

def remove_exclusion(key, item)
  raise JSS::InvalidDataError, "Exclusion key must be one of :#{@exclusion_keys.join(', :')}" unless @exclusion_keys.include? key
  raise JSS::InvalidDataError, "Item must be a #{key} name." unless item.is_a? String

  return nil unless @exclusions[key] && @exclusions[key].include?(item)

  @exclusions[key] -= [item]
  @container.should_update if @container
end

#remove_inclusion(key, item) ⇒ void

This method returns an undefined value.

Remove a single item for this scope.

Examples:

remove_inclusion(:computer, "mantis")

Parameters:

  • key (Symbol)

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

  • item (String)

    the name of the item being removed

Raises:



319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/jss/api_object/scopable/scope.rb', line 319

def remove_inclusion(key, item)
  raise JSS::InvalidDataError, "Inclusion key must be one of :#{@inclusion_keys.join(', :')}" unless @inclusion_keys.include? key
  raise JSS::InvalidDataError, "Item must be a #{key} name." unless item.is_a? String

  return nil unless @inclusions[key] && @inclusions[key].include?(item)

  @inclusions[key] -= [item]

  # if ALL the @inclusion keys are empty, then set all targets to true.
  @all_targets = @inclusions.values.reject { |a| a.nil? || a.empty? }.empty?

  @container.should_update if @container
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)

    the name of the item being removed

Raises:



412
413
414
415
416
417
418
419
420
# File 'lib/jss/api_object/scopable/scope.rb', line 412

def remove_limitation(key, item)
  raise JSS::InvalidDataError, "Limitation key must be one of :#{LIMITATIONS.join(', :')}" unless LIMITATIONS.include? key
  raise JSS::InvalidDataError, "Item must be a #{key} name." unless item.is_a? String

  return nil unless @limitations[key] && @limitations[key].include?(item)

  @limitations[key] -= [item]
  @container.should_update if @container
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)


517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
# File 'lib/jss/api_object/scopable/scope.rb', line 517

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

  @inclusions.each do |klass, list|
    list_as_hash = list.map { |i| { name: i } }
    scope << SCOPING_CLASSES[klass].xml_list(list_as_hash, :name)
  end

  limitations = scope.add_element('limitations')
  @limitations.each do |klass, list|
    list_as_hash = list.map { |i| { name: i } }
    limitations << SCOPING_CLASSES[klass].xml_list(list_as_hash, :name)
  end

  exclusions = scope.add_element('exclusions')
  @exclusions.each do |klass, list|
    list_as_hash = list.map { |i| { name: i } }
    exclusions << SCOPING_CLASSES[klass].xml_list(list_as_hash, :name)
  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.

Examples:

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

Parameters:

  • key (Symbol)

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

  • list (Array)

    the names of the items being added

Raises:



436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/jss/api_object/scopable/scope.rb', line 436

def set_exclusion(key, list)
  raise JSS::InvalidDataError, "Exclusion key must be one of :#{@exclusion_keys.join(', :')}" unless @exclusion_keys.include? key
  raise JSS::InvalidDataError, "List must be an Array of #{key} names, it may be empty." unless list.is_a? Array
  return nil if list.sort == @exclusions[key].sort

  if list.empty?
    @exclusions[key] = []
    @container.should_update if @container
    return list
  end

  # check the names
  list.each do |name|
    raise JSS::NoSuchItemError, "No existing #{key} with name '#{name}'" unless check_name key, name
    case key
    when *@inclusion_keys
      raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{name}' because it's already explicitly included." if @inclusions[key] && @inclusions[key].include?(name)
    when *LIMITATIONS
      raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{name}' because it's already an explicit limitation." if @limitations[key] && @limitations[key].include?(name)
    end
  end # each

  @exclusions[key] = list
  @container.should_update if @container
end

#set_inclusion(key, list) ⇒ void

This method returns an undefined value.

Replace a list of item names for inclusion 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.

Examples:

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

Parameters:

  • key (Symbol)

    the key from #SCOPING_CLASSES for the kind of items being included, :computer, :building, etc…

  • list (Array)

    the names of the items being added

Raises:



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/jss/api_object/scopable/scope.rb', line 254

def set_inclusion(key, list)
  raise JSS::InvalidDataError, "Inclusion key must be one of :#{@inclusion_keys.join(', :')}" unless @inclusion_keys.include? key
  raise JSS::InvalidDataError, "List must be an Array of #{key} names, it may be empty." unless list.is_a? Array

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

  # emptying the list?
  if list.empty?
    @inclusion[key] = list
    # if ALL the @inclusion keys are empty, then set all targets to true.
    @all_targets =  @inclusions.values.reject { |a| a.nil? || a.empty? }.empty?
    @container.should_update if @container
    return list
  end

  # check the names
  list.each do |name|
    raise JSS::NoSuchItemError, "No existing #{key} with name '#{name}'" unless check_name key, name
    raise JSS::AlreadyExistsError, "Can't set #{key} scope to '#{name}' because it's already an explicit exclusion." if @exclusions[key] && @exclusions[key].include?(name)
  end # each

  @inclusions[key] = list
  @all_targets = false
  @container.should_update if @container
end

#set_limitation(key, list) ⇒ void

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','bar'])

Parameters:

  • key (Symbol)

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

  • list (Array)

    the names of the items being set as limitations

Raises:



349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/jss/api_object/scopable/scope.rb', line 349

def set_limitation(key, list)
  raise JSS::InvalidDataError, "Limitation key must be one of :#{LIMITATIONS.join(', :')}" unless LIMITATIONS.include? key
  raise JSS::InvalidDataError, "List must be an Array of #{key} names, it may be empty." unless list.is_a? Array
  return nil if list.sort == @limitations[key].sort

  if list.empty?
    @limitations[key] = []
    @container.should_update if @container
    return list
  end

  # check the names
  list.each do |name|
    raise JSS::NoSuchItemError, "No existing #{key} with name '#{name}'" unless check_name key, name
    raise JSS::AlreadyExistsError, "Can't set #{key} limitation for '#{name}' because it's already an explicit exclusion." if @exclusions[key] && @exclusions[key].include?(name)
  end # each

  @limitations[key] = list
  @container.should_update if @container
end