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
| | # frozen_string_literal: true
require 'rack/auth/abstract/handler'
require 'rack/auth/digest/request'
require 'rack/auth/digest/params'
require 'rack/auth/digest/nonce'
require 'digest/md5'
module Rack
module Auth
module Digest
# Rack::Auth::Digest::MD5 implements the MD5 algorithm version of
# HTTP Digest Authentication, as per RFC 2617.
#
# Initialize with the [Rack] application that you want protecting,
# and a block that looks up a plaintext password for a given username.
#
# +opaque+ needs to be set to a constant base64/hexadecimal string.
#
class MD5 < AbstractHandler
attr_accessor :opaque
attr_writer :passwords_hashed
def initialize(app, realm = nil, opaque = nil, &authenticator)
@passwords_hashed = nil
if opaque.nil? and realm.respond_to? :values_at
realm, opaque, @passwords_hashed = realm.values_at :realm, :opaque, :passwords_hashed
end
super(app, realm, &authenticator)
@opaque = opaque
end
def passwords_hashed?
!!@passwords_hashed
end
def call(env)
auth = Request.new(env)
unless auth.provided?
return unauthorized
end
if !auth.digest? || !auth.correct_uri? || !valid_qop?(auth)
return bad_request
end
if valid?(auth)
if auth.nonce.stale?
return unauthorized(challenge(stale: true))
else
env['REMOTE_USER'] = auth.username
return @app.call(env)
end
end
unauthorized
end
private
QOP = 'auth'
def params(hash = {})
Params.new do |params|
params['realm'] = realm
params['nonce'] = Nonce.new.to_s
params['opaque'] = H(opaque)
params['qop'] = QOP
hash.each { |k, v| params[k] = v }
end
end
def challenge(hash = {})
"Digest #{params(hash)}"
end
def valid?(auth)
valid_opaque?(auth) && valid_nonce?(auth) && valid_digest?(auth)
end
def valid_qop?(auth)
QOP == auth.qop
end
def valid_opaque?(auth)
H(opaque) == auth.opaque
end
def valid_nonce?(auth)
auth.nonce.valid?
end
def valid_digest?(auth)
pw = @authenticator.call(auth.username)
pw && Rack::Utils.secure_compare(digest(auth, pw), auth.response)
end
def md5(data)
::Digest::MD5.hexdigest(data)
end
alias :H :md5
def KD(secret, data)
H "#{secret}:#{data}"
end
def A1(auth, password)
"#{auth.username}:#{auth.realm}:#{password}"
end
def A2(auth)
"#{auth.method}:#{auth.uri}"
end
def digest(auth, password)
password_hash = passwords_hashed? ? password : H(A1(auth, password))
KD password_hash, "#{auth.nonce}:#{auth.nc}:#{auth.cnonce}:#{QOP}:#{H A2(auth)}"
end
end
end
end
end
|