Class: Jamf::Connection::Token

Inherits:
Object
  • Object
show all
Defined in:
lib/jamf/api/connection/token.rb

Overview

A token used for a connection to either API

Constant Summary collapse

AUTH_RSRC_VERSION =
'v1'.freeze
AUTH_RSRC =
'auth'.freeze
NEW_TOKEN_RSRC =
"#{AUTH_RSRC_VERSION}/#{AUTH_RSRC}/token".freeze
KEEP_ALIVE_RSRC =
"#{AUTH_RSRC_VERSION}/#{AUTH_RSRC}/keep-alive".freeze
INVALIDATE_RSRC =
"#{AUTH_RSRC_VERSION}/#{AUTH_RSRC}/invalidate-token".freeze
JAMF_VERSION_RSRC_VERSION =
'v1'.freeze
JAMF_VERSION_RSRC =
"#{JAMF_VERSION_RSRC_VERSION}/jamf-pro-version".freeze
OAUTH_RSRC =
'oauth'.freeze
API_CLIENT_TOKEN_RSRC =
"#{OAUTH_RSRC}/token".freeze
API_CLIENT_GRANT_TYPE =
'client_credentials'.freeze
JAMF_TRYITOUT_HOST =

Recognize the tryitout server, cuz its /auth endpoint is disabled, and it needs no tokens TODO: MOVE THIS TO THE CONNECTION CLASS

"tryitout#{Jamf::Connection::JAMFCLOUD_DOMAIN}".freeze
JAMF_TRYITOUT_TOKEN_BODY =
{
  token: 'This is a fake token, tryitout.jamfcloud.com uses internal tokens',
  expires: 2_000_000_000_000
}.freeze
MIN_REFRESH_BUFFER =

Minimum seconds before expiration that the token will automatically refresh. Used as the default if :refresh is not provided in the init params

300
REFRESH_RESULTS =

Used bu the last_refresh_result method

{
  refreshed: 'Refreshed',
  refreshed_pw: 'Refresh failed, but new token created with cached pw',
  refresh_failed: 'Refresh failed, could not create new token with cached pw',
  refresh_failed_no_pw_fallback: 'Refresh failed, but pw_fallback was false',
  expired_refreshed: 'Expired, but new token created with cached pw',
  expired_failed: 'Expired, could not create new token with cached pw',
  expired_no_pw_fallback: 'Expired, but pw_fallback was false'
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**params) ⇒ Token

Returns a new instance of Token.

Options Hash (**params):

  • :token_string (String)

    An existing valid token string. If pw_fallback is true (the default) you will also need to provide the password for the original user in the pw: parameter. If you don’t, pw_fallback will be false even if you set it to true explicitly.

  • :base_url (String, URI)

    The url for the Jamf Pro server including host and port, e.g. ‘myjss.school.edu:8443/’

  • :user (String) — default: see Connection#initialize
  • :pw (String) — default: see Connection#initialize
  • :timeout (Integer)

    The timeout for creating or refreshing the token

  • :keep_alive (Boolean) — default: see Connection#connect
  • :refresh_buffer (Integer) — default: see Connection#connect
  • :pw_fallback (Boolean) — default: see Connection#connect
  • :ssl_version (String, Symbol) — default: see Connection#connect
  • :verify_cert (Boolean) — default: see Connection#connect

Parameters:

  • The data for creating and maintaining the token



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/jamf/api/connection/token.rb', line 138

def initialize(**params)
  @valid = false
  parse_params(**params)

  if params[:token_string]
    @pw_fallback = false unless @pw
    init_from_token_string params[:token_string]

  elsif @client_id
    init_for_api_client

  elsif @user && @pw
    init_from_pw

  else
    raise ArgumentError, 'Must provide either user: & pw: or token:'
  end

  start_keep_alive if @keep_alive
  @creation_time = Time.now
end

Instance Attribute Details

#base_urlURI (readonly)

Returns The base API url, e.g. myjamf.jamfcloud.com/.

Returns:



81
82
83
# File 'lib/jamf/api/connection/token.rb', line 81

def base_url
  @base_url
end

#creation_http_responseFaraday::Response (readonly)

Returns The response object from instantiating a new Token object by creating a new token or validating a token string. This is not updated when refreshing a token, only when calling Token.new.

Returns:

  • The response object from instantiating a new Token object by creating a new token or validating a token string. This is not updated when refreshing a token, only when calling Token.new



108
109
110
# File 'lib/jamf/api/connection/token.rb', line 108

def creation_http_response
  @creation_http_response
end

#creation_timeTime (readonly) Also known as: login_time

Returns when was this Jamf::Connection::Token originally created?.

Returns:

  • when was this Jamf::Connection::Token originally created?



84
85
86
# File 'lib/jamf/api/connection/token.rb', line 84

def creation_time
  @creation_time
end

#expiresTime (readonly) Also known as: expiration

Returns:



91
92
93
# File 'lib/jamf/api/connection/token.rb', line 91

def expires
  @expires
end

#last_refreshTime (readonly)

Returns when was this token last refreshed?.

Returns:

  • when was this token last refreshed?



88
89
90
# File 'lib/jamf/api/connection/token.rb', line 88

def last_refresh
  @last_refresh
end

#pw_fallbackBoolean (readonly) Also known as: pw_fallback?

Returns Should the provided passwd be cached in memory, to be used to generate a new token, if a normal refresh fails?.

Returns:

  • Should the provided passwd be cached in memory, to be used to generate a new token, if a normal refresh fails?



101
102
103
# File 'lib/jamf/api/connection/token.rb', line 101

def pw_fallback
  @pw_fallback
end

#ssl_optionsHash (readonly)

Returns the ssl version and verify cert, to pass into faraday connections.

Returns:

  • the ssl version and verify cert, to pass into faraday connections



73
74
75
# File 'lib/jamf/api/connection/token.rb', line 73

def ssl_options
  @ssl_options
end

#ssl_versionString (readonly)

Returns the SSL version being used.

Returns:

  • the SSL version being used



66
67
68
# File 'lib/jamf/api/connection/token.rb', line 66

def ssl_version
  @ssl_version
end

#tokenString (readonly) Also known as: token_string, auth_token

Returns The token data.

Returns:

  • The token data



76
77
78
# File 'lib/jamf/api/connection/token.rb', line 76

def token
  @token
end

#userString (readonly)

Returns The user who generated this token.

Returns:

  • The user who generated this token



63
64
65
# File 'lib/jamf/api/connection/token.rb', line 63

def user
  @user
end

#verify_certBoolean (readonly) Also known as: verify_cert?

Returns are we verifying SSL certs?.

Returns:

  • are we verifying SSL certs?



69
70
71
# File 'lib/jamf/api/connection/token.rb', line 69

def verify_cert
  @verify_cert
end

Instance Method Details

#accountJamf::OAPISchemas::AuthorizationV1

the Jamf account assciated with this token, which contains info about privileges and Jamf acct group memberships and Jamf Acct settings

Returns:

  • the Authorization object



331
332
333
334
335
336
337
338
# File 'lib/jamf/api/connection/token.rb', line 331

def 
  return @account if @account

  resp = token_connection(AUTH_RSRC, token: @token).get
  return unless resp.success?

  @account = Jamf::OAPISchemas::AuthorizationV1.new resp.body
end

#expired?Boolean

Returns:



252
253
254
255
256
# File 'lib/jamf/api/connection/token.rb', line 252

def expired?
  return unless @expires

  Time.now >= @expires
end

#hostObject



226
227
228
# File 'lib/jamf/api/connection/token.rb', line 226

def host
  @base_url.host
end

#init_for_api_clientObject

Initialize for an API client



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/jamf/api/connection/token.rb', line 162

def init_for_api_client
  data = {
    client_id: @client_id,
    client_secret: Base64.decode64(@pw),
    grant_type: API_CLIENT_GRANT_TYPE
  }

  resp = api_client_auth_connection(API_CLIENT_TOKEN_RSRC).post { |req| req.body = data }

  if resp.success?
    parse_token_from_api_client_auth resp
    @creation_http_response = resp
    whoami = token_connection(AUTH_RSRC, token: @token).get
    @user = "#{whoami.body[:account][:username]} (API Client)"
  elsif resp.status == 401
    raise Jamf::AuthenticationError, 'Incorrect client_id or client_secret'
  else
    # TODO: better error reporting here
    raise Jamf::AuthenticationError, "An error occurred while authenticating client_id: #{resp.body}"
  end
ensure
  @pw = nil
end

#init_from_pwObject

Initialize from password



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/jamf/api/connection/token.rb', line 188

def init_from_pw
  resp = token_connection(NEW_TOKEN_RSRC).post

  if resp.success?
    parse_token_from_response resp
    @last_refresh = Time.now
    @creation_http_response = resp
  elsif resp.status == 401
    raise Jamf::AuthenticationError, 'Incorrect name or password'
  else
    # TODO: better error reporting here
    raise Jamf::AuthenticationError, "An error occurred while authenticating: #{resp.body}"
  end
ensure
  @pw = nil unless @pw_fallback
end

#init_from_token_string(str) ⇒ Object

Initialize from token string

Raises:



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/jamf/api/connection/token.rb', line 207

def init_from_token_string(str)
  resp = token_connection(AUTH_RSRC, token: str).get
  raise Jamf::InvalidDataError, 'Token is not valid' unless resp.success?

  @creation_http_response = resp
  @token = str
  @user = resp.body.dig :account, :username

  # if we were given a pw for the user, and expect to use it, validate it now
  if @pw && @pw_fallback
    resp = token_connection(NEW_TOKEN_RSRC).post
    raise Jamf::AuthenticationError, "Incorrect password provided for token string (user: #{@user})" unless resp.success?
  end

  # use this token to get a fresh one with a known expiration
  refresh
end

#invalidateObject Also known as: destroy

Make this token invalid



381
382
383
384
385
# File 'lib/jamf/api/connection/token.rb', line 381

def invalidate
  @valid = !token_connection(INVALIDATE_RSRC, token: @token).post.success?
  @pw = nil
  stop_keep_alive
end

#jamf_buildString

Returns:



245
246
247
248
# File 'lib/jamf/api/connection/token.rb', line 245

def jamf_build
  fetch_jamf_version unless @jamf_build
  @jamf_build
end

#jamf_versionGem::Version

Returns:



238
239
240
241
# File 'lib/jamf/api/connection/token.rb', line 238

def jamf_version
  fetch_jamf_version unless @jamf_version
  @jamf_version
end

#last_refresh_resultString?

What happened the last time we tried to refresh? See REFRESH_RESULTS

Returns:

  • result or nil if never refreshed



322
323
324
# File 'lib/jamf/api/connection/token.rb', line 322

def last_refresh_result
  REFRESH_RESULTS[@last_refresh_result]
end

#next_refreshTime?

when is the next rerefresh going to happen, if we are set to keep alive?

Returns:

  • the time of the next scheduled refresh, or nil if not keep_alive?



261
262
263
264
265
# File 'lib/jamf/api/connection/token.rb', line 261

def next_refresh
  return unless keep_alive?

  @expires - @refresh_buffer
end

#portInteger

Returns:



232
233
234
# File 'lib/jamf/api/connection/token.rb', line 232

def port
  @base_url.port
end

#refreshTime Also known as: keep_alive

Use this token to get a fresh one. If a pw is provided try to use it to get a new token if a proper refresh fails.

Parameters:

  • Optional password to use if token refresh fails. Must be the correct passwd or the token’s user (obviously)

Returns:

  • the new expiration time



349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/jamf/api/connection/token.rb', line 349

def refresh
  # already expired?
  if expired?
    # try the passwd if we have it
    return refresh_with_pw(:expired_refreshed, :expired_failed) if @pw

    # no passwd fallback? no chance!
    @last_refresh_result = :expired_no_pw_fallback
    raise Jamf::InvalidTokenError, 'Token has expired'
  end

  # Now try a normal refresh of our non-expired token
  keep_alive_token_resp = token_connection(KEEP_ALIVE_RSRC, token: @token).post

  if keep_alive_token_resp.success?
    parse_token_from_response keep_alive_token_resp
    @last_refresh_result = :refreshed
    @last_refresh = Time.now
    return expires
  end

  # if we're here, the normal refresh failed, so try the pw
  return refresh_with_pw(:refreshed_pw, :refresh_failed) if @pw

  # if we're here, no pw? no chance!
  @last_refresh_result = :refresh_failed_no_pw_fallback
  raise 'An error occurred while refreshing the token'
end

#secs_remainingFloat

Returns:



290
291
292
293
294
# File 'lib/jamf/api/connection/token.rb', line 290

def secs_remaining
  return unless @expires

  @expires - Time.now
end

#secs_to_refreshFloat?

how many secs until the next refresh? will return 0 during the actual refresh process.

Returns:

  • Seconds until the next scheduled refresh, or nil if not keep_alive?



272
273
274
275
276
277
# File 'lib/jamf/api/connection/token.rb', line 272

def secs_to_refresh
  return unless keep_alive?

  secs = next_refresh - Time.now
  secs.negative? ? 0 : secs
end

#start_keep_alivevoid

This method returns an undefined value.

creates a thread that loops forever, sleeping most of the time, but waking up every 60 seconds to see if the token is expiring in the next @refresh_buffer seconds.

If so, the token is refreshed, and we keep looping and sleeping.

Sets @keep_alive_thread to the Thread object



398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# File 'lib/jamf/api/connection/token.rb', line 398

def start_keep_alive
  return if @keep_alive_thread
  raise 'Token expired, cannot refresh' if expired?

  @keep_alive_thread =
    Thread.new do
      loop do
        sleep 60
        begin
          next if secs_remaining > @refresh_buffer

          refresh
        rescue
          # TODO: Some kind of error reporting
          next
        end
      end # loop
    end # thread
end

#stop_keep_alivevoid

This method returns an undefined value.

Kills the @keep_alive_thread, if it exists, and sets



423
424
425
426
427
428
# File 'lib/jamf/api/connection/token.rb', line 423

def stop_keep_alive
  return unless @keep_alive_thread

  @keep_alive_thread.kill if @keep_alive_thread.alive?
  @keep_alive_thread = nil
end

#time_remainingString

Returns e.g. “1 week 6 days 23 hours 49 minutes 56 seconds”.

Returns:

  • e.g. “1 week 6 days 23 hours 49 minutes 56 seconds”



298
299
300
301
302
# File 'lib/jamf/api/connection/token.rb', line 298

def time_remaining
  return unless @expires

  JSS.humanize_secs secs_remaining
end

#time_to_refreshString?

Returns e.g. “1 week 6 days 23 hours 49 minutes 56 seconds”

Returns:



282
283
284
285
286
# File 'lib/jamf/api/connection/token.rb', line 282

def time_to_refresh
  return unless keep_alive?

  Jamf.humanize_secs secs_to_refresh
end

#valid?Boolean

Returns:



306
307
308
309
310
311
312
313
314
315
# File 'lib/jamf/api/connection/token.rb', line 306

def valid?
  @valid =
    if expired?
      false
    elsif !@token
      false
    else
      token_connection(AUTH_RSRC, token: @token).get.success?
    end
end