ruby-jss 1.3.3 → 1.6.0b1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ruby-jss might be problematic. Click here for more details.

Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +142 -0
  3. data/THANKS.md +3 -2
  4. data/lib/jamf.rb +18 -17
  5. data/lib/jamf/api/base_classes/collection_resource.rb +613 -0
  6. data/lib/jamf/api/{abstract_classes → base_classes}/json_object.rb +109 -101
  7. data/lib/jamf/api/{abstract_classes → base_classes}/prestage.rb +55 -30
  8. data/lib/jamf/api/{abstract_classes → base_classes}/resource.rb +10 -6
  9. data/lib/jamf/api/{abstract_classes → base_classes}/singleton_resource.rb +4 -3
  10. data/lib/jamf/api/connection.rb +13 -9
  11. data/lib/jamf/api/connection/api_error.rb +8 -8
  12. data/lib/jamf/api/connection/token.rb +36 -15
  13. data/lib/jamf/api/json_objects/device_enrollment_device.rb +14 -7
  14. data/lib/jamf/api/json_objects/{location.rb → device_enrollment_device_sync_state.rb} +27 -41
  15. data/lib/jamf/api/json_objects/device_enrollment_sync_status.rb +1 -1
  16. data/lib/jamf/api/json_objects/{attachment.rb → locale.rb} +14 -23
  17. data/lib/jamf/api/json_objects/md_prestage_name.rb +1 -1
  18. data/lib/jamf/api/json_objects/md_prestage_names.rb +2 -2
  19. data/lib/jamf/api/json_objects/md_prestage_skip_setup_items.rb +50 -1
  20. data/lib/jamf/api/json_objects/prestage_assignment.rb +2 -2
  21. data/lib/jamf/api/json_objects/prestage_location.rb +3 -3
  22. data/lib/jamf/api/json_objects/prestage_purchasing_data.rb +7 -7
  23. data/lib/jamf/api/json_objects/prestage_scope.rb +1 -1
  24. data/lib/jamf/api/{resources/collection_resources → json_objects}/time_zone.rb +9 -23
  25. data/lib/jamf/api/mixins/{abstract.rb → base_class.rb} +34 -16
  26. data/lib/jamf/api/mixins/bulk_deletable.rb +27 -6
  27. data/lib/jamf/api/mixins/change_log.rb +201 -51
  28. data/lib/jamf/api/{resources/collection_resources/computer.rb → mixins/filterable.rb} +19 -17
  29. data/lib/jamf/api/mixins/pageable.rb +208 -0
  30. data/lib/jamf/api/{json_objects/installed_application.rb → mixins/sortable.rb} +33 -33
  31. data/lib/jamf/api/resources/collection_resources/building.rb +16 -9
  32. data/lib/jamf/api/resources/collection_resources/category.rb +5 -4
  33. data/lib/jamf/api/resources/collection_resources/computer_prestage.rb +12 -5
  34. data/lib/jamf/api/resources/collection_resources/department.rb +1 -3
  35. data/lib/jamf/api/resources/collection_resources/device_enrollment.rb +13 -13
  36. data/lib/jamf/api/resources/collection_resources/inventory_preload_record.rb +11 -3
  37. data/lib/jamf/api/resources/collection_resources/mobile_device_prestage.rb +25 -23
  38. data/lib/jamf/api/resources/collection_resources/script.rb +61 -25
  39. data/lib/jamf/api/resources/singleton_resources/app_store_country_codes.rb +15 -5
  40. data/lib/jamf/api/resources/singleton_resources/locales.rb +155 -0
  41. data/lib/jamf/api/resources/singleton_resources/time_zones.rb +213 -0
  42. data/lib/jamf/client.rb +3 -3
  43. data/lib/jamf/client/management_action.rb +2 -3
  44. data/lib/jamf/composer.rb +2 -2
  45. data/lib/jamf/utility.rb +35 -7
  46. data/lib/jamf/validate.rb +63 -24
  47. data/lib/jamf/version.rb +1 -1
  48. data/lib/jss.rb +2 -2
  49. data/lib/jss/api_connection.rb +114 -406
  50. data/lib/jss/api_object.rb +10 -20
  51. data/lib/jss/api_object/advanced_search.rb +27 -26
  52. data/lib/jss/api_object/app_store_country_codes.rb +298 -0
  53. data/lib/jss/api_object/categorizable.rb +1 -1
  54. data/lib/jss/api_object/computer.rb +13 -0
  55. data/lib/jss/api_object/configuration_profile.rb +61 -5
  56. data/lib/jss/api_object/directory_binding_type.rb +66 -60
  57. data/lib/jss/api_object/directory_binding_type/active_directory.rb +71 -34
  58. data/lib/jss/api_object/directory_binding_type/admitmac.rb +536 -467
  59. data/lib/jss/api_object/directory_binding_type/centrify.rb +21 -7
  60. data/lib/jss/api_object/directory_binding_type/open_directory.rb +4 -4
  61. data/lib/jss/api_object/distribution_point.rb +2 -2
  62. data/lib/jss/api_object/dock_item.rb +102 -96
  63. data/lib/jss/api_object/ebook.rb +1 -2
  64. data/lib/jss/api_object/extendable.rb +1 -1
  65. data/lib/jss/api_object/extension_attribute.rb +4 -3
  66. data/lib/jss/api_object/group.rb +33 -2
  67. data/lib/jss/api_object/mac_application.rb +107 -8
  68. data/lib/jss/api_object/network_segment.rb +45 -13
  69. data/lib/jss/api_object/patch_source.rb +10 -9
  70. data/lib/jss/api_object/policy.rb +267 -28
  71. data/lib/jss/api_object/printer.rb +10 -4
  72. data/lib/jss/api_object/scopable.rb +10 -15
  73. data/lib/jss/api_object/scopable/scope.rb +389 -73
  74. data/lib/jss/api_object/script.rb +242 -352
  75. data/lib/jss/api_object/self_servable.rb +17 -9
  76. data/lib/jss/api_object/uploadable.rb +1 -1
  77. data/lib/jss/api_object/user.rb +43 -2
  78. data/lib/jss/api_object/vpp_account.rb +209 -0
  79. data/lib/jss/api_object/vppable.rb +169 -13
  80. data/lib/jss/client/management_action.rb +1 -2
  81. data/lib/jss/composer.rb +2 -2
  82. data/lib/jss/exceptions.rb +3 -0
  83. data/lib/jss/server.rb +15 -0
  84. data/lib/jss/utility.rb +213 -45
  85. data/lib/jss/validate.rb +53 -10
  86. data/lib/jss/version.rb +1 -1
  87. metadata +50 -66
  88. data/lib/jamf/api/abstract_classes/advanced_search.rb +0 -86
  89. data/lib/jamf/api/abstract_classes/collection_resource.rb +0 -433
  90. data/lib/jamf/api/abstract_classes/generic_reference.rb +0 -145
  91. data/lib/jamf/api/abstract_classes/prestage_skip_setup_items.rb +0 -126
  92. data/lib/jamf/api/json_objects/account_prefs.rb +0 -79
  93. data/lib/jamf/api/json_objects/android_details.rb +0 -139
  94. data/lib/jamf/api/json_objects/appletv_details.rb +0 -110
  95. data/lib/jamf/api/json_objects/cellular_network.rb +0 -151
  96. data/lib/jamf/api/json_objects/computer_prestage_skip_setup_items.rb +0 -67
  97. data/lib/jamf/api/json_objects/criterion.rb +0 -152
  98. data/lib/jamf/api/json_objects/extension_attribute_value.rb +0 -128
  99. data/lib/jamf/api/json_objects/installed_certificate.rb +0 -53
  100. data/lib/jamf/api/json_objects/installed_configuration_profile.rb +0 -67
  101. data/lib/jamf/api/json_objects/installed_ebook.rb +0 -58
  102. data/lib/jamf/api/json_objects/installed_provisioning_profile.rb +0 -59
  103. data/lib/jamf/api/json_objects/ios_details.rb +0 -244
  104. data/lib/jamf/api/json_objects/mobile_device_details.rb +0 -219
  105. data/lib/jamf/api/json_objects/mobile_device_security.rb +0 -101
  106. data/lib/jamf/api/json_objects/purchasing_data.rb +0 -125
  107. data/lib/jamf/api/mixins/locatable.rb +0 -124
  108. data/lib/jamf/api/mixins/referable.rb +0 -92
  109. data/lib/jamf/api/resources/collection_resources/account.rb +0 -163
  110. data/lib/jamf/api/resources/collection_resources/advanced_mobile_device_search.rb +0 -52
  111. data/lib/jamf/api/resources/collection_resources/advanced_user_search.rb +0 -52
  112. data/lib/jamf/api/resources/collection_resources/extension_attribute.rb +0 -45
  113. data/lib/jamf/api/resources/collection_resources/mobile_device.rb +0 -315
  114. data/lib/jamf/api/resources/collection_resources/site.rb +0 -77
  115. data/lib/jamf/api/resources/singleton_resources/authorization.rb +0 -88
  116. data/lib/jamf/api/resources/singleton_resources/client_checkin_settings.rb +0 -139
  117. data/lib/jamf/api/resources/singleton_resources/reenrollment_settings.rb +0 -95
@@ -51,12 +51,12 @@ module JSS
51
51
  # - :security
52
52
  #
53
53
  # Additionally, items that apper in macOS Slf Svc have these keys:
54
- # - :self_service_display_name
54
+ # - :self_service_display_name (but not JSS::MacApplication)
55
55
  # - :install_button_text
56
- # - :reinstall_button_text
56
+ # - :reinstall_button_text (but not JSS::MacApplication)
57
57
  # - :force_users_to_view_description
58
58
  # - :notification
59
- # - :notification_location # PENDING API FIX
59
+ # - :notification_location # PENDING API FIX, and also, not JSS::MacApplication
60
60
  # - :notification_subject
61
61
  # - :notification_message
62
62
  #
@@ -178,15 +178,14 @@ module JSS
178
178
  notification_reminders: true
179
179
  },
180
180
  JSS::MacApplication => {
181
- # in_self_service_data_path was finally implemnted in JamfPro 10.9
182
- # Jamf Product Issue [PI-003773]
183
- in_self_service_data_path: [:general, :deployment_type],
181
+ in_self_service_data_path: %i[general deployment_type],
184
182
  in_self_service: MAKE_AVAILABLE,
185
183
  not_in_self_service: AUTO_INSTALL_OR_PROMPT,
186
184
  targets: [:macos],
187
185
  payload: :app,
188
186
  can_display_in_categories: true,
189
187
  can_feature_in_categories: true,
188
+ notifications_supported: :ssvc_and_nctr,
190
189
  url_entity: 'app'
191
190
  # OTHER BUG: no notification options seem to be changable via the API
192
191
  },
@@ -329,6 +328,7 @@ module JSS
329
328
  #
330
329
  def self_service_view_url
331
330
  return nil unless @self_service_data_config[:url_entity]
331
+
332
332
  "#{USER_URL_BASE}#{@self_service_data_config[:url_entity]}&id=#{id}&action=#{USER_URL_VIEW_ACTION}"
333
333
  end
334
334
 
@@ -336,6 +336,7 @@ module JSS
336
336
  #
337
337
  def self_service_execute_url
338
338
  return nil unless @self_service_data_config[:url_entity]
339
+
339
340
  "#{USER_URL_BASE}#{@self_service_data_config[:url_entity]}&id=#{id}&action=#{USER_URL_EXEC_ACTION}"
340
341
  end
341
342
 
@@ -349,6 +350,7 @@ module JSS
349
350
  def self_service_description=(new_val)
350
351
  new_val = new_val.strip
351
352
  return if @self_service_description == new_val
353
+
352
354
  @self_service_description = new_val
353
355
  @need_to_update = true
354
356
  end
@@ -820,7 +822,7 @@ module JSS
820
822
  # ssvc subset...
821
823
  add_in_self_service_xml doc_root
822
824
 
823
- subset_key = @self_service_data_config[:self_service_subset] ? @self_service_data_config[:self_service_subset] : :self_service
825
+ subset_key = @self_service_data_config[:self_service_subset] || :self_service
824
826
 
825
827
  ssvc = doc_root.add_element subset_key.to_s
826
828
 
@@ -857,6 +859,7 @@ module JSS
857
859
  # add the xml specific to profiles
858
860
  def add_self_service_profile_xml(ssvc, doc_root)
859
861
  return unless self_service_payload == :profile
862
+
860
863
  if self_service_targets.include? :ios
861
864
  sec = ssvc.add_element('security')
862
865
  sec.add_element('removal_disallowed').text = PROFILE_REMOVAL_BY_USER[@self_service_user_removable]
@@ -871,6 +874,7 @@ module JSS
871
874
  def add_self_service_category_xml(ssvc)
872
875
  cats = ssvc.add_element('self_service_categories')
873
876
  return if self_service_categories.empty?
877
+
874
878
  self_service_categories.each do |cat|
875
879
  catelem = cats.add_element('category')
876
880
  catelem.add_element('name').text = cat[:name]
@@ -882,10 +886,14 @@ module JSS
882
886
  # set macOS settings in ssvc xml
883
887
  def add_self_service_macos_xml(ssvc)
884
888
  return unless self_service_targets.include? :macos
885
- ssvc.add_element('self_service_display_name').text = self_service_display_name if self_service_display_name
889
+
886
890
  ssvc.add_element('install_button_text').text = self_service_install_button_text if self_service_install_button_text
887
- ssvc.add_element('reinstall_button_text').text = self_service_reinstall_button_text if self_service_reinstall_button_text
888
891
  ssvc.add_element('force_users_to_view_description').text = self_service_force_users_to_view_description.to_s
892
+
893
+ return if self.class == JSS::MacApplication
894
+
895
+ ssvc.add_element('self_service_display_name').text = self_service_display_name if self_service_display_name
896
+ ssvc.add_element('reinstall_button_text').text = self_service_reinstall_button_text if self_service_reinstall_button_text
889
897
  end
890
898
 
891
899
 
@@ -103,7 +103,7 @@ module JSS
103
103
  # Methods
104
104
  #####################################
105
105
 
106
- #
106
+
107
107
  # Upload a file to the JSS via the REST Resource of the
108
108
  # object to which this module is mixed in.
109
109
  #
@@ -144,7 +144,7 @@ module JSS
144
144
 
145
145
  ### @return [Array<Hash>]
146
146
  ###
147
- ### The vpp assignments associated with this user
147
+ ### The user-based vpp assignments associated with this user
148
148
  ###
149
149
  ### Each Hash has then :id and :name for one assignment
150
150
  ###
@@ -168,7 +168,7 @@ module JSS
168
168
  @phone_number = @init_data[:phone_number]
169
169
  @position = @init_data[:position]
170
170
  @ldap_server = JSS::APIObject.get_name @init_data[:ldap_server]
171
- @ldap_server_id = @init_data[:ldap_server][:id]
171
+ @ldap_server_id = @init_data[:ldap_server][:id] unless @init_data[:ldap_server].nil?
172
172
  @sites = @init_data[:sites] ? @init_data[:sites] : []
173
173
 
174
174
  if @init_data[:links]
@@ -249,6 +249,47 @@ module JSS
249
249
  @need_to_update = true
250
250
  end
251
251
 
252
+ # Workaround for the recurring Jamf Classic API Bug where
253
+ # JSON is missing data that should come in an array of hashes, but
254
+ # only comes as a hash with a single hash inside, with data for only
255
+ # the last item in the XML array.
256
+ #
257
+ # When needed, we fetch and parse the XML, which has the desired data.
258
+ # Use any truthy parameter to re-fetch the XML data, otherwise the
259
+ # data last fetched is used.
260
+ #
261
+ # In this case, the user group data is fetched as XML and returned as
262
+ # an Array of Hashes, one per group the user is a member of. Each hash
263
+ # containing three Symbol keys:
264
+ #
265
+ # id: Integer, the group id
266
+ # name: String, the group name
267
+ # is_smart: Boolean, is it a smart group or a static group?
268
+ #
269
+ # @param refresh[Boolean] Re-fetch the group data from the API
270
+ #
271
+ # @return [Array<Hash>] The groups the user is a member of.
272
+ #
273
+ def user_groups(refresh = false)
274
+ @grp_array = nil if refresh
275
+ return @grp_array if @grp_array
276
+
277
+ @grp_array = []
278
+ raw_xml = @api.get_rsrc "/users/id/#{@id}", :xml
279
+ xmlroot = REXML::Document.new(raw_xml).root
280
+ xml_grps = xmlroot.elements['user_groups']
281
+
282
+ xml_grps.each do |xml_grp|
283
+ next if xml_grp.name == 'size'
284
+
285
+ gid = xml_grp.elements['id'].text.to_i
286
+ gname = xml_grp.elements['name'].text
287
+ smart = xml_grp.elements['is_smart'].text == 'true'
288
+ @grp_array << { id: gid, name: gname, is_smart: smart }
289
+ end # groups.each
290
+
291
+ @grp_array
292
+ end # user_groups
252
293
 
253
294
  #####################################
254
295
  ### Private Instance Methods
@@ -0,0 +1,209 @@
1
+ ### Copyright 2020 Pixar
2
+
3
+ ###
4
+ ### Licensed under the Apache License, Version 2.0 (the "Apache License")
5
+ ### with the following modification; you may not use this file except in
6
+ ### compliance with the Apache License and the following modification to it:
7
+ ### Section 6. Trademarks. is deleted and replaced with:
8
+ ###
9
+ ### 6. Trademarks. This License does not grant permission to use the trade
10
+ ### names, trademarks, service marks, or product names of the Licensor
11
+ ### and its affiliates, except as required to comply with Section 4(c) of
12
+ ### the License and to reproduce the content of the NOTICE file.
13
+ ###
14
+ ### You may obtain a copy of the Apache License at
15
+ ###
16
+ ### http://www.apache.org/licenses/LICENSE-2.0
17
+ ###
18
+ ### Unless required by applicable law or agreed to in writing, software
19
+ ### distributed under the Apache License with the above modification is
20
+ ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21
+ ### KIND, either express or implied. See the Apache License for the specific
22
+ ### language governing permissions and limitations under the Apache License.
23
+ ###
24
+ ###
25
+
26
+ ###
27
+ module JSS
28
+
29
+ # A VPP account defined in the JSS
30
+ #
31
+ class VPPAccount < JSS::APIObject
32
+
33
+ # Mix-Ins
34
+ #####################################
35
+ include JSS::Updatable
36
+ include JSS::Sitable
37
+
38
+ # Class Constants
39
+ #####################################
40
+
41
+ ### The base for REST resources of this class
42
+ RSRC_BASE = 'vppaccounts'.freeze
43
+
44
+ ### the hash key used for the JSON list output of all objects in the JSS
45
+ RSRC_LIST_KEY = :vpp_accounts
46
+
47
+ ### The hash key used for the JSON object output.
48
+ ### It's also used in various error messages
49
+ RSRC_OBJECT_KEY = :vpp_account
50
+
51
+ SITE_SUBSET = :top
52
+
53
+ # Attributes
54
+ #####################################
55
+
56
+ # @return [String] The full name of the local contact person for the acct
57
+ attr_reader :contact
58
+
59
+ # @return [String] The service token for connecting to the account at Apple.
60
+ # Currently not visible, appears as '***************'
61
+ attr_reader :service_token
62
+
63
+ # @return [String] The name of the company associated with the Acct/Token
64
+ attr_reader :account_name
65
+
66
+ # @return [Time] The expiration date of the Acct/Token
67
+ attr_reader :expiration_date
68
+
69
+ # @return [String] The location associated with the Acct/Token
70
+ attr_reader :location_name
71
+ alias location location_name
72
+
73
+ # @return [String] The Country Code associated with the acct
74
+ attr_reader :country
75
+
76
+ # @return [String] The AppleID associated with the acct
77
+ attr_reader :apple_id
78
+
79
+ # @return [Boolean] Automatically populate purchased content from Apple
80
+ # School Manager or Apple Business Manager in Jamf Pro
81
+ attr_reader :populate_catalog_from_vpp_content
82
+
83
+ # @return [Boolean] Display a notification to users on their mobile devices
84
+ # when a volume purchased app in a user-based volume assignment is no
85
+ # longer assigned to them
86
+ attr_reader :notify_disassociation
87
+
88
+ # @return [Boolean] Automatically register users that have Managed Apple IDs
89
+ # so they do not receive an invitation and are not prompted to register
90
+ # with volume purchasing
91
+ attr_reader :auto_register_managed_users
92
+
93
+ # Constructor
94
+ #####################################
95
+
96
+ # See JSS::APIObject#initialize
97
+ #
98
+ def initialize(args = {})
99
+ super
100
+ @contact = @init_data[:contact]
101
+ @service_token = @init_data[:service_token]
102
+ @account_name = @init_data[:account_name]
103
+ @expiration_date = @init_data[:expiration_date].to_s.empty? ? nil : Time.parse(@init_data[:expiration_date])
104
+ @location_name = @init_data[:location_name]
105
+ @country = @init_data[:country]
106
+ @apple_id = @init_data[:apple_id]
107
+ @populate_catalog_from_vpp_content = @init_data[:populate_catalog_from_vpp_content]
108
+ @notify_disassociation = @init_data[:notify_disassociation]
109
+ @auto_register_managed_users = @init_data[:auto_register_managed_users]
110
+ end
111
+
112
+ # Public Instance Methods
113
+ #####################################
114
+
115
+ # @param new_val[String] the value
116
+ #
117
+ # @return [void]
118
+ #
119
+ def contact=(new_val = @contact)
120
+ return if new_val == @contact
121
+
122
+ @contact = JSS::Validate.non_empty_string new_val, 'Contact must be a non-empty String'
123
+ @need_to_update = true
124
+ end
125
+
126
+ # @param new_val[String] the value
127
+ #
128
+ # @return [void]
129
+ #
130
+ def country=(new_val = @country)
131
+ return if new_val == @country
132
+
133
+ @country = JSS::Validate.app_store_country_code new_val
134
+ @need_to_update = true
135
+ end
136
+
137
+ # @param new_val[String] the value
138
+ #
139
+ # @return [void]
140
+ #
141
+ def apple_id=(new_val = @apple_id)
142
+ return if new_val == @apple_id
143
+
144
+ @apple_id = JSS::Validate.email_address new_val
145
+ @need_to_update = true
146
+ end
147
+
148
+ # @param new_val[String] the value
149
+ #
150
+ # @return [void]
151
+ #
152
+ def populate_catalog_from_vpp_content=(new_val = @populate_catalog_from_vpp_content)
153
+ return if new_val == @populate_catalog_from_vpp_content
154
+
155
+ @populate_catalog_from_vpp_content = JSS::Validate.boolean new_val
156
+ @need_to_update = true
157
+ end
158
+
159
+ # @param new_val[String] the value
160
+ #
161
+ # @return [void]
162
+ #
163
+ def notify_disassociation=(new_val = @notify_disassociation)
164
+ return if new_val == @notify_disassociation
165
+
166
+ @notify_disassociation = JSS::Validate.boolean new_val
167
+ @need_to_update = true
168
+ end
169
+
170
+ # @param new_val[String] the value
171
+ #
172
+ # @return [void]
173
+ #
174
+ def auto_register_managed_users=(new_val = @auto_register_managed_users)
175
+ return if new_val == @auto_register_managed_users
176
+
177
+ @auto_register_managed_users = JSS::Validate.boolean new_val
178
+ @need_to_update = true
179
+ end
180
+
181
+ # Private Instance Methods
182
+ #####################################
183
+ private
184
+
185
+ # Return a String with the XML Resource
186
+ # for submitting creation or changes to the JSS via
187
+ # the API via the Creatable or Updatable modules
188
+ #
189
+ # Most classes will redefine this method.
190
+ #
191
+ def rest_xml
192
+ doc = REXML::Document.new APIConnection::XML_HEADER
193
+ tmpl = doc.add_element self.class::RSRC_OBJECT_KEY.to_s
194
+ tmpl.add_element('name').text = @name
195
+ tmpl.add_element('contact').text = @contact
196
+ tmpl.add_element('country').text = @country
197
+ tmpl.add_element('apple_id').text = @apple_id
198
+ tmpl.add_element('populate_catalog_from_vpp_content').text = @populate_catalog_from_vpp_content.to_s
199
+ tmpl.add_element('notify_disassociation').text = @notify_disassociation.to_s
200
+ tmpl.add_element('auto_register_managed_users').text = @auto_register_managed_users.to_s
201
+
202
+ add_site_to_xml doc
203
+
204
+ doc.to_s
205
+ end
206
+
207
+ end # VPPAccount
208
+
209
+ end # JSS
@@ -26,9 +26,16 @@
26
26
  ###
27
27
  module JSS
28
28
 
29
- # A mix-in module to handle VPP-related data in API objects that can be
29
+ # A mix-in module to handleVPP-related data in API objects that can be
30
30
  # assigned via VPP.
31
31
  #
32
+ # NOTE: For now we are only working with device-based VPP assignments,
33
+ # which are done via the scope of the VPPable object (macapp, mobdevapp, ebook)
34
+ #
35
+ # User-based APP assignments will require the creation of a VPPAssignment class,
36
+ # and a VPPAssignmentScope class, since those scopes are very limited compared
37
+ # to ordinary scope.
38
+ #
32
39
  # To use this module, merely `include VPPable` when defining your
33
40
  # subclass of JSS::APIObject
34
41
  #
@@ -40,6 +47,50 @@ module JSS
40
47
  #####################################
41
48
  VPPABLE = true
42
49
 
50
+ # Mixed-in Class Methods
51
+ #
52
+ # This is a common technique to get class methods mixed in when
53
+ # you 'include' a module full of instance methods
54
+ #####################################
55
+
56
+ def self.included(klass)
57
+ klass.extend(ClassMethods)
58
+ end
59
+
60
+ # Methods in here will become class methods of the
61
+ # classes that include VPPable
62
+ module ClassMethods
63
+
64
+ # The names and assignment data for all class members that have
65
+ # VPP licenses that can be assigned by device.
66
+ # The assignment data is a hash of three keys pointing to integers:
67
+ # {
68
+ # total: int,
69
+ # used: int,
70
+ # remaining: int
71
+ # }
72
+ #
73
+ # WARNING: This must instantiate all objects, so is slow
74
+ #
75
+ # @return [Hash{String=>Hash}] The names and assignment data
76
+ def all_vpp_device_assignable
77
+ data = {}
78
+ all_ids.each do |id|
79
+ obj = fetch id: id
80
+ next unless obj.vpp_device_based?
81
+
82
+ data[obj.name] = {
83
+ total: obj.vpp_licenses_total,
84
+ used: obj.vpp_licenses_used,
85
+ remaining: obj.vpp_licenses_remaining
86
+ }
87
+ end
88
+ data
89
+ end # all_vpp_device_assignable
90
+
91
+ end # module ClassMethods
92
+
93
+
43
94
  # Mixed-in Attributes
44
95
  #####################################
45
96
 
@@ -48,6 +99,7 @@ module JSS
48
99
 
49
100
  # @return [Integer]
50
101
  attr_reader :vpp_admin_account_id
102
+ alias vpp_account_id vpp_admin_account_id
51
103
 
52
104
  # @return [Boolean]
53
105
  attr_reader :assign_vpp_device_based_licenses
@@ -55,8 +107,7 @@ module JSS
55
107
 
56
108
  # @return [Integer]
57
109
  attr_reader :total_vpp_licenses
58
- alias vpp_total_licenses total_vpp_licenses
59
- alias vpp_license_count total_vpp_licenses
110
+ alias vpp_licenses_total total_vpp_licenses
60
111
 
61
112
  # @return [Integer]
62
113
  attr_reader :remaining_vpp_licenses
@@ -66,22 +117,126 @@ module JSS
66
117
  attr_reader :used_vpp_licenses
67
118
  alias vpp_licenses_used used_vpp_licenses
68
119
 
120
+ #### How to assign VPP content & view assignments
121
+ #
122
+ # When doing device-based assignments, they are made via the
123
+ # Scope of the VPPable Object.
124
+ #
125
+ # There is no indication in the device's API data that an app/book was
126
+ # installed/licensed via VPP, it just shows up in the
127
+ # list of installed apps like any other.
128
+ #
129
+ # When doing user-based assignments, they are made via the (limited)
130
+ # scope of a 'Volume Assignment' object in Users -> Volume Assignement
131
+ # These objects are sort of like policies or config profiles in that they have
132
+ # payloads, and can assign multiple things at once (iosapps, macapps, ebooks)
133
+ # These are available as vppassignment objects in the API.
134
+ #
135
+ # User-based assignments show up in the User's Jamf record
136
+ # Users -> username -> (vpp acct name)
137
+ # There you'll see the names of objects assigned to the user, and the
138
+ # devices on which they've accepted the VPP invitation. In the User's
139
+ # API data, there isaa 'vpp_assignments' arry of hash's like this:
140
+ # [{:id=>13733, :uid=>"258_13733"}]
141
+ # However, that 'id' is not the id of any known vppassignment object, and
142
+ # the uid is... ?? The object model at Developer.jamf.com says those
143
+ # values should be an id and a name, probably pointing to a vppassignment
144
+ # object, but that isn't the case.
145
+ #
146
+ #
147
+ #### Figuring out how many, and where VPP lic. are used....
148
+ #
149
+ # IF dev. based assignement is turned on, then
150
+ # the VPPable object (app, ebook) in the API will show the total numbers
151
+ # of both user and device based assignments:
152
+ #
153
+ # "vpp": {
154
+ # "assign_vpp_device_based_licenses": true,
155
+ # "vpp_admin_account_id": 1,
156
+ # "total_vpp_licenses": 2,
157
+ # "remaining_vpp_licenses": 0,
158
+ # "used_vpp_licenses": 2
159
+ # }
160
+ #
161
+ # However, if assign_vpp_device_based_licenses is false, meaning
162
+ # all assignments are user-based, then no other info is shown in the API.
163
+ #
164
+ # In that case, in the UI, you can see the total assignments in a table
165
+ # in Settings -> Global Mgmt -> Volume Purch -> Content -> (ios/mac)
166
+ # The numbers shown there indicate all assignments, whether user- or
167
+ # deviced-based, just like the numbers in the API data for the VPPable
168
+ # object, if they are there.
169
+ # But there's no equivalent for that table data directly in the API when
170
+ # device-based is false.
171
+ #
172
+ # Also in the UI you can see the intividual computers, mobiledevs, and users
173
+ # to whom an object is assigned, no matter how it was assigned. Go to
174
+ # Users -> Volume Assignments -> [any assigment object] -> Apps/Books -> ios/mac
175
+ # and click on the number in the rightmost 'in use' column, and you'll
176
+ # see a page with 3 tabs, showing the individual computers, mobdevs, or users
177
+ # with the app/ebook assigned. EXCEPT this doesn't seem to expand
178
+ # scoped groups - when I added a static computer group with one computer to
179
+ # the scope of a MacApp, the total in-use count went up from 6 to 7, but the
180
+ # list of computers two which it was assigned still showed only 6. :-(
181
+ #
182
+ # You can also get to the same page via: Users->SeachVolumeContent
183
+ # then perform a simple search, and in the results page, click on the in-use
184
+ # number. If you click on the VolumeAssignments number you'll see a
185
+ # breakdown of the device assignments (from the app itself) and user assignments
186
+ # and their scopes, but the scopes will not expand any groups, just list them.
187
+ #
188
+ # So 2 questions:
189
+ # 1) How to see the total/used/remaining licenses for a VPPable object in the
190
+ # API, regardless of how it's deployed
191
+ #
192
+ # - first look at the VPPable object, and if the data is there, yer done.
193
+ # - If not, then the object is only assigned to users, so we can loop thru
194
+ # the vppassignment objects and count things up.
195
+ #
196
+ # 2) How to learn where the VPPable object is actually assigned - i.e.
197
+ # a list of users and/or devices. Note: this isn't a list of where it's
198
+ # installed, but to whom/where it is assigned.
199
+ #
200
+ # - TLDR: no scopable object in Jamf gives you such a list, so we probably
201
+ # don't need it.
202
+ #
203
+ # In the UI, the page you get when clicking the 'in use' column of various
204
+ # 'volume content' lists (see above) gets you the individually assigned
205
+ # hardware or users, but doesn't show those via groups.
206
+ # In the API - there doesn't seem to be any access at all, other than the
207
+ # scopes of the VPPable Object itself, and any vppassignments that contain it.
208
+ # Scanning through them is probably the only option, but could be slow once
209
+ # there are many - and expanding those scopes into an actual list of users
210
+ # and devices would be a pain to write
211
+ #
212
+
213
+ # Mixed-in Instance Methods
214
+ #####################################
69
215
 
70
216
  # Set whether or not the VPP licenses should be assigned
71
- # by device rather than by user
217
+ # by device as well as (or.. instead of?) by user
72
218
  #
73
219
  # @param new_val[Boolean] The new value
74
220
  #
75
221
  # @return [void]
76
222
  #
77
223
  def assign_vpp_device_based_licenses=(new_val)
78
- return nil if new_val == @assign_vpp_device_based_licenses
79
- raise JSS::InvalidDataError, 'New value must be true or false' unless new_val.jss_boolean?
80
- @assign_vpp_device_based_licenses = new_val
224
+ return if new_val == @assign_vpp_device_based_licenses
225
+
226
+ @assign_vpp_device_based_licenses = JSS::Validate.boolean new_val
81
227
  @need_to_update = true
82
228
  end
83
229
  alias vpp_device_based= assign_vpp_device_based_licenses=
84
230
 
231
+ # @return [String] The name of the vpp admin acct for this object
232
+ #
233
+ def vpp_admin_account_name
234
+ return unless @vpp_admin_account_id.is_a? Integer
235
+
236
+ JSS::VPPAccount.map_all_ids_to(:name)[@vpp_admin_account_id]
237
+ end
238
+ alias vpp_account_name vpp_admin_account_name
239
+
85
240
  # Mixed-in Private Instance Methods
86
241
  #####################################
87
242
  private
@@ -92,11 +247,12 @@ module JSS
92
247
  #
93
248
  def parse_vpp
94
249
  @vpp_codes = @init_data[:vpp_codes]
95
- @vpp_admin_account_id = @init_data[:vpp][:vpp_admin_account_id]
96
- @assign_vpp_device_based_licenses = @init_data[:vpp][:assign_vpp_device_based_licenses]
97
- @total_vpp_licenses = @init_data[:vpp][:total_vpp_licenses]
98
- @remaining_vpp_licenses = @init_data[:vpp][:remaining_vpp_licenses]
99
- @used_vpp_licenses = @init_data[:vpp][:used_vpp_licenses]
250
+ vpp_data = @init_data[:vpp]
251
+ @vpp_admin_account_id = vpp_data[:vpp_admin_account_id]
252
+ @assign_vpp_device_based_licenses = vpp_data[:assign_vpp_device_based_licenses]
253
+ @total_vpp_licenses = vpp_data[:total_vpp_licenses]
254
+ @remaining_vpp_licenses = vpp_data[:remaining_vpp_licenses]
255
+ @used_vpp_licenses = vpp_data[:used_vpp_licenses]
100
256
  end
101
257
 
102
258
  # Insert an appropriate vpp element into the XML for sending changes
@@ -109,7 +265,7 @@ module JSS
109
265
  def add_vpp_xml(xdoc)
110
266
  doc_root = xdoc.root
111
267
  vpp = doc_root.add_element 'vpp'
112
- vpp.add_element('assign_vpp_device_based_licenses').text = @assign_vpp_device_based_licenses
268
+ vpp.add_element('assign_vpp_device_based_licenses').text = @assign_vpp_device_based_licenses.to_s
113
269
  end
114
270
 
115
271
  end # VPPable