Class: CF::UAA::TokenIssuer

Inherits:
Object
  • Object
show all
Includes:
Http
Defined in:
lib/uaa/token_issuer.rb

Overview

Client Apps that want to get access to resource servers on behalf of their users need to get tokens via authcode and implicit flows, request scopes, etc., but they don't need to process tokens. This class is for these use cases.

In general most of this class is an implementation of the client pieces of the OAuth2 protocol. See http://tools.ietf.org/html/rfc6749

Constant Summary

Constants included from Http

Http::FORM_UTF8, Http::JSON_UTF8

Instance Method Summary collapse

Methods included from Http

basic_auth, #initialize_http_options, #logger, #logger=, #set_request_handler, #trace?

Constructor Details

#initialize(target, client_id, client_secret = nil, options = {}) ⇒ TokenIssuer

Returns a new instance of TokenIssuer.

Parameters:



136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/uaa/token_issuer.rb', line 136

def initialize(target, client_id, client_secret = nil, options = {})
  @target, @client_id, @client_secret = target, client_id, client_secret
  @client_assertion = options[:client_assertion] || nil
  @token_target = options[:token_target] || target
  @key_style = options[:symbolize_keys] ? :sym : nil
  @basic_auth = options[:basic_auth] == true ? true : false
  @client_auth_method = options[:client_auth_method] || 'client_secret_basic'
  @code_verifier = options[:code_verifier] || nil
  if @code_verifier.nil? && options[:use_pkce] && options[:use_pkce] == true
    @code_verifier = get_verifier
  end
  initialize_http_options(options)
end

Instance Method Details

#authcode_grant(authcode_uri, callback_query) ⇒ TokenInfo

Uses the instance client credentials in addition to callback_query to get a token via the authorization code grant.

Parameters:

  • authcode_uri (String)

    must be from a previous call to #authcode_uri and contains state used to validate the contents of the reply from the server.

  • callback_query (String)

    must be the query portion of the URL received by the client after the user's browser is redirected back from the server. It contains the authorization code.

Returns:

See Also:



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/uaa/token_issuer.rb', line 254

def authcode_grant(authcode_uri, callback_query)
  ac_params = Util.decode_form(URI.parse(authcode_uri).query)
  unless ac_params['state'] && ac_params['redirect_uri']
    raise ArgumentError, "authcode redirect must happen before authcode grant"
  end
  begin
    params = Util.decode_form(callback_query)
    authcode = params['code']
    raise BadResponse unless params['state'] == ac_params['state'] && authcode
  rescue URI::InvalidURIError, ArgumentError, BadResponse
    raise BadResponse, "received invalid response from target #{@target}"
  end
  if not @code_verifier.nil?
    request_token(grant_type: 'authorization_code', code: authcode,
        redirect_uri: ac_params['redirect_uri'], code_verifier: @code_verifier)
  else
    request_token(grant_type: 'authorization_code', code: authcode,
                  redirect_uri: ac_params['redirect_uri'])
  end
end

#authcode_uri(redirect_uri, scope = nil) ⇒ String

Constructs a uri that the client is to return to the browser to direct the user to the authorization server to get an authcode.

Parameters:

  • redirect_uri (String)

    is embedded in the returned uri so the server can redirect the user back to the caller's endpoint.

Returns:

  • (String)

    uri which



240
241
242
# File 'lib/uaa/token_issuer.rb', line 240

def authcode_uri(redirect_uri, scope = nil)
  @target + authorize_path_args('code', redirect_uri, scope)
end

#autologin_uri(redirect_uri, credentials, scope = nil) ⇒ String

A UAA extension to OAuth2 that allows a client to pre-authenticate a user at the start of an authorization code flow. By passing in the user's credentials the server can establish a session with the user's browser without reprompting for authentication. This is useful for user account management apps so that they can create a user account, or reset a password for the user, without requiring the user to type in their credentials again.

Parameters:

  • credentials (String)

    (see #implicit_grant_with_creds)

  • redirect_uri (String)

    (see #authcode_uri)

Returns:

  • (String)

    uri which

Raises:



225
226
227
228
229
230
231
232
233
# File 'lib/uaa/token_issuer.rb', line 225

def autologin_uri(redirect_uri, credentials, scope = nil)
  headers = {'content-type' => FORM_UTF8, 'accept' => JSON_UTF8,
      'authorization' => Http.basic_auth(@client_id, @client_secret) }
  body = Util.encode_form(credentials)
  reply = json_parse_reply(nil, *request(@target, :post, "/autologin", body, headers))
  raise BadResponse, "no autologin code in reply" unless reply['code']
  @target + authorize_path_args('code', redirect_uri, scope,
      random_state, code: reply['code'])
end

#client_credentials_grant(scope = nil, client_assertion = nil) ⇒ TokenInfo

Uses the instance client credentials to get a token with a client credentials grant. See http://tools.ietf.org/html/rfc6749#section-4.4

Returns:



320
321
322
# File 'lib/uaa/token_issuer.rb', line 320

def client_credentials_grant(scope = nil, client_assertion = nil)
  request_token(grant_type: 'client_credentials', scope: scope, client_assertion: client_assertion)
end

#get_challengeObject

Calculates the challenge from code_verifier



285
286
287
# File 'lib/uaa/token_issuer.rb', line 285

def get_challenge
  @challenge ||= Digest::SHA256.base64digest(get_verifier).tr("+/", "-_").tr("=", "")
end

#get_verifierObject

Generates a random verifier for PKCE usage



276
277
278
279
280
281
282
# File 'lib/uaa/token_issuer.rb', line 276

def get_verifier
  if not @code_verifier.nil?
    @verifier = @code_verifier
  else
    @verifier ||= SecureRandom.base64(96).tr("+/", "-_").tr("=", "")
  end
end

#implicit_grant(implicit_uri, callback_fragment) ⇒ TokenInfo

Gets a token via an implicit grant.

Parameters:

  • implicit_uri (String)

    must be from a previous call to #implicit_uri, contains state used to validate the contents of the reply from the server.

  • callback_fragment (String)

    must be the fragment portion of the URL received by the user's browser after the server redirects back to the redirect_uri that was given to #implicit_uri. How the application gets the contents of the fragment is application specific -- usually some javascript in the page at the redirect_uri.

Returns:

See Also:



207
208
209
210
211
212
213
# File 'lib/uaa/token_issuer.rb', line 207

def implicit_grant(implicit_uri, callback_fragment)
  in_params = Util.decode_form(URI.parse(implicit_uri).query)
  unless in_params['state'] && in_params['redirect_uri']
    raise ArgumentError, "redirect must happen before implicit grant"
  end
  parse_implicit_params(callback_fragment, in_params['state'])
end

#implicit_grant_with_creds(credentials, scope = nil) ⇒ TokenInfo

Gets an access token in a single call to the UAA with the user credentials used for authentication.

Parameters:

  • credentials

    should be an object such as a hash that can be converted to a json representation of the credential name/value pairs corresponding to the keys retrieved by #prompts.

Returns:



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/uaa/token_issuer.rb', line 166

def implicit_grant_with_creds(credentials, scope = nil)
  # this manufactured redirect_uri is a convention here, not part of OAuth2
  redir_uri = "https://uaa.cloudfoundry.com/redirect/#{@client_id}"
  response_type = "token"
  response_type = "#{response_type} id_token" if scope && (scope.include? "openid")
  uri = authorize_path_args(response_type, redir_uri, scope, state = random_state)

  # the accept header is only here so the uaa will issue error replies in json to aid debugging
  headers = {'content-type' => FORM_UTF8, 'accept' => JSON_UTF8 }
  body = Util.encode_form(credentials.merge(source: 'credentials'))
  status, body, headers = request(@target, :post, uri, body, headers)
  raise BadResponse, "status #{status}" unless status == 302
  req_uri, reply_uri = URI.parse(redir_uri), URI.parse(headers['location'])
  fragment, reply_uri.fragment = reply_uri.fragment, nil
  raise BadResponse, "bad location header" unless req_uri == reply_uri
  parse_implicit_params(fragment, state)
rescue URI::Error => e
  raise BadResponse, "bad location header in reply: #{e.message}"
end

#implicit_uri(redirect_uri, scope = nil) ⇒ String

Constructs a uri that the client is to return to the browser to direct the user to the authorization server to get an authcode.

Parameters:

  • redirect_uri (String)

    (see #authcode_uri)

Returns:

  • (String)


190
191
192
193
194
# File 'lib/uaa/token_issuer.rb', line 190

def implicit_uri(redirect_uri, scope = nil)
  response_type = "token"
  response_type = "#{response_type} id_token" if scope && (scope.include? "openid")
  @target + authorize_path_args(response_type, redirect_uri, scope)
end

#jwt_bearer_grant(assertion, scope = nil, client_assertion = nil) ⇒ TokenInfo

Gets an access token with the user assertion used for authentication via the jwt bearer authorization grant. See http://tools.ietf.org/html/rfc7523#section-2.1.

Parameters:

  • assertion

    should be an id_token from a previous IdP token request

Returns:



336
337
338
# File 'lib/uaa/token_issuer.rb', line 336

def jwt_bearer_grant(assertion, scope = nil, client_assertion = nil)
  request_token(grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion: assertion, scope: scope, client_assertion: client_assertion)
end

#owner_password_credentials_grant(credentials) ⇒ TokenInfo

Gets an access token with the user credentials used for authentication via the owner password grant. See http://tools.ietf.org/html/rfc6749#section-4.3.

Parameters:

  • credentials

    should be an object such as a hash that can be converted to a json representation of the credential name/value pairs corresponding to the keys retrieved by #prompts.

Returns:



312
313
314
315
# File 'lib/uaa/token_issuer.rb', line 312

def owner_password_credentials_grant(credentials)
  credentials[:grant_type] = 'password'
  request_token(credentials)
end

#owner_password_grant(username, password, scope = nil) ⇒ TokenInfo

Uses the instance client credentials in addition to the username and password to get a token via the owner password grant. See http://tools.ietf.org/html/rfc6749#section-4.3.

Returns:



293
294
295
296
# File 'lib/uaa/token_issuer.rb', line 293

def owner_password_grant(username, password, scope = nil)
  request_token(grant_type: 'password', username: username,
      password: password, scope: scope)
end

#passcode_grant(passcode, scope = nil) ⇒ TokenInfo

Uses a one-time passcode obtained from the UAA to get a token.

Returns:



301
302
303
# File 'lib/uaa/token_issuer.rb', line 301

def passcode_grant(passcode, scope = nil)
  request_token(grant_type: 'password', passcode: passcode, scope: scope)
end

#promptsHash

Allows an app to discover what credentials are required for #implicit_grant_with_creds.

Returns:

  • (Hash)

    of credential names with type and suggested prompt value, e.g. {"username":["text","Email"],"password":["password","Password"]}

Raises:



154
155
156
157
158
# File 'lib/uaa/token_issuer.rb', line 154

def prompts
  reply = json_get(@target, '/login')
  return reply[jkey :prompts] if reply && reply[jkey :prompts]
  raise BadResponse, "No prompts in response from target #{@target}"
end

#refresh_token_grant(refresh_token, scope = nil) ⇒ TokenInfo

Uses the instance client credentials and the given refresh_token to get a new access token. See http://tools.ietf.org/html/rfc6749#section-6

Returns:

  • (TokenInfo)

    which may include a new refresh token as well as an access token.



327
328
329
# File 'lib/uaa/token_issuer.rb', line 327

def refresh_token_grant(refresh_token, scope = nil)
  request_token(grant_type: 'refresh_token', refresh_token: refresh_token, scope: scope)
end