ruby-jss 1.2.3 → 1.2.4a1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/jamf.rb +169 -0
- data/lib/jamf/api/abstract_classes/collection_resource.rb +422 -0
- data/lib/jamf/api/abstract_classes/generic_reference.rb +145 -0
- data/lib/jamf/api/abstract_classes/json_object.rb +1074 -0
- data/lib/jamf/api/abstract_classes/prestage.rb +219 -0
- data/lib/jamf/api/abstract_classes/prestage_skip_setup_items.rb +126 -0
- data/lib/jamf/api/abstract_classes/resource.rb +250 -0
- data/lib/jamf/api/abstract_classes/singleton_resource.rb +87 -0
- data/lib/jamf/api/attribute_classes/ip_address.rb +66 -0
- data/lib/jamf/api/attribute_classes/timestamp.rb +144 -0
- data/lib/jamf/api/connection.rb +734 -0
- data/lib/jamf/api/connection/api_error.rb +111 -0
- data/lib/jamf/api/connection/api_error_styleguide.rb +96 -0
- data/lib/jamf/api/connection/token.rb +220 -0
- data/lib/jamf/api/json_objects/account_prefs.rb +79 -0
- data/lib/jamf/api/json_objects/android_details.rb +139 -0
- data/lib/jamf/api/json_objects/appletv_details.rb +110 -0
- data/lib/jamf/api/json_objects/attachment.rb +68 -0
- data/lib/jamf/api/json_objects/cellular_network.rb +151 -0
- data/lib/jamf/api/json_objects/change_log_entry.rb +77 -0
- data/lib/jamf/api/json_objects/computer_prestage_skip_setup_items.rb +67 -0
- data/lib/jamf/api/json_objects/country.rb +51 -0
- data/lib/jamf/api/json_objects/extension_attribute_value.rb +128 -0
- data/lib/jamf/api/json_objects/installed_application.rb +59 -0
- data/lib/jamf/api/json_objects/installed_certificate.rb +53 -0
- data/lib/jamf/api/json_objects/installed_configuration_profile.rb +67 -0
- data/lib/jamf/api/json_objects/installed_ebook.rb +58 -0
- data/lib/jamf/api/json_objects/installed_provisioning_profile.rb +59 -0
- data/lib/jamf/api/json_objects/inventory_preload_extension_attribute.rb +52 -0
- data/lib/jamf/api/json_objects/ios_details.rb +244 -0
- data/lib/jamf/api/json_objects/location.rb +95 -0
- data/lib/jamf/api/json_objects/md_prestage_name.rb +57 -0
- data/lib/jamf/api/json_objects/md_prestage_names.rb +82 -0
- data/lib/jamf/api/json_objects/md_prestage_skip_setup_items.rb +165 -0
- data/lib/jamf/api/json_objects/mobile_device_details.rb +219 -0
- data/lib/jamf/api/json_objects/mobile_device_security.rb +101 -0
- data/lib/jamf/api/json_objects/prestage_assignment.rb +61 -0
- data/lib/jamf/api/json_objects/prestage_location.rb +104 -0
- data/lib/jamf/api/json_objects/prestage_purchasing_data.rb +132 -0
- data/lib/jamf/api/json_objects/prestage_scope.rb +54 -0
- data/lib/jamf/api/json_objects/prestage_sync_status.rb +63 -0
- data/lib/jamf/api/json_objects/purchasing_data.rb +125 -0
- data/lib/jamf/api/mixins/abstract.rb +58 -0
- data/lib/jamf/api/mixins/bulk_deletable.rb +39 -0
- data/lib/jamf/api/mixins/change_log.rb +136 -0
- data/lib/jamf/api/mixins/extendable.rb +75 -0
- data/lib/jamf/api/mixins/immutable.rb +39 -0
- data/lib/jamf/api/mixins/locatable.rb +124 -0
- data/lib/jamf/api/mixins/lockable.rb +48 -0
- data/lib/jamf/api/mixins/referable.rb +92 -0
- data/lib/jamf/api/mixins/searchable.rb +202 -0
- data/lib/jamf/api/mixins/uncreatable.rb +40 -0
- data/lib/jamf/api/mixins/undeletable.rb +40 -0
- data/lib/jamf/api/resources/collection_resources/account.rb +163 -0
- data/lib/jamf/api/resources/collection_resources/building.rb +114 -0
- data/lib/jamf/api/resources/collection_resources/category.rb +82 -0
- data/lib/jamf/api/resources/collection_resources/computer.rb +49 -0
- data/lib/jamf/api/resources/collection_resources/computer_prestage.rb +80 -0
- data/lib/jamf/api/resources/collection_resources/department.rb +79 -0
- data/lib/jamf/api/resources/collection_resources/extension_attribute.rb +45 -0
- data/lib/jamf/api/resources/collection_resources/inventory_preload_record.rb +274 -0
- data/lib/jamf/api/resources/collection_resources/md_prestage.rb +139 -0
- data/lib/jamf/api/resources/collection_resources/mobile_device.rb +315 -0
- data/lib/jamf/api/resources/collection_resources/script.rb +190 -0
- data/lib/jamf/api/resources/collection_resources/site.rb +77 -0
- data/lib/jamf/api/resources/singleton_resources/app_store_country_codes.rb +131 -0
- data/lib/jamf/api/resources/singleton_resources/authorization.rb +88 -0
- data/lib/jamf/api/resources/singleton_resources/client_checkin_settings.rb +139 -0
- data/lib/jamf/api/resources/singleton_resources/reenrollment_settings.rb +95 -0
- data/lib/jamf/client.rb +301 -0
- data/lib/jamf/client/jamf_binary.rb +132 -0
- data/lib/jamf/client/jamf_helper.rb +298 -0
- data/lib/jamf/client/management_action.rb +114 -0
- data/lib/jamf/compatibility.rb +88 -0
- data/lib/jamf/composer.rb +190 -0
- data/lib/jamf/configuration.rb +281 -0
- data/lib/jamf/exceptions.rb +107 -0
- data/lib/jamf/ruby_extensions.rb +36 -0
- data/lib/jamf/ruby_extensions/array.rb +35 -0
- data/lib/jamf/ruby_extensions/array/predicates.rb +46 -0
- data/lib/jamf/ruby_extensions/array/utils.rb +47 -0
- data/lib/jamf/ruby_extensions/filetest.rb +32 -0
- data/lib/jamf/ruby_extensions/filetest/predicates.rb +46 -0
- data/lib/jamf/ruby_extensions/hash.rb +33 -0
- data/lib/jamf/ruby_extensions/hash/backports.rb +92 -0
- data/lib/jamf/ruby_extensions/ipaddr.rb +37 -0
- data/lib/jamf/ruby_extensions/ipaddr/utils.rb +95 -0
- data/lib/jamf/ruby_extensions/object.rb +30 -0
- data/lib/jamf/ruby_extensions/object/predicates.rb +51 -0
- data/lib/jamf/ruby_extensions/pathname.rb +39 -0
- data/lib/jamf/ruby_extensions/pathname/predicates.rb +50 -0
- data/lib/jamf/ruby_extensions/pathname/utils.rb +75 -0
- data/lib/jamf/ruby_extensions/string.rb +35 -0
- data/lib/jamf/ruby_extensions/string/backports.rb +66 -0
- data/lib/jamf/ruby_extensions/string/conversions.rb +65 -0
- data/lib/jamf/ruby_extensions/string/predicates.rb +47 -0
- data/lib/jamf/utility.rb +423 -0
- data/lib/jamf/validate.rb +224 -0
- data/lib/jamf/version.rb +32 -0
- data/lib/jpapi.rb +26 -0
- data/lib/jss/version.rb +1 -1
- 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
|