aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/md5.rb
blob: e579dc96325b3f23529fef2d4673446df638bc53 (plain) (blame)
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
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(*args)
          super
          @passwords_hashed = nil
        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'.freeze

        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)
          digest(auth, @authenticator.call(auth.username)) == 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