heroku-bouncer 0.8.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 85c6929c3208eb743c042d9dbef6f9d43db08987
4
- data.tar.gz: b08dd7bac4866f854c050a65011ec6403936cb93
2
+ SHA256:
3
+ metadata.gz: 37f9d4d239fa9aa8ededbab910a22f0f21b4ed3eb9a7f8c360f7b1ae1dbb0395
4
+ data.tar.gz: ba7c31e928e4187053469ff3ebfb73ebc32e27d82d5f7921e9e26839b382bd75
5
5
  SHA512:
6
- metadata.gz: 5370531c477251b6247116d86597c9cc4d35ade54fa1dd5bb513e2c51a5db3f09b926a99cf573a5b1674c5dcc0980887c41db12598d9f9459c8f93ae011e17c2
7
- data.tar.gz: a748a855a7be7e07e9756c5e9407f2db0a08d12dedff83f0fe4473a53ecc205058a6b1eb2113f7ff524bb5fae25d642dc83fcc1dc18e23d3bb1218319dbf671b
6
+ metadata.gz: 07b8bbb1ee393c0e001a7787b94fe1ba56fc8e40d5c7dde1cb2170e5b89a38c01ff0981161e241f1148553ef52f727079b9fa3dc496d2644b239189ec4a6743f
7
+ data.tar.gz: c63b4d54c69fc32a6981a23770e7db871d832851a9a467d0026ac75f12d47d814ab7c2c3b8e8ce8790a9ad2af1cd5c079994c212edd2980e5c270ba75d218cff
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ # 1.0.0
2
+
3
+ * #71: Ruby >= 3.1 support and Ruby < 3 deprecation, supports Sinatra >= 3 and drops support for Sinatra 1 and 2.
4
+
5
+ # 0.9.0
6
+
7
+ * #68: Loosen `omniauth-heroku` constraint, allowing `>= 0.1, < 2`,
8
+ enabling support of OmniAuth 2. This also adds the new [`:login_path`
9
+ option](README#prompt-to-login). @stevenharman
10
+ * #66: Loosen Faraday constraints, allowing `>= 0.8", < 2`. @stevenharman
11
+
1
12
  # 0.8.0
2
13
 
3
14
  * #55: Ruby >= 2.4 support and Ruby <2.2 deprecation. Thanks @maxbeizer!
data/README.md CHANGED
@@ -1,5 +1,4 @@
1
- [![Build Status](https://travis-ci.org/heroku/heroku-bouncer.png)](https://travis-ci.org/heroku/heroku-bouncer)
2
- [![Dependency Status](https://gemnasium.com/heroku/heroku-bouncer.png)](https://gemnasium.com/heroku/heroku-bouncer)
1
+ [![Build Status](https://github.com/sharpstone/heroku-bouncer/actions/workflows/ci.yml/badge.svg)](https://github.com/sharpstone/heroku-bouncer/actions)
3
2
 
4
3
  # Heroku Bouncer
5
4
 
@@ -8,11 +7,11 @@ requires Heroku OAuth on all requests.
8
7
 
9
8
  ## Ruby and Rack compatibility
10
9
 
11
- * **Ruby**: Versions >= 0.8.0 require Ruby >= 2.2. If you need a version
12
- that works with prior versions of Ruby, please use version `~> 0.7.1`.
13
- Note, however, that 0.7.1 does not support Rack 2 (Rails 5).
10
+ * **Ruby**: Versions `>= 0.8.0` require Ruby >= 2.2. Versions `>= 1.0.0` require Ruby >= 3.1.
11
+ If you need a version that works with prior versions of Ruby, please use version `~> 0.7.1`.
12
+ Note, however, that `0.7.1` does not support Rack 2 (Rails 5+).
14
13
 
15
- * **Rack**: Rack 1 and 2 are supported.
14
+ * **Rack**: Versions `< 1.0.0` support Rack 1 and 2. Versions `>= 1.0.0` support Rack 2 and 3.
16
15
 
17
16
  ## Demo
18
17
 
@@ -23,7 +22,7 @@ Sinatra app that uses heroku-bouncer.
23
22
 
24
23
  1. Install the Heroku OAuth CLI plugin.
25
24
 
26
- ```sh
25
+ ```console
27
26
  heroku plugins:install heroku-cli-oauth
28
27
  ```
29
28
 
@@ -31,7 +30,7 @@ Sinatra app that uses heroku-bouncer.
31
30
  callback endpoint. Use `http://localhost:5000/auth/heroku/callback`
32
31
  for local development with Foreman.
33
32
 
34
- ```sh
33
+ ```console
35
34
  heroku clients:create localhost http://localhost:5000/auth/heroku/callback
36
35
  heroku clients:create myapp https://myapp.herokuapp.com/auth/heroku/callback
37
36
  ```
@@ -113,8 +112,8 @@ Here are the supported options you can pass to the middleware:
113
112
  representing the user. If the lambda evaluates to true, allow the user
114
113
  through. If false, redirects to `redirect_url`. By default, all users are
115
114
  allowed through after authenticating.
116
- * `redirect_url`: Where unauthorized users are redirected to. Defaults to
117
- `www.heroku.com`.
115
+ * `login_path`: Where unauthorized users are redirected so they can be [prompted to login](#prompt-to-login). Defaults to `heroku-bouncer`'s own `/auth/login` path.
116
+ * `redirect_url`: Where unauthorized users are redirected during the OAuth callback phase. Defaults to `www.heroku.com`.
118
117
  * `expose_token`: Expose the OAuth token in the session, allowing you to
119
118
  make API calls as the user. Default: `false`
120
119
  * `expose_email`: Expose the user's email address in the session.
@@ -138,7 +137,6 @@ Here are the supported options you can pass to the middleware:
138
137
 
139
138
  You use these by passing a hash to the `use` call, for example:
140
139
 
141
-
142
140
  ```ruby
143
141
  use Heroku::Bouncer,
144
142
  oauth: { id: "...", secret: "...", scope: "global" },
@@ -146,6 +144,29 @@ use Heroku::Bouncer,
146
144
  expose_token: true
147
145
  ```
148
146
 
147
+ ### Prompt to Login
148
+
149
+ To mitigate Cross-Site Request Forgery attacks, [OmniAuth no longer allows `GET` requests to the `/auth/heroku`](https://github.com/omniauth/omniauth/wiki/Upgrading-to-2.0) path.
150
+ To support this, `heroku-bouncer` no longer redirects unauthorized requests to the `/auth/heroku` path.
151
+ Instead users are redirected to `/auth/login` where a simple HTML template is rendered, prompting the user to authenticate with Heroku.
152
+
153
+ The template includes a `<form>` with a button which will `POST` to the `/auth/heroku` path.
154
+ It also includes the Authenticity Token from `Rack::Protection`.
155
+ The view provides no styling; it is the most basic example of what's required.
156
+
157
+ To render your own prompt UI, provide the `:login_path` option.
158
+ Unauthenticated users will be redirected to this path, allowing you to control the UI.
159
+ The resulting page should render an HTML `<form>` which will `POST` to the `/auth/heroku` path.
160
+ The form needs to include a field named `authenticity_token` with the token from `Rack::Protection`.
161
+
162
+ An example to get you started:
163
+
164
+ ```erb
165
+ <form method="post" action="/auth/heroku">
166
+ <input type="hidden" name="authenticity_token" value="<%= request.env["rack.session"]["csrf"] %>">
167
+ <button type="submit">Sign in via Heroku</button>
168
+ ```
169
+
149
170
  ## How to get the data
150
171
 
151
172
  Based on your choice of the expose options above, the middleware adds
@@ -161,13 +182,13 @@ You can access this in Sinatra and Rails by `request.env[key]`, e.g.
161
182
 
162
183
  ## Using the Heroku API
163
184
 
164
- If you set `expose_token` to `true`, you'll get an API token that you
185
+ If you set `expose_token` to `true`, you'll get an OAuth token that you
165
186
  can use to make Heroku API calls on behalf of the logged-in user using
166
- [heroku.rb][] .
187
+ the [platform API][platform-api].
167
188
 
168
189
  ```ruby
169
- heroku = Heroku::API.new(:api_key => request.env["bouncer.token"])
170
- apps = heroku.get_apps.body
190
+ heroku = PlatformAPI.connect_oauth(request.env["bouncer.token"])
191
+ info = heroku.app.info('sushi')
171
192
  ```
172
193
 
173
194
  Keep in mind that this adds substantial security risk to your
@@ -206,7 +227,8 @@ replay attacks if the data is obtained in its entirety. SSL and session
206
227
  timeouts should be used to help mitigate this risk. CSRF tokens for any
207
228
  actions that modify data are also recommended.
208
229
 
209
- [Rack::Builder]: http://rack.rubyforge.org/doc/Rack/Builder.html
230
+ ## Related Documentation
231
+
210
232
  [inheritance]: https://gist.github.com/wuputah/5534428
211
233
  [OAuth scope]: https://devcenter.heroku.com/articles/oauth#scopes
212
- [heroku.rb]: https://github.com/heroku/heroku.rb
234
+ [platform-api]: https://github.com/heroku/platform-api
@@ -1,5 +1,6 @@
1
1
  require 'heroku/bouncer/middleware'
2
2
  require 'rack/builder'
3
+ require 'rack/protection'
3
4
  require 'omniauth-heroku'
4
5
 
5
6
  class Heroku::Bouncer::Builder
@@ -8,6 +9,7 @@ class Heroku::Bouncer::Builder
8
9
  builder = Rack::Builder.new
9
10
  id, secret, scope = extract_options!(options)
10
11
  unless options[:disabled]
12
+ builder.use Rack::Protection
11
13
  builder.use OmniAuth::Builder do
12
14
  provider :heroku, id, secret, :scope => scope
13
15
  end
@@ -6,10 +6,12 @@ require 'heroku/bouncer/json_parser'
6
6
  require 'heroku/bouncer/decrypted_hash'
7
7
 
8
8
  class Heroku::Bouncer::Middleware < Sinatra::Base
9
-
10
9
  DecryptedHash = ::Heroku::Bouncer::DecryptedHash
11
10
  UnableToFetchUserError = Class.new(RuntimeError)
12
11
 
12
+ DEFAULT_LOGIN_PATH = "/auth/login".freeze
13
+ private_constant :DEFAULT_LOGIN_PATH
14
+
13
15
  enable :raise_errors
14
16
  disable :show_exceptions
15
17
 
@@ -20,8 +22,10 @@ class Heroku::Bouncer::Middleware < Sinatra::Base
20
22
  # super is not called; we're not using sinatra if we're disabled
21
23
  else
22
24
  super(app)
25
+ @disabled = false
23
26
  @cookie_secret = extract_option(options, :secret, SecureRandom.hex(64))
24
27
  @allow_if_user = extract_option(options, :allow_if_user, nil)
28
+ @login_path = extract_option(options, :login_path, DEFAULT_LOGIN_PATH)
25
29
  @redirect_url = extract_option(options, :redirect_url, 'https://www.heroku.com')
26
30
 
27
31
  # backwards-compatibilty for `herokai_only`:
@@ -125,7 +129,7 @@ class Heroku::Bouncer::Middleware < Sinatra::Base
125
129
  auth_url = ENV["HEROKU_AUTH_URL"] || "https://id.heroku.com"
126
130
  logout_url = "#{auth_url}/logout"
127
131
 
128
- # id.heroku.com whitelists this return_to param, as any auth provider should do
132
+ # id.heroku.com allowlists this return_to param, as any auth provider should do
129
133
  logout_url += "?url=#{params['return_to']}" if params['return_to']
130
134
 
131
135
  redirect to(logout_url)
@@ -138,15 +142,24 @@ class Heroku::Bouncer::Middleware < Sinatra::Base
138
142
  end
139
143
 
140
144
  # login, setting the URL to return to
141
- get '/auth/login' do
145
+ get DEFAULT_LOGIN_PATH do
142
146
  if params['return_to'] && params['return_to'].length <= 255
143
147
  store_write(:return_to, params['return_to'])
144
148
  end
145
- redirect to('/auth/heroku')
149
+
150
+ if custom_login_path?
151
+ redirect to(login_path)
152
+ else
153
+ erb(:login, locals: {
154
+ authenticity_token: request.env["rack.session"]["csrf"]
155
+ })
156
+ end
146
157
  end
147
158
 
148
159
  private
149
160
 
161
+ attr_reader :login_path
162
+
150
163
  def unlock_session_data(env, &block)
151
164
  decrypt_store(env)
152
165
  yield
@@ -154,8 +167,20 @@ private
154
167
  encrypt_store(env)
155
168
  end
156
169
 
170
+ def auth_paths
171
+ @auth_paths ||= [
172
+ "/auth/heroku/callback",
173
+ "/auth/heroku",
174
+ "/auth/failure",
175
+ "/auth/sso-logout",
176
+ "/auth/logout",
177
+ DEFAULT_LOGIN_PATH,
178
+ login_path
179
+ ].compact.freeze
180
+ end
181
+
157
182
  def auth_request?
158
- %w[/auth/heroku/callback /auth/heroku /auth/failure /auth/sso-logout /auth/logout /auth/login].include?(request.path_info)
183
+ auth_paths.include?(request.path_info)
159
184
  end
160
185
 
161
186
  def session_nonce_mismatch?
@@ -175,13 +200,17 @@ private
175
200
  ts.nil? || Time.now.to_i > ts
176
201
  end
177
202
 
203
+ def custom_login_path?
204
+ login_path != DEFAULT_LOGIN_PATH
205
+ end
206
+
178
207
  def skip?(env)
179
208
  @skip && @skip.call(env)
180
209
  end
181
210
 
182
211
  def require_authentication
183
212
  store_write(:return_to, request.url)
184
- redirect to('/auth/heroku')
213
+ redirect to(login_path)
185
214
  end
186
215
 
187
216
  def extract_option(options, option, default = nil)
@@ -257,5 +286,4 @@ private
257
286
  return_to.port = port unless port == 80
258
287
  return_to.to_s
259
288
  end
260
-
261
289
  end
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Login with Heroku</title>
6
+ <%# Avoid extra requests for favicon.ico %>
7
+ <link rel="icon" href="data:;base64,iVBORw0KGgo=">
8
+ </head>
9
+ <body class="heroku_bouncer">
10
+ <form method="post" action="/auth/heroku" class="heroku_bouncer-login_form">
11
+ <input type="hidden" name="authenticity_token" value="<%= authenticity_token %>">
12
+ <button type="submit">Login with Heroku</button>
13
+ </form>
14
+ </body>
15
+ </html>
metadata CHANGED
@@ -1,97 +1,109 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heroku-bouncer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Dance
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-08-07 00:00:00.000000000 Z
11
+ date: 2024-06-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: omniauth-heroku
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0.1'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '2'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
29
  version: '0.1'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '2'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: sinatra
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
37
  - - ">="
32
38
  - !ruby/object:Gem::Version
33
- version: '1.0'
39
+ version: '3.0'
34
40
  - - "<"
35
41
  - !ruby/object:Gem::Version
36
- version: '3'
42
+ version: '4'
37
43
  type: :runtime
38
44
  prerelease: false
39
45
  version_requirements: !ruby/object:Gem::Requirement
40
46
  requirements:
41
47
  - - ">="
42
48
  - !ruby/object:Gem::Version
43
- version: '1.0'
49
+ version: '3.0'
44
50
  - - "<"
45
51
  - !ruby/object:Gem::Version
46
- version: '3'
52
+ version: '4'
47
53
  - !ruby/object:Gem::Dependency
48
54
  name: faraday
49
55
  requirement: !ruby/object:Gem::Requirement
50
56
  requirements:
51
- - - "~>"
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 2.0.1
60
+ - - "<"
52
61
  - !ruby/object:Gem::Version
53
- version: '0.8'
62
+ version: '3'
54
63
  type: :runtime
55
64
  prerelease: false
56
65
  version_requirements: !ruby/object:Gem::Requirement
57
66
  requirements:
58
- - - "~>"
67
+ - - ">="
59
68
  - !ruby/object:Gem::Version
60
- version: '0.8'
69
+ version: 2.0.1
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: '3'
61
73
  - !ruby/object:Gem::Dependency
62
74
  name: rack
63
75
  requirement: !ruby/object:Gem::Requirement
64
76
  requirements:
65
77
  - - ">="
66
78
  - !ruby/object:Gem::Version
67
- version: '1.0'
79
+ version: '2.0'
68
80
  - - "<"
69
81
  - !ruby/object:Gem::Version
70
- version: '3'
82
+ version: '4'
71
83
  type: :runtime
72
84
  prerelease: false
73
85
  version_requirements: !ruby/object:Gem::Requirement
74
86
  requirements:
75
87
  - - ">="
76
88
  - !ruby/object:Gem::Version
77
- version: '1.0'
89
+ version: '2.0'
78
90
  - - "<"
79
91
  - !ruby/object:Gem::Version
80
- version: '3'
92
+ version: '4'
81
93
  - !ruby/object:Gem::Dependency
82
94
  name: rake
83
95
  requirement: !ruby/object:Gem::Requirement
84
96
  requirements:
85
97
  - - "~>"
86
98
  - !ruby/object:Gem::Version
87
- version: '10.0'
99
+ version: 12.3.3
88
100
  type: :development
89
101
  prerelease: false
90
102
  version_requirements: !ruby/object:Gem::Requirement
91
103
  requirements:
92
104
  - - "~>"
93
105
  - !ruby/object:Gem::Version
94
- version: '10.0'
106
+ version: 12.3.3
95
107
  - !ruby/object:Gem::Dependency
96
108
  name: minitest
97
109
  requirement: !ruby/object:Gem::Requirement
@@ -126,28 +138,42 @@ dependencies:
126
138
  requirements:
127
139
  - - "~>"
128
140
  - !ruby/object:Gem::Version
129
- version: '0.6'
141
+ version: '2'
130
142
  type: :development
131
143
  prerelease: false
132
144
  version_requirements: !ruby/object:Gem::Requirement
133
145
  requirements:
134
146
  - - "~>"
135
147
  - !ruby/object:Gem::Version
136
- version: '0.6'
148
+ version: '2'
137
149
  - !ruby/object:Gem::Dependency
138
150
  name: mocha
139
151
  requirement: !ruby/object:Gem::Requirement
140
152
  requirements:
141
153
  - - "~>"
142
154
  - !ruby/object:Gem::Version
143
- version: '1.1'
155
+ version: '2.2'
156
+ type: :development
157
+ prerelease: false
158
+ version_requirements: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - "~>"
161
+ - !ruby/object:Gem::Version
162
+ version: '2.2'
163
+ - !ruby/object:Gem::Dependency
164
+ name: nokogiri
165
+ requirement: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - "~>"
168
+ - !ruby/object:Gem::Version
169
+ version: 1.16.4
144
170
  type: :development
145
171
  prerelease: false
146
172
  version_requirements: !ruby/object:Gem::Requirement
147
173
  requirements:
148
174
  - - "~>"
149
175
  - !ruby/object:Gem::Version
150
- version: '1.1'
176
+ version: 1.16.4
151
177
  - !ruby/object:Gem::Dependency
152
178
  name: delorean
153
179
  requirement: !ruby/object:Gem::Requirement
@@ -164,7 +190,7 @@ dependencies:
164
190
  version: '2.1'
165
191
  description: ID please.
166
192
  email:
167
- - jd@heroku.com
193
+ - jd@wuputah.com
168
194
  executables: []
169
195
  extensions: []
170
196
  extra_rdoc_files:
@@ -183,11 +209,12 @@ files:
183
209
  - lib/heroku/bouncer/json_parser.rb
184
210
  - lib/heroku/bouncer/lockbox.rb
185
211
  - lib/heroku/bouncer/middleware.rb
212
+ - lib/heroku/bouncer/views/login.erb
186
213
  homepage: https://github.com/heroku/heroku-bouncer
187
214
  licenses:
188
215
  - MIT
189
216
  metadata: {}
190
- post_install_message:
217
+ post_install_message:
191
218
  rdoc_options: []
192
219
  require_paths:
193
220
  - lib
@@ -195,16 +222,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
195
222
  requirements:
196
223
  - - ">="
197
224
  - !ruby/object:Gem::Version
198
- version: '2.2'
225
+ version: '3.1'
199
226
  required_rubygems_version: !ruby/object:Gem::Requirement
200
227
  requirements:
201
228
  - - ">="
202
229
  - !ruby/object:Gem::Version
203
230
  version: '0'
204
231
  requirements: []
205
- rubyforge_project:
206
- rubygems_version: 2.5.1
207
- signing_key:
232
+ rubygems_version: 3.5.10
233
+ signing_key:
208
234
  specification_version: 4
209
235
  summary: Rapidly add Heroku OAuth to your Ruby app.
210
236
  test_files: