lib/rufus/verbs/digest.rb in rufus-verbs-0.10 vs lib/rufus/verbs/digest.rb in rufus-verbs-1.0.0
- old
+ new
@@ -1,261 +1,252 @@
-#
#--
-# Copyright (c) 2008, John Mettraux, jmettraux@gmail.com
+# Copyright (c) 2008-2010, John Mettraux, jmettraux@gmail.com
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
-# (MIT license)
+# Made in Japan.
#++
-#
-#
-# John Mettraux
-#
-# Made in Japan
-#
-# 2008/01/21
-#
require 'digest/md5'
module Rufus
module Verbs
+ #
+ # Specified by http://www.ietf.org/rfc/rfc2617.txt
+ # Inspired by http://segment7.net/projects/ruby/snippets/digest_auth.rb
+ #
+ # The EndPoint classes mixes this in to support digest authentication.
+ #
+ module DigestAuthMixin
+
#
- # Specified by http://www.ietf.org/rfc/rfc2617.txt
- # Inspired by http://segment7.net/projects/ruby/snippets/digest_auth.rb
+ # Makes sure digest_auth is on
#
- # The EndPoint classes mixes this in to support digest authentication.
- #
- module DigestAuthMixin
+ def digest_auth (req, opts)
- #
- # Makes sure digest_auth is on
- #
- def digest_auth (req, opts)
+ #return if no_digest_auth
+ # already done in add_authentication()
- #return if no_digest_auth
- # already done in add_authentication()
+ @cnonce ||= generate_cnonce
+ @nonce_count ||= 0
- @cnonce ||= generate_cnonce
- @nonce_count ||= 0
+ mention_digest_auth(req, opts) \
+ and return
- mention_digest_auth(req, opts) \
- and return
+ mention_digest_auth(req, opts) \
+ if request_challenge(req, opts)
+ end
- mention_digest_auth(req, opts) \
- if request_challenge(req, opts)
- end
+ #
+ # Sets the 'Authorization' header with the appropriate info.
+ #
+ def mention_digest_auth (req, opts)
- #
- # Sets the 'Authorization' header with the appropriate info.
- #
- def mention_digest_auth (req, opts)
+ return false unless @challenge
- return false unless @challenge
+ req['Authorization'] = generate_header req, opts
- req['Authorization'] = generate_header req, opts
+ true
+ end
- true
- end
+ #
+ # Interprets the information in the response's 'Authorization-Info'
+ # header.
+ #
+ def check_authentication_info (res, opts)
- #
- # Interprets the information in the response's 'Authorization-Info'
- # header.
- #
- def check_authentication_info (res, opts)
+ return if no_digest_auth
+ # not using digest authentication
- return if no_digest_auth
- # not using digest authentication
+ return unless @challenge
+ # not yet authenticated
- return unless @challenge
- # not yet authenticated
+ authinfo = AuthInfo.new res
+ @challenge.nonce = authinfo.nextnonce
+ end
- authinfo = AuthInfo.new res
- @challenge.nonce = authinfo.nextnonce
- end
+ protected
- protected
+ #
+ # Returns true if :digest_authentication is set at endpoint
+ # or request level.
+ #
+ def no_digest_auth
- #
- # Returns true if :digest_authentication is set at endpoint
- # or request level.
- #
- def no_digest_auth
+ (not o(opts, :digest_authentication))
+ end
- (not o(opts, :digest_authentication))
- end
+ #
+ # To be enhanced.
+ #
+ # (For example http://www.intertwingly.net/blog/1585.html)
+ #
+ def generate_cnonce
- #
- # To be enhanced.
- #
- # (For example http://www.intertwingly.net/blog/1585.html)
- #
- def generate_cnonce
+ Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535)))
+ end
- Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535)))
- end
+ def request_challenge (req, opts)
- def request_challenge (req, opts)
+ op = opts.dup
- op = opts.dup
+ op[:digest_authentication] = false
+ # preventing an infinite loop
- op[:digest_authentication] = false
- # preventing an infinite loop
+ method = req.class.const_get(:METHOD).downcase.to_sym
+ #method = :get
- method = req.class.const_get(:METHOD).downcase.to_sym
- #method = :get
+ res = request(method, op)
- res = request(method, op)
+ return false if res.code.to_i != 401
- return false if res.code.to_i != 401
+ @challenge = Challenge.new res
- @challenge = Challenge.new res
+ true
+ end
- true
- end
+ #
+ # Generates an MD5 digest of the arguments (joined by ":").
+ #
+ def h (*args)
- #
- # Generates an MD5 digest of the arguments (joined by ":").
- #
- def h (*args)
+ Digest::MD5.hexdigest(args.join(":"))
+ end
- Digest::MD5.hexdigest(args.join(":"))
- end
+ #
+ # Generates the Authentication header that will be returned
+ # to the server.
+ #
+ def generate_header (req, opts)
- #
- # Generates the Authentication header that will be returned
- # to the server.
- #
- def generate_header (req, opts)
+ @nonce_count += 1
- @nonce_count += 1
+ user, pass = o(opts, :digest_authentication)
+ realm = @challenge.realm || ""
+ method = req.class.const_get(:METHOD)
+ path = opts[:path]
- user, pass = o(opts, :digest_authentication)
- realm = @challenge.realm || ""
- method = req.class.const_get(:METHOD)
- path = opts[:path]
+ a1 = if @challenge.algorithm == 'MD5-sess'
+ h(h(user, realm, pass), @challenge.nonce, @cnonce)
+ else
+ h(user, realm, pass)
+ end
- a1 = if @challenge.algorithm == 'MD5-sess'
- h(h(user, realm, pass), @challenge.nonce, @cnonce)
- else
- h(user, realm, pass)
- end
+ a2, qop = if @challenge.qop.include?("auth-int")
+ [ h(method, path, h(req.body)), "auth-int" ]
+ else
+ [ h(method, path), "auth" ]
+ end
- a2, qop = if @challenge.qop.include?("auth-int")
- [ h(method, path, h(req.body)), "auth-int" ]
- else
- [ h(method, path), "auth" ]
- end
+ nc = ('%08x' % @nonce_count)
- nc = ('%08x' % @nonce_count)
+ digest = h(
+ #a1, @challenge.nonce, nc, @cnonce, @challenge.qop, a2)
+ a1, @challenge.nonce, nc, @cnonce, "auth", a2)
- digest = h(
- #a1, @challenge.nonce, nc, @cnonce, @challenge.qop, a2)
- a1, @challenge.nonce, nc, @cnonce, "auth", a2)
+ header = ""
+ header << "Digest username=\"#{user}\", "
+ header << "realm=\"#{realm}\", "
+ header << "qop=\"#{qop}\", "
+ header << "uri=\"#{path}\", "
+ header << "nonce=\"#{@challenge.nonce}\", "
+ #header << "nc=##{nc}, "
+ header << "nc=#{nc}, "
+ header << "cnonce=\"#{@cnonce}\", "
+ header << "algorithm=\"#{@challenge.algorithm}\", "
+ #header << "algorithm=\"MD5-sess\", "
+ header << "response=\"#{digest}\", "
+ header << "opaque=\"#{@challenge.opaque}\""
- header = ""
- header << "Digest username=\"#{user}\", "
- header << "realm=\"#{realm}\", "
- header << "qop=\"#{qop}\", "
- header << "uri=\"#{path}\", "
- header << "nonce=\"#{@challenge.nonce}\", "
- #header << "nc=##{nc}, "
- header << "nc=#{nc}, "
- header << "cnonce=\"#{@cnonce}\", "
- header << "algorithm=\"#{@challenge.algorithm}\", "
- #header << "algorithm=\"MD5-sess\", "
- header << "response=\"#{digest}\", "
- header << "opaque=\"#{@challenge.opaque}\""
+ header
+ end
- header
- end
+ #
+ # A common parent class for Challenge and AuthInfo.
+ # Their header parsing code is here.
+ #
+ class ServerReply
- #
- # A common parent class for Challenge and AuthInfo.
- # Their header parsing code is here.
- #
- class ServerReply
+ def initialize (res)
- def initialize (res)
+ s = res[header_name]
+ return nil unless s
- s = res[header_name]
- return nil unless s
+ s = s[7..-1] if s[0, 6] == "Digest"
- s = s[7..-1] if s[0, 6] == "Digest"
+ s = s.split ","
- s = s.split ","
+ s.each do |e|
- s.each do |e|
+ k, v = parse_entry e
- k, v = parse_entry e
+ if k == 'stale'
+ @stale = (v.downcase == 'true')
+ elsif k == 'nc'
+ @nc = v.to_i
+ elsif k == 'qop'
+ @qop = v.split ","
+ else
+ instance_variable_set "@#{k}".to_sym, v
+ end
+ end
+ end
- if k == 'stale'
- @stale = (v.downcase == 'true')
- elsif k == 'nc'
- @nc = v.to_i
- elsif k == 'qop'
- @qop = v.split ","
- else
- instance_variable_set "@#{k}".to_sym, v
- end
- end
- end
+ protected
- protected
+ def parse_entry (e)
- def parse_entry (e)
+ k, v = e.split '=', 2
+ v = v[1..-2] if v[0, 1] == '"'
+ [ k.strip, v.strip ]
+ end
+ end
- k, v = e.split "=", 2
- v = v[1..-2] if v[0, 1] == '"'
- [ k.strip, v.strip ]
- end
- end
+ #
+ # Used when parsing a 'www-authenticate' header challenge.
+ #
+ class Challenge < ServerReply
- #
- # Used when parsing a 'www-authenticate' header challenge.
- #
- class Challenge < ServerReply
+ attr_accessor \
+ :opaque, :algorithm, :qop, :stale, :nonce, :realm, :charset
- attr_accessor \
- :opaque, :algorithm, :qop, :stale, :nonce, :realm, :charset
+ def header_name
+ 'www-authenticate'
+ end
+ end
- def header_name
- 'www-authenticate'
- end
- end
+ #
+ # Used when parsing a 'authentication-info' header info.
+ #
+ class AuthInfo < ServerReply
- #
- # Used when parsing a 'authentication-info' header info.
- #
- class AuthInfo < ServerReply
+ attr_accessor \
+ :cnonce, :rspauth, :nextnonce, :qop, :nc
- attr_accessor \
- :cnonce, :rspauth, :nextnonce, :qop, :nc
-
- def header_name
- 'authentication-info'
- end
- end
+ def header_name
+ 'authentication-info'
+ end
end
+ end
end
end