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 +5 -4
- data/lib/rack/auth/cookie.rb +117 -30
- data/rack-auth-cookie.gemspec +1 -1
- metadata +2 -2
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
|
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
|
-
|
25
|
-
invalid, then env['AUTH_USER'] is deleted and env['AUTH_FAIL'] is set to
|
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.
|
data/lib/rack/auth/cookie.rb
CHANGED
@@ -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
|
55
|
+
return finish(@app, env, cookie_value)
|
37
56
|
end
|
38
57
|
|
39
|
-
|
58
|
+
if hash_data["AUTH_EXPIRE_DATETIME"] < Time.now.utc
|
59
|
+
auth_fail = "Timed out due to inactivity"
|
60
|
+
end
|
40
61
|
|
41
|
-
|
42
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
52
|
-
|
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
|
-
|
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
|
-
|
67
|
-
env['
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
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}--#{
|
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
|
data/rack-auth-cookie.gemspec
CHANGED
@@ -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.
|
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.
|
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-
|
13
|
+
date: 2009-12-19 00:00:00 -07:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|