heroku-bouncer 0.3.4 → 0.4.0.pre

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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +6 -6
  3. data/README.md +36 -13
  4. data/lib/heroku/bouncer.rb +127 -22
  5. metadata +8 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b0a0fcbf6c90f52b0178b11532f52f99c89d882f
4
- data.tar.gz: b98b20f813286ef2057f194b8060f325a78ffe89
3
+ metadata.gz: f43138f01404ab7e324200582dfb741e4eaaccdd
4
+ data.tar.gz: 066eb52d989918961505cd21f760b7421a3fd40d
5
5
  SHA512:
6
- metadata.gz: a64178645292a555e6dda738b02a795ce7405e649cf7467d458736da1108d74a67b03d99cc7f9f1d35681a251dc80e090df4dac9d8143a29ed07d4dadc15f0d0
7
- data.tar.gz: 1a6ae3ff4a85d68e9be0507edf3309dab70de8221d84587a19fdefe0918f5d2a617ebbf6cba46bbc981d3af159bb1ae9101675efafe40ae97ff04fd6560da40a
6
+ metadata.gz: f9b41150e950a3f5342d5f909197f38c7896a240a2e384b28e470d1217c600586a9631e5735fe1e5bf65eb3c50363f83190b03df507f985f551780707fde1cf0
7
+ data.tar.gz: c12572fc71cb948758547a3580e73bb0393cb75479f6e64eb6b514b54ee9997185ac511955e8dd381917eebc33cbb298ebc2032c1a192a9f380cc5dc2aab1cae
data/Gemfile.lock CHANGED
@@ -1,8 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- heroku-bouncer (0.2.1)
5
- encrypted_cookie (~> 0.0.4)
4
+ heroku-bouncer (0.3.3)
6
5
  faraday (~> 0.8)
7
6
  multi_json (~> 1.0)
8
7
  omniauth-heroku (>= 0.1.0)
@@ -11,14 +10,14 @@ PATH
11
10
  GEM
12
11
  remote: https://rubygems.org/
13
12
  specs:
14
- encrypted_cookie (0.0.4)
15
- faraday (0.8.7)
16
- multipart-post (~> 1.1)
13
+ faraday (0.8.8)
14
+ multipart-post (~> 1.2.0)
17
15
  hashie (2.0.5)
18
16
  httpauth (0.2.0)
19
17
  jwt (0.1.8)
20
18
  multi_json (>= 1.5)
21
- multi_json (1.7.7)
19
+ minitest (5.0.8)
20
+ multi_json (1.8.0)
22
21
  multipart-post (1.2.0)
23
22
  oauth2 (0.8.1)
24
23
  faraday (~> 0.8)
@@ -49,3 +48,4 @@ PLATFORMS
49
48
 
50
49
  DEPENDENCIES
51
50
  heroku-bouncer!
51
+ minitest (~> 5.0)
data/README.md CHANGED
@@ -10,7 +10,13 @@ Sinatra app that uses heroku-bouncer.
10
10
 
11
11
  ## Use
12
12
 
13
- 1. Create your OAuth client using `/auth/heroku/callback` as your
13
+ 1. Install the Heroku OAuth CLI plugin.
14
+
15
+ ```sh
16
+ heroku plugins:install git://github.com/heroku/heroku-oauth.git
17
+ ```
18
+
19
+ 2. Create your OAuth client using `/auth/heroku/callback` as your
14
20
  callback endpoint. Use `http://localhost:5000/auth/heroku/callback`
15
21
  for local development with Foreman.
16
22
 
@@ -19,26 +25,43 @@ Sinatra app that uses heroku-bouncer.
19
25
  heroku clients:register myapp https://myapp.herokuapp.com/auth/heroku/callback
20
26
  ```
21
27
 
22
- 2. Set `HEROKU_OAUTH_ID` and `HEROKU_OAUTH_SECRET` in your environment.
23
- 3. Set the `COOKIE_SECRET` environment variable to a long random string.
28
+ 3. Set `HEROKU_OAUTH_ID` and `HEROKU_OAUTH_SECRET` in your environment.
29
+ 4. Set the `COOKIE_SECRET` environment variable to a long random string.
24
30
  Otherwise, the OAuth ID and secret are concatenated for use as a secret.
25
- 4. Use the middleware.
31
+ 5. Use the middleware as follows:
26
32
 
27
- **Rack, Sinatra, and Rails 4**
33
+ **Rack**
28
34
 
29
- Add a `use` line to `config.ru`:
35
+ `Heroku::Bouncer` requires a session middleware to be mounted above
36
+ it. Pure Rack apps will need to add such a middleware if they don't
37
+ already have one. In `config.ru`:
30
38
 
31
39
  ```ruby
32
- require ::File.expand_path('../config/environment', __FILE__)
33
-
34
- use ::Heroku::Bouncer
35
- run Rails.application
40
+ require 'rack/session/cookie'
41
+ require 'heroku/bouncer'
42
+ require 'my_app'
43
+
44
+ # use `openssl rand -base64 32` to generate a secret
45
+ use Rack::Session::Cookie, secret: "..."
46
+ use Heroku::Bouncer
47
+ run MyApp
36
48
  ```
37
49
 
38
- The middleware does not work properly when configured inside
39
- Rails 4.
50
+ **Sinatra**
51
+
52
+ `Heroku::Bouncer` can be run like a Rack app, but since a Sinatra
53
+ app can mount Rack middleware, it may be easier to mount it inside
54
+ the app and use Sinatra's session.
55
+
56
+ ```ruby
57
+ class MyApp < Sinatra::Base
58
+ ...
59
+ enable :sessions, secret: "..."
60
+ use ::Heroku::Bouncer
61
+ ...
62
+ ```
40
63
 
41
- **Rails 3**
64
+ **Rails**
42
65
 
43
66
  Add a middleware configuration line to `config/application.rb`:
44
67
 
@@ -2,7 +2,7 @@ require 'sinatra/base'
2
2
  require 'omniauth-heroku'
3
3
  require 'faraday'
4
4
  require 'multi_json'
5
- require 'encrypted_cookie'
5
+ require 'openssl'
6
6
 
7
7
  unless defined?(Heroku)
8
8
  module Heroku; end
@@ -13,17 +13,13 @@ class Heroku::Bouncer < Sinatra::Base
13
13
  $stderr.puts "[warn] heroku-bouncer: HEROKU_ID detected, please use HEROKU_OAUTH_ID instead" if ENV.has_key?('HEROKU_ID')
14
14
  $stderr.puts "[warn] heroku-bouncer: HEROKU_SECRET detected, please use HEROKU_OAUTH_SECRET instead" if ENV.has_key?('HEROKU_SECRET')
15
15
 
16
- ID = (ENV['HEROKU_OAUTH_ID'] || ENV['HEROKU_ID']).to_s
17
- SECRET = (ENV['HEROKU_OAUTH_SECRET'] || ENV['HEROKU_SECRET']).to_s
16
+ ID = (ENV['HEROKU_OAUTH_ID'] || ENV['HEROKU_ID']).to_s
17
+ SECRET = (ENV['HEROKU_OAUTH_SECRET'] || ENV['HEROKU_SECRET']).to_s
18
+ COOKIE_SECRET = (ENV['COOKIE_SECRET'] || (ID + SECRET)).to_s
18
19
 
19
20
  enable :raise_errors
20
21
  disable :show_exceptions
21
22
 
22
- use Rack::Session::EncryptedCookie,
23
- :secret => (ENV['COOKIE_SECRET'] || (ID + SECRET)).to_s,
24
- :expire_after => 8 * 60 * 60,
25
- :key => (ENV['COOKIE_NAME'] || '_bouncer_session').to_s
26
-
27
23
  # sets up the /auth/heroku endpoint
28
24
  unless ID.empty? || SECRET.empty?
29
25
  use OmniAuth::Builder do
@@ -47,14 +43,27 @@ class Heroku::Bouncer < Sinatra::Base
47
43
  end
48
44
 
49
45
  def call(env)
50
- @disabled ? @app.call(env) : super(env)
46
+ if @disabled
47
+ @app.call(env)
48
+ else
49
+ unlock_session_data(env) do
50
+ super(env)
51
+ end
52
+ end
53
+ end
54
+
55
+ def unlock_session_data(env, &block)
56
+ decrypt_store(env)
57
+ return_value = yield
58
+ encrypt_store(env)
59
+ return_value
51
60
  end
52
61
 
53
62
  before do
54
- if session[:store] && session[:store][:user]
63
+ if store_read(:user)
55
64
  expose_store
56
65
  elsif ! %w[/auth/heroku/callback /auth/heroku /auth/failure /auth/sso-logout /auth/logout].include?(request.path)
57
- session[:return_to] = request.url
66
+ store_write(:return_to, request.url)
58
67
  redirect to('/auth/heroku')
59
68
  end
60
69
  end
@@ -68,14 +77,14 @@ class Heroku::Bouncer < Sinatra::Base
68
77
  url = @herokai_only.is_a?(String) ? @herokai_only : 'https://www.heroku.com'
69
78
  redirect to(url) and return
70
79
  end
71
- @expose_user ? store(:user, user) : store(:user, true)
72
- store(:email, user['email']) if @expose_email
80
+ @expose_user ? store_write(:user, user) : store_write(:user, true)
81
+ store_write(:email, user['email']) if @expose_email
73
82
  else
74
- store(:user, true)
83
+ store_write(:user, true)
75
84
  end
76
85
 
77
- store(:token, token) if @expose_token
78
- redirect to(session.delete(:return_to) || '/')
86
+ store_write(:token, token) if @expose_token
87
+ redirect to(store_delete(:return_to) || '/')
79
88
  end
80
89
 
81
90
  # something went wrong
@@ -97,10 +106,90 @@ class Heroku::Bouncer < Sinatra::Base
97
106
  redirect to("/")
98
107
  end
99
108
 
109
+ # Encapsulates encrypting and decrypting a hash of data. Does not store the
110
+ # key that is passed in.
111
+ class DecryptedHash < Hash
112
+
113
+ def initialize(decrypted_hash = nil)
114
+ super
115
+ replace(decrypted_hash) if decrypted_hash
116
+ end
117
+
118
+ def self.unlock(data, key)
119
+ if data && data = Lockbox.new(key).unlock(data)
120
+ data, digest = data.split("--")
121
+ if digest == Lockbox.generate_hmac(data, key)
122
+ data = data.unpack('m*').first
123
+ data = Marshal.load(data)
124
+ new(data)
125
+ else
126
+ new
127
+ end
128
+ else
129
+ new
130
+ end
131
+ end
132
+
133
+ def lock(key)
134
+ # marshal a Hash, not a DecryptedHash
135
+ data = {}.replace(self)
136
+ data = Marshal.dump(data)
137
+ data = [data].pack('m*')
138
+ data = "#{data}--#{Lockbox.generate_hmac(data, key)}"
139
+ Lockbox.new(key).lock(data)
140
+ end
141
+
142
+ end
143
+
144
+ class Lockbox < BasicObject
145
+
146
+ def initialize(key)
147
+ @key = key
148
+ end
149
+
150
+ def lock(str)
151
+ aes = ::OpenSSL::Cipher::Cipher.new('aes-128-cbc').encrypt
152
+ aes.key = @key
153
+ iv = ::OpenSSL::Random.random_bytes(aes.iv_len)
154
+ aes.iv = iv
155
+ [iv + (aes.update(str) << aes.final)].pack('m0')
156
+ end
157
+
158
+ # decrypts string. returns nil if an error occurs
159
+ #
160
+ # returns nil if openssl raises an error during decryption (data
161
+ # manipulation, key change, implementation change), or if the text to
162
+ # decrypt is too short to possibly be good aes data.
163
+ def unlock(str)
164
+ str = str.unpack('m0').first
165
+ aes = ::OpenSSL::Cipher::Cipher.new('aes-128-cbc').decrypt
166
+ aes.key = @key
167
+ iv = str[0, aes.iv_len]
168
+ aes.iv = iv
169
+ crypted_text = str[aes.iv_len..-1]
170
+ return nil if crypted_text.nil? || iv.nil?
171
+ aes.update(crypted_text) << aes.final
172
+ rescue
173
+ nil
174
+ end
175
+
176
+ private
177
+
178
+ def self.generate_hmac(data, key)
179
+ ::OpenSSL::HMAC.hexdigest(::OpenSSL::Digest::SHA1.new, key, data)
180
+ end
181
+ end
182
+
100
183
  private
101
184
 
102
- def destroy_session
103
- session = nil if session
185
+ def decrypt_store(env)
186
+ env["rack.session"][:bouncer] =
187
+ DecryptedHash.unlock(env["rack.session"][:bouncer], COOKIE_SECRET)
188
+ end
189
+
190
+ def encrypt_store(env)
191
+ env["rack.session"][:bouncer] =
192
+ env["rack.session"][:bouncer].lock(COOKIE_SECRET)
104
193
  end
105
194
 
106
195
  def extract_option(options, option, default = nil)
@@ -115,15 +204,31 @@ private
115
204
  end.body)
116
205
  end
117
206
 
118
- def store(key, value)
119
- session[:store] ||= {}
120
- session[:store][key] = value
207
+ def store
208
+ session[:bouncer]
209
+ end
210
+
211
+ def store_write(key, value)
212
+ store[key] = value
213
+ end
214
+
215
+ def store_read(key)
216
+ store.fetch(key, nil)
217
+ end
218
+
219
+ def store_delete(key)
220
+ store.delete(key)
221
+ end
222
+
223
+ def destroy_session
224
+ session = nil if session
121
225
  end
122
226
 
123
227
  def expose_store
124
- session[:store].each_pair do |key, value|
228
+ store.each_pair do |key, value|
125
229
  request.env["bouncer.#{key}"] = value
126
230
  end
127
231
  end
128
232
 
233
+
129
234
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heroku-bouncer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.4.0.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Dance
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-24 00:00:00.000000000 Z
11
+ date: 2013-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: omniauth-heroku
@@ -67,19 +67,19 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: encrypted_cookie
70
+ name: minitest
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ~>
74
74
  - !ruby/object:Gem::Version
75
- version: 0.0.4
76
- type: :runtime
75
+ version: '5.0'
76
+ type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - ~>
81
81
  - !ruby/object:Gem::Version
82
- version: 0.0.4
82
+ version: '5.0'
83
83
  description: ID please.
84
84
  email:
85
85
  - jd@heroku.com
@@ -108,9 +108,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
108
108
  version: '0'
109
109
  required_rubygems_version: !ruby/object:Gem::Requirement
110
110
  requirements:
111
- - - '>='
111
+ - - '>'
112
112
  - !ruby/object:Gem::Version
113
- version: '0'
113
+ version: 1.3.1
114
114
  requirements: []
115
115
  rubyforge_project:
116
116
  rubygems_version: 2.0.3