ruby-jss 1.4.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +95 -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 +16 -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 +0 -2
  35. data/lib/jamf/api/resources/collection_resources/device_enrollment.rb +10 -10
  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 +3 -19
  51. data/lib/jss/api_object/categorizable.rb +1 -1
  52. data/lib/jss/api_object/computer.rb +13 -0
  53. data/lib/jss/api_object/configuration_profile.rb +61 -5
  54. data/lib/jss/api_object/directory_binding_type.rb +66 -60
  55. data/lib/jss/api_object/directory_binding_type/active_directory.rb +71 -34
  56. data/lib/jss/api_object/directory_binding_type/admitmac.rb +536 -467
  57. data/lib/jss/api_object/directory_binding_type/centrify.rb +21 -7
  58. data/lib/jss/api_object/directory_binding_type/open_directory.rb +4 -4
  59. data/lib/jss/api_object/distribution_point.rb +2 -2
  60. data/lib/jss/api_object/dock_item.rb +102 -96
  61. data/lib/jss/api_object/extendable.rb +1 -1
  62. data/lib/jss/api_object/group.rb +33 -2
  63. data/lib/jss/api_object/network_segment.rb +45 -13
  64. data/lib/jss/api_object/patch_source.rb +10 -9
  65. data/lib/jss/api_object/policy.rb +155 -25
  66. data/lib/jss/api_object/printer.rb +10 -4
  67. data/lib/jss/api_object/scopable.rb +10 -15
  68. data/lib/jss/api_object/scopable/scope.rb +31 -30
  69. data/lib/jss/api_object/script.rb +242 -352
  70. data/lib/jss/api_object/user.rb +1 -1
  71. data/lib/jss/client/management_action.rb +1 -2
  72. data/lib/jss/composer.rb +2 -2
  73. data/lib/jss/exceptions.rb +3 -0
  74. data/lib/jss/server.rb +15 -0
  75. data/lib/jss/utility.rb +213 -45
  76. data/lib/jss/version.rb +1 -1
  77. metadata +46 -64
  78. data/lib/jamf/api/abstract_classes/advanced_search.rb +0 -86
  79. data/lib/jamf/api/abstract_classes/collection_resource.rb +0 -433
  80. data/lib/jamf/api/abstract_classes/generic_reference.rb +0 -145
  81. data/lib/jamf/api/abstract_classes/prestage_skip_setup_items.rb +0 -126
  82. data/lib/jamf/api/json_objects/account_prefs.rb +0 -79
  83. data/lib/jamf/api/json_objects/android_details.rb +0 -139
  84. data/lib/jamf/api/json_objects/appletv_details.rb +0 -110
  85. data/lib/jamf/api/json_objects/cellular_network.rb +0 -151
  86. data/lib/jamf/api/json_objects/computer_prestage_skip_setup_items.rb +0 -67
  87. data/lib/jamf/api/json_objects/criterion.rb +0 -152
  88. data/lib/jamf/api/json_objects/extension_attribute_value.rb +0 -128
  89. data/lib/jamf/api/json_objects/installed_certificate.rb +0 -53
  90. data/lib/jamf/api/json_objects/installed_configuration_profile.rb +0 -67
  91. data/lib/jamf/api/json_objects/installed_ebook.rb +0 -58
  92. data/lib/jamf/api/json_objects/installed_provisioning_profile.rb +0 -59
  93. data/lib/jamf/api/json_objects/ios_details.rb +0 -244
  94. data/lib/jamf/api/json_objects/mobile_device_details.rb +0 -219
  95. data/lib/jamf/api/json_objects/mobile_device_security.rb +0 -101
  96. data/lib/jamf/api/json_objects/purchasing_data.rb +0 -125
  97. data/lib/jamf/api/mixins/locatable.rb +0 -124
  98. data/lib/jamf/api/mixins/referable.rb +0 -92
  99. data/lib/jamf/api/resources/collection_resources/account.rb +0 -163
  100. data/lib/jamf/api/resources/collection_resources/advanced_mobile_device_search.rb +0 -52
  101. data/lib/jamf/api/resources/collection_resources/advanced_user_search.rb +0 -52
  102. data/lib/jamf/api/resources/collection_resources/extension_attribute.rb +0 -45
  103. data/lib/jamf/api/resources/collection_resources/mobile_device.rb +0 -315
  104. data/lib/jamf/api/resources/collection_resources/site.rb +0 -77
  105. data/lib/jamf/api/resources/singleton_resources/authorization.rb +0 -88
  106. data/lib/jamf/api/resources/singleton_resources/client_checkin_settings.rb +0 -139
  107. data/lib/jamf/api/resources/singleton_resources/reenrollment_settings.rb +0 -95
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef0484427943d39089bb11eb73af827c5137d79550c34ea5fdf7ca683c7c69f0
4
- data.tar.gz: '09abd63abb133ee6c34aa2fcb9ae8ccc3e11b3248061ee410223bd8f25f5a7ae'
3
+ metadata.gz: 4e0dafc4e4ff96d1752578ca2cdb38bd4410f6aa5a32d42d02b8abf499269ab0
4
+ data.tar.gz: 5d4cb7b7846541246c029f9d562fed7a30eba677aeae8f50e186d0ebcef33a97
5
5
  SHA512:
6
- metadata.gz: 962236f6ca861fbfa3f706df0d8d977f15fb1436238f7d9c061ae8043c4148b956fc187a9858f9444b8461cc7d707067278e6a9030c676af26febdc565b0cd0f
7
- data.tar.gz: 7f7027d207de8ea3fd3d1c010483a9e261d4962857ec48ac6f4df0ba4d38526720ff3700078900e1df2182f191a0aaf7c3284ef3438714e1fbc9118f3aeb03ca
6
+ metadata.gz: ca56f63003f45b7eeeda5adc2843dc399c4944126d116fc16dd8ce73b3a5af70333a581847b5e15641b76171e8bf6e7969622a52052fff1d25fbace4564c36d3
7
+ data.tar.gz: 37dd3abb514c208cda9329296ce1b1f4aa9746adb1a93837920d47a62aa171db20bcd9aa20927012445e979b8b69ecc07642202eaef6f0faa9252b2bd28df9dc
data/CHANGES.md CHANGED
@@ -4,6 +4,101 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## \[1.6.0] - 2021-05-??
8
+
9
+ ### Fixed
10
+
11
+ - Creating a JSS::User no longer requires a valid LDAP server. Many thanks to @aaron-mmt for filing and fixing this issue!
12
+
13
+ - HTTP 409 errors are handled more appropriately, and should report the actual error message from the server, e.g. 'Duplicate Primary MAC Address'
14
+
15
+ ### Changed
16
+
17
+ - ruby-jss no longer uses the 'plist' gem due to a remote code execution security issue when using `Plist.parse_xml`. Plists are now handled by the CFPropertyList gem. The existing wrapper method `JSS.parse_plist` bas been updated to use the new gem, and a new wrapper method has been added to convert ruby data to XML plist: `JSS.xml_plist_from(data)`. All internal references to methods from the insecure 'plist' gem have been replaced with calls to those wrapper methods.
18
+
19
+ Many many thanks to actae0n of Blacksun Hackers Club for reporting this security issue and providing examples of how it could be exploited.
20
+
21
+ - In preparation for the removal of the 'runScript' command in the jamf binary, JSS::Script no longer uses it within the 'run' instance method. Instead, it just does what the jamf binary did: It creates a private temp folder, writes the script to disk in that temp folder, executes the script with any given params, then deletes the folder, returning the exit status and output from the script.
22
+
23
+ - Jamf::Script#run now takes parameters in the named params `p4:` through `p11:` for consistency with other parts of ruby-jss.
24
+
25
+ - Since Jamf Scripts can no longer be stored in a distribution point, which according to Jamf has been the case for a while, all code for dealing with them that way has been removed.
26
+
27
+ - JSS::NetworkSegment#include? now uses Range#cover? under the hood, rather than Range#include?, which can take a very very long time with large segments.
28
+
29
+ - JSS.expand_min_os has been updated to handle Apple's new version numbers for macOS. This method takes a string like '>=10.14.3' and expands it into a large array of greater OS versions and is used by the 'os_limitations' method of Packages and Scripts. For any range of versions that includes Big Sur, both '11.x.x' and '10.16' as included in the output, to catch machines that may have SYSTEM_VERSION_COMPAT set in their env.
30
+
31
+
32
+ ## \[1.5.3] - 2020-12-28
33
+
34
+ ### Fixed
35
+
36
+ - Classic API connections were not setting their default timeouts properly when first connected. This was causing an error in Policy#flush_logs
37
+
38
+ ## \[1.5.2] - 2020-12-21
39
+
40
+ ### Added
41
+
42
+ - JSS::Policy#flush_logs can now be called as a class method JSS::Policy.flush_logs, passing in the policy names or ids, without instantiating the policy
43
+
44
+ - Both the class and instance 'flush_logs' methods for JSS::Policy take a named parameter 'computers:' which is an array of the computer identifiers for which the policy should be flushed.
45
+
46
+ - JSS::Computer instances now have a 'flush_policy_logs' method which is a wrapper for calling JSS::Policy.flush_logs for just that computer
47
+
48
+ - JSS::ConfigurationProfile: #update/#save now takes boolean param redeploy_to_all: which defaults to false. The default means redeploy only to newly assigned machines in scope. Setting this to true will push the profile out to all machines in scope, even if they already have the profile.
49
+
50
+ ### Changed
51
+
52
+ - JSS.expand_min_os, used to expand strings like '>=10.14.5' into comma-separated versions to be used in Package and Script os_limitations, has been updated to handle Big Sur being both 10.16 and 11.0, and for future OSes to be 12.x, 13.x etc.
53
+ NOTE: If you've used this feature in the past, you might want to look at your package and script seetings and update them, since they will refer to OSes 10.17 and higher.
54
+
55
+ - JSS::APIConnection: initialize @object_list_cache as an empty hash. This provides more useful error messages when forgetting to pass non-default connection objects, and the default one is unused.
56
+
57
+ ### Fixed
58
+
59
+ - JSS::Scopable::Scope#remove_target and #remove_limitation didn't always remove the item.
60
+
61
+ - JSS::Scopable::Scope: when calling the API for any reason, we now pass in the .api connection of the container. Not doing so when using a non-default connection object would cause problems.
62
+
63
+
64
+ ## \[1.5.1] - 2020-11-16
65
+
66
+ IMPORTANT: New minimum require ruby version is 2.3.0
67
+
68
+ Big thanks to @cybertunnel for many enhancements and fixes.
69
+
70
+ ### Added
71
+
72
+ - The .all method for subclasses of Jamf::CollectionResource now fully supports server-side paging, sorting and filtering (for endpoints that support RSQL filters). See the docs/comments for Jamf::CollectionResource.all for details
73
+
74
+ - JSS::ConfigurationProfile subclasses now have a #payload_content=(new_content) method, which takes an Array of Hashes to replace the PayloadContent of the Payload of the profile. All converstion to an XML plist (which is then embedded into the API XML) is handled automatically. WARNING: This is experimental and can easily break your profile if you aren't careful.
75
+
76
+ - JSS::Server#update_activation_code method was added
77
+
78
+ - Group#set_static and #set_smart can convert smart groups to static and static to smart
79
+
80
+ ### Changed
81
+
82
+ - Minimum required ruby version is 2.3.0
83
+
84
+ - The JSS Module now uses the faraday gem, rather than rest-client, as the underlying REST/HTTP engine for communicating with the Classic API. This brings it in line with the Jamf module which has always used faraday for connecting to the Jamf Pro API. Faraday has fewer dependencies, none of which need to be compiled. This means that installing ruby-jss on a Mac no longer requires the XCode command-line tools.
85
+
86
+ - The Jamf module, for accessing the Jamf Pro API, now requires Jamf Pro 10.25 or higher. While still in 'beta', the Jamf Pro API is becoming more stable and in compliance with standards. The Jamf module continues to be updated to work with the modernized endpoints of the JP API. Some related changes:
87
+ - The ids of JP API collection objects are Strings containing Integers.
88
+ - Boolean property names no longer start with 'is', tho aliases ending with '?' are still automatically created.
89
+
90
+ - Removed dependency on net-ldap, which hasn't been used in a while
91
+
92
+ - Removed the redundant JSS::APIConnection instance methods that were just wrappers for various APIObject subclass Class methods, e.g. `JSS.api.valid_id :computers, 'compName'`. Please use the class method directly, e.g. `JSS::Computer.valid_id 'compName'`
93
+
94
+ ### Fixed
95
+
96
+ - PatchSource.fetch was totally broken, now fixed
97
+
98
+ - Category object's parse_category not properly referencing API object during execution
99
+
100
+ - Many small bugs and typos.
101
+
7
102
  ## \[1.4.1] - 2020-10-01
8
103
 
9
104
  ### Added
data/THANKS.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # With Thanks To...
2
2
 
3
+ * Everyone who contributes to this project
3
4
  * Pixar Systems Management
4
- * The Pixar Mac Team
5
+ * The Apple @ Pixar team
5
6
  * JAMF Folks
6
- * Alex
7
+ * Alex
data/lib/jamf.rb CHANGED
@@ -41,10 +41,7 @@ require 'shellwords'
41
41
  require 'digest'
42
42
  require 'open3'
43
43
 
44
-
45
44
  ### Gems
46
- require 'rest-client'
47
- require 'plist'
48
45
 
49
46
  # Used, among other places, in the Connection::APIError class
50
47
  require 'immutable-struct'
@@ -88,19 +85,14 @@ module Jamf
88
85
  # AUTOLOADING
89
86
  ##################################
90
87
 
91
- # Top-level API Abstract Classes
92
- autoload :JSONObject, 'jamf/api/abstract_classes/json_object'
93
- autoload :Resource, 'jamf/api/abstract_classes/resource'
94
- autoload :SingletonResource, 'jamf/api/abstract_classes/singleton_resource'
95
- autoload :CollectionResource, 'jamf/api/abstract_classes/collection_resource'
96
-
97
- # Abstract Classes used for JSONObject subclasses
98
- autoload :AdvancedSearch, 'jamf/api/abstract_classes/advanced_search'
99
- autoload :Prestage, 'jamf/api/abstract_classes/prestage'
100
- autoload :PrestageSkipSetupItems, 'jamf/api/abstract_classes/prestage_skip_setup_items'
88
+ # Top-level API Base Classes
89
+ autoload :JSONObject, 'jamf/api/base_classes/json_object'
90
+ autoload :Resource, 'jamf/api/base_classes/resource'
91
+ autoload :SingletonResource, 'jamf/api/base_classes/singleton_resource'
92
+ autoload :CollectionResource, 'jamf/api/base_classes/collection_resource'
101
93
 
102
- # Abstract Classes not used for JSONObject subclasses
103
- autoload :GenericReference, 'jamf/api/abstract_classes/generic_reference'
94
+ # Base Classes used for JSONObject subclasses
95
+ autoload :Prestage, 'jamf/api/base_classes/prestage'
104
96
 
105
97
  # MixIn Modules
106
98
  autoload :ChangeLog, 'jamf/api/mixins/change_log'
@@ -112,7 +104,11 @@ module Jamf
112
104
  autoload :UnCreatable, 'jamf/api/mixins/uncreatable'
113
105
  autoload :Immutable, 'jamf/api/mixins/immutable'
114
106
  autoload :UnDeletable, 'jamf/api/mixins/undeletable'
115
- autoload :Abstract, 'jamf/api/mixins/abstract'
107
+ autoload :BaseClass, 'jamf/api/mixins/base_class'
108
+ autoload :Pageable, 'jamf/api/mixins/pageable'
109
+ autoload :Filterable, 'jamf/api/mixins/filterable'
110
+ autoload :Sortable, 'jamf/api/mixins/sortable'
111
+ autoload :BulkDeletable, 'jamf/api/mixins/bulk_deletable'
116
112
 
117
113
  # Utility modules
118
114
  autoload :Validate, 'jamf/validate'
@@ -126,6 +122,7 @@ module Jamf
126
122
  autoload :Country, 'jamf/api/json_objects/country'
127
123
  autoload :Criterion, 'jamf/api/json_objects/criterion'
128
124
  autoload :DeviceEnrollmentDevice, 'jamf/api/json_objects/device_enrollment_device'
125
+ autoload :DeviceEnrollmentDeviceSyncState, 'jamf/api/json_objects/device_enrollment_device_sync_state'
129
126
  autoload :DeviceEnrollmentSyncStatus, 'jamf/api/json_objects/device_enrollment_sync_status'
130
127
  autoload :ExtensionAttributeValue, 'jamf/api/json_objects/extension_attribute_value'
131
128
  autoload :InstalledApplication, 'jamf/api/json_objects/installed_application'
@@ -135,6 +132,7 @@ module Jamf
135
132
  autoload :InstalledProvisioningProfile, 'jamf/api/json_objects/installed_provisioning_profile'
136
133
  autoload :InventoryPreloadExtensionAttribute, 'jamf/api/json_objects/inventory_preload_extension_attribute'
137
134
  autoload :IosDetails, 'jamf/api/json_objects/ios_details'
135
+ autoload :Locale, 'jamf/api/json_objects/locale'
138
136
  autoload :Location, 'jamf/api/json_objects/location'
139
137
  autoload :PrestageLocation, 'jamf/api/json_objects/prestage_location'
140
138
  autoload :PrestageSyncStatus, 'jamf/api/json_objects/prestage_sync_status'
@@ -147,16 +145,20 @@ module Jamf
147
145
  autoload :PrestagePurchasingData, 'jamf/api/json_objects/prestage_purchasing_data'
148
146
  autoload :PrestageScope, 'jamf/api/json_objects/prestage_scope'
149
147
  autoload :PrestageAssignment, 'jamf/api/json_objects/prestage_assignment'
148
+ autoload :TimeZone, 'jamf/api/json_objects/time_zone'
150
149
 
151
150
  # Subclasses of SingletonResource
152
151
  autoload :ClientCheckInSettings, 'jamf/api/resources/singleton_resources/client_checkin_settings'
153
152
  autoload :ReEnrollmentSettings, 'jamf/api/resources/singleton_resources/reenrollment_settings'
154
153
  autoload :AppStoreCountryCodes, 'jamf/api/resources/singleton_resources/app_store_country_codes'
154
+ autoload :TimeZones, 'jamf/api/resources/singleton_resources/time_zones'
155
+ autoload :Locales, 'jamf/api/resources/singleton_resources/locales'
155
156
 
156
157
  # Subclasses of CollectionResource
157
158
  autoload :AdvancedMobileDeviceSearch, 'jamf/api/resources/collection_resources/advanced_mobile_device_search'
158
159
  autoload :AdvancedUserSearch, 'jamf/api/resources/collection_resources/advanced_user_search'
159
160
  autoload :Attachment, 'jamf/api/resources/collection_resources/attachment'
161
+ autoload :Category, 'jamf/api/resources/collection_resources/category'
160
162
  autoload :Building, 'jamf/api/resources/collection_resources/building'
161
163
  autoload :Computer, 'jamf/api/resources/collection_resources/computer'
162
164
  autoload :ComputerPrestage, 'jamf/api/resources/collection_resources/computer_prestage'
@@ -168,7 +170,6 @@ module Jamf
168
170
  autoload :MobileDevicePrestage, 'jamf/api/resources/collection_resources/mobile_device_prestage'
169
171
  autoload :Site, 'jamf/api/resources/collection_resources/site'
170
172
  autoload :Script, 'jamf/api/resources/collection_resources/script'
171
- autoload :TimeZone, 'jamf/api/resources/collection_resources/time_zone'
172
173
 
173
174
  # other classes used as attributes inside the resource classes
174
175
  autoload :IPAddress, 'jamf/api/attribute_classes/ip_address'
@@ -0,0 +1,613 @@
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
+ # The module
27
+ module Jamf
28
+
29
+ # A Collection Resource in Jamf Pro
30
+ #
31
+ # See {Jamf::Resource} for general info about API resources.
32
+ #
33
+ # Collection resources have more than one resource within them, and those
34
+ # can (usually) be created and deleted as well as fetched and updated.
35
+ # The entire collection (or a part of it) can also be retrieved as an Array.
36
+ # When the whole collection is retrieved, the result may be cached for future
37
+ # use.
38
+ #
39
+ # # Subclassing
40
+ #
41
+ # ## Creatability, & Deletability
42
+ #
43
+ # Sometimes the API doesn't support creation of new members of the collection.
44
+ # If that's the case, just extend the subclass with Jamf::UnCreatable
45
+ # and the '.create' class method will raise an error.
46
+ #
47
+ # Similarly for deletion of members: if the API doesn't have a way to delete
48
+ # them, extend the subclass with Jamf::UnDeletable
49
+ #
50
+ # See also Jamf::JSONObject, which talks about extending subclasses
51
+ # with Jamf::Immutable
52
+ #
53
+ # ## Bulk Deletion
54
+ #
55
+ # Some collection resources have a resource for bulk deletion, passing in
56
+ # a JSON array of ids to delete.
57
+ #
58
+ # If so, just define a BULK_DELETE_RSRC, and the .delete class method
59
+ # will use it, rather than making multiple calls to delete individual
60
+ # items. See Jamf::Category::BULK_DELETE_RSRC for an example
61
+ #
62
+ # @abstract
63
+ #
64
+ class CollectionResource < Jamf::Resource
65
+
66
+ extend Jamf::BaseClass
67
+ extend Jamf::Pageable
68
+ extend Jamf::Sortable
69
+ extend Jamf::Filterable
70
+
71
+ include Comparable
72
+
73
+ # Public Class Methods
74
+ #####################################
75
+
76
+ # @return [Array<Symbol>] the attribute names that are marked as identifiers
77
+ #
78
+ def self.identifiers
79
+ self::OBJECT_MODEL.select { |_attr, deets| deets[:identifier] }.keys
80
+ end
81
+
82
+ def self.count(cnx: Jamf.cnx)
83
+ collection_count(rsrc_path, cnx: Jamf.cnx)
84
+ end
85
+
86
+
87
+ # Get all instances of a CollectionResource, possibly limited by a filter.
88
+ #
89
+ # When called without specifying paged:, sort:, or filter: (see below)
90
+ # this method will return a single Array of all items of its
91
+ # CollectionResouce subclass, in the server's default sort order. This
92
+ # full list is cached for future use (see Caching, below)
93
+ #
94
+ # However, the Array can be sorted by the server, filtered to contain only
95
+ # matching objects, or 'paged', i.e. retrieved in successive Arrays of a
96
+ # certain size.
97
+ #
98
+ # Sorting, filtering, and paging can all be used at the same time.
99
+ #
100
+ # #### Server-side Sorting
101
+ #
102
+ # Sorting criteria can be provided in the String format 'property:direction',
103
+ # where direction is 'asc' or 'desc' E.g.
104
+ # "username:asc"
105
+ #
106
+ # Multiple properties are supported, either as separate strings in an Array,
107
+ # or a single string, comma separated. E.g.
108
+ #
109
+ # "username:asc,timestamp:desc"
110
+ # is the same as
111
+ # ["username:asc", "timestamp:desc"]
112
+ #
113
+ # which will sort by username alphabetically, and within each username,
114
+ # sort by timestamp newest first.
115
+ #
116
+ # Please see the JamfPro API documentation for the resource for details
117
+ # about available sorting properties and default sorting criteria
118
+ #
119
+ # #### Filtering
120
+ #
121
+ # Some CollectionResouces support RSQL filters to limit which objects
122
+ # are returned. These filters can be applied using the filter: parameter,
123
+ # in which case this `all` method will return `all that match the filter`.
124
+ #
125
+ # If the resource doesn't support filters, the filter parameter is ignored.
126
+ #
127
+ # Please see the JamfPro API documentation for the resource to see if
128
+ # filters are supported, and a list of available fields.
129
+ #
130
+ # #### Paging
131
+ #
132
+ # To reduce server load and local memory usage, you can request the results
133
+ # in 'pages', i.e. successivly retrieved Arrays, using the paged: and page_size:
134
+ # parameters.
135
+ #
136
+ # When paged: is truthy, the call to `all` returns the first group of objects
137
+ # containing however many are specified by page_size: The default page size
138
+ # is 100, the minimum is 1, and the maximum is 2000.
139
+ #
140
+ # Once you have made a paged call to `all`, you must use the `next_page_of_all`
141
+ # method to get the next Array of objects. That method merely repeats the last
142
+ # request made by `all` after incrementing the page number by 1.
143
+ # When `next_page_of_all` returns an empty array, you have retrieved all
144
+ # availalble objects.
145
+ #
146
+ # `next_page_of_all` always reflects the last _paged_ call to `all`. Any
147
+ # subsequent paged call to `all` will reset the paging process for that
148
+ # collection class, and any unfinished previous paged calls to `all` will
149
+ # be forgotten
150
+ #
151
+ # #### Instantiation
152
+ #
153
+ # All data from the API comes from the server in JSON format, mostly as
154
+ # JSON 'objects', which are the equivalent of ruby Hashes.
155
+ # When fetching an individual instance of an object from the API, ruby-jss
156
+ # uses the JSON Hash to create the ruby object, i.e. to 'instantiate' it as
157
+ # an instance of its class. Doing this for many objects can slow things down.
158
+ #
159
+ # Because of this, the 'all' method defaults to returning an Array of the
160
+ # minimally-processed JSON Hashes it gets from the API. If you can get your
161
+ # desired data from these Hashes, it's far more efficient to do so.
162
+ #
163
+ # However sometimes you really need the fully instantiated ruby objects for
164
+ # all of them - especially if you're using filters and not actually processing
165
+ # all items of the class. In such cases you can pass a truthy value to the
166
+ # instantiate: parameter, and the Array will contain fully instantiated
167
+ # ruby objects, not Hashes of API data.
168
+ #
169
+ # #### Caching
170
+ #
171
+ # When called without specifying paged:, sort:, or filter:
172
+ # this method will return a single Array of all items of its
173
+ # CollectionResouce subclass, in the server's default sort order.
174
+ #
175
+ # This Array is cached in ruby-jss, and future calls to this method without
176
+ # those parameters will return the cached Array. Use `refresh: true` to
177
+ # re-request that Array from the server. Note that the cache is of the raw
178
+ # JSON Hash data. Using 'instantiate:' will still be slower as each item in
179
+ # the cache is instantiated. See 'Instantiation' above.
180
+ #
181
+ # Some other class methods, e.g. .all_names, will generate or use this cached
182
+ # Array to derive their values.
183
+ #
184
+ # If any of the parameters paged:, sort:, or filter: are used, an API
185
+ # request is made every time, and no caches are used or stored.
186
+ #
187
+ #######
188
+ #
189
+ # @param sort [String, Array<String>] Server-side sorting criteria in the
190
+ # format: property:direction, where direction is 'asc' or 'desc'. Multiple
191
+ # properties are supported, either as separate strings in an Array, or
192
+ # a single string, comma separated.
193
+ #
194
+ # @param filter [String] An RSQL filter string. Not all collection resources
195
+ # currently support filters, and if they don't, this will be ignored.
196
+ #
197
+ # @param paged [Boolean] Defaults to false. Returns only the first page of
198
+ # `page_size` objects. Use {.next_page_of_all} to retrieve each successive
199
+ # page.
200
+ #
201
+ # @param page_size [Integer] How many items are returned per page? Minimum
202
+ # is 1, maximum is 2000, default is 100. Ignored unless paged: is truthy.
203
+ # Note: the final page may contain fewer items than the page_size
204
+ #
205
+ # @param refresh [Boolean] re-fetch and re-cache the full list of all instances.
206
+ # Ignored if paged:, page_size:, sort:, filter: or instantiate: are used.
207
+ #
208
+ # @param instantiate [Boolean] Defaults to false. Should the items in the
209
+ # returned Array(s) be ruby instances of the CollectionObject subclass, or
210
+ # plain Hashes of data as returned by the API?
211
+ #
212
+ # @param cnx [Jamf::Connection] The API connection to use, default: Jamf.cnx
213
+ #
214
+ # @return [Array<Hash, Jamf::CollectionResource>] The objects in the collection
215
+ #
216
+ def self.all(sort: nil, filter: nil, paged: nil, page_size: nil, refresh: false, instantiate: false, cnx: Jamf.cnx)
217
+ stop_if_base_class
218
+
219
+ # use the cache if not paging, filtering or sorting
220
+ return cached_all(refresh, instantiate, cnx) if !paged && !sort && !filter
221
+
222
+ # we are sorting, filtering or paging
223
+ sort = parse_collection_sort(sort)
224
+ filter = parse_collection_filter(filter)
225
+
226
+ result =
227
+ if paged
228
+ first_collection_page(rsrc_path, page_size, sort, filter, cnx)
229
+ else
230
+ fetch_all_collection_pages(rsrc_path, sort, filter, cnx)
231
+ end
232
+ instantiate ? result.map { |m| new m } : result
233
+ end
234
+
235
+ # PRIVATE
236
+ # return the cached/cachable version of .all, possibly instantiated
237
+ #
238
+ # @param refresh [Boolean] refetch the cache from the server?
239
+ #
240
+ # @param instantiate [Boolean] Return an array of instantiated objects, vs
241
+ # JSON hashes?
242
+ #
243
+ # @param cnx [Jamf::Connection] The Connection to use
244
+ #
245
+ # @return [Array<Hash,Object>] All the objects in the collection
246
+ #
247
+ def self.cached_all(refresh, instantiate, cnx)
248
+ cnx.collection_cache[self] = nil if refresh
249
+ unless cnx.collection_cache[self]
250
+ sort = nil
251
+ filter = nil
252
+ cnx.collection_cache[self] = fetch_all_collection_pages(rsrc_path, sort, filter, cnx)
253
+ end
254
+ instantiate ? cnx.collection_cache[self].map { |m| new m } : cnx.collection_cache[self]
255
+ end
256
+ private_class_method :cached_all
257
+
258
+
259
+ # Fetch the next page of a paged .all request. See
260
+ # {Jamf::Pagable.next_collection_page}
261
+ def self.next_page_of_all
262
+ next_collection_page
263
+ end
264
+
265
+ # An array of the ids for all collection members. According to the
266
+ # specs ALL collection resources must have an ID, which is used in the
267
+ # resource path.
268
+ #
269
+ # NOTE: This method uses the cached version of .all
270
+ #
271
+ # @param refresh (see .all)
272
+ #
273
+ # @param cnx (see .all)
274
+ #
275
+ # @return [Array<Integer>]
276
+ #
277
+ def self.all_ids(refresh = false, cnx: Jamf.cnx)
278
+ all(refresh: refresh, cnx: cnx).map { |m| m[:id] }
279
+ end
280
+
281
+ # A Hash of all members of this collection where the keys are some
282
+ # identifier and values are any other attribute.
283
+ #
284
+ # NOTE: This method uses the cached version of .all
285
+ #
286
+ # @param ident [Symbol] An identifier of this Class, used as the key
287
+ # for the mapping Hash. Aliases are acceptable, e.g. :sn for :serialNumber
288
+ #
289
+ # @param to [Symbol] The attribute to which the ident will be mapped.
290
+ # Aliases are acceptable, e.g. :name for :displayName
291
+ #
292
+ # @param refresh (see .all)
293
+ #
294
+ # @param cnx (see .all)
295
+ #
296
+ # @return [Hash {Symbol: Object}] A Hash of identifier mapped to attribute
297
+ #
298
+ def self.map_all(ident, to:, cnx: Jamf.cnx, refresh: false)
299
+ real_ident = attr_key_for_alias ident
300
+ raise Jamf::InvalidDataError, "No identifier #{ident} for class #{self}" unless
301
+ identifiers.include? real_ident
302
+
303
+ real_to = attr_key_for_alias to
304
+ raise Jamf::NoSuchItemError, "No attribute #{to} for class #{self}" unless self::OBJECT_MODEL.key? real_to
305
+
306
+ list = all refresh: refresh, cnx: cnx
307
+ to_class = self::OBJECT_MODEL[real_to][:class]
308
+ mapped = list.map do |i|
309
+ [
310
+ i[real_ident],
311
+ to_class.is_a?(Symbol) ? i[real_to] : to_class.new(i[real_to])
312
+ ]
313
+ end # do i
314
+ mapped.to_h
315
+ end
316
+
317
+ # Given a key (identifier) and value for this collection, return the raw data
318
+ # Hash (the JSON object) for the matching API object or nil if there's no
319
+ # match for the given value.
320
+ #
321
+ # In general you should use this if the form:
322
+ #
323
+ # raw_data identifier: value
324
+ #
325
+ # where identifier is one of the available identifiers for this class
326
+ # like id:, name:, serialNumber: etc.
327
+ #
328
+ # In the unlikely event that you dont know which identifier a value is for
329
+ # or want to be able to take any of them without specifying, then
330
+ # you can use
331
+ #
332
+ # raw_data some_value
333
+ #
334
+ # If some_value is an integer or a string containing an integer, it
335
+ # is assumed to be an :id otherwise all the available identifers
336
+ # are searched, in the order you see them when you call <class>.identifiers
337
+ #
338
+ # If no matching object is found, nil is returned.
339
+ #
340
+ # Everything except :id is treated as a case-insensitive String
341
+ #
342
+ # @param value [String, Integer] The identifier value to search fors
343
+ #
344
+ # @param key: [Symbol] The identifier being used for the search.
345
+ # E.g. if :serialNumber, then the value must be a known serial number, it
346
+ # is not checked against other identifiers. Defaults to :id
347
+ #
348
+ # @param cnx: (see .all)
349
+ #
350
+ # @return [Hash, nil] the basic dataset of the matching object,
351
+ # or nil if it doesn't exist
352
+ #
353
+ def self.raw_data(value = nil, cnx: Jamf.cnx, **ident_and_val)
354
+ stop_if_base_class
355
+
356
+ # given a value with no ident key
357
+ return raw_data_by_value_only(value, cnx: Jamf.cnx) if value
358
+
359
+ # if we're here, we should know our ident key and value
360
+ ident, value = ident_and_val.first
361
+ raise ArgumentError, 'Required parameter "identifier: value", where identifier is id:, name: etc.' unless ident && value
362
+
363
+ return raw_data_by_id(value, cnx: cnx) if ident == :id
364
+ return unless identifiers.include? ident
365
+
366
+ raw_data_by_other_identifier(ident, value, cnx: cnx)
367
+ end
368
+
369
+ # Match the given value in all possibly identifiers
370
+ def self.raw_data_by_value_only(value, cnx: Jamf.cnx)
371
+ return raw_data_by_id(value, cnx: cnx) if value.to_s.j_integer?
372
+
373
+ identifiers.each do |ident|
374
+ next if ident == :id
375
+
376
+ id = raw_data_by_other_identifier(ident, value, cnx: cnx)
377
+ return id if id
378
+ end # identifiers.each
379
+ return
380
+ end
381
+ private_class_method :raw_data_by_value_only
382
+
383
+ # get the basic dataset by id, with optional
384
+ # request params to get more than basic data
385
+ def self.raw_data_by_id(id, request_params: nil, cnx: Jamf.cnx)
386
+ cnx.get "#{rsrc_path}/#{id}#{request_params}"
387
+ rescue => e
388
+ return if e.httpStatus == 404
389
+
390
+ raise e
391
+ end
392
+ private_class_method :raw_data_by_id
393
+
394
+ # Given an indentier attr. key, and a value,
395
+ # return the id where that ident has that value, or nil
396
+ #
397
+ def self.raw_data_by_other_identifier(identifier, value, refresh: false, cnx: Jamf.cnx)
398
+ # if the API supports filtering by this identifier, just use that
399
+ return all(filter: "#{identifier}=='#{value}'", paged: true, page_size: 1, cnx: cnx).first if self::OBJECT_MODEL[identifier][:filter_key]
400
+
401
+ # otherwise we have to loop thru all the objects looking for the value
402
+ all(refresh: refresh, cnx: cnx).each { |data| return data if data[identifier].to_s.casecmp? value.to_s }
403
+
404
+ nil
405
+ end
406
+ private_class_method :raw_data_by_other_identifier
407
+
408
+ # Look up the valid ID for any arbitrary identifier.
409
+ # In general you should use this if the form:
410
+ #
411
+ # valid_id identifier: value
412
+ #
413
+ # where identifier is one of the available identifiers for this class
414
+ # like id:, name:, serialNumber: etc.
415
+ #
416
+ # In the unlikely event that you dont know which identifier a value is for
417
+ # or want to be able to take any of them without specifying, then
418
+ # you can use
419
+ #
420
+ # valid_id some_value
421
+ #
422
+ # If some_value is an integer or a string containing an integer, it
423
+ # is assumed to be an id: otherwise all the available identifers
424
+ # are searched, in the order you see them when you call <class>.identifiers
425
+ #
426
+ # If no matching object is found, nil is returned.
427
+ #
428
+ # WARNING: Do not use this to look up ids for getting the
429
+ # raw API data for an object. Since this calls .raw_data
430
+ # itself, it is redundant to use .valid_id to get an id
431
+ # to then pass on to .raw_data
432
+ # Use raw_data directly like this:
433
+ # data = raw_data(ident: val)
434
+ #
435
+ #
436
+ # @param value [String,Integer] A value for an arbitrary identifier
437
+ #
438
+ # @param cnx [Jamf::Connection] The connection to use. default: Jamf.cnx
439
+ #
440
+ # @param ident_and_val [Hash{Symbol: String}] The identifier key and the value
441
+ # to look for in that key, e.g. name: 'foo' or serialNumber: 'ASDFGH'
442
+ #
443
+ # @return [String, nil] The id (integer-in-string) of the object, or nil
444
+ # if no match found
445
+ #
446
+ def self.valid_id(value = nil, cnx: Jamf.cnx, **ident_and_val)
447
+ raw_data(value, cnx: cnx, **ident_and_val)&.dig(:id)
448
+ end
449
+
450
+ # Bu default, subclasses are creatable, i.e. new instances can be created
451
+ # with .create, and added to the JSS with .save
452
+ # If a subclass is NOT creatble for any reason, just add
453
+ # extend Jamf::UnCreatable
454
+ # and this method will return false
455
+ #
456
+ # @return [Boolean]
457
+ def self.creatable?
458
+ true
459
+ end
460
+
461
+ # Make a new thing to be added to the API
462
+ def self.create(**params)
463
+ stop_if_base_class
464
+
465
+ raise Jamf::UnsupportedError, "#{self}'s are not currently creatable via the API" unless creatable?
466
+
467
+ # Which connection to use
468
+ cnx = params.delete :cnx
469
+ cnx ||= Jamf.cnx
470
+
471
+ params.delete :id # no such animal when .creating
472
+ params.keys.each do |param|
473
+ raise ArgumentError, "Unknown parameter: #{param}" unless self::OBJECT_MODEL.key? param
474
+
475
+ if params[param].is_a? Array
476
+ params[param].map! { |val| validate_attr param, val, cnx: cnx }
477
+ else
478
+ params[param] = validate_attr param, params[param], cnx: cnx
479
+ end
480
+ end
481
+
482
+ params[:creating_from_create] = true
483
+ new params, cnx: cnx
484
+ end
485
+
486
+ # Retrieve a member of a CollectionResource from the API
487
+ #
488
+ # To create new members to be added to the JSS, use
489
+ # {Jamf::CollectionResource.create}
490
+ #
491
+ # You must know the specific identifier attribute you're looking up, e.g.
492
+ # :id or :name or :udid, (or an aliase thereof) then you can specify it like
493
+ # `.fetch name: 'somename'`, or `.fetch udid: 'someudid'`
494
+ #
495
+ # @param cnx[Jamf::Connection] the connection to use to fetch the object
496
+ #
497
+ # @param ident_and_val[Hash] an identifier attribute key and a search value
498
+ #
499
+ # @return [CollectionResource] The ruby-instance of a Jamf object
500
+ #
501
+ def self.fetch(random = nil, cnx: Jamf.cnx, **ident_and_val)
502
+ stop_if_base_class
503
+ ident, value = ident_and_val.first
504
+ data =
505
+ if random
506
+ all.sample
507
+ elsif ident && value
508
+ raw_data(cnx: cnx, **ident_and_val)
509
+ end
510
+ raise Jamf::NoSuchItemError, "No matching #{self}" unless data
511
+
512
+ new data, cnx: cnx
513
+ end # fetch
514
+
515
+ # By default, CollectionResource subclass instances are deletable.
516
+ # If not, just extend the subclass with Jamf::UnDeletable, and this
517
+ # will return false, and .delete & #delete will raise errors
518
+ def self.deletable?
519
+ true
520
+ end
521
+
522
+ # Delete one or more objects by id
523
+ #
524
+ # @param ids [Array<String,Integer>] The ids to delete
525
+ #
526
+ # @param cnx [Jamf::Connection] The connection to use, default: Jamf.cnx
527
+ #
528
+ # @return [Array<Jamf::Connection::APIError::ErrorInfo] Info about any ids
529
+ # that failed to be deleted.
530
+ #
531
+ def self.delete(*ids, cnx: Jamf.cnx)
532
+ raise Jamf::UnsupportedError, "Deleting #{self} objects is not currently supported" unless deletable?
533
+
534
+ return bulk_delete(ids, cnx: Jamf.cnx) if ancestors.include? Jamf::BulkDeletable
535
+
536
+ errs = []
537
+ ids.each do |id_to_delete|
538
+ begin
539
+ cnx.delete "#{rsrc_path}/#{id_to_delete}"
540
+ rescue Jamf::Connection::APIError => e
541
+ raise e unless e.httpStatus == 404
542
+
543
+ errs += e.errors
544
+ end # begin
545
+ end # ids.each
546
+ errs
547
+ end
548
+
549
+ # Private Class Methods
550
+ #####################################
551
+
552
+ # TODO: better pluralizing?
553
+ #
554
+ def self.create_list_methods(attr_name, attr_def)
555
+ list_method_name = "all_#{attr_name}s"
556
+
557
+ define_singleton_method(list_method_name) do |refresh = false, cnx: Jamf.cnx|
558
+ all_list = all(refresh: refresh, cnx: cnx)
559
+ if attr_def[:class].is_a? Symbol
560
+ all_list.map { |i| i[attr_name] }.uniq
561
+ else
562
+ all_list.map { |i| attr_def[:class].new i[attr_name] }
563
+ end
564
+ end # define_singleton_method
565
+
566
+ return unless attr_def[:aliases]
567
+
568
+ # aliases - TODO: is there a more elegant way?
569
+ attr_def[:aliases].each do |a|
570
+ define_singleton_method("all_#{a}s") do |refresh = false, cnx: Jamf.cnx|
571
+ send list_method_name, refresh, cnx: cnx
572
+ end # define_singleton_method
573
+ end # each alias
574
+ end # create_list_methods
575
+ private_class_method :create_list_methods
576
+
577
+
578
+ # Instance Methods
579
+ #####################################
580
+
581
+ def exist?
582
+ !@id.nil?
583
+ end
584
+
585
+ def rsrc_path
586
+ return unless exist?
587
+
588
+ "#{self.class.rsrc_path}/#{@id}"
589
+ end
590
+
591
+ def delete
592
+ raise Jamf::UnsupportedError, "Deleting #{self} objects is not currently supported" unless self.class.deletable?
593
+
594
+ @cnx.delete rsrc_path
595
+ end
596
+
597
+ # Two collection resource objects are the same if their id's are the same
598
+ def <=>(other)
599
+ id <=> other.id
600
+ end
601
+
602
+ # Private Instance Methods
603
+ ############################################
604
+ private
605
+
606
+ def create_in_jamf
607
+ result = @cnx.post self.class.rsrc_path, to_jamf
608
+ @id = result[:id]
609
+ end
610
+
611
+ end # class CollectionResource
612
+
613
+ end # module JAMF