opentoken 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -10,7 +10,6 @@ begin
10
10
  gem.email = "ryan@socialcast.com"
11
11
  gem.homepage = "http://github.com/wireframe/opentoken"
12
12
  gem.authors = ["Ryan Sonnek"]
13
- gem.add_dependency "activesupport", ">=2.3.4"
14
13
  gem.add_development_dependency "shoulda", ">= 0"
15
14
  gem.add_development_dependency "timecop", ">=0.3.4"
16
15
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.2.1
data/lib/opentoken.rb CHANGED
@@ -4,6 +4,8 @@ require 'digest/sha1'
4
4
  require 'zlib'
5
5
  require 'stringio'
6
6
  require 'cgi'
7
+ require File.join(File.dirname(__FILE__), 'opentoken', 'key_value_serializer')
8
+ require File.join(File.dirname(__FILE__), 'opentoken', 'password_key_generator')
7
9
 
8
10
  class OpenToken
9
11
  class TokenExpiredError < StandardError; end
@@ -90,6 +92,7 @@ class OpenToken
90
92
  rescue Zlib::BufError
91
93
  Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(compressed_payload[2, compressed_payload.size])
92
94
  end
95
+ puts 'EXPANDED PAYLOAD', unparsed_payload if DEBUG
93
96
 
94
97
  #validate payload hmac
95
98
  mac = "0x01".hex.chr
@@ -102,7 +105,10 @@ class OpenToken
102
105
  raise "HMAC for payload was #{hash} and expected to be #{payload_hmac}" unless payload_hmac == hash
103
106
  end
104
107
 
105
- @payload = KeyValueSerializer.deserialize CGI::unescapeHTML(unparsed_payload)
108
+ unescaped_payload = CGI::unescapeHTML(unparsed_payload)
109
+ puts 'UNESCAPED PAYLOAD', unescaped_payload if DEBUG
110
+ @payload = KeyValueSerializer.deserialize unescaped_payload
111
+ puts @payload.inspect if DEBUG
106
112
  raise TokenExpiredError.new("#{Time.now.utc} is not within token duration: #{self.start_at} - #{self.end_at}") if self.expired?
107
113
  end
108
114
 
@@ -151,176 +157,3 @@ class OpenToken
151
157
  end
152
158
  end
153
159
  end
154
-
155
- class PasswordKeyGenerator
156
- SHA1_DIGEST = OpenSSL::Digest::Digest.new('sha1')
157
-
158
- def self.generate(password, cipher_suite)
159
- salt = 0.chr * 8
160
- self.generate_impl(password, cipher_suite, salt, 1000)
161
- end
162
-
163
- def self.generate_block(password, salt, count, index)
164
- mac = salt
165
- mac += [index].pack("N")
166
-
167
- result = OpenSSL::HMAC.digest(SHA1_DIGEST, password, mac)
168
- cur = result
169
-
170
- i_count = 1
171
- while i_count < count
172
- i_count +=1
173
-
174
- cur = OpenSSL::HMAC.digest(SHA1_DIGEST, password, cur)
175
-
176
- 20.times do |i|
177
- result[i] = result[i] ^ cur[i]
178
- end
179
- end
180
-
181
- return result
182
- end
183
-
184
- def self.generate_impl(password, cipher, salt, iterations)
185
- return unless cipher[:algorithm]
186
-
187
- key_size = cipher[:key_length] / 8
188
- numblocks = key_size / 20
189
- numblocks += 1 if (key_size % 20) > 0
190
-
191
- # Generate the appropriate number of blocks and write their output to
192
- # the key bytes; note that it's important to start from 1 (vs. 0) as the
193
- # initial block number affects the hash. It's not clear that this fact
194
- # is stated explicitly anywhere, but without this approach, the generated
195
- # keys will not match up with test cases defined in RFC 3962.
196
- key_buffer_index = 0
197
- key = ""
198
-
199
- numblocks.times do |i|
200
- i+=1 # Previously zero based, needs to be 1 based
201
- block = self.generate_block(password, salt, iterations, i)
202
- len = [20, (key_size - key_buffer_index)].min
203
- key += block[0, len]
204
- key_buffer_index += len
205
- end
206
-
207
- return key
208
- end
209
- end
210
-
211
- class KeyValueSerializer
212
- LINE_START = 0
213
- EMPTY_SPACE = 1
214
- VALUE_START = 2
215
- LINE_END = 3
216
- IN_KEY = 4
217
- IN_VALUE = 5
218
- IN_QUOTED_VALUE = 6
219
-
220
- def self.unescape_value(value)
221
- value.gsub("\\\"", "\"").gsub("\'", "'")
222
- end
223
-
224
- def self.deserialize(string)
225
- result = {}
226
- state = LINE_START
227
- open_quote_char = 0.chr
228
- currkey = ""
229
- token = ""
230
- nextval = ""
231
-
232
- string.split(//).each do |c|
233
-
234
- nextval = c
235
-
236
- case c
237
- when "\t"
238
- if state == IN_KEY
239
- # key ends
240
- currkey = token
241
- token = ""
242
- state = EMPTY_SPACE
243
- elsif state == IN_VALUE
244
- # non-quoted value ends
245
- result[currkey] = self.deserialize(token)
246
- token = ""
247
- state = LINE_END
248
- elsif state == IN_QUOTED_VALUE
249
- token += c
250
- end
251
- when " "
252
- if state == IN_KEY
253
- # key ends
254
- currkey = token
255
- token = ""
256
- state = EMPTY_SPACE
257
- elsif state == IN_VALUE
258
- # non-quoted value ends
259
- result[currkey] = self.deserialize(token)
260
- token = ""
261
- state = LINE_END
262
- elsif state == IN_QUOTED_VALUE
263
- token += c
264
- end
265
- when "\n"
266
- # newline
267
- if (state == IN_VALUE) || (state == VALUE_START)
268
- result[currkey] = self.unescape_value(token)
269
- token = ""
270
- state = LINE_START
271
- elsif state == LINE_END
272
- token = ""
273
- state = LINE_START
274
- elsif state == IN_QUOTED_VALUE
275
- token += c
276
- end
277
- when "="
278
- if state == IN_KEY
279
- currkey = token
280
- token = ""
281
- state = VALUE_START
282
- elsif (state == IN_QUOTED_VALUE) || (state == IN_VALUE)
283
- token += c
284
- end
285
- when "\""
286
- if state == IN_QUOTED_VALUE
287
- if (c == open_quote_char) && (token[token.size-1] != "\\")
288
- result[currkey] = self.unescape_value(token)
289
- token = ""
290
- state = LINE_END
291
- else
292
- token += c
293
- end
294
- elsif state == VALUE_START
295
- state = IN_QUOTED_VALUE
296
- open_quote_char = c
297
- end
298
- when "'"
299
- if state == IN_QUOTED_VALUE
300
- if (c == open_quote_char) && (token[token.size-1] != "\\")
301
- result[currkey] = self.unescape_value(token)
302
- token = ""
303
- state = LINE_END
304
- else
305
- token += c
306
- end
307
- else state == VALUE_START
308
- state = IN_QUOTED_VALUE
309
- open_quote_char = c
310
- end
311
- else
312
- if state == LINE_START
313
- state = IN_KEY
314
- elsif state == VALUE_START
315
- state = IN_VALUE
316
- end
317
- token += c
318
- end
319
-
320
- if (state == IN_QUOTED_VALUE) || (state == IN_VALUE)
321
- result[currkey] = unescape_value(token)
322
- end
323
- end
324
- result
325
- end
326
- end
@@ -0,0 +1,115 @@
1
+ class KeyValueSerializer
2
+ LINE_START = 0
3
+ EMPTY_SPACE = 1
4
+ VALUE_START = 2
5
+ LINE_END = 3
6
+ IN_KEY = 4
7
+ IN_VALUE = 5
8
+ IN_QUOTED_VALUE = 6
9
+
10
+ def self.unescape_value(value)
11
+ value.gsub("\\\"", "\"").gsub("\\\'", "'")
12
+ end
13
+
14
+ def self.deserialize(string)
15
+ result = {}
16
+ state = LINE_START
17
+ open_quote_char = 0.chr
18
+ currkey = ""
19
+ token = ""
20
+ nextval = ""
21
+
22
+ string.split(//).each do |c|
23
+ nextval = c
24
+
25
+ case c
26
+ when "\t"
27
+ if state == IN_KEY
28
+ # key ends
29
+ currkey = token
30
+ token = ""
31
+ state = EMPTY_SPACE
32
+ elsif state == IN_VALUE
33
+ # non-quoted value ends
34
+ result[currkey] = self.deserialize(token)
35
+ token = ""
36
+ state = LINE_END
37
+ elsif state == IN_QUOTED_VALUE
38
+ token += c
39
+ end
40
+ when " "
41
+ if state == IN_KEY
42
+ # key ends
43
+ currkey = token
44
+ token = ""
45
+ state = EMPTY_SPACE
46
+ elsif state == IN_VALUE
47
+ # non-quoted value ends
48
+ result[currkey] = self.deserialize(token)
49
+ token = ""
50
+ state = LINE_END
51
+ elsif state == IN_QUOTED_VALUE
52
+ token += c
53
+ end
54
+ when "\n"
55
+ # newline
56
+ if (state == IN_VALUE) || (state == VALUE_START)
57
+ result[currkey] = self.unescape_value(token)
58
+ token = ""
59
+ state = LINE_START
60
+ elsif state == LINE_END
61
+ token = ""
62
+ state = LINE_START
63
+ elsif state == IN_QUOTED_VALUE
64
+ token += c
65
+ end
66
+ when "="
67
+ if state == IN_KEY
68
+ currkey = token
69
+ token = ""
70
+ state = VALUE_START
71
+ elsif (state == IN_QUOTED_VALUE) || (state == IN_VALUE)
72
+ token += c
73
+ end
74
+ when "\""
75
+ if state == IN_QUOTED_VALUE
76
+ if (c == open_quote_char) && (token[token.size-1] != "\\"[0])
77
+ result[currkey] = self.unescape_value(token)
78
+ token = ""
79
+ state = LINE_END
80
+ else
81
+ token += c
82
+ end
83
+ elsif state == VALUE_START
84
+ state = IN_QUOTED_VALUE
85
+ open_quote_char = c
86
+ end
87
+ when "'"
88
+ if state == IN_QUOTED_VALUE
89
+ if (c == open_quote_char) && (token[token.size-1] != "\\"[0])
90
+ result[currkey] = self.unescape_value(token)
91
+ token = ""
92
+ state = LINE_END
93
+ else
94
+ token += c
95
+ end
96
+ else state == VALUE_START
97
+ state = IN_QUOTED_VALUE
98
+ open_quote_char = c
99
+ end
100
+ else
101
+ if state == LINE_START
102
+ state = IN_KEY
103
+ elsif state == VALUE_START
104
+ state = IN_VALUE
105
+ end
106
+ token += c
107
+ end
108
+
109
+ if (state == IN_QUOTED_VALUE) || (state == IN_VALUE)
110
+ result[currkey] = unescape_value(token)
111
+ end
112
+ end
113
+ result
114
+ end
115
+ end
@@ -0,0 +1,55 @@
1
+ class PasswordKeyGenerator
2
+ SHA1_DIGEST = OpenSSL::Digest::Digest.new('sha1')
3
+
4
+ def self.generate(password, cipher_suite)
5
+ salt = 0.chr * 8
6
+ self.generate_impl(password, cipher_suite, salt, 1000)
7
+ end
8
+
9
+ def self.generate_block(password, salt, count, index)
10
+ mac = salt
11
+ mac += [index].pack("N")
12
+
13
+ result = OpenSSL::HMAC.digest(SHA1_DIGEST, password, mac)
14
+ cur = result
15
+
16
+ i_count = 1
17
+ while i_count < count
18
+ i_count +=1
19
+
20
+ cur = OpenSSL::HMAC.digest(SHA1_DIGEST, password, cur)
21
+
22
+ 20.times do |i|
23
+ result[i] = result[i] ^ cur[i]
24
+ end
25
+ end
26
+
27
+ return result
28
+ end
29
+
30
+ def self.generate_impl(password, cipher, salt, iterations)
31
+ return unless cipher[:algorithm]
32
+
33
+ key_size = cipher[:key_length] / 8
34
+ numblocks = key_size / 20
35
+ numblocks += 1 if (key_size % 20) > 0
36
+
37
+ # Generate the appropriate number of blocks and write their output to
38
+ # the key bytes; note that it's important to start from 1 (vs. 0) as the
39
+ # initial block number affects the hash. It's not clear that this fact
40
+ # is stated explicitly anywhere, but without this approach, the generated
41
+ # keys will not match up with test cases defined in RFC 3962.
42
+ key_buffer_index = 0
43
+ key = ""
44
+
45
+ numblocks.times do |i|
46
+ i+=1 # Previously zero based, needs to be 1 based
47
+ block = self.generate_block(password, salt, iterations, i)
48
+ len = [20, (key_size - key_buffer_index)].min
49
+ key += block[0, len]
50
+ key_buffer_index += len
51
+ end
52
+
53
+ return key
54
+ end
55
+ end
data/opentoken.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{opentoken}
8
- s.version = "0.2.0"
8
+ s.version = "0.2.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Ryan Sonnek"]
12
- s.date = %q{2011-01-12}
12
+ s.date = %q{2011-01-13}
13
13
  s.description = %q{parse opentoken properties passed for Single Signon requests}
14
14
  s.email = %q{ryan@socialcast.com}
15
15
  s.extra_rdoc_files = [
@@ -23,6 +23,8 @@ Gem::Specification.new do |s|
23
23
  "Rakefile",
24
24
  "VERSION",
25
25
  "lib/opentoken.rb",
26
+ "lib/opentoken/key_value_serializer.rb",
27
+ "lib/opentoken/password_key_generator.rb",
26
28
  "opentoken.gemspec",
27
29
  "test/helper.rb",
28
30
  "test/test_opentoken.rb"
@@ -40,16 +42,13 @@ Gem::Specification.new do |s|
40
42
  s.specification_version = 3
41
43
 
42
44
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
43
- s.add_runtime_dependency(%q<activesupport>, [">= 2.3.4"])
44
45
  s.add_development_dependency(%q<shoulda>, [">= 0"])
45
46
  s.add_development_dependency(%q<timecop>, [">= 0.3.4"])
46
47
  else
47
- s.add_dependency(%q<activesupport>, [">= 2.3.4"])
48
48
  s.add_dependency(%q<shoulda>, [">= 0"])
49
49
  s.add_dependency(%q<timecop>, [">= 0.3.4"])
50
50
  end
51
51
  else
52
- s.add_dependency(%q<activesupport>, [">= 2.3.4"])
53
52
  s.add_dependency(%q<shoulda>, [">= 0"])
54
53
  s.add_dependency(%q<timecop>, [">= 0.3.4"])
55
54
  end
@@ -44,5 +44,17 @@ class TestOpentoken < Test::Unit::TestCase
44
44
  end
45
45
  end
46
46
  end
47
+
48
+ context "parsing token with attribute value containing apostrophe" do
49
+ setup do
50
+ Timecop.travel(Time.iso8601('2011-01-13T11:08:01Z')) do
51
+ @opentoken = "T1RLAQLIjiqgexqi1PQcEKCetvGoSYR2jhDFSIfE5ctlSBxEnq3S1ydjAADQUNRIKJx6_14aE3MQZnDABupGJrKNfoJHFS5VOnKexjMtboeOgst31Hf-D9CZBrpB7Jv0KBwnQ7DN3HizecPT76oX3UGtq_Vi5j5bKYCeObYm9W6h7NY-VzcZY5TTqIuulc2Jit381usAWZ2Sv1c_CWwhrH4hw-x7vUQMSjErvXK1qvsrFCpfNr7XlArx0HjI6kT5XEaHgQNdC0zrLw9cZ4rewoEisR3H5oM7B6gMaP82wTSFVBXvpn5r0KT-Iuc3JuG2en1zVh3GNf110oQCKQ**"
52
+ @token = OpenToken.new @opentoken, :password => @password
53
+ end
54
+ end
55
+ should 'preserve apostrophe in attribute payload' do
56
+ assert_equal "D'angelo", @token[:last_name]
57
+ end
58
+ end
47
59
  end
48
60
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opentoken
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 21
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 0
10
- version: 0.2.0
9
+ - 1
10
+ version: 0.2.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ryan Sonnek
@@ -15,29 +15,13 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-12 00:00:00 -06:00
18
+ date: 2011-01-13 00:00:00 -06:00
19
19
  default_executable:
20
20
  dependencies:
21
- - !ruby/object:Gem::Dependency
22
- name: activesupport
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 11
30
- segments:
31
- - 2
32
- - 3
33
- - 4
34
- version: 2.3.4
35
- type: :runtime
36
- version_requirements: *id001
37
21
  - !ruby/object:Gem::Dependency
38
22
  name: shoulda
39
23
  prerelease: false
40
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ requirement: &id001 !ruby/object:Gem::Requirement
41
25
  none: false
42
26
  requirements:
43
27
  - - ">="
@@ -47,11 +31,11 @@ dependencies:
47
31
  - 0
48
32
  version: "0"
49
33
  type: :development
50
- version_requirements: *id002
34
+ version_requirements: *id001
51
35
  - !ruby/object:Gem::Dependency
52
36
  name: timecop
53
37
  prerelease: false
54
- requirement: &id003 !ruby/object:Gem::Requirement
38
+ requirement: &id002 !ruby/object:Gem::Requirement
55
39
  none: false
56
40
  requirements:
57
41
  - - ">="
@@ -63,7 +47,7 @@ dependencies:
63
47
  - 4
64
48
  version: 0.3.4
65
49
  type: :development
66
- version_requirements: *id003
50
+ version_requirements: *id002
67
51
  description: parse opentoken properties passed for Single Signon requests
68
52
  email: ryan@socialcast.com
69
53
  executables: []
@@ -80,6 +64,8 @@ files:
80
64
  - Rakefile
81
65
  - VERSION
82
66
  - lib/opentoken.rb
67
+ - lib/opentoken/key_value_serializer.rb
68
+ - lib/opentoken/password_key_generator.rb
83
69
  - opentoken.gemspec
84
70
  - test/helper.rb
85
71
  - test/test_opentoken.rb