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.

Parameters:

  • params (Hash)

    The data for creating and maintaining the 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


156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/jamf/api/connection/token.rb', line 156

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:



99
100
101
# File 'lib/jamf/api/connection/token.rb', line 99

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:

  • (Faraday::Response)

    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



126
127
128
# File 'lib/jamf/api/connection/token.rb', line 126

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:

  • (Time)

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



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

def creation_time
  @creation_time
end

#expiresTime (readonly) Also known as: expiration

Returns:



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

def expires
  @expires
end

#last_refreshTime (readonly)

Returns when was this token last refreshed?.

Returns:

  • (Time)

    when was this token last refreshed?



106
107
108
# File 'lib/jamf/api/connection/token.rb', line 106

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:

  • (Boolean)

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



119
120
121
# File 'lib/jamf/api/connection/token.rb', line 119

def pw_fallback
  @pw_fallback
end

#ssl_optionsHash (readonly)

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

Returns:

  • (Hash)

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



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

def ssl_options
  @ssl_options
end

#ssl_versionString (readonly)

Returns the SSL version being used.

Returns:

  • (String)

    the SSL version being used



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

def ssl_version
  @ssl_version
end

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

Returns The token data.

Returns:



94
95
96
# File 'lib/jamf/api/connection/token.rb', line 94

def token
  @token
end

#userString (readonly)

Returns The user who generated this token.

Returns:

  • (String)

    The user who generated this token



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

def user
  @user
end

#verify_certBoolean (readonly) Also known as: verify_cert?

Returns are we verifying SSL certs?.

Returns:

  • (Boolean)

    are we verifying SSL certs?



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

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:



349
350
351
352
353
354
355
356
# File 'lib/jamf/api/connection/token.rb', line 349

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:

  • (Boolean)


270
271
272
273
274
# File 'lib/jamf/api/connection/token.rb', line 270

def expired?
  return unless @expires

  Time.now >= @expires
end

#hostObject



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

def host
  @base_url.host
end

#init_for_api_clientObject

Initialize for an API client



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/jamf/api/connection/token.rb', line 180

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



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

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



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/jamf/api/connection/token.rb', line 225

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



399
400
401
402
403
# File 'lib/jamf/api/connection/token.rb', line 399

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

#jamf_buildString

Returns:



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

def jamf_build
  fetch_jamf_version unless @jamf_build
  @jamf_build
end

#jamf_versionGem::Version

Returns:

  • (Gem::Version)


256
257
258
259
# File 'lib/jamf/api/connection/token.rb', line 256

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:

  • (String, nil)

    result or nil if never refreshed



340
341
342
# File 'lib/jamf/api/connection/token.rb', line 340

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:

  • (Time, nil)

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



279
280
281
282
283
# File 'lib/jamf/api/connection/token.rb', line 279

def next_refresh
  return unless keep_alive?

  @expires - @refresh_buffer
end

#portInteger

Returns:

  • (Integer)


250
251
252
# File 'lib/jamf/api/connection/token.rb', line 250

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:

  • pw (String)

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

Returns:

  • (Time)

    the new expiration time



367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/jamf/api/connection/token.rb', line 367

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:

  • (Float)


308
309
310
311
312
# File 'lib/jamf/api/connection/token.rb', line 308

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:

  • (Float, nil)

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



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

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



416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/jamf/api/connection/token.rb', line 416

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



441
442
443
444
445
446
# File 'lib/jamf/api/connection/token.rb', line 441

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:

  • (String)

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



316
317
318
319
320
# File 'lib/jamf/api/connection/token.rb', line 316

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:



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

def time_to_refresh
  return unless keep_alive?

  Jamf.humanize_secs secs_to_refresh
end

#valid?Boolean

Returns:

  • (Boolean)


324
325
326
327
328
329
330
331
332
333
# File 'lib/jamf/api/connection/token.rb', line 324

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