Creates an OAuth 2.0 client.
@param [Hash] options
The configuration parameters for the client. - <code>:authorization_uri</code> - The authorization server's HTTP endpoint capable of authenticating the end-user and obtaining authorization. - <code>:token_credential_uri</code> - The authorization server's HTTP endpoint capable of issuing tokens and refreshing expired tokens. - <code>:client_id</code> - A unique identifier issued to the client to identify itself to the authorization server. - <code>:client_secret</code> - A shared symmetric secret issued by the authorization server, which is used to authenticate the client. - <code>:scope</code> - The scope of the access request, expressed either as an Array or as a space-delimited String. - <code>:state</code> - An arbitrary string designed to allow the client to maintain state. - <code>:code</code> - The authorization code received from the authorization server. - <code>:redirect_uri</code> - The redirection URI used in the initial request. - <code>:username</code> - The resource owner's username. - <code>:password</code> - The resource owner's password. - <code>:issuer</code> - Issuer ID when using assertion profile - <code>:person</code> - Target user for assertions - <code>:expiry</code> - Number of seconds assertions are valid for - <code>:signing_key</code> - Signing key when using assertion profile - <code>:refresh_token</code> - The refresh token associated with the access token to be refreshed. - <code>:access_token</code> - The current access token for this client. - <code>:id_token</code> - The current ID token for this client. - <code>:extension_parameters</code> - When using an extension grant type, this the set of parameters used by that extension.
@example
client = Signet::OAuth2::Client.new( :authorization_endpoint_uri => 'https://example.server.com/authorization', :token_endpoint_uri => 'https://example.server.com/token', :client_id => 'anonymous', :client_secret => 'anonymous', :scope => 'example', :redirect_uri => 'https://example.client.com/oauth' )
@see #update!
# File lib/signet/oauth_2/client.rb, line 93 def initialize(options={}) self.update!(options) end
Returns the access token associated with this client.
@return [String] The access token.
# File lib/signet/oauth_2/client.rb, line 629 def access_token return @access_token ||= nil end
Sets the access token associated with this client.
@param [String] new_access_token
The access token.
# File lib/signet/oauth_2/client.rb, line 638 def access_token=(new_access_token) @access_token = new_access_token end
Returns the issuer ID associated with this client. Used only by the assertion grant type.
@return [String] Target audience ID.
# File lib/signet/oauth_2/client.rb, line 502 def audience return @audience end
Sets the target audience ID when issuing assertions. Used only by the assertion grant type.
@param [String] new_audience
Target audience ID
# File lib/signet/oauth_2/client.rb, line 512 def audience=(new_audience) @audience = new_audience end
Removes all credentials from the client.
# File lib/signet/oauth_2/client.rb, line 742 def clear_credentials! @access_token = nil @refresh_token = nil @id_token = nil @username = nil @password = nil @code = nil @issued_at = nil @expires_in = nil end
Returns the client identifier for this client.
@return [String] The client identifier.
# File lib/signet/oauth_2/client.rb, line 316 def client_id return @client_id end
Sets the client identifier for this client.
@param [String] new_client_id
The client identifier.
# File lib/signet/oauth_2/client.rb, line 325 def client_id=(new_client_id) @client_id = new_client_id end
Returns the client secret for this client.
@return [String] The client secret.
# File lib/signet/oauth_2/client.rb, line 333 def client_secret return @client_secret end
Sets the client secret for this client.
@param [String] new_client_secret
The client secret.
# File lib/signet/oauth_2/client.rb, line 342 def client_secret=(new_client_secret) @client_secret = new_client_secret end
Returns the authorization code issued to this client. Used only by the authorization code access grant type.
@return [String] The authorization code.
# File lib/signet/oauth_2/client.rb, line 403 def code return @code end
Sets the authorization code issued to this client. Used only by the authorization code access grant type.
@param [String] new_code
The authorization code.
# File lib/signet/oauth_2/client.rb, line 413 def code=(new_code) @code = new_code end
Returns the decoded ID token associated with this client.
@param [OpenSSL::PKey::RSA, Object] public_key
The public key to use to verify the ID token. Skips verification if omitted.
@return [String] The decoded ID token.
# File lib/signet/oauth_2/client.rb, line 667 def decoded_id_token(public_key=nil) decoded = JWT.decode(self.id_token, public_key, !!public_key) if !decoded.has_key?('aud') raise Signet::UnsafeOperationError, 'No ID token audience declared.' elsif decoded['aud'] != self.client_id raise Signet::UnsafeOperationError, 'ID token audience did not match Client ID.' end return decoded end
Returns true if the access token has expired.
@return [TrueClass, FalseClass]
The expiration state of the access token.
# File lib/signet/oauth_2/client.rb, line 735 def expired? return self.expires_at != nil && Time.now >= self.expires_at end
Returns the timestamp the access token will expire at.
@return [Integer] The access token lifetime.
# File lib/signet/oauth_2/client.rb, line 722 def expires_at if @issued_at && @expires_in return @issued_at + @expires_in else return nil end end
Returns the lifetime of the access token in seconds.
@return [Integer] The access token lifetime.
# File lib/signet/oauth_2/client.rb, line 682 def expires_in return @expires_in end
Sets the lifetime of the access token in seconds. Resets the issued timestamp.
@param [String] new_expires_in
The access token lifetime.
# File lib/signet/oauth_2/client.rb, line 692 def expires_in=(new_expires_in) if new_expires_in != nil @expires_in = new_expires_in.to_i @issued_at = Time.now else @expires_in, @issued_at = nil, nil end end
Returns the number of seconds assertions are valid for Used only by the assertion grant type.
@return [Fixnum] Assertion expiry, in seconds
# File lib/signet/oauth_2/client.rb, line 543 def expiry return @expiry end
Sets the number of seconds assertions are valid for Used only by the assertion grant type.
@param [String] new_expiry
Assertion expiry, in seconds
# File lib/signet/oauth_2/client.rb, line 553 def expiry=(new_expiry) @expiry = new_expiry end
Returns the set of extension parameters used by the client. Used only by extension access grant types.
@return [Hash] The extension parameters.
# File lib/signet/oauth_2/client.rb, line 589 def extension_parameters return @extension_parameters ||= {} end
Sets extension parameters used by the client. Used only by extension access grant types.
@param [Hash] new_extension_parameters
The parameters.
# File lib/signet/oauth_2/client.rb, line 599 def extension_parameters=(new_extension_parameters) if new_extension_parameters.respond_to?(:to_hash) @extension_parameters = new_extension_parameters.to_hash else raise TypeError, "Expected Hash, got #{new_extension_parameters.class}." end end
# File lib/signet/oauth_2/client.rb, line 860 def fetch_access_token(options={}) options[:connection] ||= Faraday.default_connection request = self.generate_access_token_request(options) request_env = request.to_env(options[:connection]) request_env[:request] ||= request response = options[:connection].app.call(request_env) if response.status.to_i == 200 return ::Signet::OAuth2.parse_json_credentials(response.body) elsif [400, 401, 403].include?(response.status.to_i) message = 'Authorization failed.' if response.body.to_s.strip.length > 0 message += " Server message:\n#{response.body.to_s.strip}" end raise ::Signet::AuthorizationError.new( message, :request => request, :response => response ) else message = "Unexpected status code: #{response.status}." if response.body.to_s.strip.length > 0 message += " Server message:\n#{response.body.to_s.strip}" end raise ::Signet::AuthorizationError.new( message, :request => request, :response => response ) end end
# File lib/signet/oauth_2/client.rb, line 887 def fetch_access_token!(options={}) token_hash = self.fetch_access_token(options) if token_hash # No-op for grant types other than `authorization_code`. # An authorization code is a one-time use token and is immediately # revoked after usage. self.code = nil self.issued_at = Time.now self.update_token!(token_hash) end return token_hash end
Transmits a request for a protected resource.
@param [Hash] options
The configuration parameters for the request. - <code>:request</code> - A pre-constructed request. An OAuth 2 Authorization header will be added to it, as well as an explicit Cache-Control `no-store` directive. - <code>:method</code> - The HTTP method for the request. Defaults to 'GET'. - <code>:uri</code> - The URI for the request. - <code>:headers</code> - The HTTP headers for the request. - <code>:body</code> - The HTTP body for the request. - <code>:realm</code> - The Authorization realm. See RFC 2617. - <code>:connection</code> - The HTTP connection to use. Must be of type <code>Faraday::Connection</code>.
@example
# Using Net::HTTP response = client.fetch_protected_resource( :uri => 'http://www.example.com/protected/resource' )
@example
# Using Typhoeus response = client.fetch_protected_resource( :request => Typhoeus::Request.new( 'http://www.example.com/protected/resource' ), :adapter => HTTPAdapter::TyphoeusAdapter.new, :connection => connection )
@return [Array] The response object.
# File lib/signet/oauth_2/client.rb, line 1015 def fetch_protected_resource(options={}) options[:connection] ||= Faraday.default_connection request = self.generate_authenticated_request(options) request_env = request.to_env(options[:connection]) request_env[:request] ||= request response = options[:connection].app.call(request_env) if response.status.to_i == 401 # When accessing a protected resource, we only want to raise an # error for 401 responses. message = 'Authorization failed.' if response.body.to_s.strip.length > 0 message += " Server message:\n#{response.body.to_s.strip}" end raise ::Signet::AuthorizationError.new( message, :request => request, :response => response ) else return response end end
Generates a request for token credentials.
@param [Hash] options
The configuration parameters for the request. - <code>:code</code> - The authorization code.
@return [Array] The request object.
# File lib/signet/oauth_2/client.rb, line 816 def generate_access_token_request(options={}) if self.token_credential_uri == nil raise ArgumentError, 'Missing token endpoint URI.' end options[:connection] ||= Faraday.default_connection method = 'POST' parameters = {"grant_type" => self.grant_type} case self.grant_type when 'authorization_code' parameters['code'] = self.code parameters['redirect_uri'] = self.redirect_uri when 'password' parameters['username'] = self.username parameters['password'] = self.password when 'refresh_token' parameters['refresh_token'] = self.refresh_token when 'urn:ietf:params:oauth:grant-type:jwt-bearer' parameters['assertion'] = self.to_jwt(options) else if self.redirect_uri # Grant type was intended to be `authorization_code` because of # the presence of the redirect URI. raise ArgumentError, 'Missing authorization code.' end parameters.merge!(self.extension_parameters) end parameters['client_id'] = self.client_id unless self.client_id.nil? parameters['client_secret'] = self.client_secret unless self.client_secret.nil? headers = [ ['Cache-Control', 'no-store'], ['Content-Type', 'application/x-www-form-urlencoded'] ] return options[:connection].build_request( method.to_s.downcase.to_sym ) do |req| req.url(Addressable::URI.parse( self.token_credential_uri ).normalize.to_s) req.headers = Faraday::Utils::Headers.new(headers) req.body = Addressable::URI.form_encode(parameters) end end
Generates an authenticated request for protected resources.
@param [Hash] options
The configuration parameters for the request. - <code>:request</code> - A pre-constructed request. An OAuth 2 Authorization header will be added to it, as well as an explicit Cache-Control `no-store` directive. - <code>:method</code> - The HTTP method for the request. Defaults to 'GET'. - <code>:uri</code> - The URI for the request. - <code>:headers</code> - The HTTP headers for the request. - <code>:body</code> - The HTTP body for the request. - <code>:realm</code> - The Authorization realm. See RFC 2617.
@return [Faraday::Request] The request object.
# File lib/signet/oauth_2/client.rb, line 927 def generate_authenticated_request(options={}) if self.access_token == nil raise ArgumentError, 'Missing access token.' end options = { :realm => nil }.merge(options) if options[:request].kind_of?(Faraday::Request) request = options[:request] else if options[:request].kind_of?(Array) method, uri, headers, body = options[:request] else method = options[:method] || :get uri = options[:uri] headers = options[:headers] || [] body = options[:body] || '' end headers = headers.to_a if headers.kind_of?(Hash) request_components = { :method => method, :uri => uri, :headers => headers, :body => body } # Verify that we have all pieces required to return an HTTP request request_components.each do |(key, value)| unless value raise ArgumentError, "Missing :#{key} parameter." end end method = method.to_s.downcase.to_sym request = options[:connection].build_request(method.to_s.downcase.to_sym) do |req| req.url(Addressable::URI.parse(uri).normalize.to_s) req.headers = Faraday::Utils::Headers.new(headers) req.body = body end end request['Authorization'] = ::Signet::OAuth2.generate_bearer_authorization_header( self.access_token, options[:realm] ? [['realm', options[:realm]]] : nil ) request['Cache-Control'] = 'no-store' return request end
Returns the inferred grant type, based on the current state of the client object. Returns `"none"` if the client has insufficient information to make an in-band authorization request.
@return [String]
The inferred grant type.
# File lib/signet/oauth_2/client.rb, line 761 def grant_type @grant_type ||= nil if @grant_type return @grant_type else if self.code && self.redirect_uri 'authorization_code' elsif self.refresh_token 'refresh_token' elsif self.username && self.password 'password' elsif self.issuer && self.signing_key 'urn:ietf:params:oauth:grant-type:jwt-bearer' else # We don't have sufficient auth information, assume an out-of-band # authorization arrangement between the client and server, or an # extension grant type. nil end end end
# File lib/signet/oauth_2/client.rb, line 783 def grant_type=(new_grant_type) case new_grant_type when 'authorization_code', 'refresh_token', 'password', 'client_credentials' @grant_type = new_grant_type else @grant_type = Addressable::URI.parse(new_grant_type) end end
Returns the ID token associated with this client.
@return [String] The ID token.
# File lib/signet/oauth_2/client.rb, line 646 def id_token return @id_token ||= nil end
Sets the ID token associated with this client.
@param [String] new_id_token
The ID token.
# File lib/signet/oauth_2/client.rb, line 655 def id_token=(new_id_token) @id_token = new_id_token end
Returns the timestamp the access token was issued at.
@return [Integer] The access token issuance time.
# File lib/signet/oauth_2/client.rb, line 705 def issued_at return @issued_at end
Sets the timestamp the access token was issued at.
@param [String] new_issued_at
The access token issuance time.
# File lib/signet/oauth_2/client.rb, line 714 def issued_at=(new_issued_at) @issued_at = new_issued_at end
Returns the issuer ID associated with this client. Used only by the assertion grant type.
@return [String] Issuer id.
# File lib/signet/oauth_2/client.rb, line 483 def issuer return @issuer end
Sets the issuer ID associated with this client. Used only by the assertion grant type.
@param [String] new_issuer
Issuer ID (typical in email adddress form).
# File lib/signet/oauth_2/client.rb, line 493 def issuer=(new_issuer) @issuer = new_issuer end
Returns the password associated with this client. Used only by the resource owner password credential access grant type.
@return [String] The password.
# File lib/signet/oauth_2/client.rb, line 464 def password return @password end
Sets the password associated with this client. Used only by the resource owner password credential access grant type.
@param [String] new_password
The password.
# File lib/signet/oauth_2/client.rb, line 474 def password=(new_password) @password = new_password end
Returns the target resource owner for impersonation. Used only by the assertion grant type.
@return [String] Target user for impersonation.
# File lib/signet/oauth_2/client.rb, line 521 def principal return @principal end
Sets the target resource owner for impersonation. Used only by the assertion grant type.
@param [String] new_person
Target user for impersonation
# File lib/signet/oauth_2/client.rb, line 531 def principal=(new_person) @principal = new_person end
Returns the redirect URI for this client.
@return [String] The redirect URI.
# File lib/signet/oauth_2/client.rb, line 421 def redirect_uri return @redirect_uri end
Sets the redirect URI for this client.
@param [String] new_redirect_uri
The redirect URI.
# File lib/signet/oauth_2/client.rb, line 430 def redirect_uri=(new_redirect_uri) new_redirect_uri = Addressable::URI.parse(new_redirect_uri) #TODO - Better solution to allow google postmessage flow. For now, make an exception to the spec. if new_redirect_uri == nil|| new_redirect_uri.absolute? || uri_is_postmessage?(new_redirect_uri) @redirect_uri = new_redirect_uri else raise ArgumentError, "Redirect URI must be an absolute URI." end end
Refresh the access token, if possible
# File lib/signet/oauth_2/client.rb, line 902 def refresh! self.fetch_access_token! end
Returns the refresh token associated with this client.
@return [String] The refresh token.
# File lib/signet/oauth_2/client.rb, line 612 def refresh_token return @refresh_token ||= nil end
Sets the refresh token associated with this client.
@param [String] new_refresh_token
The refresh token.
# File lib/signet/oauth_2/client.rb, line 621 def refresh_token=(new_refresh_token) @refresh_token = new_refresh_token end
Returns the scope for this client. Scope is a list of access ranges defined by the authorization server.
@return [Array] The scope of access the client is requesting.
# File lib/signet/oauth_2/client.rb, line 351 def scope return @scope end
Sets the scope for this client.
@param [Array, String] new_scope
The scope of access the client is requesting. This may be expressed as either an Array of String objects or as a space-delimited String.
# File lib/signet/oauth_2/client.rb, line 362 def scope=(new_scope) case new_scope when Array new_scope.each do |scope| if scope.include?(' ') raise ArgumentError, "Individual scopes cannot contain the space character." end end @scope = new_scope when String @scope = new_scope.split(' ') when nil @scope = nil else raise TypeError, "Expected Array or String, got #{new_scope.class}" end end
Algorithm used for signing JWTs @return [String] Signing algorithm
# File lib/signet/oauth_2/client.rb, line 580 def signing_algorithm self.signing_key.is_a?(String) ? "HS256" : "RS256" end
Returns the signing key associated with this client. Used only by the assertion grant type.
@return [String,OpenSSL::PKey] Signing key
# File lib/signet/oauth_2/client.rb, line 563 def signing_key return @signing_key end
Sets the signing key when issuing assertions. Used only by the assertion grant type.
@param [String, OpenSSL::Pkey] new_key
Signing key. Either private key for RSA or string for HMAC algorithm
# File lib/signet/oauth_2/client.rb, line 573 def signing_key=(new_key) @signing_key = new_key end
Returns the client's current state value.
@return [String] The state value.
# File lib/signet/oauth_2/client.rb, line 385 def state return @state end
Sets the client's current state value.
@param [String] new_state
The state value.
# File lib/signet/oauth_2/client.rb, line 394 def state=(new_state) @state = new_state end
# File lib/signet/oauth_2/client.rb, line 793 def to_jwt(options={}) now = Time.new skew = options[:skew] || 60 assertion = { "iss" => self.issuer, "scope" => self.scope.join(' '), "aud" => self.audience, "exp" => (now + self.expiry).to_i, "iat" => (now - skew).to_i } assertion['prn'] = self.person unless self.person.nil? JWT.encode(assertion, self.signing_key, self.signing_algorithm) end
Returns the token credential URI for this client.
@return [Addressable::URI] The token credential URI.
# File lib/signet/oauth_2/client.rb, line 293 def token_credential_uri return @token_credential_uri end
Sets the token credential URI for this client.
@param [Addressable::URI, String, to_str] new_token_credential_uri
The token credential URI.
# File lib/signet/oauth_2/client.rb, line 302 def token_credential_uri=(new_token_credential_uri) if new_token_credential_uri != nil new_token_credential_uri = Addressable::URI.parse(new_token_credential_uri) @token_credential_uri = new_token_credential_uri else @token_credential_uri = nil end end
Updates an OAuth 2.0 client.
@param [Hash] options
The configuration parameters for the client. - <code>:authorization_uri</code> - The authorization server's HTTP endpoint capable of authenticating the end-user and obtaining authorization. - <code>:token_credential_uri</code> - The authorization server's HTTP endpoint capable of issuing tokens and refreshing expired tokens. - <code>:client_id</code> - A unique identifier issued to the client to identify itself to the authorization server. - <code>:client_secret</code> - A shared symmetric secret issued by the authorization server, which is used to authenticate the client. - <code>:scope</code> - The scope of the access request, expressed either as an Array or as a space-delimited String. - <code>:state</code> - An arbitrary string designed to allow the client to maintain state. - <code>:code</code> - The authorization code received from the authorization server. - <code>:redirect_uri</code> - The redirection URI used in the initial request. - <code>:username</code> - The resource owner's username. - <code>:password</code> - The resource owner's password. - <code>:issuer</code> - Issuer ID when using assertion profile - <code>:audience</code> - Target audience for assertions - <code>:person</code> - Target user for assertions - <code>:expiry</code> - Number of seconds assertions are valid for - <code>:signing_key</code> - Signing key when using assertion profile - <code>:refresh_token</code> - The refresh token associated with the access token to be refreshed. - <code>:access_token</code> - The current access token for this client. - <code>:id_token</code> - The current ID token for this client. - <code>:extension_parameters</code> - When using an extension grant type, this the set of parameters used by that extension.
@example
client.update!( :code => 'i1WsRn1uB1', :access_token => 'FJQbwq9', :expires_in => 3600 )
@see Signet::OAuth2::Client#initialize @see #update_token!
# File lib/signet/oauth_2/client.rb, line 157 def update!(options={}) # Normalize key to String to allow indifferent access. options = options.inject({}) { |accu, (k, v)| accu[k.to_s] = v; accu } self.authorization_uri = options["authorization_uri"] self.token_credential_uri = options["token_credential_uri"] self.client_id = options["client_id"] self.client_secret = options["client_secret"] self.scope = options["scope"] self.state = options["state"] self.code = options["code"] self.redirect_uri = options["redirect_uri"] self.username = options["username"] self.password = options["password"] self.issuer = options["issuer"] self.person = options["person"] self.expiry = options["expiry"] || 60 self.audience = options["audience"] self.signing_key = options["signing_key"] self.extension_parameters = options["extension_parameters"] || {} self.update_token!(options) return self end
Updates an OAuth 2.0 client.
@param [Hash] options
The configuration parameters related to the token. - <code>:refresh_token</code> - The refresh token associated with the access token to be refreshed. - <code>:access_token</code> - The current access token for this client. - <code>:id_token</code> - The current ID token for this client. - <code>:expires_in</code> - The time in seconds until access token expiration. - <code>:issued_at</code> - The timestamp that the token was issued at.
@example
client.update!( :refresh_token => 'n4E9O119d', :access_token => 'FJQbwq9', :expires_in => 3600 )
@see Signet::OAuth2::Client#initialize @see #update!
# File lib/signet/oauth_2/client.rb, line 206 def update_token!(options={}) # Normalize key to String to allow indifferent access. options = options.inject({}) { |accu, (k, v)| accu[k.to_s] = v; accu } self.access_token = options["access_token"] if options["access_token"] self.expires_in = options["expires_in"] if options["expires_in"] # The refresh token may not be returned in a token response. # In which case, the old one should continue to be used. if options["refresh_token"] self.refresh_token = options["refresh_token"] end # The ID token may not be returned in a token response. # In which case, the old one should continue to be used. if options["id_token"] self.id_token = options["id_token"] end # By default, the token is issued at `Time.now` when `expires_in` is # set, but this can be used to supply a more precise time. if options["issued_at"] self.issued_at = options["issued_at"] end return self end
Returns the username associated with this client. Used only by the resource owner password credential access grant type.
@return [String] The username.
# File lib/signet/oauth_2/client.rb, line 445 def username return @username end
Sets the username associated with this client. Used only by the resource owner password credential access grant type.
@param [String] new_username
The username.
# File lib/signet/oauth_2/client.rb, line 455 def username=(new_username) @username = new_username end