balrog 0.1.0 → 2.0.1

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: 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: []