ruby-jss 1.2.3 → 1.2.4a1

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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jamf.rb +169 -0
  3. data/lib/jamf/api/abstract_classes/collection_resource.rb +422 -0
  4. data/lib/jamf/api/abstract_classes/generic_reference.rb +145 -0
  5. data/lib/jamf/api/abstract_classes/json_object.rb +1074 -0
  6. data/lib/jamf/api/abstract_classes/prestage.rb +219 -0
  7. data/lib/jamf/api/abstract_classes/prestage_skip_setup_items.rb +126 -0
  8. data/lib/jamf/api/abstract_classes/resource.rb +250 -0
  9. data/lib/jamf/api/abstract_classes/singleton_resource.rb +87 -0
  10. data/lib/jamf/api/attribute_classes/ip_address.rb +66 -0
  11. data/lib/jamf/api/attribute_classes/timestamp.rb +144 -0
  12. data/lib/jamf/api/connection.rb +734 -0
  13. data/lib/jamf/api/connection/api_error.rb +111 -0
  14. data/lib/jamf/api/connection/api_error_styleguide.rb +96 -0
  15. data/lib/jamf/api/connection/token.rb +220 -0
  16. data/lib/jamf/api/json_objects/account_prefs.rb +79 -0
  17. data/lib/jamf/api/json_objects/android_details.rb +139 -0
  18. data/lib/jamf/api/json_objects/appletv_details.rb +110 -0
  19. data/lib/jamf/api/json_objects/attachment.rb +68 -0
  20. data/lib/jamf/api/json_objects/cellular_network.rb +151 -0
  21. data/lib/jamf/api/json_objects/change_log_entry.rb +77 -0
  22. data/lib/jamf/api/json_objects/computer_prestage_skip_setup_items.rb +67 -0
  23. data/lib/jamf/api/json_objects/country.rb +51 -0
  24. data/lib/jamf/api/json_objects/extension_attribute_value.rb +128 -0
  25. data/lib/jamf/api/json_objects/installed_application.rb +59 -0
  26. data/lib/jamf/api/json_objects/installed_certificate.rb +53 -0
  27. data/lib/jamf/api/json_objects/installed_configuration_profile.rb +67 -0
  28. data/lib/jamf/api/json_objects/installed_ebook.rb +58 -0
  29. data/lib/jamf/api/json_objects/installed_provisioning_profile.rb +59 -0
  30. data/lib/jamf/api/json_objects/inventory_preload_extension_attribute.rb +52 -0
  31. data/lib/jamf/api/json_objects/ios_details.rb +244 -0
  32. data/lib/jamf/api/json_objects/location.rb +95 -0
  33. data/lib/jamf/api/json_objects/md_prestage_name.rb +57 -0
  34. data/lib/jamf/api/json_objects/md_prestage_names.rb +82 -0
  35. data/lib/jamf/api/json_objects/md_prestage_skip_setup_items.rb +165 -0
  36. data/lib/jamf/api/json_objects/mobile_device_details.rb +219 -0
  37. data/lib/jamf/api/json_objects/mobile_device_security.rb +101 -0
  38. data/lib/jamf/api/json_objects/prestage_assignment.rb +61 -0
  39. data/lib/jamf/api/json_objects/prestage_location.rb +104 -0
  40. data/lib/jamf/api/json_objects/prestage_purchasing_data.rb +132 -0
  41. data/lib/jamf/api/json_objects/prestage_scope.rb +54 -0
  42. data/lib/jamf/api/json_objects/prestage_sync_status.rb +63 -0
  43. data/lib/jamf/api/json_objects/purchasing_data.rb +125 -0
  44. data/lib/jamf/api/mixins/abstract.rb +58 -0
  45. data/lib/jamf/api/mixins/bulk_deletable.rb +39 -0
  46. data/lib/jamf/api/mixins/change_log.rb +136 -0
  47. data/lib/jamf/api/mixins/extendable.rb +75 -0
  48. data/lib/jamf/api/mixins/immutable.rb +39 -0
  49. data/lib/jamf/api/mixins/locatable.rb +124 -0
  50. data/lib/jamf/api/mixins/lockable.rb +48 -0
  51. data/lib/jamf/api/mixins/referable.rb +92 -0
  52. data/lib/jamf/api/mixins/searchable.rb +202 -0
  53. data/lib/jamf/api/mixins/uncreatable.rb +40 -0
  54. data/lib/jamf/api/mixins/undeletable.rb +40 -0
  55. data/lib/jamf/api/resources/collection_resources/account.rb +163 -0
  56. data/lib/jamf/api/resources/collection_resources/building.rb +114 -0
  57. data/lib/jamf/api/resources/collection_resources/category.rb +82 -0
  58. data/lib/jamf/api/resources/collection_resources/computer.rb +49 -0
  59. data/lib/jamf/api/resources/collection_resources/computer_prestage.rb +80 -0
  60. data/lib/jamf/api/resources/collection_resources/department.rb +79 -0
  61. data/lib/jamf/api/resources/collection_resources/extension_attribute.rb +45 -0
  62. data/lib/jamf/api/resources/collection_resources/inventory_preload_record.rb +274 -0
  63. data/lib/jamf/api/resources/collection_resources/md_prestage.rb +139 -0
  64. data/lib/jamf/api/resources/collection_resources/mobile_device.rb +315 -0
  65. data/lib/jamf/api/resources/collection_resources/script.rb +190 -0
  66. data/lib/jamf/api/resources/collection_resources/site.rb +77 -0
  67. data/lib/jamf/api/resources/singleton_resources/app_store_country_codes.rb +131 -0
  68. data/lib/jamf/api/resources/singleton_resources/authorization.rb +88 -0
  69. data/lib/jamf/api/resources/singleton_resources/client_checkin_settings.rb +139 -0
  70. data/lib/jamf/api/resources/singleton_resources/reenrollment_settings.rb +95 -0
  71. data/lib/jamf/client.rb +301 -0
  72. data/lib/jamf/client/jamf_binary.rb +132 -0
  73. data/lib/jamf/client/jamf_helper.rb +298 -0
  74. data/lib/jamf/client/management_action.rb +114 -0
  75. data/lib/jamf/compatibility.rb +88 -0
  76. data/lib/jamf/composer.rb +190 -0
  77. data/lib/jamf/configuration.rb +281 -0
  78. data/lib/jamf/exceptions.rb +107 -0
  79. data/lib/jamf/ruby_extensions.rb +36 -0
  80. data/lib/jamf/ruby_extensions/array.rb +35 -0
  81. data/lib/jamf/ruby_extensions/array/predicates.rb +46 -0
  82. data/lib/jamf/ruby_extensions/array/utils.rb +47 -0
  83. data/lib/jamf/ruby_extensions/filetest.rb +32 -0
  84. data/lib/jamf/ruby_extensions/filetest/predicates.rb +46 -0
  85. data/lib/jamf/ruby_extensions/hash.rb +33 -0
  86. data/lib/jamf/ruby_extensions/hash/backports.rb +92 -0
  87. data/lib/jamf/ruby_extensions/ipaddr.rb +37 -0
  88. data/lib/jamf/ruby_extensions/ipaddr/utils.rb +95 -0
  89. data/lib/jamf/ruby_extensions/object.rb +30 -0
  90. data/lib/jamf/ruby_extensions/object/predicates.rb +51 -0
  91. data/lib/jamf/ruby_extensions/pathname.rb +39 -0
  92. data/lib/jamf/ruby_extensions/pathname/predicates.rb +50 -0
  93. data/lib/jamf/ruby_extensions/pathname/utils.rb +75 -0
  94. data/lib/jamf/ruby_extensions/string.rb +35 -0
  95. data/lib/jamf/ruby_extensions/string/backports.rb +66 -0
  96. data/lib/jamf/ruby_extensions/string/conversions.rb +65 -0
  97. data/lib/jamf/ruby_extensions/string/predicates.rb +47 -0
  98. data/lib/jamf/utility.rb +423 -0
  99. data/lib/jamf/validate.rb +224 -0
  100. data/lib/jamf/version.rb +32 -0
  101. data/lib/jpapi.rb +26 -0
  102. data/lib/jss/version.rb +1 -1
  103. metadata +104 -4
@@ -0,0 +1,87 @@
1
+ # Copyright 2019 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
+ # Classes
30
+ #####################################
31
+
32
+ # A Singleton Resource in the API.
33
+ #
34
+ # See {Jamf::Resource} for details and required constants
35
+ #
36
+ # @abstract
37
+ #
38
+ class SingletonResource < Jamf::Resource
39
+
40
+ extend Jamf::Abstract
41
+
42
+ # Public Class Methods
43
+ #####################################
44
+
45
+ # Return a SingletonResource from the API, from the cache if already cached
46
+ # or retrieving from the API and caching it if neededl
47
+ #
48
+ # @param reload[Boolean] If already cached, re-cache from the API.
49
+ # WARNING: unsaved changes will be lost.
50
+ #
51
+ # @param version[String] the API resource version to use.
52
+ # Defaults to the RSRC_VERSION for the class.
53
+ #
54
+ # @param cnx[Jamf::Connection] The API connection to use
55
+ #
56
+ # @return [Jamf::SingletonResource] The ruby-instance of a Jamf resource
57
+ #
58
+ def self.fetch(reload = false, cnx: Jamf.cnx)
59
+ cnx.singleton_cache[self] = nil if reload
60
+ cached = cnx.singleton_cache[self]
61
+ return cached if cached
62
+
63
+ data = cnx.get "#{self::RSRC_VERSION}/#{self::RSRC_PATH}"
64
+ cnx.singleton_cache[self] = new data, cnx: cnx
65
+ end # fetch
66
+
67
+ def self.flushcache(cnx: Jamf.cnx)
68
+ validate_not_abstract
69
+ cnx.singleton_cache[self] = nil
70
+ end
71
+
72
+ # Instance Methods
73
+ #####################################
74
+
75
+ # only have one path
76
+ def rsrc_path
77
+ self.class.rsrc_path
78
+ end
79
+
80
+ # singltons always exist
81
+ def exist?
82
+ true
83
+ end
84
+
85
+ end # class APIObject
86
+
87
+ end # module JAMF
@@ -0,0 +1,66 @@
1
+ # Copyright 2019 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
+ # handle nils and IPAddrs
30
+ # def new(an_ip, cnx: nil)
31
+ # return nil if an_ip.nil?
32
+ # an_ip = an_ip.to_s if an_ip.is_a? IPAddr
33
+ # super an_ip, cnx: cnx
34
+ # end
35
+
36
+ # A wrapper for IPAddr - allowing initialize to take an unused cnx:
37
+ # and providing #to_jamf
38
+ #
39
+ class IPAddress < IPAddr
40
+
41
+ # @param an_ip[String,IPAddr]
42
+ #
43
+ # @param cnx [void] unused, but required
44
+ #
45
+ def initialize(an_ip, cnx: nil)
46
+ cnx.to_s # shutup rubocop.
47
+
48
+ if an_ip.nil?
49
+ @empty_ip = true
50
+ return
51
+ end
52
+
53
+ super an_ip
54
+ end
55
+
56
+ # @return [String] the IP formatted for passing to the API as a string.
57
+ #
58
+ def to_jamf
59
+ return Jamf::BLANK if @empty_ip
60
+ to_s
61
+ end
62
+
63
+
64
+ end # class Timestamp
65
+
66
+ end # module
@@ -0,0 +1,144 @@
1
+ # Copyright 2019 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
+ module Jamf
27
+
28
+ # A timestamp as used in the JAMF API JSON data
29
+ #
30
+ # Instantiate with a String in iso6801 format (used in the API JSON for
31
+ # all(?) time/date values), or with a Time or Jamf::Timestamp instance, or with
32
+ # an Integer unix epoch, which is treated Jamf-style if 1_000_000_000_000 or
33
+ # higher
34
+ #
35
+ # To unset a timestamp value, instantiate with nil or an empty string. The
36
+ # Time value will be '1970-01-01 00:00:00 -0000', the unix epoch,
37
+ # and the to_jamf method will return an empty string.
38
+ #
39
+ # NOTE: Passing '1970-01-01 00:00:00 -0000' or the equivalent explicitly
40
+ # will NOT be treated as an empty timestamp, but as that actual value.
41
+ # You must pass nil or an empty string to indicate an empty value
42
+ #
43
+ # TODO: Find out: will an empty string work, e.g. in ext attrs with a DATE
44
+ # value, when used in criteria?
45
+ #
46
+ # This class is a subclass of Time, so all Time methods are available.
47
+ # - use .to_i for a unix epoch in seconds
48
+ # - use .to_f for a unix epoch with fractions
49
+ #
50
+ # Use #to_jamf to get the formated string to use in JSON for sending to the
51
+ # API - it *should* always be in ISO8601 format
52
+ #
53
+ class Timestamp < ::Time
54
+
55
+ # When we are unsetting a timestamp by intializing with nil,
56
+ # we still have to have a time object - so use the unix epoch
57
+ NIL_TIMESTAMP = Time.at 0
58
+
59
+ # Integers with this value or higher are a jamf-style epoch,
60
+ # meaning the first 10 digits are a unix epoch, and the last 3
61
+ # are milliseconds. Integers below this shouldn't appear, but
62
+ # will be treated as a regular unix epoch.
63
+ # (999_999_999_999 = 33658-09-27 01:46:39 UTC)
64
+ J_EPOCH_INT_START = 1_000_000_000_000
65
+
66
+ # Stings containing integers of this length are a jamf-style epoch,
67
+ # meaning the first 10 digits are a unix epoch, and the last 3
68
+ # are milliseconds. This length-test will be valid until the year 2286.
69
+ J_EPOCH_STR_LEN = 13
70
+
71
+ # @param tstamp[String,Integer,Time] A representation of a timestampe
72
+ #
73
+ # @param _args [void] unused, but required for JSONObject init.
74
+ #
75
+ def initialize(tstamp, **_args)
76
+ # use a Time object to parse the input and generate our own
77
+ # object
78
+ time = parse_init_tstamp(tstamp)
79
+
80
+ super(
81
+ time.year,
82
+ time.month,
83
+ time.day,
84
+ time.hour,
85
+ time.min,
86
+ (time.sec + (time.usec/1_000_000.0)).round(3),
87
+ time.utc_offset
88
+ )
89
+ end
90
+
91
+ # @return [Integer] the milliseconds of the Time
92
+ def msec
93
+ return 0 if @empty_timestamp
94
+
95
+ (usec / 1000.0).round
96
+ end
97
+
98
+ # @return [String] the timestamp formatted for passing to the API as a string.
99
+ def to_jamf
100
+ return Jamf::BLANK if @empty_timestamp
101
+
102
+ iso8601
103
+ end
104
+
105
+ def to_jamf_epoch
106
+ (to_f.round(3) * 1000).to_i
107
+ end
108
+
109
+ # Private Instance Methods
110
+ ################################
111
+ private
112
+
113
+ # @param tstamp @see #initialize
114
+ # @return [Time]
115
+ def parse_init_tstamp(tstamp)
116
+ case tstamp
117
+ when Time
118
+ tstamp
119
+
120
+ when Integer
121
+ Time.at real_epoch_from_j_epoch(tstamp)
122
+
123
+ when /^\d+$/
124
+ Time.at real_epoch_from_j_epoch(tstamp.to_i)
125
+
126
+ when Jamf::BLANK, nil
127
+ @empty_timestamp = true
128
+ NIL_TIMESTAMP
129
+
130
+ else
131
+ Time.parse tstamp.to_s
132
+ end # case
133
+ end
134
+
135
+ # convert an integer into a float if needed for parsing
136
+ # @param j_epoch [Integer]
137
+ # @return [Integer, Float]
138
+ def real_epoch_from_j_epoch(j_epoch)
139
+ j_epoch >= J_EPOCH_INT_START ? (j_epoch / 1000.0) : j_epoch
140
+ end
141
+
142
+ end # class Timestamp
143
+
144
+ end # module
@@ -0,0 +1,734 @@
1
+ # Copyright 2019 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
+ require 'faraday' # >= 0.17.0
25
+ require 'faraday_middleware' # >= 0.13.0
26
+
27
+ require 'jamf/api/connection/token'
28
+ require 'jamf/api/connection/api_error'
29
+
30
+ # The module
31
+ module Jamf
32
+
33
+ # Changes from classic Jamf::APIconnection
34
+ # - uses Faraday as the REST engine
35
+ # - accepts a url with connect/initialize
36
+ # - only supports https, no http
37
+ # - no xml
38
+ # - tokens & keep_alive
39
+ # - no object class method wrappers in connection objects,
40
+ # only passing connection objects into the class methods
41
+ #
42
+ class Connection
43
+
44
+ # Class Constants
45
+ #####################################
46
+
47
+ # The start of the path for API resources
48
+ RSRC_BASE = 'uapi'.freeze
49
+
50
+ # The API version must be this or higher
51
+ MIN_API_VERSION = Gem::Version.new('1.0')
52
+
53
+ HTTPS_SCHEME = 'https'.freeze
54
+
55
+ # The https default SSL port, default for Jamf Cloud servers
56
+ HTTPS_SSL_PORT = 443
57
+
58
+ # The Jamf default SSL port, default for on-prem servers
59
+ ON_PREM_SSL_PORT = 8443
60
+
61
+ # if either of these is specified, we'll default to SSL
62
+ SSL_PORTS = [ON_PREM_SSL_PORT, HTTPS_SSL_PORT].freeze
63
+
64
+ # Recognize Jamf Cloud servers
65
+ JAMFCLOUD_DOMAIN = '.jamfcloud.com'.freeze
66
+
67
+ # JamfCloud connections default to 443, not 8443
68
+ JAMFCLOUD_PORT = HTTPS_SSL_PORT
69
+
70
+ # Default open-connection timeout in seconds
71
+ DFT_OPEN_TIMEOUT = 60
72
+
73
+ # Default response timeout in seconds
74
+ DFT_TIMEOUT = 60
75
+
76
+ # The Default SSL Version
77
+ DFT_SSL_VERSION = 'TLSv1_2'.freeze
78
+
79
+ # refresh token if less than this many seconds until
80
+ # expiration. Default is 30 minutes if not specified
81
+ DFT_TOKEN_REFRESH = 60 * 30
82
+
83
+ # pre-existing tokens must have this many seconds before
84
+ # before they expire
85
+ TOKEN_REUSE_MIN_LIFE = 60
86
+
87
+ HTTP_ACCEPT_HEADER = 'Accept'
88
+ HTTP_CONTENT_TYPE_HEADER = 'Content-Type'
89
+
90
+ MIME_JSON = 'application/json'
91
+
92
+ SLASH = '/'.freeze
93
+
94
+ VALID_URL_REGEX = /\A#{URI.regexp(%w[https])}\z/.freeze
95
+
96
+ NOT_CONNECTED = 'Not Connected'.freeze
97
+
98
+ # Only these variables are displayed with PrettyPrint
99
+ # This avoids, especially, the caches, which are available
100
+ # as attr_readers
101
+ PP_VARS = %i[
102
+ @name
103
+ @connected
104
+ @host
105
+ @port
106
+ @user
107
+ @base_url
108
+ @ssl_options
109
+ @open_timeout
110
+ @timeout
111
+ @login_time
112
+ @keep_alive
113
+ @token_refresh
114
+ ].freeze
115
+
116
+ # Attributes
117
+ #####################################
118
+
119
+ # @return [String, nil]
120
+ attr_reader :name
121
+
122
+ # @return [String, nil]
123
+ attr_reader :host
124
+
125
+ # @return [Integer, nil]
126
+ attr_reader :port
127
+
128
+ # @return [String, nil]
129
+ attr_reader :user
130
+
131
+ # @return [Integer, nil]
132
+ attr_reader :timeout
133
+
134
+ # @return [Jamf::Connection::Token, nil]
135
+ attr_reader :token
136
+
137
+ # @return [Integer] Refresh the token this many seconds before it expires
138
+ attr_reader :token_refresh
139
+
140
+ # @return [String, nil]
141
+ attr_reader :base_url
142
+
143
+ # @return [Boolean]
144
+ attr_reader :connected
145
+ alias connected? connected
146
+
147
+ # @return [RestClient::Resource] the underlying rest resource
148
+ attr_reader :rest_cnx
149
+
150
+ # when was this connection logged in?
151
+ attr_reader :login_time
152
+
153
+ # @return [Hash]
154
+ # This Hash holds the most recently fetched instance of a SingletonResource
155
+ # subclass, keyed by the subclass itself.
156
+ #
157
+ # SingletonResource.fetch will return the instance from here, if it exists,
158
+ # unless the first parameter is truthy.
159
+
160
+ attr_reader :singleton_cache
161
+
162
+ # @return [Hash]
163
+ # This Hash holds the most recent API data (an Array of Hashes) for the list
164
+ # of all items in a CollectionResource subclass, keyed by the subclass itself.
165
+ #
166
+ # CollectionResource.all return the appropriate data from here, if it exists,
167
+ #
168
+ # See the CollectionResource.all class method.
169
+ attr_reader :collection_cache
170
+
171
+ # @return [Hash]
172
+ # This hash holds ExtensionAttribute instances, which are used
173
+ # for validating values passed to Extendable.set_ext_attr.
174
+ attr_reader :ext_attr_cache
175
+
176
+ # @return [Faraday::Response] The response object from the last API access.
177
+ attr_reader :last_http_response
178
+
179
+ # Constructor
180
+ #####################################
181
+
182
+ # @see #connect
183
+ def initialize(url = nil, **params)
184
+ @name = params.delete :name
185
+ @name ||= NOT_CONNECTED
186
+ connect(url, params) unless params[:at_load]
187
+ end
188
+
189
+ # Public Instance Methods
190
+ #####################################
191
+
192
+ # Connect this Connection object to an Jamf Pro API
193
+ #
194
+ # The first parameter may be a URL (must be https) from which
195
+ # the host & port will be used, and if present, the user and password
196
+ # E.g.
197
+ # connect 'https://myuser:pass@host.domain.edu:8443'
198
+ #
199
+ # which is the same as:
200
+ # connect host: 'host.domain.edu', port: 8443, user: 'myuser', pw: 'pass'
201
+ #
202
+ # When using a URL, other parameters below may be specified, however
203
+ # host: and port: parameters will be ignored, since they came from the URL,
204
+ # as will user: and :pw, if they are present in the URL. If the URL doesn't
205
+ # contain user and pw, they can be provided via the parameters, or left
206
+ # to default values.
207
+ #
208
+ # ### Passwords
209
+ # The pw: parameter also accepts the symbols :prompt, and :stdin[X]
210
+ #
211
+ # If :prompt, the user is promted on the commandline to enter the password
212
+ # for the :user.
213
+ #
214
+ # If :stdin the password is read from the first line of stdin
215
+ #
216
+ # If :stdinX (where X is an integer) the password is read from the Xth
217
+ # line of stdin.see {Jamf.stdin}
218
+ #
219
+ # If omitted, and running from an interactive terminal, the user is
220
+ # prompted as with :prompt
221
+ #
222
+ # ### Tokens
223
+ # Instead of a user and password, you may specify a valid 'token:', either:
224
+ #
225
+ # A Jamf::Connection::Token object, which can be extracted from an active
226
+ # Jamf::Connection via its #token method
227
+ #
228
+ # or
229
+ #
230
+ # A token string e.g. "eyJhdXR...6EKoo" from any source can also be used.
231
+ #
232
+ #
233
+ # Any values available via Jamf.config will be used if they are not provided
234
+ # in the parameters.
235
+ #
236
+ # @param host: [String] The API server hostname. The param 'server:' is a
237
+ # synonym
238
+ #
239
+ # @param port: [Integer] The API server port. If omitted, the value from
240
+ # Jamf.config will be used. If no config value, defaults to 443 if the
241
+ # host ends with 'jamfcloud.com' or 8443 otherwise
242
+ #
243
+ # @param user: [String] The username for the API connection
244
+ #
245
+ # @param pw: [String, Symbol] The password for the user, :prompt, or :stdin[X]
246
+ #
247
+ # @param token: [Jamf::Connection::Token, String] An existing, valid token.
248
+ # When used, there's no need to provide user: or pw:.
249
+ #
250
+ # @param open_timeout: [Integer] The number of seconds for initial contact
251
+ # with the host.
252
+ #
253
+ # @param timeout: [Integer] The number of seconds for a full response from
254
+ # the host.
255
+ #
256
+ # @param ssl_version: [Symbol] The SSL version, e.g. :TLSv1_2
257
+ #
258
+ # @param verify_cert: [Boolean] Should the SSL certificate be verified?
259
+ # Default is true, should only be set to false if using a on-prem
260
+ # server with a self-signed certificate, which is rare
261
+ #
262
+ def connect(url = nil, **params)
263
+ # This sets all the instance vars to nil, and flushes/creates the caches
264
+ disconnect
265
+
266
+ # parse the url if one was given
267
+ params[:url] = url
268
+ parse_url params if params[:url]
269
+
270
+ # apply defaults from config, client, and then this class.
271
+ apply_connection_defaults params
272
+
273
+ # confirm we know a host, port, user and pw
274
+ verify_basic_params params
275
+ parse_connect_params params
276
+
277
+ @token = acquire_token params
278
+ @login_time = @token.login_time
279
+ @user ||= @token.user
280
+
281
+ # if we're here we have a valid token
282
+ @rest_cnx = create_connection
283
+
284
+ validate_api_version
285
+
286
+ @connected = true
287
+
288
+ @keep_alive = params[:keep_alive].nil? ? false : params[:keep_alive]
289
+ @keep_alive && start_keep_alive
290
+ to_s
291
+ end # connect
292
+
293
+ def disconnect
294
+ # reset everything except the name & timeouts
295
+ @connected = false
296
+ @login_time = nil
297
+ @host = nil
298
+ @port = nil
299
+ @user = nil
300
+ @token = nil
301
+ @base_url = nil
302
+ @rest_cnx = nil
303
+ @ssl_version = nil
304
+ flushcache
305
+ end
306
+
307
+ # Same as disconnect, but invalidates the token
308
+ def logout
309
+ @token.destroy
310
+ disconnect
311
+ end
312
+
313
+ def get(rsrc)
314
+ validate_connected
315
+ resp = @rest_cnx.get rsrc
316
+ @last_http_response = resp
317
+ return resp.body if resp.success?
318
+
319
+ raise Jamf::Connection::APIError.new(resp)
320
+ end
321
+
322
+ # GET a rsrc without doing any JSON parsing, using
323
+ # a temporary Faraday connection object
324
+ def download(rsrc)
325
+ temp_cnx = create_connection(false)
326
+ resp = temp_cnx.get rsrc
327
+ @last_http_response = resp
328
+ return resp.body if resp.success?
329
+
330
+ raise Jamf::Connection::APIError.new(resp)
331
+ end
332
+
333
+ def post(rsrc, data)
334
+ validate_connected
335
+ resp = @rest_cnx.post(rsrc) do |req|
336
+ req.body = data
337
+ end
338
+ @last_http_response = resp
339
+ return resp.body if resp.success?
340
+
341
+ raise Jamf::Connection::APIError.new(resp)
342
+ end
343
+
344
+ def put(rsrc, data)
345
+ validate_connected
346
+ resp = @rest_cnx.put(rsrc) do |req|
347
+ req.body = data
348
+ end
349
+ @last_http_response = resp
350
+ return resp.body if resp.success?
351
+
352
+ raise Jamf::Connection::APIError.new(resp)
353
+ end
354
+
355
+ def patch(rsrc, data)
356
+ validate_connected
357
+ resp = @rest_cnx.patch(rsrc) do |req|
358
+ req.body = data
359
+ end
360
+ @last_http_response = resp
361
+ return resp.body if resp.success?
362
+
363
+ raise Jamf::Connection::APIError.new(resp)
364
+ end
365
+
366
+ def delete(rsrc)
367
+ validate_connected
368
+ resp = @rest_cnx.delete rsrc
369
+ @last_http_response = resp
370
+ return resp.body if resp.success?
371
+
372
+ raise Jamf::Connection::APIError.new(resp)
373
+ end
374
+
375
+ # A useful string about this connection
376
+ #
377
+ # @return [String]
378
+ #
379
+ def to_s
380
+ "Jamf::Connection: https://#{@user}@#{@host}:#{@port}"
381
+ end
382
+
383
+ def keep_alive?
384
+ !@keep_alive_thread.nil?
385
+ end
386
+
387
+ def keep_alive=(bool)
388
+ bool ? start_keep_alive : stop_keep_alive
389
+ end
390
+
391
+ # This should take effect even if we're already running the keep_alive thread
392
+ #
393
+ def token_refresh=(secs)
394
+ raise ArgumentError, 'Value must be an Integer number of seconds' unless secs.is_a? Integer
395
+
396
+ @token_refresh = secs
397
+ end
398
+
399
+ def api_version
400
+ @token.api_version
401
+ end
402
+
403
+ # Flush the collection and/or ea cache for the given class,
404
+ # or all cached data
405
+ # @param klass[Class] the class of cache to flush
406
+ #
407
+ # @return [void]
408
+ #
409
+ def flushcache(klass = nil)
410
+ if klass
411
+ @collection_cache.delete klass
412
+ @singleton_cache.delete klass
413
+ @ext_attr_cache.delete klass
414
+ else
415
+ @collection_cache = {}
416
+ @singleton_cache = {}
417
+ @ext_attr_cache = {}
418
+ end
419
+ end
420
+
421
+ # Remove large cached items from
422
+ # the instance_variables used to create
423
+ # pretty-print (pp) output.
424
+ #
425
+ # @return [Array] the desired instance_variables
426
+ #
427
+ def pretty_print_instance_variables
428
+ PP_VARS
429
+ end
430
+
431
+ # Private Insance Methods
432
+ ####################################
433
+ private
434
+
435
+ # given a token string or a password, get a valid token
436
+ # Token.new will raise an exception if the token string or
437
+ # credentials are invalid
438
+ def token_from(type, data)
439
+ token_params = {
440
+ user: @user,
441
+ base_url: @base_url,
442
+ timeout: @timeout,
443
+ ssl_options: @ssl_options
444
+ }
445
+
446
+ case type
447
+ when :token_string
448
+ token_params[:token_string] = data
449
+ when :pw
450
+ token_params[:pw] = data
451
+ end
452
+ self.class::Token.new token_params
453
+ end
454
+
455
+ # raise exception if not connected
456
+ def validate_connected
457
+ raise Jamf::InvalidConnectionError, 'Not Connected. Use .connect first.' unless connected?
458
+ end
459
+
460
+ # raise exception if API version is too low.
461
+ def validate_api_version
462
+ vers = api_version
463
+ return if Gem::Version.new(vers) >= MIN_API_VERSION
464
+
465
+ raise Jamf::InvalidConnectionError, "API version '#{vers}' too low, must be >= '#{MIN_API_VERSION}'"
466
+ end
467
+
468
+ def parse_url(params)
469
+ url = URI.parse params[:url].to_s
470
+ raise ArgumentError, 'Invalid url, scheme must be https' unless url.scheme == HTTPS_SCHEME
471
+
472
+ params[:user] = url.user
473
+ params[:pw] = url.password
474
+ params[:host] = url.host
475
+ params[:port] = url.port
476
+ end
477
+
478
+ # Apply defaults from the Jamf.config,
479
+ # then from the Jamf::Client,
480
+ # then from the module defaults
481
+ # to the params for the #connect method
482
+ #
483
+ # @param params[Hash] The params for #connect
484
+ #
485
+ # @return [Hash] The params with defaults applied
486
+ #
487
+ def apply_connection_defaults(params)
488
+ apply_defaults_from_config(params)
489
+ # TODO: when clients are moved over
490
+ # apply_defaults_from_client(params)
491
+ apply_module_defaults(params)
492
+
493
+ # if we have a TTY, pw defaults to :prompt
494
+ params[:pw] ||= :prompt if STDIN.tty?
495
+ end
496
+
497
+ # Apply defaults from the Jamf.config
498
+ # to the params for the #connect method
499
+ #
500
+ # @param params[Hash] The params for #connect
501
+ #
502
+ # @return [Hash] The params with defaults applied
503
+ #
504
+ def apply_defaults_from_config(params)
505
+ # settings from config if they aren't in the params
506
+ params[:host] ||= Jamf.config.api_server_name
507
+ params[:port] ||= Jamf.config.api_server_port
508
+ params[:user] ||= Jamf.config.api_username
509
+ params[:timeout] ||= Jamf.config.api_timeout
510
+ params[:open_timeout] ||= Jamf.config.api_timeout_open
511
+ params[:ssl_version] ||= Jamf.config.api_ssl_version
512
+
513
+ # if verify cert was not in the params, get it from the prefs.
514
+ # We can't use ||= because the desired value might be 'false'
515
+ params[:verify_cert] = Jamf.config.api_verify_cert if params[:verify_cert].nil?
516
+ end # apply_defaults_from_config
517
+
518
+ # Apply defaults from the Jamf::Client
519
+ # to the params for the #connect method
520
+ #
521
+ # @param params[Hash] The params for #connect
522
+ #
523
+ # @return [Hash] The params with defaults applied
524
+ #
525
+ def apply_defaults_from_client(params)
526
+ return unless Jamf::Client.installed?
527
+
528
+ # these settings can come from the jamf binary config,
529
+ # if this machine is a Jamf client.
530
+ params[:host] ||= Jamf::Client.jss_server
531
+ params[:port] ||= Jamf::Client.jss_port.to_i
532
+ end
533
+
534
+ # Apply the module defaults to the params for the #connect method
535
+ #
536
+ # @param params[Hash] The params for #connect
537
+ #
538
+ # @return [Hash] The params with defaults applied
539
+ #
540
+ def apply_module_defaults(params)
541
+ params[:port] ||= params[:host].to_s.end_with?(JAMFCLOUD_DOMAIN) ? JAMFCLOUD_PORT : ON_PREM_SSL_PORT
542
+ params[:timeout] ||= DFT_TIMEOUT
543
+ params[:open_timeout] ||= DFT_OPEN_TIMEOUT
544
+ params[:ssl_version] ||= DFT_SSL_VERSION
545
+ end
546
+
547
+ # From whatever was given in params[:pw], figure out the real password
548
+ #
549
+ # @param params[Hash] The params for #connect
550
+ #
551
+ # @return [String] The password for the connection
552
+ #
553
+ def acquire_password(params)
554
+ if params[:pw] == :prompt
555
+ Jamf.prompt_for_password "Enter the password for Jamf user #{params[:user]}@#{params[:host]}:"
556
+ elsif params[:pw].is_a?(Symbol) && params[:pw].to_s.start_with?('stdin')
557
+ params[:pw].to_s =~ /^stdin(\d+)$/
558
+ line = Regexp.last_match(1)
559
+ line ||= 1
560
+ Jamf.stdin line
561
+ else
562
+ params[:pw]
563
+ end
564
+ end
565
+
566
+ # Raise execeptions if we don't have essential data for a new connection
567
+ #
568
+ # @param params[Hash] The params for #connect
569
+ #
570
+ # @return [void]
571
+ #
572
+ def verify_basic_params(params)
573
+ # if given a Token object, it has host, port, user, and base_url
574
+ # and will be verified and parsed
575
+ return if params[:token].is_a? self.class::Token
576
+
577
+ # must have a host, accept :server as well as :host
578
+ params[:host] ||= params[:server]
579
+ raise Jamf::MissingDataError, 'No Jamf :host specified, or in configuration.' unless params[:host]
580
+
581
+ # no need for user or pass if using a token string
582
+ return if params[:token]
583
+
584
+ raise Jamf::MissingDataError, 'No Jamf :user specified, or in configuration.' unless params[:user]
585
+ raise Jamf::MissingDataError, "No :pw specified for user '#{params[:user]}'" unless params[:pw]
586
+ end
587
+
588
+ def parse_connect_params(params)
589
+ @host = params[:host]
590
+ @port = params[:port]
591
+ @port ||= @host.end_with?(JAMFCLOUD_DOMAIN) ? JAMFCLOUD_PORT : ON_PREM_SSL_PORT
592
+ @user = params[:user]
593
+ @token_refresh = params[:token_refresh] || DFT_TOKEN_REFRESH
594
+ @timeout = params[:timeout] || DFT_TIMEOUT
595
+ @open_timeout = params[:open_timeout] || DFT_TIMEOUT
596
+ @base_url = URI.parse "https://#{@host}:#{@port}/#{RSRC_BASE}"
597
+ # ssl opts for faraday
598
+ # TODO: implement all of faraday's options
599
+ @ssl_options = {
600
+ verify: params[:verify_cert],
601
+ version: params[:ssl_version]
602
+ }
603
+ @name = "#{@user}@#{@host}:#{@port}" if @name == NOT_CONNECTED
604
+ end
605
+
606
+ # Get our token either from a passwd or another token or token string
607
+ def acquire_token(params)
608
+ if params[:token]
609
+ if params[:token].is_a? self.class::Token
610
+ verify_token params[:token]
611
+ parse_token params[:token]
612
+ params[:token]
613
+ else
614
+ token_from :token_string, params[:token].to_s
615
+ end
616
+ else
617
+ token_from :pw, acquire_password(params)
618
+ end
619
+ end
620
+
621
+ # Raise execeptions if we were given an unusable token
622
+ #
623
+ # @param params[Hash] The params for #connect
624
+ #
625
+ # @return [void]
626
+ #
627
+ def verify_token(token)
628
+ raise 'Cannot use token: it has expired' if token.expired?
629
+ raise 'Cannot use token: it is invalid' unless token.valid?
630
+ raise "Cannot use token: it expires in less than #{TOKEN_REUSE_MIN_LIFE} seconds" if token.secs_remaining < TOKEN_REUSE_MIN_LIFE
631
+ end
632
+
633
+ def parse_token(token)
634
+ @host = token.host
635
+ @port = token.port
636
+ @user = token.user
637
+ @base_url = token.base_url
638
+ end
639
+
640
+ # create the faraday connection object
641
+ def create_connection(parse_json = true)
642
+ Faraday.new(@base_url, ssl: @ssl_options) do |cnx|
643
+ cnx.headers[HTTP_ACCEPT_HEADER] = MIME_JSON
644
+ cnx.headers[:authorization] = @token.auth_token
645
+ cnx.request :json if parse_json
646
+ cnx.response :json, parser_options: { symbolize_names: true } if parse_json
647
+ cnx.options[:timeout] = @timeout
648
+ cnx.options[:open_timeout] = @open_timeout
649
+ cnx.use Faraday::Adapter::NetHttp
650
+ end
651
+ end
652
+
653
+ # creates a thread that loops forever, sleeping most of the time, but
654
+ # waking up every 60 seconds to see if the token is expiring in the
655
+ # next @token_refresh seconds.
656
+ #
657
+ # If so, the token is refreshed, and we keep looping and sleeping.
658
+ #
659
+ # Sets @keep_alive_thread to the Thread object
660
+ #
661
+ # @return [void]
662
+ #
663
+ def start_keep_alive
664
+ return if @keep_alive_thread
665
+ raise 'Token expired' if @token.expired?
666
+
667
+ @keep_alive_thread =
668
+ Thread.new do
669
+ loop do
670
+ sleep 60
671
+ next if @token.secs_remaining > @token_refresh
672
+
673
+ @token.keep_alive
674
+ end # loop
675
+ end # thread
676
+ end
677
+
678
+ # Kills the @keep_alive_thread, if it exists, and sets
679
+ # @keep_alive_thread to nil
680
+ #
681
+ # @return [void]
682
+ #
683
+ def stop_keep_alive
684
+ return unless @keep_alive_thread
685
+
686
+ @keep_alive_thread.kill
687
+ @keep_alive_thread = nil
688
+ end
689
+
690
+ end # class Connection
691
+
692
+ # Jamf module methods dealing with the active connection
693
+
694
+ # @return [Jamf::Connection] the active connection
695
+ #
696
+ def self.cnx
697
+ @active_connection ||= Connection.new
698
+ end
699
+
700
+ # Create a new Connection object and use it as the active_connection,
701
+ # replacing the current active_connection. If connection options are provided,
702
+ # they are passed to the connect method immediately, otherwise
703
+ # Jamf.cnx.connect must be called before attemting to use the
704
+ # connection.
705
+ #
706
+ # @param (See Jamf::Connection#connect)
707
+ #
708
+ # @return [APIConnection] the new, active connection
709
+ #
710
+ def self.connect(url = nil, **params)
711
+ @active_connection = Connection.new url, params
712
+ @active_connection.to_s
713
+ end
714
+
715
+ # Switch the connection used for all API interactions to the
716
+ # one provided. See {Jamf::APIConnection} for details and examples
717
+ # of using multiple connections
718
+ #
719
+ # @param connection [APIConnection] The APIConnection to use for future
720
+ # API calls. If omitted, use the default connection created when ruby-jss
721
+ # was loaded (which may or may not yet be connected)
722
+ #
723
+ # @return [APIConnection] The connection now being used.
724
+ #
725
+ def self.cnx=(connection)
726
+ raise 'API connections must be instances of Jamf::Connection' unless connection.is_a? Jamf::Connection
727
+
728
+ @active_connection = connection
729
+ end
730
+
731
+ # create the default connection
732
+ connect(at_load: true) unless @active_connection
733
+
734
+ end # module Jamf