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:



183
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
# File 'lib/jss/api_object/scopable/scope.rb', line 183

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)


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

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:



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

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:



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

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:



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

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:



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

def limitations
  @limitations
end

#target_classObject (readonly)

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



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

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?



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

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:



480
481
482
483
484
485
486
487
488
489
490
491
492
493
# File 'lib/jss/api_object/scopable/scope.rb', line 480

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_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:



390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/jss/api_object/scopable/scope.rb', line 390

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

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

This method returns an undefined value.

Add a single item a 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(: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:



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

def add_target(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

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



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

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:



506
507
508
509
510
511
512
513
514
# File 'lib/jss/api_object/scopable/scope.rb', line 506

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_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:



417
418
419
420
421
422
423
424
425
# File 'lib/jss/api_object/scopable/scope.rb', line 417

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

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

    the name of the item being removed

Raises:



323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/jss/api_object/scopable/scope.rb', line 323

def remove_target(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

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


522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
# File 'lib/jss/api_object/scopable/scope.rb', line 522

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:



441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
# File 'lib/jss/api_object/scopable/scope.rb', line 441

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_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:



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 354

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

#set_targets(key, list) ⇒ void Also known as: set_inclusion

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…

Examples:

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

Parameters:

  • key (Symbol)

    the key from #SCOPING_CLASSES for the kind of items

  • list (Array)

    the names of the items being added

Raises:



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

def set_targets(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