module Jamf::Connection::Connect

This module defines constants and methods used for processing the connection parameters, acquiring passwords and tokens, and creating the connection objects to the Classic and Jamf Pro APIs. It also defines the disconnection methods

Public Instance Methods

connect(url = nil, **params) click to toggle source

Connect to the both the Classic and Jamf Pro APIs

IMPORTANT: http (non-SSL, unencrypted) connections are not allowed.

The first parameter may be a URL (must be https) from which the host & port will be used, and if present, the user and password E.g.

connect 'https://myuser:pass@host.domain.edu:8443'

which is the same as:

connect host: 'host.domain.edu', port: 8443, user: 'myuser', pw: 'pass'

When using a URL, other parameters below may be specified, however host: and port: parameters will be ignored, since they came from the URL, as will user: and :pw, if they are present in the URL. If the URL doesn’t contain user and pw, they can be provided via the parameters, or left to default values.

### Passwords

The pw: parameter also accepts the symbols :prompt, and :stdin

If :prompt, the user is promted on the commandline to enter the password for the :user.

If :stdin, the password is read from the first line of stdin

If :stdinX, (where X is an integer) the password is read from the Xth line of stdin.see {Jamf.stdin}

If omitted, and running from an interactive terminal, the user is prompted as with :prompt

### Tokens Instead of a user and password, you may specify a valid ‘token:’, which is either:

A Jamf::Connection::Token object, which can be extracted from an active Jamf::Connection via its token method

or

A valid token string e.g. “eyJhdXR…6EKoo” from any source can also be used.

When using an existing token or token string, the username used to create the token will be read from the server. However, if you don’t also provide the users password using the pw: parameter, then the pw_fallback option will always be false.

### Default values

Any values available via JSS.config will be used if they are not provided in the parameters. See {Jamf::Configuration}. If there are no config values then a built-in default is used if available.

### API Clients

As of Jamf Pro 10.49, API connections can be made using “API Clients” which are assigned to various “API Roles”, as well as regular Jamf Pro accounts.

Connections made with API Client credentials are different from regular connections:

- Their expiration period can vary based on the Client definition
- The expirations are usually quite short, less than the default 30 min. session timeout
- They cannot be kept alive, they will become invalid when the expiration time arrives.
- The API endpoints and data exchange used for making API Client connections are
  different from those used by normal connections.

To make a connection using an API Client, pass in the client_id: and client_secret: instead of user: and pw:

@param url The URL to use for the connection. Must be ‘https’.

The host, port, and (if provided), user and spassword will be extracted.
Any of those params explicitly provided will be ignored if present in
the url

@param params the keyed parameters for connection.

@option params :host the hostname of the JSS API server, required

if not defined in JSS.config

@option params :server_path If your JSS is not at the root of the

server, e.g. if it's at
  https://myjss.myserver.edu:8443/dev_mgmt/jssweb
rather than
  https://myjss.myserver.edu:8443/
then use this parameter to specify the path below the root e.g:
  server_path: 'dev_mgmt/jssweb'

@option params :user a JSS user who has API privs, required if not

defined in Jamf::CONFIG.
NOTE: To use an API Client (Jamf pro 10.49 and up),
provide client_id: instead of user:

@option params :client_id The Client ID of an “API Client”

available in Jamf Pro 10.49 and up. Use this instead of user:

@option params :client_secret The Client Secret of an “API Client”

available in Jamf Pro 10.49 and up. Use this instead of pw:

@option params :pw[String, Symbol] The user’s password, :prompt, or :stdin

If :prompt, the user is promted on the commandline to enter the password
If :stdin#, the password is read from a line of std in represented by
the digit at #, so :stdin3 reads the passwd from the third line of
standard input. Defaults to line 1, if no digit is supplied. see {JSS.stdin}
NOTE: To use an API Client (Jamf pro 10.49 and up),
provide client_secret: instead of pw:

@option params :port the port number to connect with, defaults

to 443 for Jamf Cloud hosts, 8443 for on-prem hosts

@option params :ssl_version[String, Symbol] The SSL version to use. Default

is TLSv1_2

@option params :verify_cert should SSL certificates be verified.

Defaults to true.

@option params :open_timeout the number of seconds to wait for an

initial response, defaults to 60

@option params :timeout the number of seconds before an API call

times out, defaults to 60

@option params :keep_alive Should the token for the connection

for be automatically refreshed before it expires? Default is true

@option params :token_refresh_buffer If keep_alive, refresh the

token this many seconds before it expires.
Must be >= Jamf::Connection::Token::MIN_REFRESH_BUFFER, which is the default

@option params :pw_fallback [Boolean] If keep_alive, should the passwd be

cached in memory and used to create a new token, if there are problems
with the normal token refresh process?

@option params :sticky_session [Boolean] Use a ‘sticky session’? Default is false.

The hostname of Jamf Cloud urls does not point to a single https server,
but any node of a cluster. Those nodes often take time to see changes
made in other node. Sometimes, its important to perform a series of API
actions to the same node, to avoid sync-timing problems between node. Setting
sticky_session to true will cause all communication for this Connection to go
through the one specific node it first connected ith.
This is only relevant to Jamf Cloud connections, and will raise an exception
is used with on-prem Jamf Pro servers.
NOTE: It is not always appropriate to use this feature, and inapproriate use
may negatively impact server performance. For more info, see
https://developer.jamf.com/developer-guide/docs/sticky-sessions-for-jamf-cloud

@return [String] connection description, the output of to_s

    # File lib/jamf/api/connection/connect.rb
187 def connect(url = nil, **params)
188   raise ArgumentError, 'No url or connection parameters provided' if url.nil? && params.empty?
189 
190   # reset all values, flush caches
191   disconnect
192 
193   # If there's a Token object in :token, this sets @token,
194   # and adds host, port, user from that token
195   parse_token params
196 
197   # Get host, port, user and pw from a URL, add to params if needed
198   parse_url url, params
199 
200   # apply defaults from config, client, and then ruby-jss itself.
201   apply_default_params params
202 
203   # Once we're here, all params have been parsed & defaulted into the
204   # params hash, so make sure we have the minimum needed params for a connection
205   verify_basic_params params
206 
207   # it there's no @token yet, get one from a token string or a password
208   create_token_if_needed(params)
209 
210   # We have to have a usable connection to do this, so it has to come after
211   # all the stuff above
212   verify_server_version
213 
214   @timeout = params[:timeout]
215   @open_timeout = params[:open_timeout]
216 
217   @connect_time = Time.now
218   @name ||= "#{user}@#{host}:#{port}"
219 
220   @c_base_url = base_url + Jamf::Connection::CAPI_RSRC_BASE
221   @jp_base_url = base_url + Jamf::Connection::JPAPI_RSRC_BASE
222 
223   # the faraday connection objects
224   @c_cnx = create_classic_connection
225   @jp_cnx = create_jp_connection
226 
227   # set the connection objects to sticky if desired. enforce booleans
228   self.sticky_session = params[:sticky_session] ? true : false
229 
230   @connected = true
231 
232   to_s
233 end
Also aliased as: login
disconnect() click to toggle source

With a REST connection, there isn’t any real “connection” to disconnect from So to disconnect, we just unset all our credentials.

@return [void]

    # File lib/jamf/api/connection/connect.rb
278 def disconnect
279   flushcache
280   @token&.stop_keep_alive
281 
282   @connect_time = nil
283   @jp_cnx = nil
284   @c_cnx = nil
285   @c_base_url = nil
286   @jp_base_url = nil
287   @server_path = nil
288   @token = nil
289   @sticky_session_cookie = nil
290   @sticky_session = nil
291   @connected = false
292   :disconnected
293 end
enable_sticky_session(headers) click to toggle source

If a sticky_session was requested when the connection was made, and we are connected to a jamf cloud server, the token’s http response contains the cookie we need to send with every request to ensure a stickey session.

    # File lib/jamf/api/connection/connect.rb
241 def enable_sticky_session(headers)
242   # commas separate the cookies
243   raw_cookies = headers[Jamf::Connection::SET_COOKIE_HEADER].split(/\s*,\s*/)
244 
245   raw_cookies.each do |rc|
246     # semicolons separate the attributes of the cookie,
247     # with its name and value being the first pair.
248     cookie_data = rc.split(/\s*;\s*/).first
249 
250     # attribute name and value are separated by '='
251     cookie_name, cookie_value = cookie_data.split('=')
252     next unless cookie_name == Jamf::Connection::STICKY_SESSION_COOKIE_NAME
253 
254     @sticky_session_cookie = "#{Jamf::Connection::STICKY_SESSION_COOKIE_NAME}=#{cookie_value}"
255     jp_cnx.headers[Jamf::Connection::COOKIE_HEADER] = @sticky_session_cookie
256     c_cnx.headers[Jamf::Connection::COOKIE_HEADER] = @sticky_session_cookie
257     return @sticky_session_cookie
258   end
259   # be sure to return nil if there was no appropriate cookie,
260   # which means we aren't using Jamf Cloud
261 
262   nil
263 end
login(url = nil, **params)
Alias for: connect
logout() click to toggle source

Same as disconnect, but invalidates the token on the server first

    # File lib/jamf/api/connection/connect.rb
297 def logout
298   @token&.invalidate
299   disconnect
300 end
validate_connected() click to toggle source

raise exception if not connected, and make sure we’re using the current token

    # File lib/jamf/api/connection/connect.rb
267 def validate_connected
268   using_dft = 'Jamf.cnx' if self == Jamf.cnx
269   raise Jamf::InvalidConnectionError, "Connection '#{@name}' Not Connected. Use #{using_dft}.connect first." unless connected?
270 end

Private Instance Methods

acquire_password(host, user, pw) click to toggle source

From whatever was given in args, figure out the real password

@param args The args for connect

@return [String] The password for the connection

    # File lib/jamf/api/connection/connect.rb
531 def acquire_password(host, user, pw)
532   if pw == :prompt
533     JSS.prompt_for_password "Enter the password for JSS user #{user}@#{host}:"
534   elsif pw.is_a?(Symbol) && args[:pw].to_s.start_with?('stdin')
535     pw.to_s =~ /^stdin(\d+)$/
536     line = Regexp.last_match(1)
537     line ||= 1
538     JSS.stdin line
539   else
540     pw
541   end
542 end
apply_default_params(params) click to toggle source

Apply defaults to the unset params for the connect method First apply them from from the Jamf.config, then from the Jamf::Client (read from the jamf binary config), then from the Jamf module defaults

@param params The params for connect

@return [Hash] The params with defaults applied

    # File lib/jamf/api/connection/connect.rb
359 def apply_default_params(params)
360   # must have a host, but accept legacy :server as well as :host
361   params[:host] ||= params[:server]
362 
363   # if we have no port set by this point, set to cloud port
364   # if host is a cloud host. But leave port nil for other hosts
365   # (will be set via client defaults or module defaults)
366   params[:port] ||= Jamf::Connection::JAMFCLOUD_PORT if params[:host].to_s.end_with?(Jamf::Connection::JAMFCLOUD_DOMAIN)
367 
368   # if we're using an API client, the id and secret are synonyms of the user and pw
369   params[:user] ||= params[:client_id]
370   params[:pw] ||= params[:client_secret]
371 
372   apply_defaults_from_config(params)
373 
374   apply_defaults_from_client(params)
375 
376   apply_module_defaults(params)
377 end
apply_defaults_from_client(params) click to toggle source

Apply defaults from the Jamf::Client to the params for the connect method

@param params The params for connect

@return [Hash] The params with defaults applied

    # File lib/jamf/api/connection/connect.rb
409 def apply_defaults_from_client(params)
410   return unless Jamf::Client.installed?
411 
412   # these settings can come from the jamf binary config,
413   # if this machine is a Jamf client.
414   params[:host] ||= Jamf::Client.jss_server
415   params[:port] ||= Jamf::Client.jss_port.to_i
416 rescue
417   nil
418 end
apply_defaults_from_config(params) click to toggle source

Apply defaults from the Jamf.config to the params for the connect method

@param params The params for connect

@return [Hash] The params with defaults applied

    # File lib/jamf/api/connection/connect.rb
387 def apply_defaults_from_config(params)
388   # settings from config if they aren't in the params
389   params[:host] ||= JSS.config.api_server_name
390   params[:port] ||= JSS.config.api_server_port
391   params[:user] ||= JSS.config.api_username
392   params[:timeout] ||= JSS.config.api_timeout
393   params[:open_timeout] ||= JSS.config.api_timeout_open
394   params[:ssl_version] ||= JSS.config.api_ssl_version
395 
396   # if verify cert was not in the params, get it from the prefs.
397   # We can't use ||= because the desired value might be 'false'
398   params[:verify_cert] = JSS.config.api_verify_cert if params[:verify_cert].nil?
399 end
apply_module_defaults(params) click to toggle source

Apply the module defaults to the params for the connect method

@param params The params for connect

@return [Hash] The params with defaults applied

    # File lib/jamf/api/connection/connect.rb
427 def apply_module_defaults(params)
428   # if we have no port set by this point, assume on-prem.
429   params[:port] ||= Jamf::Connection::ON_PREM_SSL_PORT
430   params[:timeout] ||= Jamf::Connection::DFT_TIMEOUT
431   params[:open_timeout] ||= Jamf::Connection::DFT_OPEN_TIMEOUT
432   params[:ssl_version] ||= Jamf::Connection::DFT_SSL_VERSION
433   params[:token_refresh_buffer] ||= Jamf::Connection::Token::MIN_REFRESH_BUFFER
434   # if we have a TTY, pw defaults to :prompt
435   params[:pw] ||= :prompt if $stdin.tty?
436 end
build_base_url(params) click to toggle source

Build the base URL for the API connection

@param args The args for connect

@return [String] The URI encoded URL

    # File lib/jamf/api/connection/connect.rb
511 def build_base_url(params)
512   # if we parsed a URL directly from connect' first parameter, then use that.
513   return params[:given_url] if params[:given_url]
514 
515   # trim any potential leading & trailing slash on server_path,
516   # ensure a trailing slash below
517   server_path = params[:server_path].to_s.delete_prefix '/'
518   server_path.delete_suffix! '/'
519 
520   # and here's the URL
521   "#{Jamf::Connection::HTTPS_SCHEME}://#{params[:host]}:#{params[:port]}/#{server_path}/"
522 end
create_token_if_needed(params) click to toggle source

it there’s no @token yet, get one from a token string or a password

    # File lib/jamf/api/connection/connect.rb
465 def create_token_if_needed(params)
466   return if @token
467 
468   if params[:token].is_a? String
469     # if pw_fallback, the pw must be acquired, since it isn't in the token
470     # Can't do this yet, cuz we need to create the Token instance first in order
471     # to learn who the user is!
472     #  params[:pw] = acquire_password(params[:host], params[:user], params[:pw]) if params[:pw_fallback]
473     token_src = :token_string
474   else
475     params[:pw] = acquire_password(params[:host], params[:user], params[:pw])
476     token_src = :pw
477   end
478   @token = token_from token_src, params
479 end
parse_token(params) click to toggle source

Get host, port, & user from a Token object

    # File lib/jamf/api/connection/connect.rb
308 def parse_token(params)
309   return unless params[:token].is_a? Jamf::Connection::Token
310 
311   verify_token params[:token]
312   @token = params[:token]
313 end
parse_url(url, params) click to toggle source

Get host, port, user and pw from a URL, overriding any already in the params

@return [String, nil] the pw if present

    # File lib/jamf/api/connection/connect.rb
335 def parse_url(url, params)
336   return unless url
337 
338   url = URI.parse url.to_s
339   raise ArgumentError, 'Invalid url, scheme must be https' unless url.scheme == Jamf::Connection::HTTPS_SCHEME
340 
341   # this removes any user and pw from the url, so we can give it to the token
342   params[:given_url] = "#{url.scheme}://#{url.host}:#{url.port}#{url.path}/"
343   params[:host] = url.host
344   params[:port] = url.port
345   params[:user] = url.user if url.user
346   params[:pw] = url.password if url.password
347 end
token_from(type, params) click to toggle source

given a token string or a password, get a valid token Token.new will raise an exception if the token string or credentials are invalid

    # File lib/jamf/api/connection/connect.rb
485 def token_from(type, params)
486   token_params = {
487     base_url: build_base_url(params),
488     user: params[:user],
489     client_id: params[:client_id],
490     client_secret: params[:client_secret],
491     timeout: params[:timeout],
492     keep_alive: params[:keep_alive],
493     refresh_buffer: params[:token_refresh_buffer],
494     pw_fallback: params[:pw_fallback],
495     ssl_version: params[:ssl_version],
496     verify_cert: params[:verify_cert]
497   }
498   token_params[:token_string] = params[:token] if type == :token_string
499   token_params[:pw] = params[:pw] unless params[:pw].is_a? Symbol
500 
501   self.class::Token.new(**token_params)
502 end
verify_basic_params(params) click to toggle source

Raise execeptions if we don’t have essential data for a new connection namely a host, user, and pw

@param params The params for connect

@return [void]

    # File lib/jamf/api/connection/connect.rb
446 def verify_basic_params(params)
447   # if given a Token object, it has host, port, user, and base_url
448   # and is already parsed
449   return if @token
450 
451   # must have a host, it could have come from a url, or a param
452   raise Jamf::MissingDataError, 'No Jamf :host specified in params or configuration.' unless params[:host]
453 
454   # no need for user or pass if using a token string
455   # (tho a pw might be given)
456   return if params[:token].is_a? String
457 
458   # must have user and pw
459   raise Jamf::MissingDataError, 'No Jamf :user specified in params or configuration.' unless params[:user]
460   raise Jamf::MissingDataError, "No :pw specified for user '#{params[:user]}'" unless params[:pw]
461 end
verify_server_version() click to toggle source

raise error if the server version is too old @return [void]

    # File lib/jamf/api/connection/connect.rb
547 def verify_server_version
548   return if jamf_version >= Jamf::Connection::MIN_JAMF_VERSION
549 
550   raise(
551     Jamf::InvalidConnectionError,
552     "This version of ruby-jss requires Jamf server version #{Jamf::Connection::MIN_JAMF_VERSION} or higher. #{host} is running #{jamf_version}"
553   )
554 end
verify_token(token) click to toggle source

Raise execeptions if we were given an unusable token object

@param params The params for connect

@return [void]

    # File lib/jamf/api/connection/connect.rb
322 def verify_token(token)
323   raise Jamf::InvalidConnectionError, 'Cannot use token: it has expired' if token.expired?
324   raise Jamf::InvalidConnectionError, 'Cannot use token: it is invalid' unless token.valid?
325   return if token.secs_remaining >= Jamf::Connection::TOKEN_REUSE_MIN_LIFE
326 
327   raise Jamf::InvalidConnectionError, "Cannot use token: it expires in less than #{Jamf::Connection::TOKEN_REUSE_MIN_LIFE} seconds"
328 end