balrog 0.1.0 → 2.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d547df9399a56595525e88ceedc04c00d5cb8f85cee27416207936a073c7dd1a
4
- data.tar.gz: '075180b9db86f64cc1f158d03e9b7207979b9cf0641af2d849f1cdc64b7117b8'
3
+ metadata.gz: c908a9916866b576a2914041573fed0ae64555a1956b3a94a3c36e37476e7cf6
4
+ data.tar.gz: 814d65c1b25cfe3e3a561daed8091643694ce81a70eb543eaa40c09939689183
5
5
  SHA512:
6
- metadata.gz: 8b2232fc4ef9d7f6f520e6b38f9d495d192258429451fe664ba526f49859f089682219b998dcc0ae5f1d3170cb3943a048a5638475d7869162f2ed63b44f3612
7
- data.tar.gz: b7a2f314a0b1cd7afd0ff667c2d6799f760ac0c91be8e1f204c108a2a10ffb09df9f99aa594e161168782f03926c95ae012f02b508c1e2fe62bd88e2b8169fc3
6
+ metadata.gz: 58872926e04fd6bafd1bdb12915d5a3e0aad33a7e79a82ac074cad45bd8da011cd1f760aae6cb6a8035fd2f1beb2d4a6f16cc2b2c4e3a1ec78718eef4f61b732
7
+ data.tar.gz: b1c7fd33821672a9f59818fe5a543fca3c5d14221d1fac6ff23b5566ec53e63cc2d8f07323057793094edbb7bf5673c72f64bd802cc97febda0973eb5169e7d3
@@ -0,0 +1,56 @@
1
+ version: 2.1
2
+
3
+ jobs:
4
+ build:
5
+ docker:
6
+ - image: circleci/ruby:2.6.2-node-browsers
7
+ - image: circleci/redis:5.0.4
8
+
9
+ working_directory: ~/repo/spec/dummy-rails-app
10
+
11
+ steps:
12
+ - checkout:
13
+ path: ~/repo
14
+
15
+ # Download and cache dependencies
16
+ - restore_cache:
17
+ name: Restore Rubygems cache
18
+ keys:
19
+ - v1-rubygems-{{ checksum "Gemfile.lock" }}
20
+ # fallback to using the latest cache if no exact match is found
21
+ - v1-rubygems-
22
+
23
+ - run:
24
+ name: Install bundler
25
+ command: |
26
+ gem install bundler:2.0.1
27
+ - run:
28
+ name: Install Ruby dependencies
29
+ command: |
30
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
31
+
32
+ - save_cache:
33
+ name: Save Rubygems cache
34
+ paths:
35
+ - ./vendor/bundle
36
+ key: v1-rubygems-{{ checksum "Gemfile.lock" }}
37
+
38
+ # run tests!
39
+ - run:
40
+ name: run tests
41
+ command: |
42
+ mkdir /tmp/test-results
43
+ TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"
44
+
45
+ bundle exec rspec --format progress \
46
+ --format RspecJunitFormatter \
47
+ --out /tmp/test-results/rspec.xml \
48
+ --format progress \
49
+ $TEST_FILES
50
+
51
+ # collect reports
52
+ - store_test_results:
53
+ path: /tmp/test-results
54
+ - store_artifacts:
55
+ path: /tmp/test-results
56
+ destination: test-results
data/.gitignore CHANGED
@@ -11,3 +11,5 @@
11
11
  .rspec_status
12
12
 
13
13
  .DS_Store
14
+
15
+ **/dump.rdb
@@ -0,0 +1,27 @@
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
+
11
+ # 1.1.0
12
+
13
+ - added `Balrog::Middleware#set_session_expiry`, which would force end users to login again after a certain period of time.
14
+ - added `balrog:view` generator, enabling users to modify their Balrog gate view.
15
+
16
+ # 1.0.0
17
+
18
+ - added `Balrog::RoutesMiddleware` module, which can be used to protect mounted Rack applications.
19
+ - dropped support for Rails < 5.
20
+
21
+ # 0.2.0
22
+
23
+ - added `balrog_logout_button` view helper method.
24
+
25
+ # 0.1.0
26
+
27
+ - initial release.
@@ -1,28 +1,151 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- balrog (0.1.0)
4
+ balrog (2.0.1)
5
5
  bcrypt (~> 3.0)
6
+ rails (>= 5)
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
10
- bcrypt (3.1.12)
11
+ actioncable (6.0.2.2)
12
+ actionpack (= 6.0.2.2)
13
+ nio4r (~> 2.0)
14
+ websocket-driver (>= 0.6.1)
15
+ actionmailbox (6.0.2.2)
16
+ actionpack (= 6.0.2.2)
17
+ activejob (= 6.0.2.2)
18
+ activerecord (= 6.0.2.2)
19
+ activestorage (= 6.0.2.2)
20
+ activesupport (= 6.0.2.2)
21
+ mail (>= 2.7.1)
22
+ actionmailer (6.0.2.2)
23
+ actionpack (= 6.0.2.2)
24
+ actionview (= 6.0.2.2)
25
+ activejob (= 6.0.2.2)
26
+ mail (~> 2.5, >= 2.5.4)
27
+ rails-dom-testing (~> 2.0)
28
+ actionpack (6.0.2.2)
29
+ actionview (= 6.0.2.2)
30
+ activesupport (= 6.0.2.2)
31
+ rack (~> 2.0, >= 2.0.8)
32
+ rack-test (>= 0.6.3)
33
+ rails-dom-testing (~> 2.0)
34
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
35
+ actiontext (6.0.2.2)
36
+ actionpack (= 6.0.2.2)
37
+ activerecord (= 6.0.2.2)
38
+ activestorage (= 6.0.2.2)
39
+ activesupport (= 6.0.2.2)
40
+ nokogiri (>= 1.8.5)
41
+ actionview (6.0.2.2)
42
+ activesupport (= 6.0.2.2)
43
+ builder (~> 3.1)
44
+ erubi (~> 1.4)
45
+ rails-dom-testing (~> 2.0)
46
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
47
+ activejob (6.0.2.2)
48
+ activesupport (= 6.0.2.2)
49
+ globalid (>= 0.3.6)
50
+ activemodel (6.0.2.2)
51
+ activesupport (= 6.0.2.2)
52
+ activerecord (6.0.2.2)
53
+ activemodel (= 6.0.2.2)
54
+ activesupport (= 6.0.2.2)
55
+ activestorage (6.0.2.2)
56
+ actionpack (= 6.0.2.2)
57
+ activejob (= 6.0.2.2)
58
+ activerecord (= 6.0.2.2)
59
+ marcel (~> 0.3.1)
60
+ activesupport (6.0.2.2)
61
+ concurrent-ruby (~> 1.0, >= 1.0.2)
62
+ i18n (>= 0.7, < 2)
63
+ minitest (~> 5.1)
64
+ tzinfo (~> 1.1)
65
+ zeitwerk (~> 2.2)
66
+ bcrypt (3.1.13)
67
+ builder (3.2.4)
68
+ concurrent-ruby (1.1.6)
69
+ crass (1.0.6)
11
70
  diff-lcs (1.3)
71
+ erubi (1.9.0)
72
+ globalid (0.4.2)
73
+ activesupport (>= 4.2.0)
74
+ i18n (1.8.2)
75
+ concurrent-ruby (~> 1.0)
76
+ loofah (2.5.0)
77
+ crass (~> 1.0.2)
78
+ nokogiri (>= 1.5.9)
79
+ mail (2.7.1)
80
+ mini_mime (>= 0.1.1)
81
+ marcel (0.3.3)
82
+ mimemagic (~> 0.3.2)
83
+ method_source (1.0.0)
84
+ mimemagic (0.3.4)
85
+ mini_mime (1.0.2)
86
+ mini_portile2 (2.4.0)
87
+ minitest (5.14.0)
88
+ nio4r (2.5.2)
89
+ nokogiri (1.10.9)
90
+ mini_portile2 (~> 2.4.0)
91
+ rack (2.2.2)
92
+ rack-test (1.1.0)
93
+ rack (>= 1.0, < 3)
94
+ rails (6.0.2.2)
95
+ actioncable (= 6.0.2.2)
96
+ actionmailbox (= 6.0.2.2)
97
+ actionmailer (= 6.0.2.2)
98
+ actionpack (= 6.0.2.2)
99
+ actiontext (= 6.0.2.2)
100
+ actionview (= 6.0.2.2)
101
+ activejob (= 6.0.2.2)
102
+ activemodel (= 6.0.2.2)
103
+ activerecord (= 6.0.2.2)
104
+ activestorage (= 6.0.2.2)
105
+ activesupport (= 6.0.2.2)
106
+ bundler (>= 1.3.0)
107
+ railties (= 6.0.2.2)
108
+ sprockets-rails (>= 2.0.0)
109
+ rails-dom-testing (2.0.3)
110
+ activesupport (>= 4.2.0)
111
+ nokogiri (>= 1.6)
112
+ rails-html-sanitizer (1.3.0)
113
+ loofah (~> 2.3)
114
+ railties (6.0.2.2)
115
+ actionpack (= 6.0.2.2)
116
+ activesupport (= 6.0.2.2)
117
+ method_source
118
+ rake (>= 0.8.7)
119
+ thor (>= 0.20.3, < 2.0)
12
120
  rake (10.5.0)
13
- rspec (3.8.0)
14
- rspec-core (~> 3.8.0)
15
- rspec-expectations (~> 3.8.0)
16
- rspec-mocks (~> 3.8.0)
17
- rspec-core (3.8.0)
18
- rspec-support (~> 3.8.0)
19
- rspec-expectations (3.8.2)
121
+ rspec (3.9.0)
122
+ rspec-core (~> 3.9.0)
123
+ rspec-expectations (~> 3.9.0)
124
+ rspec-mocks (~> 3.9.0)
125
+ rspec-core (3.9.0)
126
+ rspec-support (~> 3.9.0)
127
+ rspec-expectations (3.9.0)
20
128
  diff-lcs (>= 1.2.0, < 2.0)
21
- rspec-support (~> 3.8.0)
22
- rspec-mocks (3.8.0)
129
+ rspec-support (~> 3.9.0)
130
+ rspec-mocks (3.9.0)
23
131
  diff-lcs (>= 1.2.0, < 2.0)
24
- rspec-support (~> 3.8.0)
25
- rspec-support (3.8.0)
132
+ rspec-support (~> 3.9.0)
133
+ rspec-support (3.9.0)
134
+ sprockets (4.0.0)
135
+ concurrent-ruby (~> 1.0)
136
+ rack (> 1, < 3)
137
+ sprockets-rails (3.2.1)
138
+ actionpack (>= 4.0)
139
+ activesupport (>= 4.0)
140
+ sprockets (>= 3.0.0)
141
+ thor (1.0.1)
142
+ thread_safe (0.3.6)
143
+ tzinfo (1.2.7)
144
+ thread_safe (~> 0.1)
145
+ websocket-driver (0.7.1)
146
+ websocket-extensions (>= 0.1.0)
147
+ websocket-extensions (0.1.4)
148
+ zeitwerk (2.3.0)
26
149
 
27
150
  PLATFORMS
28
151
  ruby
data/README.md CHANGED
@@ -2,22 +2,35 @@
2
2
 
3
3
  ![Balrog logo](https://user-images.githubusercontent.com/32128719/55335192-9566a000-5492-11e9-9449-746de68fbe94.png)
4
4
 
5
- Balrog is a lightweight authorization library for Ruby on Rails that can
6
- protect your routes with a single username & password combination.
7
-
8
- Balrog is an alternative to `http_basic_authentication_with` that provides some
9
- advantages:
10
-
11
- * Uses a password hash instead of a plaintext password.
12
- * Provides a lightweight HTML form instead of inconsistent basic
13
- authentication.
14
- * Better support for password managers (which often don't support basic
15
- authentication dialog boxes).
16
-
17
- ## Requirements
18
-
19
- Balrog is designed to be used with Ruby on Rails applications, and has been
20
- tested against Ruby on Rails 5.
5
+ [![Gem Version](https://badge.fury.io/rb/balrog.svg)](https://badge.fury.io/rb/balrog)
6
+ [![CircleCI](https://circleci.com/gh/pixielabs/balrog.svg?style=svg)](https://circleci.com/gh/pixielabs/balrog)
7
+
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. 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)
21
34
 
22
35
  ## Installation
23
36
 
@@ -29,10 +42,10 @@ gem 'balrog'
29
42
 
30
43
  Run the installer to generate an initializer:
31
44
 
32
- ```
45
+ ```shell
33
46
  $ bundle exec rails generate balrog:install
34
- Enter New Password:
35
- Confirm New Password:
47
+ Enter New Password:
48
+ Confirm New Password:
36
49
  create config/initializers/balrog.rb
37
50
  $
38
51
  ```
@@ -60,8 +73,169 @@ class AdminController < ApplicationController
60
73
  end
61
74
  ```
62
75
 
76
+ ## Restricting access to mounted Rack applications within config/routes.rb
77
+
78
+ Use the `.use` [method](https://www.rubydoc.info/gems/rack/Rack%2FBuilder:use) to add Balrog to the 'stack'.
79
+
80
+ For example with Sidekiq::Web...
81
+
82
+ ```ruby
83
+ # Then we tell SideKiq to use Balrog::RoutesMiddleware
84
+ Sidekiq::Web.use Balrog::RoutesMiddleware
85
+
86
+ mount Sidekiq::Web => '/sidekiq'
87
+ ```
88
+
89
+ N.B. If you are mounting Sidekiq Web, you need to [disable Sidekiq Web's session in config/initializers/sidekiq.rb](https://github.com/mperham/sidekiq/issues/3377#issuecomment-381254940).
90
+
91
+ ```ruby
92
+ require 'sidekiq/web'
93
+
94
+ # In order to force sidekiq to use the rails app's session,
95
+ # we need to disable the Sidekiq's session.
96
+ Sidekiq::Web.disable(:sessions)
97
+ ```
98
+
99
+ ## Logout button
100
+
101
+ To add a logout button, you can call the `balrog_logout_button` view helper
102
+ method and pass in a hash of HTML options to style it. After logout, the user
103
+ will be redirected to the root of the app.
104
+
105
+ For example, in your view:
106
+
107
+ ```erb
108
+ <ul class='nav'>
109
+ <li>....</li>
110
+ <li><%= balrog_logout_button 'Admin Logout' %></li>
111
+ <li>....</li>
112
+ </ul>
113
+ ```
114
+
115
+ Other usage examples:
116
+
117
+ ```erb
118
+ <%= balrog_logout_button %>
119
+ <%= balrog_logout_button "Leave this place" %>
120
+ <%= balrog_logout_button "Click me", class: 'fancy-button--with-custom-text' %>
121
+ <%= balrog_logout_button class: 'fancy-button--with-default-text' %>
122
+ ```
123
+
124
+ ## Changing session expiry length
125
+
126
+ `set_session_expiry` requires the user to login again after a period of time.
127
+ To customise this value, open `config/initializers/balrog.rb` after running `balrog:install`
128
+ and change the argument being passed to `set_session_expiry`.
129
+
130
+ The argument passed to `set_session_expiry` can be any of the
131
+ [Rails time extensions](https://api.rubyonrails.org/classes/Numeric.html).
132
+
133
+ If you don't want sessions to expire, remove `set_session_expiry`
134
+ from the initializer completely.
135
+
136
+ ```ruby
137
+ Balrog::Middleware.setup do |config|
138
+ config.password_hash '$2a$12$BLz7XCFdG9YfwL64KlTgY.T3FY55aQk8SZEzHfpHfw15F2uN1kuSi'
139
+ config.set_session_expiry 30.minutes
140
+ end
141
+ ```
142
+
143
+ ## Configuring the Balrog gate view
144
+
145
+ We built Balrog to have a default view and stylesheet so that you can drop
146
+ Balrog into your project and everything should “just work”.
147
+ However, we don't want to be in your way if you needed to customise
148
+ your Balrog gate view.
149
+
150
+ If you want to customise the Balrog view, you can run the `balrog:view`
151
+ generator, which will copy the required view and layout to your application:
152
+
153
+ ```shell
154
+ $ rails generate balrog:view
155
+ ```
156
+
157
+ After running the generator, you can now add elements and classes to the
158
+ `views/balrog/gate.html.erb`, add styles to the
159
+ `assets/stylesheets/application.css` and import the application stylesheet in
160
+ `app/views/layouts/balrog.html.erb`. For an example, see the
161
+ [dummy-rails-app](https://github.com/pixielabs/balrog/tree/master/spec/dummy-rails-app) in the spec folder.
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
+
63
219
  ## Contributing
64
220
 
221
+ ### Running the tests
222
+
223
+ Tests are part of the dummy Rails app within the spec folder. To run the tests:
224
+
225
+ ```
226
+ $ cd spec/dummy-rails-app
227
+ $ bundle
228
+ $ rails generate active_record:session_migration
229
+ $ redis-server
230
+ ```
231
+
232
+ Then in a different terminal:
233
+
234
+ ```
235
+ $ cd spec/dummy-rails-app
236
+ $ rspec
237
+ ```
238
+
65
239
  Before contributing, please read the [code of conduct](CODE_OF_CONDUCT.md).
66
240
  - Check out the latest master to make sure the feature hasn't been implemented
67
241
  or the bug hasn't been fixed yet.
@@ -74,11 +248,7 @@ Before contributing, please read the [code of conduct](CODE_OF_CONDUCT.md).
74
248
  want to have your own version, or is otherwise necessary, that is fine, but
75
249
  please isolate to its own commit so we can cherry-pick around it.
76
250
 
77
-
78
251
  ## TODO
79
252
 
80
253
  * Restricting access via `routes.rb`
81
- * Logout
82
254
  * Test coverage
83
- * Check it's OK with Ruby on Rails 6
84
- * Expire sessions
@@ -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,26 +1,17 @@
1
- <!doctype html>
2
- <html lang=en>
3
- <head>
4
- <meta charset=utf-8>
5
- <title>Login</title>
6
- <%= stylesheet_link_tag "balrog/gate" %>
7
- </head>
8
-
9
- <body>
10
-
11
- <section>
12
- <form action='/balrog/signin' method='POST'>
13
- <input autofocus type='password' name='password' placeholder='Password'>
14
- <button type='submit'>Login</button>
15
- </form>
16
- </section>
17
-
18
- <footer>
19
- <a href="https://github.com/pixielabs/balrog" target="_blank">
20
- <%= image_tag "balrog/logo.png", class: 'logo' %>
21
- </a>
22
- </footer>
23
-
24
- </body>
25
-
26
- </html>
1
+ <section>
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>
17
+ </section>
@@ -0,0 +1,22 @@
1
+ <!doctype html>
2
+ <html lang=en>
3
+ <head>
4
+ <meta charset=utf-8>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Login</title>
7
+ <%= stylesheet_link_tag "balrog/gate" %>
8
+ </head>
9
+
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ <footer>
15
+ <a href="https://github.com/pixielabs/balrog" target="_blank">
16
+ <%= image_tag "balrog/logo.png", class: 'logo' %>
17
+ </a>
18
+ </footer>
19
+
20
+ </body>
21
+
22
+ </html>
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.require_paths = ["lib"]
25
25
 
26
26
  spec.add_dependency "bcrypt", "~> 3.0"
27
+ spec.add_dependency "rails", ">=5"
27
28
 
28
29
  spec.add_development_dependency "bundler", "~> 2.0"
29
30
  spec.add_development_dependency "rake", "~> 10.0"
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "bundler/setup"
4
- require "balrog"
4
+ require_relative "../lib/balrog"
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
@@ -1,6 +1,9 @@
1
- require_relative "balrog/version"
1
+ require 'rails'
2
+
2
3
  module Balrog
4
+ require_relative 'balrog/version'
3
5
  require_relative 'balrog/middleware'
6
+ require_relative 'balrog/routes_middleware'
4
7
  require_relative 'balrog/engine'
5
8
  require_relative 'balrog/rake_tasks'
6
9
  require_relative 'balrog/generators'
@@ -1,13 +1,20 @@
1
- require_relative 'helpers'
2
-
3
1
  class Balrog::Engine < Rails::Engine
4
- # Make the Balrog helpers available in any controller.
5
- initializer "balrog.configure_rails_initialization" do
2
+ # Make authenticate_with_balrog! available.
3
+ initializer "balrog.action_controller" do
6
4
  ActiveSupport.on_load(:action_controller) do
5
+ require_relative 'helpers'
7
6
  include Balrog::Helpers
8
7
  end
9
8
  end
10
9
 
10
+ # Add balrog_logout_button as a global view helper.
11
+ initializer "balrog.action_view" do
12
+ ActiveSupport.on_load(:action_view) do
13
+ require_relative 'view_helpers'
14
+ include Balrog::ViewHelpers
15
+ end
16
+ end
17
+
11
18
  # Precompile the Balrog assets
12
19
  initializer "balrog.assets.precompile" do |app|
13
20
  app.config.assets.precompile += %w(
@@ -15,4 +22,16 @@ class Balrog::Engine < Rails::Engine
15
22
  balrog/logo.png
16
23
  )
17
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
18
37
  end
@@ -1,6 +1,6 @@
1
1
  # This Railtie makes the Balrog Generators available from the command line.
2
2
  class Balrog::Generators < Rails::Railtie
3
3
  generators do
4
- require File.join(File.dirname(__FILE__), 'generators', 'install_generator')
4
+ Dir[File.join(__dir__, 'generators', '*.rb')].each { |file| require file }
5
5
  end
6
6
  end
@@ -6,8 +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}'
9
+ Balrog::Middleware.setup do |config|
10
+ config.set_password_hash '#{password_hash}'
11
+ config.set_session_expiry 30.minutes
11
12
  end
12
13
  EOF
13
14
  create_file "config/initializers/balrog.rb", contents
@@ -0,0 +1,25 @@
1
+ class Balrog::ViewGenerator < Rails::Generators::Base
2
+
3
+ desc "Copies the Balrog gate view and layout into your application, where you can edit and style them."
4
+ def copy_gate_view
5
+ gate_view = File.open(
6
+ File.join(__dir__, '../../../', 'app/views/balrog/gate.html.erb'),
7
+ 'r')
8
+
9
+ content = gate_view.read
10
+ gate_view.close
11
+
12
+ create_file "app/views/balrog/gate.html.erb", content
13
+ end
14
+
15
+ def copy_layout
16
+ gate_view = File.open(
17
+ File.join(__dir__, '../../../', 'app/views/layouts/balrog.html.erb'),
18
+ 'r')
19
+
20
+ content = gate_view.read
21
+ gate_view.close
22
+
23
+ create_file "app/views/layouts/balrog.html.erb", content
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ # Contains authentication logic to check the user has been authenticated,
2
+ # and that the session hasn't expired.
3
+ module Balrog::Guard
4
+ def authenticated?(balrog_session)
5
+ @balrog_session = balrog_session&.with_indifferent_access
6
+ previously_authenticated? && still_valid?
7
+ end
8
+
9
+ private
10
+
11
+ # A method to check that the user has been authenticated before.
12
+ def previously_authenticated?
13
+ return false unless @balrog_session
14
+ @balrog_session[:value] == 'authenticated'
15
+ end
16
+
17
+ # A method to check that the authentication has not expired.
18
+ def still_valid?
19
+ # If the user did not set configured the Balrog session
20
+ # to expire, the cookie is valid.
21
+ return true unless @balrog_session[:expiry_date]
22
+ DateTime.current < @balrog_session[:expiry_date]
23
+ end
24
+ end
@@ -1,8 +1,12 @@
1
+ require_relative 'guard'
2
+
1
3
  # Helpers methods are made available in all controllers by the code in engine.rb.
2
4
  module Balrog::Helpers
5
+ include Balrog::Guard
6
+
3
7
  def authenticate_with_balrog!
4
- unless session[:balrog] == 'authenticated'
5
- render 'balrog/gate', layout: nil
8
+ unless authenticated?(session[:balrog])
9
+ render 'balrog/gate', layout: 'balrog'
6
10
  end
7
11
  end
8
12
  end
@@ -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,60 +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)
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)
24
36
  else
25
37
  @app.call(env)
26
38
  end
27
39
  end
28
40
 
29
- private
30
-
31
- def password_hash(input)
32
- @password_hash = BCrypt::Password.new(input)
41
+ def self.setup
42
+ yield self
33
43
  end
34
44
 
35
- def handle_login(env)
36
- if env['rack.request.form_hash']
37
- submitted_password = env['rack.request.form_hash']['password']
38
- end
39
-
40
- unless submitted_password
41
- return [302, {"Location" => referer}, [""]]
42
- end
45
+ private
43
46
 
44
- unless @password_hash
45
- warn <<~EOF
47
+ def self.set_password_hash(input)
48
+ @@password_hash = BCrypt::Password.new(input)
49
+ end
46
50
 
47
- !! Balrog has not been configured with a password_hash. You shall not
48
- !! pass! When adding Balrog::Middleware to your middleware stack, pass
49
- !! in a block and call `password_hash` passing in a bcrypt hash.
50
- !!
51
- !! Check out https://github.com/pixielabs/balrog for more information.
51
+ def self.set_omniauth(provider, *args)
52
+ @@omniauth_config = {
53
+ provider: provider,
54
+ args: args
55
+ }
56
+ end
52
57
 
53
- EOF
54
- end
58
+ def self.set_domain_whitelist(*domains)
59
+ @@domain_whitelist = domains
60
+ end
55
61
 
56
- if @password_hash == submitted_password
57
- env['rack.session'][:balrog] = 'authenticated'
58
- end
62
+ def self.set_session_expiry(time_period)
63
+ @@session_length = time_period
64
+ end
59
65
 
60
- referer = env["HTTP_REFERER"] || '/'
66
+ def login_request?(path, method)
67
+ method == 'POST' && path == '/balrog/signin'
68
+ end
61
69
 
62
- [302, {"Location" => referer}, [""]]
70
+ def omniauth_request?(path, method)
71
+ omniauth_config &&
72
+ method == "GET" &&
73
+ path == "/auth/#{omniauth_config[:provider]}/callback"
63
74
  end
64
75
 
76
+ def logout_request?(path, method)
77
+ method == "DELETE" && path == '/balrog/logout'
78
+ end
65
79
  end
66
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
@@ -0,0 +1,30 @@
1
+ require_relative 'guard'
2
+
3
+ # Public: Balrog routes middleware that redirects the user to a security
4
+ # gate unless the session includes { 'balrog' => 'authenticated' }.
5
+ #
6
+ # In order to protect SideKiq Web you would do something like this:
7
+ #
8
+ # require 'sidekiq/web'
9
+ #
10
+ # Sidekiq::Web.disable(:sessions)
11
+ # Sidekiq::Web.use Balrog::RoutesMiddleware
12
+ #
13
+ # mount Sidekiq::Web => '/sidekiq'
14
+
15
+ class Balrog::RoutesMiddleware
16
+ include Balrog::Guard
17
+
18
+ def initialize(app)
19
+ @app = app
20
+ end
21
+
22
+ def call(env)
23
+ unless authenticated?(env['rack.session']['balrog'])
24
+ html = ApplicationController.renderer.render 'balrog/gate', layout: 'balrog'
25
+ return [200, {"Content-Type" => "text/html"}, [html]]
26
+ end
27
+ @app.call(env)
28
+ end
29
+ end
30
+
@@ -1,3 +1,3 @@
1
1
  module Balrog
2
- VERSION = "0.1.0"
2
+ VERSION = "2.0.1"
3
3
  end
@@ -0,0 +1,24 @@
1
+ # ViewHelpers methods are made available in all views by the code in engine.rb.
2
+ module Balrog::ViewHelpers
3
+ def balrog_logout_button(options = nil, html_options = nil)
4
+ name = 'Logout'
5
+ html_options ||= {}
6
+ html_options[:method] = 'delete'
7
+
8
+ if options.is_a?(String)
9
+ name = options
10
+ elsif options.is_a?(Hash)
11
+ html_options = html_options.merge(options)
12
+ end
13
+
14
+ button_to(name, '/balrog/logout', html_options)
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
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: 0.1.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pixie Labs
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-01 00:00:00.000000000 Z
11
+ date: 2020-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bcrypt
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '5'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '5'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -74,9 +88,11 @@ executables: []
74
88
  extensions: []
75
89
  extra_rdoc_files: []
76
90
  files:
91
+ - ".circleci/config.yml"
77
92
  - ".gitignore"
78
93
  - ".rspec"
79
94
  - ".travis.yml"
95
+ - CHANGELOG.md
80
96
  - CODE_OF_CONDUCT.md
81
97
  - Gemfile
82
98
  - Gemfile.lock
@@ -86,6 +102,7 @@ files:
86
102
  - app/assets/images/balrog/logo.png
87
103
  - app/assets/stylesheets/balrog/gate.css
88
104
  - app/views/balrog/gate.html.erb
105
+ - app/views/layouts/balrog.html.erb
89
106
  - balrog.gemspec
90
107
  - bin/console
91
108
  - bin/setup
@@ -93,17 +110,22 @@ files:
93
110
  - lib/balrog/engine.rb
94
111
  - lib/balrog/generators.rb
95
112
  - lib/balrog/generators/install_generator.rb
113
+ - lib/balrog/generators/view_generator.rb
114
+ - lib/balrog/guard.rb
96
115
  - lib/balrog/helpers.rb
97
116
  - lib/balrog/middleware.rb
117
+ - lib/balrog/middleware/controller.rb
98
118
  - lib/balrog/password_hasher.rb
99
119
  - lib/balrog/rake_tasks.rb
120
+ - lib/balrog/routes_middleware.rb
100
121
  - lib/balrog/tasks/generate_hash.rake
101
122
  - lib/balrog/version.rb
123
+ - lib/balrog/view_helpers.rb
102
124
  homepage: https://github.com/pixielabs/balrog
103
125
  licenses:
104
126
  - MIT
105
127
  metadata: {}
106
- post_install_message:
128
+ post_install_message:
107
129
  rdoc_options: []
108
130
  require_paths:
109
131
  - lib
@@ -118,8 +140,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
118
140
  - !ruby/object:Gem::Version
119
141
  version: '0'
120
142
  requirements: []
121
- rubygems_version: 3.0.2
122
- signing_key:
143
+ rubygems_version: 3.0.3
144
+ signing_key:
123
145
  specification_version: 4
124
146
  summary: An alternative to HTTP basic auth
125
147
  test_files: []