opentoken 0.2.0 → 0.2.1

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/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