Merged updates from trunk into stable branch
[feedcatcher.git] / vendor / rails / actionpack / lib / action_controller / vendor / rack-1.0 / rack / auth / digest / md5.rb
1 require 'rack/auth/abstract/handler'
2 require 'rack/auth/digest/request'
3 require 'rack/auth/digest/params'
4 require 'rack/auth/digest/nonce'
5 require 'digest/md5'
6
7 module Rack
8 module Auth
9 module Digest
10 # Rack::Auth::Digest::MD5 implements the MD5 algorithm version of
11 # HTTP Digest Authentication, as per RFC 2617.
12 #
13 # Initialize with the [Rack] application that you want protecting,
14 # and a block that looks up a plaintext password for a given username.
15 #
16 # +opaque+ needs to be set to a constant base64/hexadecimal string.
17 #
18 class MD5 < AbstractHandler
19
20 attr_accessor :opaque
21
22 attr_writer :passwords_hashed
23
24 def initialize(*args)
25 super
26 @passwords_hashed = nil
27 end
28
29 def passwords_hashed?
30 !!@passwords_hashed
31 end
32
33 def call(env)
34 auth = Request.new(env)
35
36 unless auth.provided?
37 return unauthorized
38 end
39
40 if !auth.digest? || !auth.correct_uri? || !valid_qop?(auth)
41 return bad_request
42 end
43
44 if valid?(auth)
45 if auth.nonce.stale?
46 return unauthorized(challenge(:stale => true))
47 else
48 env['REMOTE_USER'] = auth.username
49
50 return @app.call(env)
51 end
52 end
53
54 unauthorized
55 end
56
57
58 private
59
60 QOP = 'auth'.freeze
61
62 def params(hash = {})
63 Params.new do |params|
64 params['realm'] = realm
65 params['nonce'] = Nonce.new.to_s
66 params['opaque'] = H(opaque)
67 params['qop'] = QOP
68
69 hash.each { |k, v| params[k] = v }
70 end
71 end
72
73 def challenge(hash = {})
74 "Digest #{params(hash)}"
75 end
76
77 def valid?(auth)
78 valid_opaque?(auth) && valid_nonce?(auth) && valid_digest?(auth)
79 end
80
81 def valid_qop?(auth)
82 QOP == auth.qop
83 end
84
85 def valid_opaque?(auth)
86 H(opaque) == auth.opaque
87 end
88
89 def valid_nonce?(auth)
90 auth.nonce.valid?
91 end
92
93 def valid_digest?(auth)
94 digest(auth, @authenticator.call(auth.username)) == auth.response
95 end
96
97 def md5(data)
98 ::Digest::MD5.hexdigest(data)
99 end
100
101 alias :H :md5
102
103 def KD(secret, data)
104 H([secret, data] * ':')
105 end
106
107 def A1(auth, password)
108 [ auth.username, auth.realm, password ] * ':'
109 end
110
111 def A2(auth)
112 [ auth.method, auth.uri ] * ':'
113 end
114
115 def digest(auth, password)
116 password_hash = passwords_hashed? ? password : H(A1(auth, password))
117
118 KD(password_hash, [ auth.nonce, auth.nc, auth.cnonce, QOP, H(A2(auth)) ] * ':')
119 end
120
121 end
122 end
123 end
124 end