balrog 1.1.0 → 2.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
2
  SHA256:
3
- metadata.gz: 232b0520af207a4da00798bba32874e5ef4ee33c55419793c7ec23e8312c5712
4
- data.tar.gz: 4fc0f719261215f6b2c657f4d2a1964df096d92b67cf2b94f1585ec99a609b94
3
+ metadata.gz: ed08c9a6aa5d68fc2c81f784ea2bd330353c64a1e8ee8729acc6dc65fa4e0da2
4
+ data.tar.gz: 826d7ab7ed00637b9ac8642b44b93909b727bd2ae0971a872fab695d96d9f677
5
5
  SHA512:
6
- metadata.gz: 4521321149b2a0636694f064e43cb20e8416c8eb2f93fd90668195d82a68dfd17bca183618664049139428dec095e7a74f1754b0b66a650d71d3d78610a91806
7
- data.tar.gz: 4d7fbda793ea374006246abae4c9f1aa62b9369972e33f94aa090a152c799a2800d0081b29ec65a1a8d9a1b2a351f11bfe7ce9a85a47ff0637b54b769a707e69
6
+ metadata.gz: 0c0577f269883d820021d35af147e6746868c1dd20581d25919e9f3938657bc780571ae5978f13d8c7caea91baf63c39539271b2cffbbf3f2d306eef7b911120
7
+ data.tar.gz: 884d62c762f25c50c562d190d93452438680b33607cd53d4dd50921a2c9fa13419e1aae2ccd5cfeb8b34f9f2582ab4348e87b53961f853c441f2607df3d04fd7
@@ -1,5 +1,16 @@
1
+ # 2.0.0
2
+
3
+ - Enhancements
4
+ - added support for single sign-on.
5
+
6
+ - BREAKING
7
+ - Balrog can no longer be initialized via `Rails.application.config.middleware.use Balrog::Middleware`. Instead, you need to configure Balrog with `Balrog::Middleware.setup`. See the [README](https://github.com/pixielabs/balrog#Upgrading-from-1.1-to-2.0) for more info.
8
+ - The instance method `Balrog::Middleware#password_hash` has been converted into a class method `Balrog::Middleware.set_password_hash`. See the [README](https://github.com/pixielabs/balrog#Upgrading-from-1.1-to-2.0) for more info.
9
+ - The instance method `Balrog::Middleware#set_session_expiry` has been converted into a class method `Balrog::Middleware.set_session_expiry`. See the [README](https://github.com/pixielabs/balrog#Upgrading-from-1.1-to-2.0) for more info.
10
+
1
11
  # 1.1.0
2
- - added `Balrog::Middleware#session_expires_after`, which would force end users to login again after a certain period of time.
12
+
13
+ - added `Balrog::Middleware#set_session_expiry`, which would force end users to login again after a certain period of time.
3
14
  - added `balrog:view` generator, enabling users to modify their Balrog gate view.
4
15
 
5
16
  # 1.0.0
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- balrog (1.1.0)
4
+ balrog (2.0.0)
5
5
  bcrypt (~> 3.0)
6
6
  rails (>= 5)
7
7
 
data/README.md CHANGED
@@ -6,17 +6,31 @@
6
6
  [![CircleCI](https://circleci.com/gh/pixielabs/balrog.svg?style=svg)](https://circleci.com/gh/pixielabs/balrog)
7
7
 
8
8
  Balrog is a lightweight authorization library for Ruby on Rails >= 5 written by
9
- [Pixie Labs](https://pixielabs.io) that can protect your routes with a single
10
- username & password combination.
11
-
12
- Balrog is an alternative to `http_basic_authentication_with` that provides some
13
- advantages:
14
-
15
- * Uses a password hash instead of a plaintext password.
16
- * Provides a lightweight HTML form instead of inconsistent basic
17
- authentication.
18
- * Better support for password managers (which often don't support basic
19
- authentication dialog boxes).
9
+ [Pixie Labs](https://pixielabs.io) that can protect your routes. Balrog can be
10
+ configured to authorize users using a simple password or single sign-on or both.
11
+
12
+ - If you choose to protect your routes with a password, the password will be
13
+ stored as a password hash, not plain text, and Balrog provides a lightweight
14
+ HTML form that can be styled and used with password managers.
15
+ - If you choose to configure Balrog to use SSO, you can whitelist multiple email
16
+ domains, allowing groups of users access parts of your app, without circulating
17
+ a password.
18
+ - Balrog's authentication can and should be configured to expire, requiring
19
+ users to sign-in again in accordance with [OWASP](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-expiration) best practices.
20
+ - Balrog can also be used to restrict access to [mounted Rack applications](#Restricting-access-to-mounted-Rack-applications-within-config/routes.rb) like Sidekiq.
21
+
22
+ ## Table of Contents
23
+
24
+ - [Installation](#Installation)
25
+ - [Regenerating a password hash](#Regenerating-a-password-hash)
26
+ - [Restricting access in a controller](#Restricting-access-in-a-controller)
27
+ - [Restricting access to mounted Rack applications](#Restricting-access-to-mounted-Rack-applications-within-config/routes.rb)
28
+ - [Logout button](#Logout-button)
29
+ - [Changing session expiry length](#Changing-session-expiry-length)
30
+ - [Configuring the Balrog gate view](#Configuring-the-Balrog-gate-view)
31
+ - [Single Sign On](#Single-Sign-On)
32
+ - [Upgrading from 1.1 to 2.0](#Upgrading-from-1.1-to-2.0)
33
+ - [Contributing](#Contributing)
20
34
 
21
35
  ## Installation
22
36
 
@@ -120,11 +134,12 @@ If you don't want sessions to expire, remove `set_session_expiry`
120
134
  from the initializer completely.
121
135
 
122
136
  ```ruby
123
- Rails.application.config.middleware.use Balrog::Middleware do
124
- password_hash '$2a$12$BLz7XCFdG9YfwL64KlTgY.T3FY55aQk8SZEzHfpHfw15F2uN1kuSi'
125
- set_session_expiry 30.minutes
137
+ Balrog::Middleware.setup do |config|
138
+ config.password_hash '$2a$12$BLz7XCFdG9YfwL64KlTgY.T3FY55aQk8SZEzHfpHfw15F2uN1kuSi'
139
+ config.set_session_expiry 30.minutes
126
140
  end
127
141
  ```
142
+
128
143
  ## Configuring the Balrog gate view
129
144
 
130
145
  We built Balrog to have a default view and stylesheet so that you can drop
@@ -145,6 +160,62 @@ After running the generator, you can now add elements and classes to the
145
160
  `app/views/layouts/balrog.html.erb`. For an example, see the
146
161
  [dummy-rails-app](https://github.com/pixielabs/balrog/tree/master/spec/dummy-rails-app) in the spec folder.
147
162
 
163
+ ## Single Sign On
164
+
165
+ To add single sign on you will need to add the [omniauth gem](https://github.com/omniauth/omniauth)
166
+ to your gem file, along with the omniauth gem for your chosen
167
+ [provider](https://github.com/omniauth/omniauth/wiki/List-of-Strategies).
168
+
169
+ In `config/initializers/balrog.rb`, call `config.set_omniauth` in the setup block.
170
+ `.set_omniauth` takes the same arguments as the `OmniAuth::Builder#provider`
171
+ [method](https://github.com/omniauth/omniauth#getting-started),
172
+ a provider and any required keys.
173
+
174
+ To whitelist any email addresses with a specific domain, call
175
+ `config.set_domain_whitelist`in the setup block and pass in the domain.
176
+ If you want to whitelist multiple domains, you can pass multiple domains
177
+ to the `.set_domain_whitelist`.
178
+
179
+ Balrog does not require a password to be set if you wish to use single sign-on only.
180
+
181
+ ```ruby
182
+ Balrog::Middleware.setup do |config|
183
+ credentials = Rails.application.credentials
184
+ config.set_omniauth :google_oauth2, credentials.google_client_id, credentials.google_client_secret
185
+ config.set_domain_whitelist 'pixielabs.io', 'the_fellowship.com'
186
+ end
187
+ ```
188
+ **Please note:** there is currently a CSRF vulnerability which affects OmniAuth
189
+ (designated [CVE-2015-9284](https://nvd.nist.gov/vuln/detail/CVE-2015-9284))
190
+ that requires mitigation at the application level. More details on how to do
191
+ this can be found on the [Omniauth Wiki](https://github.com/omniauth/omniauth/wiki/Resolving-CVE-2015-9284).
192
+
193
+ ## Upgrading from 1.1 to 2.0
194
+
195
+ To upgrade, you will need to change your Balrog initializer.
196
+
197
+ 1. Instead of calling `Rails.application.config.middleware.use Balrog::Middleware`, you will now need to call `Balrog::Middleware.setup`.
198
+
199
+ 2. Change the block you pass into these methods. `#password_hash` and `#set_session_expiry` now need to called on a block parameter, e.g `set_session_expiry 30.minutes` needs to be changed to `config.set_session_expiry 30.minutes`.
200
+
201
+ See below for code examples.
202
+
203
+ ```ruby
204
+ # Balrog 1.1
205
+ Rails.application.config.middleware.use Balrog::Middleware do
206
+ password_hash '$2a$12$I8Fp3e2GfSdM7KFyoMx56.BVdHeeyk9DQWKkdsxw7USvU/mC8a8.q'
207
+ set_session_expiry 30.minutes
208
+ end
209
+ ```
210
+
211
+ ```ruby
212
+ # Balrog 2.0
213
+ Balrog::Middleware.setup do |config|
214
+ config.set_password_hash '$2a$12$9lquJW6mVYYS1pD1xYMGzulyC6sEDuLIUfkA/Y7F3RQ8psLNYyLeO'
215
+ config.set_session_expiry 30.minutes
216
+ end
217
+ ```
218
+
148
219
  ## Contributing
149
220
 
150
221
  ### Running the tests
@@ -169,7 +240,6 @@ Before contributing, please read the [code of conduct](CODE_OF_CONDUCT.md).
169
240
  want to have your own version, or is otherwise necessary, that is fine, but
170
241
  please isolate to its own commit so we can cherry-pick around it.
171
242
 
172
-
173
243
  ## TODO
174
244
 
175
245
  * Restricting access via `routes.rb`
@@ -27,8 +27,7 @@ footer {
27
27
  align-self: center;
28
28
  }
29
29
 
30
- button {
31
- -webkit-appearance: button;
30
+ .button_background {
32
31
  overflow: visible;
33
32
  text-transform: none;
34
33
  -webkit-transition-duration: 0.4s;
@@ -39,13 +38,25 @@ button {
39
38
  font-size: 20px;
40
39
  font-family: 'Helvetica Neue', 'Helvetica', Calibri, 'Trebuchet MS', sans-serif;
41
40
  cursor: pointer;
42
- padding: 8px 10px;
43
41
  border: 0;
44
42
  font-weight: 100;
45
43
  letter-spacing: 1px;
44
+ margin: 5px 0px;
45
+ }
46
+
47
+ button {
48
+ -webkit-appearance: button;
49
+ padding: 8px 10px;
50
+ }
51
+
52
+ .sso_button {
53
+ display: inline-block;
54
+ text-decoration: none;
55
+ padding: 8px 0px;
56
+ width: 305.438px;
46
57
  }
47
58
 
48
- button:hover {
59
+ button:hover, .sso_button:hover {
49
60
  background-color: rgb(201, 41, 41);
50
61
  }
51
62
 
@@ -1,6 +1,17 @@
1
1
  <section>
2
- <form action='/balrog/signin' method='POST'>
3
- <input autofocus type='password' name='password' placeholder='Password'>
4
- <button type='submit'>Login</button>
5
- </form>
2
+ <div>
3
+ <% if show_balrog_password_prompt? %>
4
+ <form action='/balrog/signin' method='POST'>
5
+ <input autofocus type='password' name='password' placeholder='Password'>
6
+ <button type='submit' class="button_background">Login</button>
7
+ </form>
8
+ <% end %>
9
+ <% if balrog_omniauth_configured? %>
10
+ <%= button_to(
11
+ "Sign in with SSO",
12
+ "/auth/#{Balrog::Middleware.omniauth_config[:provider]}",
13
+ class: 'button_background sso_button'
14
+ ) %>
15
+ <% end %>
16
+ </div>
6
17
  </section>
@@ -22,4 +22,16 @@ class Balrog::Engine < Rails::Engine
22
22
  balrog/logo.png
23
23
  )
24
24
  end
25
+
26
+ # Insert Balrog into middleware stack.
27
+ initializer "Balrog.middleware", after: :load_config_initializers, before: :build_middleware_stack do |app|
28
+ # If OmniAuth configured, insert OmniAuth into middleware stack.
29
+ omniauth_config = Balrog::Middleware.omniauth_config
30
+ if omniauth_config
31
+ app.middleware.use OmniAuth::Builder do
32
+ provider omniauth_config[:provider], *omniauth_config[:args]
33
+ end
34
+ end
35
+ app.middleware.use Balrog::Middleware
36
+ end
25
37
  end
@@ -6,9 +6,9 @@ class Balrog::InstallGenerator < Rails::Generators::Base
6
6
  def create_initializer_file
7
7
  password_hash = PasswordHasher.encrypt_password
8
8
  contents = <<~EOF
9
- Rails.application.config.middleware.use Balrog::Middleware do
10
- password_hash '#{password_hash}'
11
- set_session_expiry 30.minutes
9
+ Balrog::Middleware.setup do |config|
10
+ config.set_password_hash '#{password_hash}'
11
+ config.set_session_expiry 30.minutes
12
12
  end
13
13
  EOF
14
14
  create_file "config/initializers/balrog.rb", contents
@@ -1,4 +1,5 @@
1
1
  require 'bcrypt'
2
+ require_relative 'middleware/controller'
2
3
 
3
4
  # Public: Balrog middleware that handles form submissions, checking the
4
5
  # password against the configured hash, and setting a session variable if
@@ -7,80 +8,73 @@ require 'bcrypt'
7
8
  # This is typically set up in an initialize when you run
8
9
  # `rails g balrog:install`, and looks a bit like this:
9
10
  #
10
- # Rails.application.config.middleware.use Balrog::Middleware do
11
- # password_hash '<bcrypt hash>'
12
- # end
11
+ # Balrog::Middleware.setup do |config|
12
+ # config.set_password_hash '<bcrypt hash>'
13
+ # end
14
+
13
15
  class Balrog::Middleware
14
- def initialize(app, &block)
16
+ include Controller
17
+
18
+ mattr_reader :password_hash
19
+ mattr_reader :session_length
20
+ mattr_reader :omniauth_config
21
+ mattr_reader :domain_whitelist
22
+
23
+ def initialize(app)
15
24
  @app = app
16
- instance_eval(&block) if block_given?
17
25
  end
18
26
 
19
27
  def call(env)
20
28
  path = env["PATH_INFO"]
21
29
  method = env["REQUEST_METHOD"]
22
- if method == 'POST' && path == '/balrog/signin'
23
- handle_login(env)
24
- elsif method == "DELETE" && path == '/balrog/logout'
25
- handle_logout(env)
30
+ if login_request?(path, method)
31
+ password_login(env)
32
+ elsif omniauth_request?(path, method)
33
+ omniauthentication(env)
34
+ elsif logout_request?(path, method)
35
+ logout(env)
26
36
  else
27
37
  @app.call(env)
28
38
  end
29
39
  end
30
40
 
41
+ def self.setup
42
+ yield self
43
+ end
44
+
31
45
  private
32
46
 
33
- def password_hash(input)
34
- @password_hash = BCrypt::Password.new(input)
47
+ def self.set_password_hash(input)
48
+ @@password_hash = BCrypt::Password.new(input)
35
49
  end
36
50
 
37
- def set_session_expiry(time_period)
38
- @session_length = time_period
51
+ def self.set_omniauth(provider, *args)
52
+ @@omniauth_config = {
53
+ provider: provider,
54
+ args: args
55
+ }
39
56
  end
40
57
 
41
- def handle_login(env)
42
- if env['rack.request.form_hash']
43
- submitted_password = env['rack.request.form_hash']['password']
44
- end
45
-
46
- unless submitted_password
47
- return [302, {"Location" => referer}, [""]]
48
- end
49
-
50
- unless @password_hash
51
- warn <<~EOF
52
-
53
- !! Balrog has not been configured with a password_hash. You shall not
54
- !! pass! When adding Balrog::Middleware to your middleware stack, pass
55
- !! in a block and call `password_hash` passing in a bcrypt hash.
56
- !!
57
- !! Check out https://github.com/pixielabs/balrog for more information.
58
-
59
- EOF
60
- end
61
-
62
- if @password_hash == submitted_password
63
- session_data = { value: 'authenticated' }
64
- add_expiry_date!(session_data)
65
- env['rack.session'][:balrog] = session_data
66
- end
58
+ def self.set_domain_whitelist(*domains)
59
+ @@domain_whitelist = domains
60
+ end
67
61
 
68
- referer = env["HTTP_REFERER"] || '/'
62
+ def self.set_session_expiry(time_period)
63
+ @@session_length = time_period
64
+ end
69
65
 
70
- [302, {"Location" => referer}, [""]]
66
+ def login_request?(path, method)
67
+ method == 'POST' && path == '/balrog/signin'
71
68
  end
72
69
 
73
- def handle_logout(env)
74
- env['rack.session'].delete(:balrog)
75
- [302, {"Location" => '/'}, [""]]
70
+ def omniauth_request?(path, method)
71
+ omniauth_config &&
72
+ method == "GET" &&
73
+ path == "/auth/#{omniauth_config[:provider]}/callback"
76
74
  end
77
75
 
78
- # If the user configured the Balrog session to expire, add the
79
- # expiry_date to the Balrog session.
80
- def add_expiry_date!(session_data)
81
- if @session_length
82
- session_data[:expiry_date] = DateTime.current + @session_length
83
- end
76
+ def logout_request?(path, method)
77
+ method == "DELETE" && path == '/balrog/logout'
84
78
  end
85
79
  end
86
80
 
@@ -0,0 +1,106 @@
1
+ # Methods that are called in response to specific application requests.
2
+ class Balrog::Middleware
3
+ module Controller
4
+
5
+ # This method is called if a user attempts to sign in with a password
6
+ # and will authenticate the user if the password is correct.
7
+ def password_login(env)
8
+ # Extract the submitted_password from the rack request hash.
9
+ if env['rack.request.form_hash']
10
+ submitted_password = env['rack.request.form_hash']['password']
11
+ end
12
+
13
+ # If there is no submitted_password, redirect the user before authentication.
14
+ unless submitted_password
15
+ return [302, {"Location" => referer}, [""]]
16
+ end
17
+
18
+ # If there is no password_hash, alert the developer.
19
+ unless password_hash
20
+ warn <<~EOF
21
+
22
+ !! Balrog has not been configured with a password_hash. You shall not
23
+ !! pass! When adding Balrog::Middleware to your middleware stack, pass
24
+ !! in a block and call `password_hash` passing in a bcrypt hash.
25
+ !!
26
+ !! Check out https://github.com/pixielabs/balrog for more information.
27
+
28
+ EOF
29
+ end
30
+
31
+ # Authenticate the user if the submitted_password matches the password_hash.
32
+ if password_hash == submitted_password
33
+ authenticate_user(env)
34
+ end
35
+
36
+ referer = env["HTTP_REFERER"] || '/'
37
+
38
+ [302, {"Location" => referer}, [""]]
39
+ end
40
+
41
+
42
+ # This method is called if a user attempts to sign in with Single Sign-on
43
+ # and will authenticate the user if email domain has been whitelisted.
44
+ def omniauthentication(env)
45
+ # Extract the email domain from the omniauth hash.
46
+ if env['omniauth.auth']['info']['email']
47
+ user_email = env['omniauth.auth']['info']['email']
48
+ email_domain = user_email.split("@").last
49
+ end
50
+
51
+ # If there is no email domain, redirect the user before authentication.
52
+ unless email_domain
53
+ return [302, {"Location" => referer}, [""]]
54
+ end
55
+
56
+ # If there is no domain_whitelist, alert the developer.
57
+ unless domain_whitelist
58
+ warn <<~EOF
59
+
60
+ !! Balrog has not been configured with a domain_whitelist. You shall not
61
+ !! pass! When setting up Balrog::Middleware, pass in a block and
62
+ !! call `set_domain_whitelist` passing in an omniauth provider and
63
+ !! required keys.
64
+ !!
65
+ !! Check out https://github.com/pixielabs/balrog for more information.
66
+
67
+ EOF
68
+ return [302, {"Location" => referer}, [""]]
69
+ end
70
+
71
+ # Authenticate the user if the user's email domain is whitelisted.
72
+ if domain_whitelist.include?(email_domain)
73
+ authenticate_user(env)
74
+ end
75
+
76
+ referer = env["omniauth.origin"] || '/'
77
+
78
+ [302, {"Location" => referer}, [""]]
79
+ end
80
+
81
+
82
+ # This method is called if a user logs out using a balrog logout button.
83
+ # It will achieve this by removing all balrog data from the session.
84
+ def logout(env)
85
+ env['rack.session'].delete(:balrog)
86
+ [302, {"Location" => '/'}, [""]]
87
+ end
88
+
89
+ private
90
+
91
+ # This method marks the user as 'authenicated'.
92
+ def authenticate_user(env)
93
+ session_data = { value: 'authenticated' }
94
+ add_expiry_date!(session_data)
95
+ env['rack.session'][:balrog] = session_data
96
+ end
97
+
98
+ # If the user configured the Balrog session to expire, add the
99
+ # expiry_date to the Balrog session.
100
+ def add_expiry_date!(session_data)
101
+ if session_length
102
+ session_data[:expiry_date] = DateTime.current + session_length
103
+ end
104
+ end
105
+ end
106
+ end
@@ -1,3 +1,3 @@
1
1
  module Balrog
2
- VERSION = "1.1.0"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -1,4 +1,4 @@
1
- # ViewHelpers methods are made available in all controllers by the code in engine.rb.
1
+ # ViewHelpers methods are made available in all views by the code in engine.rb.
2
2
  module Balrog::ViewHelpers
3
3
  def balrog_logout_button(options = nil, html_options = nil)
4
4
  name = 'Logout'
@@ -13,4 +13,12 @@ module Balrog::ViewHelpers
13
13
 
14
14
  button_to(name, '/balrog/logout', html_options)
15
15
  end
16
+
17
+ def show_balrog_password_prompt?
18
+ !!Balrog::Middleware.password_hash || !Balrog::Middleware.omniauth_config
19
+ end
20
+
21
+ def balrog_omniauth_configured?
22
+ !!Balrog::Middleware.omniauth_config
23
+ end
16
24
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: balrog
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pixie Labs
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-11-18 00:00:00.000000000 Z
11
+ date: 2019-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bcrypt
@@ -114,6 +114,7 @@ files:
114
114
  - lib/balrog/guard.rb
115
115
  - lib/balrog/helpers.rb
116
116
  - lib/balrog/middleware.rb
117
+ - lib/balrog/middleware/controller.rb
117
118
  - lib/balrog/password_hasher.rb
118
119
  - lib/balrog/rake_tasks.rb
119
120
  - lib/balrog/routes_middleware.rb