From 945d999aadc9df41487e675fa0a863396cb54e31 Mon Sep 17 00:00:00 2001 From: pivotal Date: Fri, 2 Oct 2009 13:57:31 -0700 Subject: Digest auth option for ActiveResource. Signed-off-by: Michael Koziarski --- activeresource/lib/active_resource/base.rb | 12 ++++ activeresource/lib/active_resource/connection.rb | 90 +++++++++++++++++++++--- 2 files changed, 91 insertions(+), 11 deletions(-) (limited to 'activeresource/lib') diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index b21d8db613..ae627c365d 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -326,6 +326,17 @@ module ActiveResource @password = password end + def auth_type + if defined?(@auth_type) + @auth_type + end + end + + def auth_type=(auth_type) + @connection = nil + @auth_type = auth_type + end + # Sets the format that attributes are sent and received in from a mime type reference: # # Person.format = :json @@ -397,6 +408,7 @@ module ActiveResource @connection.proxy = proxy if proxy @connection.user = user if user @connection.password = password if password + @connection.auth_type = auth_type if auth_type @connection.timeout = timeout if timeout @connection.ssl_options = ssl_options if ssl_options @connection diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb index 9d551f04e7..98cb1a932b 100644 --- a/activeresource/lib/active_resource/connection.rb +++ b/activeresource/lib/active_resource/connection.rb @@ -17,7 +17,7 @@ module ActiveResource :head => 'Accept' } - attr_reader :site, :user, :password, :timeout, :proxy, :ssl_options + attr_reader :site, :user, :password, :auth_type, :timeout, :proxy, :ssl_options attr_accessor :format class << self @@ -57,6 +57,11 @@ module ActiveResource @password = password end + # Sets the auth type for remote service. + def auth_type=(auth_type) + @auth_type = legitimize_auth_type(auth_type) + end + # Sets the number of seconds after which HTTP requests to the remote service should time out. def timeout=(timeout) @timeout = timeout @@ -70,31 +75,31 @@ module ActiveResource # Executes a GET request. # Used to get (find) resources. def get(path, headers = {}) - format.decode(request(:get, path, build_request_headers(headers, :get)).body) + with_auth { format.decode(request(:get, path, build_request_headers(headers, :get, self.site.merge(path))).body) } end # Executes a DELETE request (see HTTP protocol documentation if unfamiliar). # Used to delete resources. def delete(path, headers = {}) - request(:delete, path, build_request_headers(headers, :delete)) + with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) } end # Executes a PUT request (see HTTP protocol documentation if unfamiliar). # Used to update resources. def put(path, body = '', headers = {}) - request(:put, path, body.to_s, build_request_headers(headers, :put)) + with_auth { request(:put, path, body.to_s, build_request_headers(headers, :put, self.site.merge(path))) } end # Executes a POST request. # Used to create new resources. def post(path, body = '', headers = {}) - request(:post, path, body.to_s, build_request_headers(headers, :post)) + with_auth { request(:post, path, body.to_s, build_request_headers(headers, :post, self.site.merge(path))) } end # Executes a HEAD request. # Used to obtain meta-information about resources, such as whether they exist and their size (via response headers). def head(path, headers = {}) - request(:head, path, build_request_headers(headers, :head)) + with_auth { request(:head, path, build_request_headers(headers, :head, self.site.merge(path))) } end @@ -198,13 +203,70 @@ module ActiveResource end # Builds headers for request to remote service. - def build_request_headers(headers, http_method=nil) - authorization_header.update(default_header).update(http_format_header(http_method)).update(headers) + def build_request_headers(headers, http_method, uri) + authorization_header(http_method, uri).update(default_header).update(http_format_header(http_method)).update(headers) + end + + def response_auth_header + @response_auth_header ||= "" + end + + def with_auth + retried ||= false + yield + rescue UnauthorizedAccess => e + raise if retried || auth_type != :digest + @response_auth_header = e.response['WWW-Authenticate'] + retried = true + retry + end + + def authorization_header(http_method, uri) + if @user || @password + if auth_type == :digest + { 'Authorization' => digest_auth_header(http_method, uri) } + else + { 'Authorization' => 'Basic ' + ["#{@user}:#{@password}"].pack('m').delete("\r\n") } + end + else + {} + end end - # Sets authorization header - def authorization_header - (@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {}) + def digest_auth_header(http_method, uri) + params = extract_params_from_response + + ha1 = Digest::MD5.hexdigest("#{@user}:#{params['realm']}:#{@password}") + ha2 = Digest::MD5.hexdigest("#{http_method.to_s.upcase}:#{uri.path}") + + params.merge!('cnonce' => client_nonce) + request_digest = Digest::MD5.hexdigest([ha1, params['nonce'], "0", params['cnonce'], params['qop'], ha2].join(":")) + "Digest #{auth_attributes_for(uri, request_digest, params)}" + end + + def client_nonce + Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535))) + end + + def extract_params_from_response + params = {} + if response_auth_header =~ /^(\w+) (.*)/ + $2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 } + end + params + end + + def auth_attributes_for(uri, request_digest, params) + [ + %Q(username="#{@user}"), + %Q(realm="#{params['realm']}"), + %Q(qop="#{params['qop']}"), + %Q(uri="#{uri.path}"), + %Q(nonce="#{params['nonce']}"), + %Q(nc="0"), + %Q(cnonce="#{params['cnonce']}"), + %Q(opaque="#{params['opaque']}"), + %Q(response="#{request_digest}")].join(", ") end def http_format_header(http_method) @@ -214,5 +276,11 @@ module ActiveResource def logger #:nodoc: Base.logger end + + def legitimize_auth_type(auth_type) + return :basic if auth_type.nil? + auth_type = auth_type.to_sym + [:basic, :digest].include?(auth_type) ? auth_type : :basic + end end end -- cgit v1.2.3 From b540eca5889d7a28fac39c9ec0df715aa89487ce Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sun, 1 Nov 2009 11:06:47 +0100 Subject: Consolidate Object#to_param and #to_query core extensions --- activeresource/lib/active_resource/base.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activeresource/lib') diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index ae627c365d..bd2abdd38e 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -8,6 +8,7 @@ require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/misc' +require 'active_support/core_ext/object/to_query' require 'set' require 'uri' -- cgit v1.2.3 From 6d808cf494e0434c814c0be9594020ad6f00ef18 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 9 Nov 2009 04:59:26 -0800 Subject: Remove reliance on string access core extension --- activeresource/lib/active_resource/base.rb | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'activeresource/lib') diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index bd2abdd38e..bfe4208d98 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -1152,15 +1152,16 @@ module ActiveResource def respond_to?(method, include_priv = false) method_name = method.to_s if attributes.nil? - return super + super elsif attributes.has_key?(method_name) - return true - elsif ['?','='].include?(method_name.last) && attributes.has_key?(method_name.first(-1)) - return true + true + elsif method_name =~ /(?:=|\?)$/ && attributes.include?($`) + true + else + # super must be called at the end of the method, because the inherited respond_to? + # would return true for generated readers, even if the attribute wasn't present + super end - # super must be called at the end of the method, because the inherited respond_to? - # would return true for generated readers, even if the attribute wasn't present - super end protected @@ -1249,13 +1250,15 @@ module ActiveResource def method_missing(method_symbol, *arguments) #:nodoc: method_name = method_symbol.to_s - case method_name.last + if method_name =~ /(=|\?)$/ + case $1 when "=" - attributes[method_name.first(-1)] = arguments.first + attributes[$`] = arguments.first when "?" - attributes[method_name.first(-1)] - else - attributes.has_key?(method_name) ? attributes[method_name] : super + attributes[$`] + end + else + attributes.include?(method_name) ? attributes[method_name] : super end end end -- cgit v1.2.3 From 76b2d3e33730fce9680d0b6e97df12b1744fb23d Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 9 Nov 2009 05:07:58 -0800 Subject: Ruby 1.9.2: URI.parse and .decode are deprecated --- activeresource/lib/active_resource/base.rb | 12 ++++++++---- activeresource/lib/active_resource/connection.rb | 9 +++++---- 2 files changed, 13 insertions(+), 8 deletions(-) (limited to 'activeresource/lib') diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index bfe4208d98..18105e8887 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -274,8 +274,8 @@ module ActiveResource @site = nil else @site = create_site_uri_from(site) - @user = URI.decode(@site.user) if @site.user - @password = URI.decode(@site.password) if @site.password + @user = uri_parser.unescape(@site.user) if @site.user + @password = uri_parser.unescape(@site.password) if @site.password end end @@ -737,12 +737,12 @@ module ActiveResource # Accepts a URI and creates the site URI from that. def create_site_uri_from(site) - site.is_a?(URI) ? site.dup : URI.parse(site) + site.is_a?(URI) ? site.dup : uri_parser.parse(site) end # Accepts a URI and creates the proxy URI from that. def create_proxy_uri_from(proxy) - proxy.is_a?(URI) ? proxy.dup : URI.parse(proxy) + proxy.is_a?(URI) ? proxy.dup : uri_parser.parse(proxy) end # contains a set of the current prefix parameters. @@ -767,6 +767,10 @@ module ActiveResource [ prefix_options, query_options ] end + + def uri_parser + @uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI + end end attr_accessor :attributes #:nodoc: diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb index 98cb1a932b..193be89a82 100644 --- a/activeresource/lib/active_resource/connection.rb +++ b/activeresource/lib/active_resource/connection.rb @@ -31,20 +31,21 @@ module ActiveResource def initialize(site, format = ActiveResource::Formats::XmlFormat) raise ArgumentError, 'Missing site URI' unless site @user = @password = nil + @uri_parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI self.site = site self.format = format end # Set URI for remote service. def site=(site) - @site = site.is_a?(URI) ? site : URI.parse(site) - @user = URI.decode(@site.user) if @site.user - @password = URI.decode(@site.password) if @site.password + @site = site.is_a?(URI) ? site : @uri_parser.parse(site) + @user = @uri_parser.unescape(@site.user) if @site.user + @password = @uri_parser.unescape(@site.password) if @site.password end # Set the proxy for remote service. def proxy=(proxy) - @proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy) + @proxy = proxy.is_a?(URI) ? proxy : @uri_parser.parse(proxy) end # Sets the user for remote service. -- cgit v1.2.3