1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
|
require 'faraday'
module OAuth2
# The OAuth2::Client class
class Client
attr_reader :id, :secret, :site
attr_accessor :options
attr_writer :connection
# Instantiate a new OAuth 2.0 client using the
# Client ID and Client Secret registered to your
# application.
#
# @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)
_opts = opts.dup
@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
#
# @param [String] the OAuth2 provider site host
def site=(value)
@connection = nil
@site = value
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)
connection.build_url(options[:authorize_url], params).to_s
end
# 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.
#
# @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 = {}) # rubocop:disable CyclomaticComplexity, MethodLength
url = 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])
case response.status
when 301, 302, 303, 307
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 200..299, 300..399
# on non-redirecting 3xx statuses, just return the response
response
when 400..599
error = Error.new(response)
fail(error) if opts.fetch(:raise_errors, options[:raise_errors])
response.error = error
response
else
error = Error.new(response)
fail(error, "Unhandled status code value of #{response.status}")
end
end
# Initializes an AccessToken by making a request to the token endpoint
#
# @param [Hash] params a Hash of params for the token endpoint
# @param [Hash] access token options, to pass to the AccessToken object
# @param [Class] class of access token for easier subclassing OAuth2::AccessToken
# @return [AccessToken] the initalized AccessToken
def get_token(params, access_token_opts = {}, access_token_class = AccessToken)
opts = {:raise_errors => options[:raise_errors], :parse => params.delete(:parse)}
if options[:token_method] == :post
headers = params.delete(:headers)
opts[:body] = params
opts[:headers] = {'Content-Type' => 'application/x-www-form-urlencoded'}
opts[:headers].merge!(headers) if headers
else
opts[:params] = params
end
response = request(options[:token_method], token_url, opts)
error = Error.new(response)
fail(error) if options[:raise_errors] && !(response.parsed.is_a?(Hash) && response.parsed['access_token'])
access_token_class.from_hash(self, response.parsed.merge(access_token_opts))
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 Implicit strategy
#
# @see http://tools.ietf.org/html/draft-ietf-oauth-v2-26#section-4.2
def implicit
@implicit ||= OAuth2::Strategy::Implicit.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
# The Client Credentials strategy
#
# @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.4
def client_credentials
@client_credentials ||= OAuth2::Strategy::ClientCredentials.new(self)
end
def assertion
@assertion ||= OAuth2::Strategy::Assertion.new(self)
end
end
end
|