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.
- 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
|