lib/uaa/token_issuer.rb in cf-uaa-lib-1.3.0 vs lib/uaa/token_issuer.rb in cf-uaa-lib-1.3.1

- old
+ new

@@ -9,55 +9,78 @@ # separate copyright notices and license terms. Your use of these # subcomponents is subject to the terms and conditions of the # subcomponent's license, as noted in the LICENSE file. #++ -# Web or Native Clients (in the OAuth2 sense) would use this class to get tokens -# that they can use to get access to resources - -# Client Apps that want to get access on behalf of their users to -# resource servers 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. - require 'securerandom' require 'uaa/http' module CF::UAA -# The Token class holds access and refresh tokens as well as token meta-data -# such as token type and expiration time. The info hash MUST include -# access_token, token_type and scope (if granted scope differs from requested -# scope). It should include expires_in. It may include refresh_token, scope, -# and other values from the auth server. +# The Token class is returned by various TokenIssuer methods. It holds access +# and refresh tokens as well as token meta-data such as token type and +# expiration time. See Token#info for contents. class Token + + # Returns a hash of information about the current token. The info hash MUST include + # access_token, token_type and scope (if granted scope differs from requested + # scope). It should include expires_in. It may include refresh_token, scope, + # and other values from the auth server. attr_reader :info - def initialize(info); @info = info end + + def initialize(info) # :nodoc: + @info = info + end + + # Returns a string for use in an authorization header that is constructed + # from contents of the Token. Typically a string such as "bearer xxxx.xxxx.xxxx". def auth_header; "#{info['token_type']} #{info['access_token']}" end end +# 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 class TokenIssuer include Http + # parameters: + # [+target+] The base URL of a UAA's oauth authorize endpoint. For example + # the target would be \https://login.cloudfoundry.com if the + # endpoint is \https://login.cloudfoundry.com/oauth/authorize. + # The target would be \http://localhost:8080/uaa if the endpoint + # is \http://localhost:8080/uaa/oauth/authorize. + # [+client_id+] The oauth2 client id. See http://tools.ietf.org/html/rfc6749#section-2.2 + # [+client_secret+] needed to authenticate the client for all grant types + # except implicit. + # [+token_target+] The base URL of the oauth token endpoint. If not specified, + # +target+ is used. def initialize(target, client_id, client_secret = nil, token_target = nil) @target, @client_id, @client_secret = target, client_id, client_secret @token_target = token_target || target end - # login prompts for use by app to collect credentials for implicit grant + # Allows an app to discover what credentials are required for + # #implicit_grant_with_creds. Returns a hash of credential names with type + # and suggested prompt value, e.g. + # {"username":["text","Email"],"password":["password","Password"]} def prompts reply = json_get @target, '/login' return reply['prompts'] if reply && reply['prompts'] raise BadResponse, "No prompts in response from target #{@target}" end - # gets an access token in a single call to the UAA with the client - # credentials used for authentication. The credentials arg should + # Gets an access token in a single call to the UAA with the user + # credentials used for authentication. The +credentials+ should # be an object such as a hash that can be converted to a json # representation of the credential name/value pairs - # as specified by the information retrieved by #prompts + # corresponding to the keys retrieved by #prompts. + # Returns a Token. 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}" uri = authorize_path_args("token", redir_uri, scope, state = SecureRandom.uuid) @@ -72,43 +95,77 @@ parse_implicit_params(fragment, state) rescue URI::Error => e raise BadResponse, "bad location header in reply: #{e.message}" end - # constructs a uri that the client is to return to the browser to direct - # the user to the authorization server to get an authcode. The redirect_uri + # Constructs a uri that the client is to return to the browser to direct + # the user to the authorization server to get an authcode. The +redirect_uri+ # is embedded in the returned uri so the authorization server can redirect # the user back to the client app. def implicit_uri(redirect_uri, scope = nil) @target + authorize_path_args("token", redirect_uri, scope) end - def implicit_grant(implicit_uri, callback_query) + # Gets a token via an implicit grant. + # [+authcode_uri+] must be from a previous call to #implicit_uri and contains + # state used to validate the contents of the reply from the + # Authorization Server. + # [+callback_fragment+] must be the fragment portion of the URL received by + # user's browser after the Authorization Server + # redirects back to the +redirect_uri+ that was given to + # #implicit_uri. How the application get's the contents + # of the fragment is application specific -- usually + # some javascript in the page at the +redirect_uri+. + # + # See http://tools.ietf.org/html/rfc6749#section-4.2 . + # + # Returns a Token. + def implicit_grant(implicit_uri, callback_fragment) in_params = Util.decode_form_to_hash(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_query, in_params['state'] + parse_implicit_params callback_fragment, in_params['state'] end + # 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 (see #prompts) the Authorization 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. def autologin_uri(redirect_uri, credentials, scope = nil) headers = {'content_type' => 'application/x-www-form-urlencoded', 'accept' => 'application/json', 'authorization' => Http.basic_auth(@client_id, @client_secret) } body = URI.encode_www_form(credentials) reply = json_parse_reply(*request(@target, :post, "/autologin", body, headers)) raise BadResponse, "no autologin code in reply" unless reply['code'] @target + authorize_path_args('code', redirect_uri, scope, SecureRandom.uuid, code: reply[:code]) end - # constructs a uri that the client is to return to the browser to direct + # Constructs a uri that the client is to return to the browser to direct # the user to the authorization server to get an authcode. The redirect_uri # is embedded in the returned uri so the authorization server can redirect # the user back to the client app. def authcode_uri(redirect_uri, scope = nil) @target + authorize_path_args('code', redirect_uri, scope) end + # Uses the instance client credentials in addition to +callback_query+ + # to get a token via the authorization code grant. + # [+authcode_uri+] must be from a previous call to #authcode_uri and contains + # state used to validate the contents of the reply from the + # Authorization Server. + # [callback_query] must be the query portion of the URL received by the + # client after the user's browser is redirected back from + # the Authorization server. It contains the authorization + # code. + # + # See http://tools.ietf.org/html/rfc6749#section-4.1 . + # + # Returns a Token. def authcode_grant(authcode_uri, callback_query) ac_params = Util.decode_form_to_hash(URI.parse(authcode_uri).query) unless ac_params['state'] && ac_params['redirect_uri'] raise ArgumentError, "authcode redirect must happen before authcode grant" end @@ -120,17 +177,27 @@ raise BadResponse, "received invalid response from target #{@target}" end request_token('grant_type' => 'authorization_code', 'code' => authcode, 'redirect_uri' => ac_params['redirect_uri']) end + # 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 a Token. def owner_password_grant(username, password, scope = nil) request_token('grant_type' => 'password', 'username' => username, 'password' => password, 'scope' => scope) end + # 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 a Token. def client_credentials_grant(scope = nil) request_token('grant_type' => 'client_credentials', 'scope' => scope) end + # 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 a Token, which may include a new refresh token as well as an access token. def refresh_token_grant(refresh_token, scope = nil) request_token('grant_type' => 'refresh_token', 'refresh_token' => refresh_token, 'scope' => scope) end private