rails_compatible_cookies_utils 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bc47b3f417503da1fc90fb24f1551e5cc8976a82
4
+ data.tar.gz: e026fdd8cd7dc13534b7ad672547a325d9ff6ee1
5
+ SHA512:
6
+ metadata.gz: 39986363fb9e3c772bebb8009fab0baad886423be46a57f67e2eda138c2dae59d3b86ac92c90401c0ffb17ae4c8b8ef2f4c53890c1209721c621f8dc841a8135
7
+ data.tar.gz: 488e3381c9765bde6d4aa9380a12629b85d0e0c3f750cd46438e15c7eb91a8fad5540179135d9785ee85c9f1513569f4ad75d00a6316dd31e339d80cc8dec25d
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.1
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rails_compatible_cookies_utils.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Rodrigo Rosenfeld Rosas
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.
data/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # RailsCompatibleCookiesUtils - Utility methods to deal with cookies managed by a Rails app
2
+
3
+ This is a Ruby-only (2.0 or later) implementation of utility methods to manage cookies
4
+ generated by a Rails app. It doesn't depend on any external gems.
5
+
6
+ Just tell RailsCompatibleCookiesUtils the Rails app secret key and it will be able to handle
7
+ signed and encrypted cookies (it can both read and write those values).
8
+
9
+ This is useful if you intend to share your cookies (or session) among different Ruby web
10
+ applications (they could be even running in the same domain behind a proxy for example) when
11
+ at least one of the applications is not a Rails one.
12
+
13
+ Or you could use this if you are switching from Rails to another Ruby web framework and want
14
+ to deal with old valid sessions after deploying (either if it's a temporary strategy or you
15
+ want to keep this encrypting and signing algorithms permanently).
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem 'rails_compatible_cookies_utils'
23
+ ```
24
+
25
+ And then execute:
26
+
27
+ $ bundle
28
+
29
+ Or install it yourself as:
30
+
31
+ $ gem install rails_compatible_cookies_utils
32
+
33
+ ## Usage
34
+
35
+ You may test the following examples in a REPL with `bin/console`.
36
+
37
+ ### Initialization
38
+
39
+ Creating an instance using the default serializer in Rails 5 generated applications (JSON):
40
+
41
+ ```ruby
42
+ cookies_utils = RailsCompatibleCookiesUtils.new 'secret_key_base'
43
+ ```
44
+
45
+ Usually you'll pass the Rails app `secret_key_base` (`Rails.application.config.secret_key_base`)
46
+ you want to share cookies with. You can also generate a Rails-like random secret key for other
47
+ purposes if you want:
48
+
49
+ ```ruby
50
+ secret_key_base = SecureRandom.hex 64
51
+ ```
52
+
53
+ In previous Rails releases the default serializer was Marshal. If your app uses it, initialize
54
+ it this way:
55
+
56
+ ```ruby
57
+ cookies_utils = RailsCompatibleCookiesUtils.new 'secret_key_base', serializer: Marshal
58
+ ```
59
+
60
+ ### Thread-safety
61
+
62
+ An instance of RailsCompatibleCookiesUtils is not thread-safe, so ensure to create a new
63
+ instance for each thread using such an instance or make the calls in within a mutex synchronized
64
+ block.
65
+
66
+ ### Read an encrypted cookie value
67
+
68
+ This would be the equivalent of `cookies.encrypted['_myapp_session']`:
69
+
70
+ ```ruby
71
+ raw_cookie_string = env['HTTP_COOKIE'] # this would be considering a Rack app
72
+ cookies_utils.decrypt_cookie_key raw_cookie_string, '_myapp_session'
73
+ ```
74
+
75
+ Or if you already have the parsed and unescaped cookie value:
76
+
77
+ ```ruby
78
+ value = cookies_utils.cookies(env['HTTP_COOKIE'])['_myapp_session']
79
+ # you may actually get the value from somewhere else
80
+ cookies_utils.decrypt value
81
+ ```
82
+
83
+ ### Write an encrypted value to the cookies
84
+
85
+ ```ruby
86
+ require 'cgi'
87
+ value = { test: true }
88
+ cookie = CGI::Cookie.new 'some_key', cookies_utils.encrypt value
89
+ # or using a framework helper:
90
+ # cookies['some_key'] = cookies_utils.encrypt value
91
+
92
+ cookies_utils.decrypt(cookies_utils.encrypt 'secret') == 'secret'
93
+ ```
94
+
95
+ ### Signed only values
96
+
97
+ Rails also allows you to just sign a cookie value without encrypting it
98
+ (`cookies.signed['key'] = 'value'`). To verify and decode the value:
99
+
100
+ ```ruby
101
+ cookies_utils.signed_cookie_key env['HTTP_COOKIE'], 'some_key'
102
+ # or cookies_utils.verify_and_deserialize signed_serialized_value
103
+ ```
104
+
105
+ Serialize and sign a cookie value with:
106
+
107
+ ```ruby
108
+ require 'cgi'
109
+ value = { test: true }
110
+ cookie = CGI::Cookie.new 'some_key', cookies_utils.serialize_and_sign value
111
+ # or using a framework helper:
112
+ # cookies['some_key'] = cookies_utils.serialize_and_sign value
113
+
114
+ cookies_utils.verify_and_deserialize(cookies_utils.serialize_and_sign 'secret') == 'secret'
115
+ ```
116
+
117
+ ### Invalid values handling
118
+
119
+ If the value is invalid `decrypt_cookie_key`, `decrypt`, `signed_cookie_key`,
120
+ `verify_and_deserialize` and `serialize_and_sign` will all return nil. All of
121
+ them have a bang variant which will raise RailsCompatibleCookiesUtils::InvalidSignature
122
+ instead in those cases (like `decrypt!`).
123
+
124
+ ## Development
125
+
126
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
127
+
128
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
129
+
130
+ ## Contributing
131
+
132
+ Bug reports and pull requests are welcome [on GitHub](https://github.com/rosenfeld/rails_compatible_cookies_utils).
133
+
134
+
135
+ ## License
136
+
137
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
138
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'rails_compatible_cookies_utils'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,157 @@
1
+ require 'rails_compatible_cookies_utils/version'
2
+ require 'json'
3
+ require 'cgi'
4
+ require 'openssl'
5
+ require 'base64'
6
+
7
+ class RailsCompatibleCookiesUtils
8
+
9
+ ##
10
+ # Initializes it by providing the required secret_key_base as defined by your Rails app.
11
+ # The default Rails serializer in recent versions is +JSON+, but +Marshal+ used to be the
12
+ # default. If your Rails application is old and still uses +Marshal+, set the +serializer+
13
+ # argument to +Marshal+: RailsCompatibleCookiesUtils.new(secret_key_base, serializer: Marshal).
14
+
15
+ def initialize(secret_key_base, serializer: JSON, encrypted_salt: 'encrypted cookie',
16
+ encrypted_signed_salt: 'signed encrypted cookie', signed_salt: 'signed cookie',
17
+ iterations: 1000, key_size: 64, cipher: 'aes-256-cbc', digest: 'SHA1')
18
+ @secret_key_base, @serializer, @encrypted_salt, @encrypted_signed_salt,
19
+ @signed_salt, @iterations, @key_size, @cipher, @digest =
20
+ secret_key_base, serializer, encrypted_salt, encrypted_signed_salt,
21
+ signed_salt, iterations, key_size, cipher, digest
22
+ end
23
+
24
+ # Decrypts an specific key from a raw cookie string. Returns nil if invalid.
25
+ def decrypt_cookie_key(cookie, key)
26
+ decrypt cookie_value(cookie, key)
27
+ end
28
+
29
+ # Decrypts an specific key from a raw cookie string.
30
+ # Raises RailsCompatibleCookiesUtils::InvalidSignature if invalid.
31
+ def decrypt_cookie_key!(cookie, key)
32
+ decrypt! cookie_value(cookie, key)
33
+ end
34
+
35
+ InvalidSignature = Class.new StandardError
36
+ # Decrypt an unescaped value and raise RailsCompatibleCookiesUtils::InvalidSignature if invalid.
37
+ def decrypt!(value)
38
+ decrypt value or raise InvalidSignature
39
+ end
40
+
41
+ # Decrypts an unescaped value and return nil if invalid.
42
+ def decrypt(value)
43
+ return nil unless encoded_encrypted = verify_and_decode(value, encrypted_signed_secret)
44
+ cipher = new_cipher
45
+ encrypted, iv = encoded_encrypted.split('--').map{|v| decode v }
46
+ cipher.decrypt
47
+ cipher.key = encrypted_secret
48
+ cipher.iv = iv
49
+ decrypted = cipher.update encrypted
50
+ decrypted << cipher.final
51
+ @serializer.load decrypted
52
+ end
53
+
54
+ # Decodes value, returning nil if it's invalid.
55
+ def verify_and_decode(value, secret)
56
+ return nil if value.nil? || !value.valid_encoding? || value.strip.empty?
57
+ data, digest = value.split '--'
58
+ return nil if [data, digest].any?{|v| v.nil? || v.strip.empty?}
59
+ return nil unless generate_digest(data, secret) == digest
60
+ decode data
61
+ end
62
+
63
+ def encrypted_secret
64
+ @encrypted_secret ||= generate_key @encrypted_salt
65
+ end
66
+
67
+ def generate_key(salt)
68
+ OpenSSL::PKCS5.pbkdf2_hmac_sha1 @secret_key_base, salt, @iterations, @key_size
69
+ end
70
+
71
+ def encrypted_signed_secret
72
+ @encrypted_signed_secret ||= generate_key @encrypted_signed_salt
73
+ end
74
+
75
+ def signed_secret
76
+ @signed_secret ||= generate_key @signed_salt
77
+ end
78
+
79
+ # Gets first value for +key+ from the raw +cookie+ (env['HTTP_COOKIE']).
80
+ def cookie_value(cookie, key)
81
+ cookies(cookie)[key]
82
+ end
83
+
84
+ # Returns a hash with the first value for each key from the raw cookie string.
85
+ def cookies(cookie)
86
+ Hash[CGI::Cookie::parse(cookie).map{|k, v| [k, v.first]}]
87
+ end
88
+
89
+ # Returns signed encrypted +value+.
90
+ def encrypt(value)
91
+ sign_and_encode _encrypt(value), encrypted_signed_secret
92
+ end
93
+
94
+ # Signs and encode value only, without encrypting (session.signed[key] = value).
95
+ def sign_and_encode(value, secret)
96
+ data = encode value
97
+ "#{data}--#{generate_digest data, secret}"
98
+ end
99
+
100
+ # Fetches a signed-only value from the raw cookie (env['HTTP_COOKIE']): session.signed['key'].
101
+ # Raises RailsCompatibleCookiesUtils::InvalidSignature if invalid.
102
+ def signed_cookie_key!(cookie, key)
103
+ signed_cookie_key cookie, key or raise InvalidSignature
104
+ end
105
+
106
+ # Fetches a signed-only value from the raw cookie (env['HTTP_COOKIE']): session.signed['key'].
107
+ # Returns nil if invalid.
108
+ def signed_cookie_key(cookie, key)
109
+ verify_and_deserialize cookie_value(cookie, key)
110
+ end
111
+
112
+ # Decodes and deserializes a signed value.
113
+ # Raises RailsCompatibleCookiesUtils::InvalidSignature if invalid.
114
+ def verify_and_deserialize!(value)
115
+ verify_and_deserialize value or raise InvalidSignature
116
+ end
117
+
118
+ # Decodes and deserializes a signed value, returning nil if invalid.
119
+ def verify_and_deserialize(value)
120
+ return nil unless decoded = verify_and_decode(value, signed_secret)
121
+ @serializer.load decoded
122
+ end
123
+
124
+ # Serializes and sign +value+ (session.signed['key'] = value)
125
+ def serialize_and_sign(value)
126
+ sign_and_encode @serializer.dump(value), signed_secret
127
+ end
128
+
129
+ private
130
+
131
+ def generate_digest(data, secret)
132
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, secret, data)
133
+ end
134
+
135
+ def encode(data)
136
+ ::Base64.strict_encode64 data
137
+ end
138
+
139
+ def decode(data)
140
+ ::Base64.strict_decode64 data
141
+ end
142
+
143
+ def new_cipher
144
+ OpenSSL::Cipher.new @cipher
145
+ end
146
+
147
+ def _encrypt(value)
148
+ cipher = new_cipher
149
+ cipher.encrypt
150
+ cipher.key = encrypted_secret
151
+ iv = cipher.random_iv
152
+
153
+ encrypted = cipher.update @serializer.dump value
154
+ encrypted << cipher.final
155
+ "#{encode encrypted}--#{encode iv}"
156
+ end
157
+ end
@@ -0,0 +1,3 @@
1
+ class RailsCompatibleCookiesUtils
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rails_compatible_cookies_utils/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'rails_compatible_cookies_utils'
8
+ spec.version = RailsCompatibleCookiesUtils::VERSION
9
+ spec.authors = ['Rodrigo Rosenfeld Rosas']
10
+ spec.email = ['rr.rosas@gmail.com']
11
+
12
+ spec.summary = %q{Provides utility methods to read and write cookies shared with a Rails app}
13
+ spec.homepage = 'https://github.com/rosenfeld/rails_compatible_cookies_utils'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) }
17
+ spec.require_paths = ['lib']
18
+
19
+ spec.add_development_dependency 'bundler', '~> 1.11'
20
+ spec.add_development_dependency 'rake', '~> 10.0'
21
+ spec.add_development_dependency 'rspec', '~> 3.0'
22
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_compatible_cookies_utils
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rodrigo Rosenfeld Rosas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-07-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description:
56
+ email:
57
+ - rr.rosas@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - bin/console
70
+ - bin/setup
71
+ - lib/rails_compatible_cookies_utils.rb
72
+ - lib/rails_compatible_cookies_utils/version.rb
73
+ - rails_compatible_cookies_utils.gemspec
74
+ homepage: https://github.com/rosenfeld/rails_compatible_cookies_utils
75
+ licenses:
76
+ - MIT
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 2.5.1
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: Provides utility methods to read and write cookies shared with a Rails app
98
+ test_files: []