opentoken-newrelic-rails23 1.2.2

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+ Gemfile.lock
21
+
22
+ ## PROJECT::SPECIFIC
data/CONTRIBUTORS.txt ADDED
@@ -0,0 +1,7 @@
1
+ Ryan Sonnek - Original Author
2
+
3
+ Dan Alvizu - contributed OpenToken encode functionality
4
+
5
+ Complete list of contributors:
6
+ https://github.com/socialcast/opentoken/contributors
7
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in opentoken-newrelic-rails23.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2011 Socialcast, Inc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # opentoken
2
+
3
+ Parse encrypted opentoken properties
4
+
5
+ see http://www.pingidentity.com/opentoken
6
+
7
+ ## Usage
8
+
9
+ ```ruby
10
+ # configure decryption with shared key
11
+ OpenToken.password = 'shared_secret_to_decrypt'
12
+
13
+ # decrypt opentoken into hash of attributes
14
+ attributes = OpenToken.decode 'opentoken-hashed-string'
15
+
16
+ # encrypt opentoken from hash of attributes
17
+ attributes = { 'subject' => 'foo', 'bar' => 'bak' }
18
+ token = OpenToken.encode attributes, OpenToken::Cipher::AES_128_CBC
19
+ ```
20
+
21
+ ## Contributing
22
+
23
+ * Fork the project
24
+ * Fix the issue
25
+ * Add tests
26
+ * Send me a pull request. Bonus points for topic branches.
27
+
28
+ see CONTRIBUTORS.txt for complete list of contributors.
29
+
30
+ ## Copyright
31
+
32
+ Copyright (c) 2011 Socialcast Inc.
33
+ See LICENSE.txt for details.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.pattern = 'test/**/test_*.rb'
8
+ test.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,182 @@
1
+ require 'base64'
2
+ require 'openssl'
3
+ require 'digest/sha1'
4
+ require 'zlib'
5
+ require 'stringio'
6
+ require 'cgi'
7
+ require 'time'
8
+ require File.join(File.dirname(__FILE__), 'opentoken-newrelic-rails23', 'token')
9
+ require File.join(File.dirname(__FILE__), 'opentoken-newrelic-rails23', 'key_value_serializer')
10
+ require File.join(File.dirname(__FILE__), 'opentoken-newrelic-rails23', 'password_key_generator')
11
+ require File.join(File.dirname(__FILE__), 'opentoken-newrelic-rails23', 'cipher')
12
+
13
+ module OpenToken
14
+ class TokenInvalidError < StandardError; end
15
+
16
+ class << self
17
+ attr_accessor :debug
18
+ def debug?
19
+ !!debug
20
+ end
21
+
22
+ attr_accessor :password
23
+ attr_accessor :token_lifetime
24
+ attr_accessor :renew_until_lifetime
25
+
26
+ def encode(attributes, cipher)
27
+ attributes['not-before'] = Time.now.utc.iso8601.to_s
28
+ attributes['not-on-or-after'] = Time.at(Time.now.to_i + token_lifetime).utc.iso8601.to_s
29
+ attributes['renew-until'] = Time.at(Time.now.to_i + renew_until_lifetime).utc.iso8601.to_s
30
+
31
+ serialized = OpenToken::KeyValueSerializer.serialize(attributes)
32
+ compressed = zip_payload serialized
33
+
34
+ key = cipher.generate_key
35
+ iv = cipher.generate_iv
36
+ encrypted = cipher.encrypt_payload compressed, key, iv
37
+
38
+ mac = []
39
+ mac << "0x01".hex.chr # OTK version
40
+ mac << cipher.suite.chr
41
+ mac << iv
42
+ mac << force_encoding(serialized, 'BINARY')
43
+ hash = OpenSSL::HMAC.digest(OpenToken::PasswordKeyGenerator::SHA1_DIGEST, key, mac.join)
44
+
45
+ token_string = ""
46
+ token_string = "OTK" + 1.chr + cipher.suite.chr
47
+ token_string += hash
48
+ token_string += cipher.iv_length.chr
49
+ token_string += iv
50
+ token_string += 0.chr # key info length
51
+ token_string += ((encrypted.length >> 8) &0xFF ).chr
52
+ token_string += (encrypted.length & 0xFF).chr
53
+ token_string += encrypted
54
+ inspect_binary_string "Unencoded", token_string
55
+ encoded = urlsafe_encode64 token_string
56
+ inspect_binary_string "Encoded", encoded
57
+ encoded
58
+ end
59
+ def decode(opentoken = nil)
60
+ verify opentoken.present?, 'Unable to parse empty token'
61
+ data = urlsafe_decode64(opentoken)
62
+ inspect_binary_string 'DATA', data
63
+
64
+ verify_header data
65
+ verify_version data
66
+
67
+ #cipher suite identifier
68
+ cipher_suite = char_value_of data[4]
69
+ cipher = OpenToken::Cipher.for_suite cipher_suite
70
+
71
+ #SHA-1 HMAC
72
+ payload_hmac = data[5..24]
73
+ inspect_binary_string "PAYLOAD HMAC [5..24]", payload_hmac
74
+
75
+ #Initialization Vector (iv)
76
+ iv_length = char_value_of data[25]
77
+ iv_end = char_value_of [26, 26 + iv_length - 1].max
78
+ iv = data[26..iv_end]
79
+ inspect_binary_string "IV [26..#{iv_end}]", iv
80
+ verify iv_length == cipher.iv_length, "Cipher expects iv length of #{cipher.iv_length} and was: #{iv_length}"
81
+
82
+ #key (not currently used)
83
+ key_length = char_value_of data[iv_end + 1]
84
+ key_end = iv_end + 1
85
+ verify key_length == 0, "Token key embedding is not currently supported. Key length is: #{key_length}"
86
+
87
+ #payload
88
+ payload_length = data[(key_end + 1)..(key_end + 2)].unpack('n').first
89
+ payload_offset = key_end + 3
90
+ encrypted_payload = data[payload_offset..(data.length - 1)]
91
+ verify encrypted_payload.length == payload_length, "Payload length is #{encrypted_payload.length} and was expected to be #{payload_length}"
92
+ inspect_binary_string "ENCRYPTED PAYLOAD [#{payload_offset}..#{data.length - 1}]", encrypted_payload
93
+
94
+ key = cipher.generate_key
95
+ inspect_binary_string 'KEY', key
96
+
97
+ compressed_payload = cipher.decrypt_payload encrypted_payload, key, iv
98
+ inspect_binary_string 'COMPRESSED PAYLOAD', compressed_payload
99
+
100
+ unparsed_payload = unzip_payload compressed_payload
101
+ puts 'EXPANDED PAYLOAD', unparsed_payload if debug?
102
+
103
+ #validate payload hmac
104
+ mac = []
105
+ mac << "0x01".hex.chr
106
+ mac << cipher_suite.chr
107
+ mac << iv
108
+ mac << key if key_length > 0 #key embedding is not currently supported
109
+ mac << unparsed_payload
110
+ hash = OpenSSL::HMAC.digest(OpenToken::PasswordKeyGenerator::SHA1_DIGEST, key, mac.join)
111
+ if (hash <=> payload_hmac) != 0
112
+ verify payload_hmac == hash, "HMAC for payload was #{hash} and expected to be #{payload_hmac}"
113
+ end
114
+
115
+ unescaped_payload = CGI::unescapeHTML(unparsed_payload)
116
+ puts 'UNESCAPED PAYLOAD', unescaped_payload if debug?
117
+ token = OpenToken::KeyValueSerializer.deserialize force_encoding(unescaped_payload, 'UTF-8')
118
+ puts token.inspect if debug?
119
+ token.validate!
120
+ token
121
+ end
122
+
123
+ private
124
+ def char_value_of(character)
125
+ if RUBY_VERSION < "1.9"
126
+ return character
127
+ else
128
+ return character.chr.ord
129
+ end
130
+ end
131
+ def verify_header(data)
132
+ header = data[0..2]
133
+ verify header == 'OTK', "Invalid token header: #{header}"
134
+ end
135
+ def verify_version(data)
136
+ version = char_value_of data[3]
137
+ verify version == 1, "Unsupported token version: '#{version}'"
138
+ end
139
+ #ruby 1.9 has Base64.urlsafe_decode64 which can be used instead of gsubbing '_' and '-'
140
+ def urlsafe_decode64(token)
141
+ string = token.gsub('*', '=').gsub('_', '/').gsub('-', '+')
142
+ data = Base64.decode64(string)
143
+ end
144
+ def urlsafe_encode64(token)
145
+ string = Base64.encode64(token);
146
+ string = string.gsub('=', '*').gsub('/', '_').gsub('+', '-').gsub(10.chr, '').gsub(11.chr, '')
147
+ string
148
+ end
149
+ def verify(assertion, message = 'Invalid Token')
150
+ raise OpenToken::TokenInvalidError.new(message) unless assertion
151
+ end
152
+ #decompress the payload
153
+ #see http://stackoverflow.com/questions/1361892/how-to-decompress-gzip-data-in-ruby
154
+ def unzip_payload(compressed_payload)
155
+ unparsed_payload = begin
156
+ Zlib::Inflate.inflate(compressed_payload)
157
+ rescue Zlib::BufError
158
+ Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(compressed_payload[2, compressed_payload.size])
159
+ end
160
+ end
161
+ def zip_payload(uncompressed)
162
+ compressed = Zlib::Deflate.deflate(uncompressed, 9)
163
+ compressed
164
+ end
165
+ def inspect_binary_string(header, string)
166
+ return unless debug?
167
+ puts "#{header}:"
168
+ index = 0
169
+ string.each_byte do |b|
170
+ puts "#{index}: #{b} => #{b.chr}"
171
+ index += 1
172
+ end
173
+ end
174
+ def force_encoding(string, encoding)
175
+ string.respond_to?(:force_encoding) ? string.force_encoding(encoding) : string
176
+ end
177
+ end
178
+ end
179
+
180
+ # intialize defaults
181
+ OpenToken.token_lifetime = 300
182
+ OpenToken.renew_until_lifetime = 43200
@@ -0,0 +1,69 @@
1
+ require 'openssl'
2
+
3
+ module OpenToken
4
+ class Cipher
5
+ class InvalidCipherError < StandardError; end
6
+
7
+ attr_reader :algorithm
8
+ attr_reader :iv_length
9
+ attr_reader :key_length
10
+ attr_reader :suite
11
+
12
+ def initialize(attrs = {})
13
+ @suite = attrs[:suite]
14
+ @iv_length = attrs[:iv_length]
15
+ @key_length = attrs[:key_length]
16
+ @algorithm = attrs[:algorithm]
17
+ end
18
+ def self.for_suite(cipher_suite)
19
+ cipher = REGISTERED_CIPHERS.detect {|c| c.suite == cipher_suite }
20
+ raise InvalidCipherError.new("Unknown cipher suite: #{cipher_suite}") unless cipher
21
+ cipher
22
+ end
23
+
24
+ def generate_key
25
+ OpenToken::PasswordKeyGenerator.generate OpenToken.password, self
26
+ end
27
+ def generate_iv
28
+ OpenSSL::Random.random_bytes(iv_length)
29
+ end
30
+
31
+ #see http://snippets.dzone.com/posts/show/4975
32
+ #see http://jdwyah.blogspot.com/2009/12/decrypting-ruby-aes-encryption.html
33
+ #see http://snippets.dzone.com/posts/show/576
34
+ def decrypt_payload(encrypted_payload, key, iv)
35
+ return encrypted_payload unless algorithm
36
+ c = crypt :decrypt, key, iv
37
+ c.update(encrypted_payload) + c.final
38
+ end
39
+ def encrypt_payload(payload, key, iv)
40
+ c = crypt :encrypt, key, iv
41
+ padding = if payload.length % iv_length == 0
42
+ iv_length
43
+ else
44
+ iv_length - (payload.length % iv_length)
45
+ end
46
+ c.update(payload + (padding.chr * padding))
47
+ end
48
+
49
+ private
50
+ def crypt(operation, key, iv)
51
+ crypt = OpenSSL::Cipher::Cipher.new(algorithm)
52
+ crypt.send operation
53
+ crypt.key = key
54
+ crypt.iv = iv
55
+ crypt
56
+ end
57
+
58
+ NULL = Cipher.new(:suite => 0, :iv_length => 0)
59
+ AES_256_CBC = Cipher.new(:suite => 1, :iv_length => 32, :key_length => 256, :algorithm => 'aes-256-cbc')
60
+ AES_128_CBC = Cipher.new(:suite => 2, :iv_length => 16, :key_length => 128, :algorithm => 'aes-128-cbc')
61
+ DES3_168_CBC = Cipher.new(:suite => 3, :iv_length => 8, :key_length => 168, :algorithm => 'des-cbc')
62
+
63
+ REGISTERED_CIPHERS = []
64
+ REGISTERED_CIPHERS << NULL
65
+ REGISTERED_CIPHERS << AES_256_CBC
66
+ REGISTERED_CIPHERS << AES_128_CBC
67
+ REGISTERED_CIPHERS << DES3_168_CBC
68
+ end
69
+ end
@@ -0,0 +1,142 @@
1
+ module OpenToken
2
+ class KeyValueSerializer
3
+ LINE_START = 0
4
+ EMPTY_SPACE = 1
5
+ VALUE_START = 2
6
+ LINE_END = 3
7
+ IN_KEY = 4
8
+ IN_VALUE = 5
9
+ IN_QUOTED_VALUE = 6
10
+
11
+ class << self
12
+ def serialize(hashmap)
13
+ result = String.new
14
+ count = 0;
15
+ hashmap.each_pair do |key,value|
16
+ if (count != 0)
17
+ result = result + "\n"
18
+ end
19
+ count +=1
20
+ result += key + "="
21
+ result += escape_value(value)
22
+ end
23
+ result
24
+ end
25
+ def deserialize(string)
26
+ result = OpenToken::Token.new
27
+ state = LINE_START
28
+ open_quote_char = 0.chr
29
+ currkey = ""
30
+ token = ""
31
+ nextval = ""
32
+
33
+ string.split(//).each do |c|
34
+ nextval = c
35
+
36
+ case c
37
+ when "\t"
38
+ if state == IN_KEY
39
+ # key ends
40
+ currkey = token
41
+ token = ""
42
+ state = EMPTY_SPACE
43
+ elsif state == IN_VALUE
44
+ # non-quoted value ends
45
+ result[currkey] = self.deserialize(token)
46
+ token = ""
47
+ state = LINE_END
48
+ elsif state == IN_QUOTED_VALUE
49
+ token += c
50
+ end
51
+ when " "
52
+ if state == IN_KEY
53
+ # key ends
54
+ currkey = token
55
+ token = ""
56
+ state = EMPTY_SPACE
57
+ elsif state == IN_VALUE
58
+ # non-quoted value ends
59
+ result[currkey] = self.deserialize(token)
60
+ token = ""
61
+ state = LINE_END
62
+ elsif state == IN_QUOTED_VALUE
63
+ token += c
64
+ end
65
+ when "\n"
66
+ # newline
67
+ if (state == IN_VALUE) || (state == VALUE_START)
68
+ result[currkey] = unescape_value(token)
69
+ token = ""
70
+ state = LINE_START
71
+ elsif state == LINE_END
72
+ token = ""
73
+ state = LINE_START
74
+ elsif state == IN_QUOTED_VALUE
75
+ token += c
76
+ end
77
+ when "="
78
+ if state == IN_KEY
79
+ currkey = token
80
+ token = ""
81
+ state = VALUE_START
82
+ elsif (state == IN_QUOTED_VALUE) || (state == IN_VALUE)
83
+ token += c
84
+ end
85
+ when "\""
86
+ if state == IN_QUOTED_VALUE
87
+ if (c == open_quote_char) && (token[token.size-1] != "\\"[0])
88
+ result[currkey] = unescape_value(token)
89
+ token = ""
90
+ state = LINE_END
91
+ else
92
+ token += c
93
+ end
94
+ elsif state == VALUE_START
95
+ state = IN_QUOTED_VALUE
96
+ open_quote_char = c
97
+ end
98
+ when "'"
99
+ if state == IN_QUOTED_VALUE
100
+ if (c == open_quote_char) && (token[token.size-1] != "\\"[0])
101
+ result[currkey] = unescape_value(token)
102
+ token = ""
103
+ state = LINE_END
104
+ else
105
+ token += c
106
+ end
107
+ else state == VALUE_START
108
+ state = IN_QUOTED_VALUE
109
+ open_quote_char = c
110
+ end
111
+ else
112
+ if state == LINE_START
113
+ state = IN_KEY
114
+ elsif state == VALUE_START
115
+ state = IN_VALUE
116
+ end
117
+ token += c
118
+ end
119
+
120
+ if (state == IN_QUOTED_VALUE) || (state == IN_VALUE)
121
+ result[currkey] = unescape_value(token)
122
+ end
123
+ end
124
+ result
125
+ end
126
+ private
127
+ def unescape_value(value)
128
+ value.gsub("\\\"", "\"").gsub("\\\'", "'")
129
+ end
130
+ def escape_value(value)
131
+ value.each_byte do |b|
132
+ c = b.chr
133
+ if c == "\n" or c == "\t" or c == " " or c == "'" or c == "\""
134
+ value = "'" + value.gsub("'", "\'").gsub("\"", "\\\"") + "'"
135
+ break
136
+ end
137
+ end
138
+ value
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,65 @@
1
+ module OpenToken
2
+ class PasswordKeyGenerator
3
+ SHA1_DIGEST = OpenSSL::Digest::Digest.new('sha1')
4
+
5
+ class << self
6
+ def generate(password, cipher)
7
+ salt = 0.chr * 8
8
+ generate_impl(password, cipher, salt, 1000)
9
+ end
10
+
11
+ private
12
+ def generate_block(password, salt, count, index)
13
+ mac = salt
14
+ mac += [index].pack("N")
15
+
16
+ result = OpenSSL::HMAC.digest(SHA1_DIGEST, password, mac)
17
+ cur = result
18
+
19
+ i_count = 1
20
+ while i_count < count
21
+ i_count +=1
22
+
23
+ cur = OpenSSL::HMAC.digest(SHA1_DIGEST, password, cur)
24
+
25
+ 20.times do |i|
26
+ if RUBY_VERSION < "1.9"
27
+ result[i] = result[i] ^ cur[i]
28
+ else
29
+ result[i] = (result[i].chr.ord ^ cur[i].chr.ord).chr
30
+ end
31
+ end
32
+ end
33
+
34
+ return result
35
+ end
36
+
37
+ def generate_impl(password, cipher, salt, iterations)
38
+ return unless cipher.algorithm
39
+
40
+ key_size = cipher.key_length / 8
41
+ numblocks = key_size / 20
42
+ numblocks += 1 if (key_size % 20) > 0
43
+
44
+ # Generate the appropriate number of blocks and write their output to
45
+ # the key bytes; note that it's important to start from 1 (vs. 0) as the
46
+ # initial block number affects the hash. It's not clear that this fact
47
+ # is stated explicitly anywhere, but without this approach, the generated
48
+ # keys will not match up with test cases defined in RFC 3962.
49
+ key_buffer_index = 0
50
+ key = ""
51
+
52
+ numblocks.times do |i|
53
+ i+=1 # Previously zero based, needs to be 1 based
54
+ block = generate_block(password, salt, iterations, i)
55
+ len = [20, (key_size - key_buffer_index)].min
56
+ key += block[0, len]
57
+ key_buffer_index += len
58
+ end
59
+
60
+ return key
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,33 @@
1
+ require 'time'
2
+ require 'active_support'
3
+
4
+ module OpenToken
5
+ class TokenExpiredError < StandardError; end
6
+
7
+ class Token < HashWithIndifferentAccess
8
+ def validate!
9
+ raise OpenToken::TokenExpiredError.new("#{Time.now.utc} is not within token duration: #{self.start_at} - #{self.end_at}") if self.expired?
10
+ end
11
+ #verify that the current time is between the not-before and not-on-or-after values
12
+ def valid?
13
+ start_at.past? && end_at.future?
14
+ end
15
+ def expired?
16
+ !valid?
17
+ end
18
+ def start_at
19
+ payload_date('not-before')
20
+ end
21
+ def end_at
22
+ payload_date('not-on-or-after')
23
+ end
24
+ def valid_until
25
+ payload_date('renew-until')
26
+ end
27
+
28
+ private
29
+ def payload_date(key)
30
+ Time.iso8601(self[key])
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module OpenToken
2
+ VERSION = '1.2.2'
3
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "opentoken-newrelic-rails23/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "opentoken-newrelic-rails23"
7
+ s.version = OpenToken::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Ryan Sonnek"]
10
+ s.email = ["ryan@socialcast.com"]
11
+ s.homepage = "http://github.com/newrelic/opentoken-newrelic-rails23"
12
+ s.summary = %q{ruby implementation of the opentoken specification, forked for Rails 2.3 compatibility. Use the non-forked version for Rails 3+}
13
+ s.description = %q{parse opentoken properties passed for Single Signon requests}
14
+
15
+ s.rubyforge_project = "opentoken-newrelic-rails23"
16
+
17
+ # If you are on a newer version of Rails, you should be using the opentoken gem, not our fork.
18
+ s.add_runtime_dependency(%q<activesupport>, ["~> 2.3.14"])
19
+ s.add_runtime_dependency(%q<i18n>, [">= 0"])
20
+ s.add_development_dependency(%q<shoulda>, ["2.11.3"])
21
+ s.add_development_dependency(%q<timecop>, ["0.3.5"])
22
+ s.add_development_dependency(%q<rake>, ["0.9.2"])
23
+
24
+ s.files = `git ls-files`.split("\n")
25
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
26
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
27
+ s.require_paths = ["lib"]
28
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+ require 'timecop'
13
+
14
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
15
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
16
+ require 'opentoken-newrelic-rails23'
17
+
18
+ class Test::Unit::TestCase
19
+ end
@@ -0,0 +1,105 @@
1
+ require 'helper'
2
+
3
+ class TestOpentoken < Test::Unit::TestCase
4
+ # OpenToken.debug = true
5
+
6
+ #"renew-until"=>"2010-03-05T07:19:15Z"
7
+ #"not-before"=>"2010-03-04T19:19:15Z"
8
+ #"not-on-or-after"=>"2010-03-04T19:24:15Z"
9
+ context "aes-128-cbc token with subject attribute" do
10
+ setup do
11
+ @opentoken = "T1RLAQJ0Ca97sl6MLJAZDa_hdFzMlicMQBDjqUzrXl0EOXKmpj5oo7L5AACgaWoW8fZizrsLbtxb_F00aTdFmhw8flGy4iGqPWPtqYpdIzQZzg5WvrvYH8Rnq7ckJpYk2YPZw6yNyA4ohG-BgFdTHc0U7CwZTFmodg1MuO0cTh7T98s2RXiTcaZa21MNO0yuXKm2Q10cbrWhnB5yHJUhSHx6JLxlgMTZ0oE0DoUOB6JmoLMYHcyL9hKRiPTh62ky_QmXRaifDNOdl4sH2w**"
12
+ @password = 'Test123'
13
+ OpenToken.password = @password
14
+ end
15
+ context "decoding token between expiration dates" do
16
+ setup do
17
+ Timecop.travel(Time.iso8601('2010-03-04T19:20:10Z')) do
18
+ assert_nothing_raised do
19
+ @token = OpenToken.decode @opentoken
20
+ end
21
+ end
22
+ end
23
+ should "decrypt subject from token payload" do
24
+ assert_equal 'john@example.com', @token[:subject]
25
+ end
26
+ should "decrypt subject using string or symbol" do
27
+ assert_equal 'john@example.com', @token['subject']
28
+ end
29
+ should "parse 'renew-until' date" do
30
+ assert_equal Time.iso8601('2010-03-05T07:19:15Z'), @token.valid_until
31
+ end
32
+ end
33
+
34
+ context "decoding token when current time is before expiration date" do
35
+ should "raise TokenExpiredError" do
36
+ Timecop.travel(Time.iso8601('2010-03-04T19:19:10Z')) do
37
+ assert_raises OpenToken::TokenExpiredError do
38
+ @token = OpenToken.decode @opentoken
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ context "decoding token when current time is equal to expiration date" do
45
+ should "raise TokenExpiredError" do
46
+ Timecop.travel(Time.iso8601('2010-03-04T19:24:15Z')) do
47
+ assert_raises OpenToken::TokenExpiredError do
48
+ @token = OpenToken.decode @opentoken
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ context "decoding token with attribute value containing apostrophe" do
55
+ setup do
56
+ Timecop.travel(Time.iso8601('2011-01-13T11:08:01Z')) do
57
+ @opentoken = "T1RLAQLIjiqgexqi1PQcEKCetvGoSYR2jhDFSIfE5ctlSBxEnq3S1ydjAADQUNRIKJx6_14aE3MQZnDABupGJrKNfoJHFS5VOnKexjMtboeOgst31Hf-D9CZBrpB7Jv0KBwnQ7DN3HizecPT76oX3UGtq_Vi5j5bKYCeObYm9W6h7NY-VzcZY5TTqIuulc2Jit381usAWZ2Sv1c_CWwhrH4hw-x7vUQMSjErvXK1qvsrFCpfNr7XlArx0HjI6kT5XEaHgQNdC0zrLw9cZ4rewoEisR3H5oM7B6gMaP82wTSFVBXvpn5r0KT-Iuc3JuG2en1zVh3GNf110oQCKQ**"
58
+ @token = OpenToken.decode @opentoken
59
+ end
60
+ end
61
+ should 'preserve apostrophe in attribute payload' do
62
+ assert_equal "D'angelo", @token[:last_name]
63
+ end
64
+ end
65
+
66
+ should 'raise invalid token error parsing nil token' do
67
+ assert_raises OpenToken::TokenInvalidError do
68
+ OpenToken.decode nil
69
+ end
70
+ end
71
+ end
72
+
73
+ context "encoding token" do
74
+ setup do
75
+ OpenToken.password = "Password1"
76
+ end
77
+ context "with aes-128-cbc and subject attribute" do
78
+ setup do
79
+ @attributesIn = { "subject" => "john", "email" => "john@example.com"}
80
+ @token = OpenToken.encode @attributesIn, OpenToken::Cipher::AES_128_CBC
81
+ end
82
+ should "be decodable" do
83
+ @attributesOut = OpenToken.decode @token
84
+ assert_equal @attributesIn, @attributesOut
85
+ end
86
+ end
87
+ end
88
+
89
+ context "encoding token with utf-8 values" do
90
+ setup do
91
+ OpenToken.password = "Password1"
92
+ end
93
+ context "with aes-128-cbc and subject attribute" do
94
+ setup do
95
+ @subject = OpenToken.send(:force_encoding, "Andr\xC3\xA9", 'UTF-8')
96
+ @attributesIn = { "subject" => @subject, "email" => "john@example.com"}
97
+ @token = OpenToken.encode @attributesIn, OpenToken::Cipher::AES_128_CBC
98
+ end
99
+ should "be decodable" do
100
+ @attributesOut = OpenToken.decode @token
101
+ assert_equal @attributesIn, @attributesOut
102
+ end
103
+ end
104
+ end
105
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: opentoken-newrelic-rails23
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 2
9
+ - 2
10
+ version: 1.2.2
11
+ platform: ruby
12
+ authors:
13
+ - Ryan Sonnek
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-05-09 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activesupport
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 31
29
+ segments:
30
+ - 2
31
+ - 3
32
+ - 14
33
+ version: 2.3.14
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: i18n
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: shoulda
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - "="
57
+ - !ruby/object:Gem::Version
58
+ hash: 37
59
+ segments:
60
+ - 2
61
+ - 11
62
+ - 3
63
+ version: 2.11.3
64
+ type: :development
65
+ version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ name: timecop
68
+ prerelease: false
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - "="
73
+ - !ruby/object:Gem::Version
74
+ hash: 25
75
+ segments:
76
+ - 0
77
+ - 3
78
+ - 5
79
+ version: 0.3.5
80
+ type: :development
81
+ version_requirements: *id004
82
+ - !ruby/object:Gem::Dependency
83
+ name: rake
84
+ prerelease: false
85
+ requirement: &id005 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - "="
89
+ - !ruby/object:Gem::Version
90
+ hash: 63
91
+ segments:
92
+ - 0
93
+ - 9
94
+ - 2
95
+ version: 0.9.2
96
+ type: :development
97
+ version_requirements: *id005
98
+ description: parse opentoken properties passed for Single Signon requests
99
+ email:
100
+ - ryan@socialcast.com
101
+ executables: []
102
+
103
+ extensions: []
104
+
105
+ extra_rdoc_files: []
106
+
107
+ files:
108
+ - .document
109
+ - .gitignore
110
+ - CONTRIBUTORS.txt
111
+ - Gemfile
112
+ - LICENSE.txt
113
+ - README.md
114
+ - Rakefile
115
+ - lib/opentoken-newrelic-rails23.rb
116
+ - lib/opentoken-newrelic-rails23/cipher.rb
117
+ - lib/opentoken-newrelic-rails23/key_value_serializer.rb
118
+ - lib/opentoken-newrelic-rails23/password_key_generator.rb
119
+ - lib/opentoken-newrelic-rails23/token.rb
120
+ - lib/opentoken-newrelic-rails23/version.rb
121
+ - opentoken-newrelic-rails23.gemspec
122
+ - test/helper.rb
123
+ - test/test_opentoken.rb
124
+ homepage: http://github.com/newrelic/opentoken-newrelic-rails23
125
+ licenses: []
126
+
127
+ post_install_message:
128
+ rdoc_options: []
129
+
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ none: false
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ hash: 3
138
+ segments:
139
+ - 0
140
+ version: "0"
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ none: false
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ hash: 3
147
+ segments:
148
+ - 0
149
+ version: "0"
150
+ requirements: []
151
+
152
+ rubyforge_project: opentoken-newrelic-rails23
153
+ rubygems_version: 1.8.10
154
+ signing_key:
155
+ specification_version: 3
156
+ summary: ruby implementation of the opentoken specification, forked for Rails 2.3 compatibility. Use the non-forked version for Rails 3+
157
+ test_files:
158
+ - test/helper.rb
159
+ - test/test_opentoken.rb