rack-auth-cookie 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README CHANGED
@@ -1,7 +1,7 @@
1
1
  = Description
2
2
  The rack-cookie library is a Rack library that uses a cookie as a token to
3
3
  authenticate requests from users that have authenticated earlier using some
4
- other way.
4
+ other method.
5
5
 
6
6
  = Prerequisites
7
7
  rack 1.0.0 or later
@@ -20,9 +20,10 @@ This rack library only handles requests that contain a cookie named "auth_token"
20
20
  (or whatever name was passed as the :cookie_name option). If that is not present,
21
21
  the request is forwarded normally with no changes to the environment.
22
22
 
23
- If the cookie is detected, then it is checked for validity. If valid, then the
24
- value for 'AUTH_USER' is copied from the cookie data into the environment. If
25
- invalid, then env['AUTH_USER'] is deleted and env['AUTH_FAIL'] is set to an error message explaining what went wrong.
23
+ If the cookie is detected, then it is checked for validity. If valid, then the values
24
+ stored in the cookie (such as 'AUTH_USER') are copied from the cookie into the
25
+ environment. If invalid, then env['AUTH_USER'] is deleted and env['AUTH_FAIL'] is set to
26
+ an error message explaining what went wrong.
26
27
 
27
28
  Note that if env['AUTH_USER'] or env['AUTH_FAIL'] are already set, then the
28
29
  request is forwarded normally with no changes to the environment.
@@ -12,6 +12,8 @@ module Rack
12
12
  @app = app
13
13
  @@secret = options[:secret]
14
14
  @@cookie_name = options[:cookie_name] || "auth_token"
15
+ @@idle_timeout = options[:idle_timeout] || 3600
16
+ @@max_lifetime = options[:max_lifetime] || 36000
15
17
  @@env = {}
16
18
  end
17
19
 
@@ -30,49 +32,101 @@ module Rack
30
32
  # and/or AUTH_FAIL and act as necessary.
31
33
  #
32
34
  def call(env)
35
+ request = Rack::Request.new(env)
36
+ auth_fail = false
37
+
38
+ # Only authenticate if there's a cookie in the request named @@cookie_name
39
+ unless request.cookies.has_key?(@@cookie_name)
40
+ return finish(@app, env)
41
+ end
42
+
43
+ # Get the data from the cookie
44
+ begin
45
+ cookie_value = request.cookies[@@cookie_name]
46
+ hash_data = read_cookie(cookie_value)
47
+ rescue Exception => e
48
+ auth_fail = e.message
49
+ end
33
50
 
34
51
  # Do not authenticate if either one of these is set
52
+ # This check is done late so that we'll have already
53
+ # checked the cookie
35
54
  if env['AUTH_USER'] || env['AUTH_FAIL']
36
- return @app.call(env)
55
+ return finish(@app, env, cookie_value)
37
56
  end
38
57
 
39
- request = Rack::Request.new(env)
58
+ if hash_data["AUTH_EXPIRE_DATETIME"] < Time.now.utc
59
+ auth_fail = "Timed out due to inactivity"
60
+ end
40
61
 
41
- # Only authenticate if there's a cookie in the request named @cookie_name
42
- unless request.cookies.has_key?(@@cookie_name)
43
- return @app.call(env)
62
+ if hash_data["AUTH_DATETIME"] + @@max_lifetime < Time.now.utc
63
+ auth_fail = "Maximum session length exceeded"
44
64
  end
45
65
 
46
- begin
47
- # Separate the cookie data and the digest
48
- cookie_with_digest = request.cookies[@@cookie_name]
49
- cookie_data, digest = cookie_with_digest.split("--")
66
+ if auth_fail
67
+ env['AUTH_FAIL'] = auth_fail
68
+ else
69
+ # Put the values from the hash into the environment
70
+ env['AUTH_USER'] = hash_data['AUTH_USER']
50
71
 
51
- # Make sure the cookie hasn't been tampered with
52
- unless digest == self.class.generate_hmac(cookie_data)
53
- env['AUTH_FAIL'] = "Invalid cookie digest!"
54
- return @app.call(env)
55
- end
72
+ env['AUTH_TYPE'] = hash_data['AUTH_TYPE']
73
+ env['AUTH_TYPE_USER'] = hash_data['AUTH_TYPE_USER']
56
74
 
57
- # Unpack the cookie data back to a hash
58
- begin
59
- cookie_data = cookie_data.unpack("m*").first
60
- cookie_data = Marshal.load(cookie_data)
61
- rescue
62
- env['AUTH_FAIL'] = "Unable to read cookie!"
63
- return @app.call(env)
64
- end
75
+ env['AUTH_TYPE_THIS_REQUEST'] = "Cookie"
65
76
 
66
- # Put the values from the hash into the environment
67
- env['AUTH_USER'] = cookie_data['AUTH_USER']
77
+ env['AUTH_DATETIME'] = hash_data['AUTH_DATETIME']
78
+ env['AUTH_EXPIRE_DATETIME'] = hash_data['AUTH_EXPIRE_DATETIME']
79
+ end
80
+
81
+ finish(@app, env, cookie_value)
82
+ end
83
+
84
+ def finish(app, env, cookie_value_from_request = nil)
85
+ status, headers, body = @app.call(env)
86
+
87
+ # Assume our cookie isn't in the response unless/until we find it
88
+ response_cookie = false
89
+
90
+ if headers.has_key?("Set-Cookie")
91
+ set_cookie = headers["Set-Cookie"]
92
+ set_cookie_pieces = set_cookie.split(";")
68
93
 
69
- env['AUTH_TYPE'] = "Cookie"
70
- rescue => err
71
- env.delete('AUTH_USER')
72
- env['AUTH_FAIL'] = "Unexpected failure during Cookie authentication"
94
+ # TODO: parse cookies from header and find @@cookie_name
95
+ set_cookie_pieces.each_with_index do |piece, index|
96
+ if piece[@@cookie_name]
97
+ response_cookie = true
98
+ end
99
+ end
73
100
  end
74
101
 
75
- @app.call(env)
102
+ # If the application isn't making any changes to the cookie, we can mess with it
103
+ if cookie_value_from_request && !response_cookie
104
+ cookie = self.class.create_auth_cookie(env)
105
+
106
+ headers["Set-Cookie"] << cookie
107
+ end
108
+
109
+ [status, headers, body]
110
+ end
111
+
112
+ def read_cookie(cookie_value)
113
+ # Separate the cookie data and the digest
114
+ raw_data, digest = cookie_value.split("--")
115
+
116
+ # Check for evidence of tampering
117
+ unless digest == self.class.generate_hmac(raw_data)
118
+ raise "Invalid cookie digest!"
119
+ end
120
+
121
+ # Unpack the cookie data back to a hash
122
+ begin
123
+ unpacked_data = raw_data.unpack("m*").first
124
+ hash_data = Marshal.load(unpacked_data)
125
+ rescue
126
+ raise "Unable to read cookie!"
127
+ end
128
+
129
+ hash_data
76
130
  end
77
131
 
78
132
  def self.cookie_name
@@ -82,19 +136,52 @@ module Rack
82
136
  def self.create_auth_token(env)
83
137
  # Copy relevant auth info for storage in a token
84
138
  auth_info = Hash.new
139
+
85
140
  auth_info['AUTH_USER'] = env['AUTH_USER']
86
141
 
142
+ auth_info['AUTH_TYPE'] = env['AUTH_TYPE'] || "Unknown"
143
+ auth_info['AUTH_TYPE_USER'] = env['AUTH_TYPE_USER'] || env['AUTH_USER']
144
+
145
+ auth_info['AUTH_DATETIME'] = env['AUTH_DATETIME'] || Time.now.utc
146
+ auth_info['AUTH_EXPIRE_DATETIME'] = env['AUTH_EXPIRE_DATETIME'] ||
147
+ env['AUTH_DATETIME'] + @@idle_timeout
148
+
87
149
  # Pack the auth_info hash for cookie storage
88
150
  cookie_data = Marshal.dump(auth_info)
89
151
  cookie_data = [cookie_data].pack("m*")
90
152
 
91
153
  # Add a digest value to cookie_data to prevent tampering
92
- "#{cookie_data}--#{self.generate_hmac(cookie_data)}"
154
+ "#{cookie_data}--#{generate_hmac(cookie_data)}"
155
+ end
156
+
157
+ def self.create_auth_cookie(env)
158
+ cookie_value = create_auth_token(env)
159
+ cookie = "#{@@cookie_name}=#{URI.escape(cookie_value)}; "
160
+ cookie += "domain=.#{top_level_domain(env)}; "
161
+ cookie += "path=/; "
162
+ cookie += "HttpOnly; "
93
163
  end
94
164
 
95
165
  def self.generate_hmac(data)
96
166
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, @@secret, data)
97
167
  end
168
+
169
+ def self.raw_host_with_port(env)
170
+ if forwarded = env["HTTP_X_FORWARDED_HOST"]
171
+ forwarded.split(/,\s?/).last
172
+ else
173
+ env['HTTP_HOST'] || "#{env['SERVER_NAME'] ||
174
+ env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
175
+ end
176
+ end
177
+
178
+ def self.host(env)
179
+ raw_host_with_port(env).sub(/:\d+$/, '')
180
+ end
181
+
182
+ def self.top_level_domain(env, tld_length = 1)
183
+ host(env).split('.').last(1 + tld_length).join('.')
184
+ end
98
185
  end
99
186
  end
100
187
  end
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
 
3
3
  Gem::Specification.new do |gem|
4
4
  gem.name = 'rack-auth-cookie'
5
- gem.version = '0.1.0'
5
+ gem.version = '0.3.0'
6
6
  gem.authors = ["Daniel Berger", "Charlie O'Keefe"]
7
7
  gem.email = 'cokeefe@globe.gov'
8
8
  gem.homepage = 'http://www.github.com/charlieok/rack-auth-cookie'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-auth-cookie
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Berger
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-12-17 00:00:00 -07:00
13
+ date: 2009-12-19 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency