lib/oauth2/client.rb in panjiva-oauth2-0.4.1 vs lib/oauth2/client.rb in panjiva-oauth2-0.5.0

- old
+ new

@@ -1,105 +1,146 @@ require 'faraday' module OAuth2 + # The OAuth2::Client class class Client - attr_accessor :id, :secret, :site, :connection, :options, :raise_errors, :token_method - attr_writer :json + attr_reader :id, :secret + attr_accessor :site, :connection, :options # Instantiate a new OAuth 2.0 client using the - # client ID and client secret registered to your + # Client ID and Client Secret registered to your # application. # - # Options: + # @param [String] client_id the client_id value + # @param [String] client_secret the client_secret value + # @param [Hash] opts the options to create the client with + # @option opts [String] :site the OAuth2 provider site host + # @option opts [String] :authorize_url ('/oauth/authorize') absolute or relative URL path to the Authorization endpoint + # @option opts [String] :token_url ('/oauth/token') absolute or relative URL path to the Token endpoint + # @option opts [Symbol] :token_method (:post) HTTP method to use to request token (:get or :post) + # @option opts [Hash] :connection_opts ({}) Hash of connection options to pass to initialize Faraday with + # @option opts [FixNum] :max_redirects (5) maximum number of redirects to follow + # @option opts [Boolean] :raise_errors (true) whether or not to raise an OAuth2::Error + # on responses with 400+ status codes + # @yield [builder] The Faraday connection builder + def initialize(client_id, client_secret, opts={}, &block) + @id = client_id + @secret = client_secret + @site = opts.delete(:site) + ssl = opts.delete(:ssl) + @options = {:authorize_url => '/oauth/authorize', + :token_url => '/oauth/token', + :token_method => :post, + :connection_opts => {}, + :connection_build => block, + :max_redirects => 5, + :raise_errors => true}.merge(opts) + @options[:connection_opts][:ssl] = ssl if ssl + end + + # Set the site host # - # <tt>:site</tt> :: Specify a base URL for your OAuth 2.0 client. - # <tt>:authorize_path</tt> :: Specify the path to the authorization endpoint. - # <tt>:authorize_url</tt> :: Specify a full URL of the authorization endpoint. - # <tt>:access_token_path</tt> :: Specify the path to the access token endpoint. - # <tt>:access_token_method</tt> :: Specify the method to use for token endpoints, can be :get or :post - # (note: for Facebook this should be :get and for Google this should be :post) - # <tt>:access_token_url</tt> :: Specify the full URL of the access token endpoint. - # <tt>:parse_json</tt> :: If true, <tt>application/json</tt> responses will be automatically parsed. - # <tt>:ssl</tt> :: Specify SSL options for the connection. - # <tt>:adapter</tt> :: The name of the Faraday::Adapter::* class to use, e.g. :net_http. To pass arguments - # to the adapter pass an array here, e.g. [:action_dispatch, my_test_session] - # <tt>:raise_errors</tt> :: Default true. When false it will then return the error status and response instead of raising an exception. - def initialize(client_id, client_secret, opts={}) - self.options = opts.dup - self.token_method = self.options.delete(:access_token_method) || :post - adapter = self.options.delete(:adapter) - ssl_opts = self.options.delete(:ssl) || {} - connection_opts = ssl_opts ? {:ssl => ssl_opts} : {} - self.id = client_id - self.secret = client_secret - self.site = self.options.delete(:site) if self.options[:site] - self.connection = Faraday::Connection.new(site, connection_opts) - self.json = self.options.delete(:parse_json) - self.raise_errors = !(self.options.delete(:raise_errors) == false) + # @param [String] the OAuth2 provider site host + def site=(value) + @connection = nil + @site = value + end - if adapter && adapter != :test - connection.build do |b| - b.adapter(*[adapter].flatten) - end + # The Faraday connection object + def connection + @connection ||= begin + conn = Faraday.new(site, options[:connection_opts]) + conn.build do |b| + options[:connection_build].call(b) + end if options[:connection_build] + conn end end + # The authorize endpoint URL of the OAuth2 provider + # + # @param [Hash] params additional query parameters def authorize_url(params=nil) - path = options[:authorize_url] || options[:authorize_path] || "/oauth/authorize" - connection.build_url(path, params).to_s + connection.build_url(options[:authorize_url], params).to_s end - def access_token_url(params=nil) - path = options[:access_token_url] || options[:access_token_path] || "/oauth/access_token" - connection.build_url(path, params).to_s + # The token endpoint URL of the OAuth2 provider + # + # @param [Hash] params additional query parameters + def token_url(params=nil) + connection.build_url(options[:token_url], params).to_s end # Makes a request relative to the specified site root. - def request(verb, url, params={}, headers={}) - if (verb == :get) || (verb == :delete) - resp = connection.run_request(verb, url, nil, headers) do |req| - req.params.update(params) - end - else - resp = connection.run_request(verb, url, params, headers) + # + # @param [Symbol] verb one of :get, :post, :put, :delete + # @param [String] url URL path of request + # @param [Hash] opts the options to make the request with + # @option opts [Hash] :params additional query parameters for the URL of the request + # @option opts [Hash, String] :body the body of the request + # @option opts [Hash] :headers http request headers + # @option opts [Boolean] :raise_errors whether or not to raise an OAuth2::Error on 400+ status + # code response for this request. Will default to client option + # @option opts [Symbol] :parse @see Response::initialize + # @yield [req] The Faraday request + def request(verb, url, opts={}) + url = self.connection.build_url(url, opts[:params]).to_s + + response = connection.run_request(verb, url, opts[:body], opts[:headers]) do |req| + yield(req) if block_given? end + response = Response.new(response, :parse => opts[:parse]) - if raise_errors - case resp.status - when 200...299 - return response_for(resp) - when 302 - return request(verb, resp.headers['location'], params, headers) - when 401 - e = OAuth2::AccessDenied.new("Received HTTP 401 during request.") - e.response = resp - raise e - when 409 - e = OAuth2::Conflict.new("Received HTTP 409 during request.") - e.response = resp - raise e - else - e = OAuth2::HTTPError.new("Received HTTP #{resp.status} during request.") - e.response = resp - raise e + case response.status + when 200..299 + response + when 300..399 + opts[:redirect_count] ||= 0 + opts[:redirect_count] += 1 + return response if opts[:redirect_count] > options[:max_redirects] + if response.status == 303 + verb = :get + opts.delete(:body) end + request(verb, response.headers['location'], opts) + when 400..599 + e = Error.new(response) + raise e if opts[:raise_errors] || options[:raise_errors] + response.error = e + response else - response_for resp + raise Error.new(response), "Unhandled status code value of #{response.status}" end end - def json?; !!@json end - - def web_server; OAuth2::Strategy::WebServer.new(self) end - def password; OAuth2::Strategy::Password.new(self) end - - private - - def response_for(resp) - if json? - return ResponseObject.from(resp) + # Initializes an AccessToken by making a request to the token endpoint + # + # @param [Hash] params a Hash of params for the token endpoint + # @return [AccessToken] the initalized AccessToken + def get_token(params) + opts = {:raise_errors => true, :parse => params.delete(:parse)} + if options[:token_method] == :post + opts[:body] = params + opts[:headers] = {'Content-Type' => 'application/x-www-form-urlencoded'} else - return ResponseString.new(resp) + opts[:params] = params end + response = request(options[:token_method], token_url, opts) + raise Error.new(response) unless response.parsed.is_a?(Hash) && response.parsed['access_token'] + AccessToken.from_hash(self, response.parsed) + end + + # The Authorization Code strategy + # + # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.1 + def auth_code + @auth_code ||= OAuth2::Strategy::AuthCode.new(self) + end + + # The Resource Owner Password Credentials strategy + # + # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.3 + def password + @password ||= OAuth2::Strategy::Password.new(self) end end end