### Copyright 2022 Pixar ### ### Licensed under the Apache License, Version 2.0 (the "Apache License") ### with the following modification; you may not use this file except in ### compliance with the Apache License and the following modification to it: ### Section 6. Trademarks. is deleted and replaced with: ### ### 6. Trademarks. This License does not grant permission to use the trade ### names, trademarks, service marks, or product names of the Licensor ### and its affiliates, except as required to comply with Section 4(c) of ### the License and to reproduce the content of the NOTICE file. ### ### You may obtain a copy of the Apache License at ### ### http://www.apache.org/licenses/LICENSE-2.0 ### ### Unless required by applicable law or agreed to in writing, software ### distributed under the Apache License with the above modification is ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ### KIND, either express or implied. See the Apache License for the specific ### language governing permissions and limitations under the Apache License. ### ### ### module Jamf # A mix-in module to handleVPP-related data in API objects that can be # assigned via VPP. # # NOTE: For now we are only working with device-based VPP assignments, # which are done via the scope of the VPPable object (macapp, mobdevapp, ebook) # # User-based APP assignments will require the creation of a VPPAssignment class, # and a VPPAssignmentScope class, since those scopes are very limited compared # to ordinary scope. # # To use this module, merely `include VPPable` when defining your # subclass of Jamf::APIObject # # classes doing so MUST call {#add_vpp_xml(xmldoc)} in their {#rest_xml} method # module VPPable # Mixed-in Constants ##################################### VPPABLE = true # Mixed-in Class Methods # # This is a common technique to get class methods mixed in when # you 'include' a module full of instance methods ##################################### def self.included(klass) klass.extend(ClassMethods) end # Methods in here will become class methods of the # classes that include VPPable module ClassMethods # The names and assignment data for all class members that have # VPP licenses that can be assigned by device. # The assignment data is a hash of three keys pointing to integers: # { # total: int, # used: int, # remaining: int # } # # WARNING: This must instantiate all objects, so is slow # # @return [Hash{String=>Hash}] The names and assignment data def all_vpp_device_assignable data = {} all_ids.each do |id| obj = fetch id: id next unless obj.vpp_device_based? data[obj.name] = { total: obj.vpp_licenses_total, used: obj.vpp_licenses_used, remaining: obj.vpp_licenses_remaining } end data end # all_vpp_device_assignable end # module ClassMethods # Mixed-in Attributes ##################################### # @return [Hash] attr_reader :vpp_codes # @return [Integer] attr_reader :vpp_admin_account_id alias vpp_account_id vpp_admin_account_id # @return [Boolean] attr_reader :assign_vpp_device_based_licenses alias vpp_device_based? assign_vpp_device_based_licenses # @return [Integer] attr_reader :total_vpp_licenses alias vpp_licenses_total total_vpp_licenses # @return [Integer] attr_reader :remaining_vpp_licenses alias vpp_licenses_remaining remaining_vpp_licenses # @return [Integer] attr_reader :used_vpp_licenses alias vpp_licenses_used used_vpp_licenses #### How to assign VPP content & view assignments # # When doing device-based assignments, they are made via the # Scope of the VPPable Object. # # There is no indication in the device's API data that an app/book was # installed/licensed via VPP, it just shows up in the # list of installed apps like any other. # # When doing user-based assignments, they are made via the (limited) # scope of a 'Volume Assignment' object in Users -> Volume Assignement # These objects are sort of like policies or config profiles in that they have # payloads, and can assign multiple things at once (iosapps, macapps, ebooks) # These are available as vppassignment objects in the API. # # User-based assignments show up in the User's Jamf record # Users -> username -> (vpp acct name) # There you'll see the names of objects assigned to the user, and the # devices on which they've accepted the VPP invitation. In the User's # API data, there isaa 'vpp_assignments' arry of hash's like this: # [{:id=>13733, :uid=>"258_13733"}] # However, that 'id' is not the id of any known vppassignment object, and # the uid is... ?? The object model at Developer.jamf.com says those # values should be an id and a name, probably pointing to a vppassignment # object, but that isn't the case. # # #### Figuring out how many, and where VPP lic. are used.... # # IF dev. based assignement is turned on, then # the VPPable object (app, ebook) in the API will show the total numbers # of both user and device based assignments: # # "vpp": { # "assign_vpp_device_based_licenses": true, # "vpp_admin_account_id": 1, # "total_vpp_licenses": 2, # "remaining_vpp_licenses": 0, # "used_vpp_licenses": 2 # } # # However, if assign_vpp_device_based_licenses is false, meaning # all assignments are user-based, then no other info is shown in the API. # # In that case, in the UI, you can see the total assignments in a table # in Settings -> Global Mgmt -> Volume Purch -> Content -> (ios/mac) # The numbers shown there indicate all assignments, whether user- or # deviced-based, just like the numbers in the API data for the VPPable # object, if they are there. # But there's no equivalent for that table data directly in the API when # device-based is false. # # Also in the UI you can see the intividual computers, mobiledevs, and users # to whom an object is assigned, no matter how it was assigned. Go to # Users -> Volume Assignments -> [any assigment object] -> Apps/Books -> ios/mac # and click on the number in the rightmost 'in use' column, and you'll # see a page with 3 tabs, showing the individual computers, mobdevs, or users # with the app/ebook assigned. EXCEPT this doesn't seem to expand # scoped groups - when I added a static computer group with one computer to # the scope of a MacApp, the total in-use count went up from 6 to 7, but the # list of computers two which it was assigned still showed only 6. :-( # # You can also get to the same page via: Users->SeachVolumeContent # then perform a simple search, and in the results page, click on the in-use # number. If you click on the VolumeAssignments number you'll see a # breakdown of the device assignments (from the app itself) and user assignments # and their scopes, but the scopes will not expand any groups, just list them. # # So 2 questions: # 1) How to see the total/used/remaining licenses for a VPPable object in the # API, regardless of how it's deployed # # - first look at the VPPable object, and if the data is there, yer done. # - If not, then the object is only assigned to users, so we can loop thru # the vppassignment objects and count things up. # # 2) How to learn where the VPPable object is actually assigned - i.e. # a list of users and/or devices. Note: this isn't a list of where it's # installed, but to whom/where it is assigned. # # - TLDR: no scopable object in Jamf gives you such a list, so we probably # don't need it. # # In the UI, the page you get when clicking the 'in use' column of various # 'volume content' lists (see above) gets you the individually assigned # hardware or users, but doesn't show those via groups. # In the API - there doesn't seem to be any access at all, other than the # scopes of the VPPable Object itself, and any vppassignments that contain it. # Scanning through them is probably the only option, but could be slow once # there are many - and expanding those scopes into an actual list of users # and devices would be a pain to write # # Mixed-in Instance Methods ##################################### # Set whether or not the VPP licenses should be assigned # by device as well as (or.. instead of?) by user # # @param new_val[Boolean] The new value # # @return [void] # def assign_vpp_device_based_licenses=(new_val) return if new_val == @assign_vpp_device_based_licenses @assign_vpp_device_based_licenses = Jamf::Validate.boolean new_val @need_to_update = true end alias vpp_device_based= assign_vpp_device_based_licenses= # @return [String] The name of the vpp admin acct for this object # def vpp_admin_account_name return unless @vpp_admin_account_id.is_a? Integer Jamf::VPPAccount.map_all_ids_to(:name)[@vpp_admin_account_id] end alias vpp_account_name vpp_admin_account_name # Mixed-in Private Instance Methods ##################################### private # Parse the vpp data from the incoming API data # # @return [void] # def parse_vpp @vpp_codes = @init_data[:vpp_codes] vpp_data = @init_data[:vpp] @vpp_admin_account_id = vpp_data[:vpp_admin_account_id] @assign_vpp_device_based_licenses = vpp_data[:assign_vpp_device_based_licenses] @total_vpp_licenses = vpp_data[:total_vpp_licenses] @remaining_vpp_licenses = vpp_data[:remaining_vpp_licenses] @used_vpp_licenses = vpp_data[:used_vpp_licenses] end # Insert an appropriate vpp element into the XML for sending changes # to the JSS # # @param xdoc[REXML::Document] The XML document to work with # # @return [void] # def add_vpp_xml(xdoc) doc_root = xdoc.root vpp = doc_root.add_element 'vpp' vpp.add_element('assign_vpp_device_based_licenses').text = @assign_vpp_device_based_licenses.to_s end end # VPPable end # JSS