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