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


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)



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

def base_url
  @base_url
end

#creation_http_responseFaraday::Response (readonly)



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



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

def creation_time
  @creation_time
end

#expiresTime (readonly) Also known as: expiration



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

def expires
  @expires
end

#last_refreshTime (readonly)



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?



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

def pw_fallback
  @pw_fallback
end

#ssl_optionsHash (readonly)



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

def ssl_options
  @ssl_options
end

#ssl_versionString (readonly)



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



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

def token
  @token
end

#userString (readonly)



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

def user
  @user
end

#verify_certBoolean (readonly) Also known as: verify_cert?



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



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



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



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



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



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?



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



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.



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



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.



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



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”



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



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