keycloak_rack 1.0.0
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 +7 -0
- data/.github/workflows/main.yml +68 -0
- data/.gitignore +8 -0
- data/.rspec +2 -0
- data/.rubocop.yml +220 -0
- data/.ruby-version +1 -0
- data/.yardopts +7 -0
- data/Appraisals +16 -0
- data/CHANGELOG.md +10 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/Gemfile +5 -0
- data/LICENSE +19 -0
- data/README.md +288 -0
- data/Rakefile +10 -0
- data/bin/appraisal +29 -0
- data/bin/console +6 -0
- data/bin/fix-appraisals +14 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/bin/rubocop +29 -0
- data/bin/yard +29 -0
- data/bin/yardoc +29 -0
- data/bin/yri +29 -0
- data/gemfiles/rack_only.gemfile +5 -0
- data/gemfiles/rack_only.gemfile.lock +204 -0
- data/gemfiles/rails_6_0.gemfile +9 -0
- data/gemfiles/rails_6_0.gemfile.lock +323 -0
- data/gemfiles/rails_6_1.gemfile +9 -0
- data/gemfiles/rails_6_1.gemfile.lock +326 -0
- data/keycloak_rack.gemspec +56 -0
- data/lib/keycloak_rack.rb +59 -0
- data/lib/keycloak_rack/authenticate.rb +115 -0
- data/lib/keycloak_rack/authorize_realm.rb +53 -0
- data/lib/keycloak_rack/authorize_resource.rb +54 -0
- data/lib/keycloak_rack/config.rb +84 -0
- data/lib/keycloak_rack/container.rb +53 -0
- data/lib/keycloak_rack/decoded_token.rb +191 -0
- data/lib/keycloak_rack/flexible_struct.rb +20 -0
- data/lib/keycloak_rack/http_client.rb +86 -0
- data/lib/keycloak_rack/import.rb +9 -0
- data/lib/keycloak_rack/key_fetcher.rb +20 -0
- data/lib/keycloak_rack/key_resolver.rb +64 -0
- data/lib/keycloak_rack/middleware.rb +132 -0
- data/lib/keycloak_rack/railtie.rb +14 -0
- data/lib/keycloak_rack/read_token.rb +40 -0
- data/lib/keycloak_rack/resource_role_map.rb +8 -0
- data/lib/keycloak_rack/role_map.rb +15 -0
- data/lib/keycloak_rack/session.rb +44 -0
- data/lib/keycloak_rack/skip_authentication.rb +44 -0
- data/lib/keycloak_rack/types.rb +42 -0
- data/lib/keycloak_rack/version.rb +6 -0
- data/lib/keycloak_rack/with_config.rb +15 -0
- data/spec/dummy/.ruby-version +1 -0
- data/spec/dummy/README.md +24 -0
- data/spec/dummy/Rakefile +8 -0
- data/spec/dummy/app/controllers/application_controller.rb +22 -0
- data/spec/dummy/app/controllers/test_controller.rb +9 -0
- data/spec/dummy/config.ru +8 -0
- data/spec/dummy/config/application.rb +52 -0
- data/spec/dummy/config/boot.rb +3 -0
- data/spec/dummy/config/environment.rb +7 -0
- data/spec/dummy/config/environments/development.rb +51 -0
- data/spec/dummy/config/environments/test.rb +51 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +9 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +10 -0
- data/spec/dummy/config/initializers/cors.rb +17 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +8 -0
- data/spec/dummy/config/initializers/inflections.rb +17 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +11 -0
- data/spec/dummy/config/keycloak.yml +12 -0
- data/spec/dummy/config/locales/en.yml +33 -0
- data/spec/dummy/config/routes.rb +5 -0
- data/spec/dummy/public/robots.txt +1 -0
- data/spec/dummy/tmp/development_secret.txt +1 -0
- data/spec/factories/decoded_token.rb +18 -0
- data/spec/factories/session.rb +21 -0
- data/spec/factories/token_payload.rb +40 -0
- data/spec/keycloak_rack/authorize_realm_spec.rb +15 -0
- data/spec/keycloak_rack/authorize_resource_spec.rb +19 -0
- data/spec/keycloak_rack/decoded_token_spec.rb +31 -0
- data/spec/keycloak_rack/key_resolver_spec.rb +95 -0
- data/spec/keycloak_rack/middleware_spec.rb +172 -0
- data/spec/keycloak_rack/rails_integration_spec.rb +43 -0
- data/spec/keycloak_rack/session_spec.rb +37 -0
- data/spec/keycloak_rack/skip_authentication_spec.rb +55 -0
- data/spec/spec_helper.rb +101 -0
- data/spec/support/contexts/mocked_keycloak.rb +63 -0
- data/spec/support/contexts/mocked_rack_application.rb +41 -0
- data/spec/support/test_key.pem +27 -0
- data/spec/support/token_helper.rb +76 -0
- metadata +616 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright 2021 Alexa Grey
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
8
|
+
so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,288 @@
|
|
1
|
+
# keycloak_rack
|
2
|
+
|
3
|
+
An opinionated, convention-over-configuration gem to authenticate Rack (and Rails) applications
|
4
|
+
against a [Keycloak](https://www.keycloak.org/) installation. It uses a lot of features from the
|
5
|
+
[dry-rb ecosystem](https://dry-rb.org), and works well in applications that do the same.
|
6
|
+
|
7
|
+
In particular, it adopts a [monadic approach](https://dry-rb.org/gems/dry-monads/1.3/) to authentication flow control,
|
8
|
+
allowing for more granularity in how the whole process is handled.
|
9
|
+
|
10
|
+
## Install
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem "keycloak_rack", "1.0.0"
|
14
|
+
```
|
15
|
+
|
16
|
+
### Ruby & Rails Versions
|
17
|
+
|
18
|
+
- Ruby 2.7, 3.0
|
19
|
+
- Rails 6.0, 6.1, or using only Rack 2.2
|
20
|
+
|
21
|
+
It has also been tested on Rails 5.2, but isn't officially supported because it doesn't support Ruby 3.
|
22
|
+
|
23
|
+
At minimum, it requires Ruby 2.7, because it makes use of [pattern matching](https://docs.ruby-lang.org/en/3.0.0/doc/syntax/pattern_matching_rdoc.html).
|
24
|
+
If you find the warning at boot annoying (I sure do), you can set `RUBYOPT='-W:no-experimental'` in your environment to silence the nag.
|
25
|
+
|
26
|
+
## Basic Usage in Rails
|
27
|
+
|
28
|
+
`KeycloakRack` attaches itself as Rack middleware and processes the `Authorization` header passed to the application (if any).
|
29
|
+
|
30
|
+
Once it runs, it attaches itself to the rack environment in a number of places,
|
31
|
+
but the primary entry point is `keycloak:session`:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class ApplicationController < ActionController::API
|
35
|
+
before_action :authenticate_user!
|
36
|
+
|
37
|
+
# @return [void]
|
38
|
+
def authenticate_user!
|
39
|
+
# KeycloakRack::Session#authenticate! implements a Dry::Matcher::ResultMatcher
|
40
|
+
request.env["keycloak:session"].authenticate! do |m|
|
41
|
+
m.success(:authenticated) do |_, token|
|
42
|
+
# this is the case when a user is successfully authenticated
|
43
|
+
|
44
|
+
# token will be a KeycloakRack::DecodedToken instance, a
|
45
|
+
# hash-like PORO that maps a number of values from the
|
46
|
+
# decoded JWT that can be used to find or upsert a user
|
47
|
+
|
48
|
+
attrs = decoded_token.slice(:keycloak_id, :email, :email_verified, :realm_access, :resource_access)
|
49
|
+
|
50
|
+
result = User.upsert attrs, returning: %i[id], unique_by: %i[keycloak_id]
|
51
|
+
|
52
|
+
@current_user = User.find result.first["id"]
|
53
|
+
end
|
54
|
+
|
55
|
+
m.success do
|
56
|
+
# When allow_anonymous is true, or
|
57
|
+
# a URI is skipped because of skip_paths, this
|
58
|
+
# case will be reached. Requests from here on
|
59
|
+
# out should be considered anonymous and treated
|
60
|
+
# accordingly
|
61
|
+
|
62
|
+
@current_user = AnonymousUser.new
|
63
|
+
end
|
64
|
+
|
65
|
+
m.failure do |code, reason|
|
66
|
+
# All authentication failures are reached here,
|
67
|
+
# assuming halt_on_auth_failure is set to false
|
68
|
+
# This allows the application to decide how it
|
69
|
+
# wants to respond
|
70
|
+
|
71
|
+
render json: { errors: [{ message: "Auth Failure" }] }, status: :forbidden
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
## Configuration
|
79
|
+
|
80
|
+
This gem uses [anyway_config](https://github.com/palkan/anyway_config), which allows you to make use of ENV vars, Rails credentials,
|
81
|
+
and simple YAML configuration files interchangeably.
|
82
|
+
|
83
|
+
At minimum, you must configure `server_url` and `realm_id` to authenticate a user's token against your Keycloak instance.
|
84
|
+
|
85
|
+
| Option | ENV | Default Value | Type | Required? | Description | Example |
|
86
|
+
| ---- | ----- | ----- | ------ | ----- | ------ | ----- |
|
87
|
+
| `server_url` | `KEYCLOAK_SERVER_URL` | `nil` | `String` | Required | The base url where your Keycloak server is located. This value can be retrieved in your Keycloak client configuration. | `auth:8080` |
|
88
|
+
| `realm_id` | `KEYCLOAK_REALM_ID` | `nil` | `String` | Required | Realm's name (not id, actually) | `master` |
|
89
|
+
| `token_leeway` | `KEYCLOAK_TOKEN_LEEWAY` | `10` | `Integer` | Optional | Number of seconds a token can expire before being rejected by the API. | `15` |
|
90
|
+
| `allow_anonymous` | `KEYCLOAK_ALLOW_ANONYMOUS` | `false` | `Boolean` | Optional | Whether to allow anonymous users to access the API. If true, authentication will not provided a decoded token instance | `true` |
|
91
|
+
| `halt_on_auth_failure` | `KEYCLOAK_HALT_ON_AUTH_FAILURE` | `true` | `Boolean` | Optional | Whether to short-circuit when a token is invalid, or otherwise fails (if `allow_anonymous` is false, token-less access counts as a failure). Set this to `false` if you want to handle failures in your application instead. | `false` |
|
92
|
+
| `cache_ttl` | `KEYCLOAK_CACHE_TTL` | `86400` | `Integer` | Optional | Interval (in seconds) to cache public keys from Keycloak. These should not change very often, so 1 day (86400) is the default. | `86400` |
|
93
|
+
| `ca_certificate_file` | `KEYCLOAK_CA_CERTIFICATE_FILE` | `nil` | `String` | Optional | Path to the certificate authority used to validate the Keycloak server certificate | `/credentials/production_root_ca_cert.pem` |
|
94
|
+
| `skip_paths` | _n/a_ | `{}` | `Hash` | Optional | Paths where token validation is skipped | `{ get: %w[/ping], post: [%r,/stats,] }`|
|
95
|
+
|
96
|
+
### Options
|
97
|
+
|
98
|
+
Because of `anyway_config`, you can create a file `config/keycloak.yml` to populate most of the settings.
|
99
|
+
|
100
|
+
```yml
|
101
|
+
default: &default
|
102
|
+
server_url: "https://keycloak.example.com/auth"
|
103
|
+
realm_id: Test
|
104
|
+
|
105
|
+
development:
|
106
|
+
<<: *default
|
107
|
+
|
108
|
+
test:
|
109
|
+
<<: *default
|
110
|
+
|
111
|
+
production:
|
112
|
+
<<: *default
|
113
|
+
```
|
114
|
+
|
115
|
+
[Rails credentials](https://guides.rubyonrails.org/security.html#custom-credentials) under the key `keycloak` will also work:
|
116
|
+
|
117
|
+
```yml
|
118
|
+
keycloak:
|
119
|
+
server_url: "https://keycloak.example.com/auth"
|
120
|
+
realm_id: "Test"
|
121
|
+
```
|
122
|
+
|
123
|
+
You can also do a more traditional approach in an initializer, but note that any changes here
|
124
|
+
will _override_ values inherited by anyway_config's approach. It's really only useful for
|
125
|
+
configuring `skip_paths`, given its support for regular expressions.
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
KeycloakRack.configure do |config|
|
129
|
+
config.server_url = ENV["KEYCLOAK_SERVER_URL"]
|
130
|
+
config.realm_id = ENV["KEYCLOAK_REALM_ID"]
|
131
|
+
config.skip_paths = {
|
132
|
+
get: ["/ping"],
|
133
|
+
post: [%r,/api/v1/analytics,]
|
134
|
+
}
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
## Usage
|
139
|
+
|
140
|
+
### Authorizing a realm role
|
141
|
+
|
142
|
+
There is a helper service that gets mounted in the Rack environment as `keycloak:authorize_realm`, and works
|
143
|
+
similarly to the session's authenticate method:
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
class UploadProcessor
|
147
|
+
def initialize(app)
|
148
|
+
@app = app
|
149
|
+
end
|
150
|
+
|
151
|
+
def call(env)
|
152
|
+
env["keycloak.authorize_realm"].call("upload_permission") do |m|
|
153
|
+
m.success do
|
154
|
+
# allow the upload to proceed
|
155
|
+
end
|
156
|
+
|
157
|
+
m.failure do
|
158
|
+
# fail the response, return 403, etc
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
app = Rack::Builder.app do
|
165
|
+
use KeycloakRack::Middleware
|
166
|
+
|
167
|
+
run UploadProcessor
|
168
|
+
end
|
169
|
+
```
|
170
|
+
|
171
|
+
### Authorizing a resource role
|
172
|
+
|
173
|
+
There is also a helper service that gets mounted as `keycloak:authorize_resource`,
|
174
|
+
for checking resource roles:
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
class WidgetCombobulator
|
178
|
+
def initialize(app)
|
179
|
+
@app = app
|
180
|
+
end
|
181
|
+
|
182
|
+
def call(env)
|
183
|
+
env["keycloak.authorize_resource"].call("widgets", "recombobulate") do |m|
|
184
|
+
m.success do
|
185
|
+
# allow the user to recombobulate the widget
|
186
|
+
end
|
187
|
+
|
188
|
+
m.failure do
|
189
|
+
# return forbidden, log the attempt, etc
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
app = Rack::Builder.app do
|
196
|
+
use KeycloakRack::Middleware
|
197
|
+
|
198
|
+
run WidgetCombobulator
|
199
|
+
end
|
200
|
+
```
|
201
|
+
|
202
|
+
### Overriding the failure response
|
203
|
+
|
204
|
+
The easiest approach would be to set `halt_on_auth_failure` to `false` and handle the failure in your application,
|
205
|
+
but the middleware has a few spots that can be hooked into with a prepended module if you'd prefer to monkey patch.
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
module Patches
|
209
|
+
module OverrideKeycloakFailureBody
|
210
|
+
# @param [Hash] env
|
211
|
+
# @param [Dry::Monads::Failure] monad
|
212
|
+
# @return [String, #to_json]
|
213
|
+
def build_failure_body(env, monad)
|
214
|
+
# You can use the #failure method on the monad to retrieve a tuple
|
215
|
+
reason, message, token, original_error = monad.failure
|
216
|
+
|
217
|
+
# reason is a symbol, like :no_token or :expired
|
218
|
+
# message is a human-readable string that explains why it failed
|
219
|
+
# token is the original token (if any) that was provided
|
220
|
+
# original_error is a possible exception that was raised (not all failures have one)
|
221
|
+
|
222
|
+
# Return any object that will JSONify itself with #to_json
|
223
|
+
|
224
|
+
{
|
225
|
+
error: "You can't sign in because: #{message}"
|
226
|
+
}
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
KeycloakRack::Middleware.prepend Patches::OverrideKeycloakFailureBody
|
232
|
+
```
|
233
|
+
|
234
|
+
If you need to return something other than JSON,
|
235
|
+
or otherwise augment the headers, you can do something like:
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
module Patches
|
239
|
+
module OverrideKeycloakFailureHeaders
|
240
|
+
# @param [Hash] env
|
241
|
+
# @param [Dry::Monads::Failure] monad
|
242
|
+
# @return [{ String => String }]
|
243
|
+
def build_failure_headers(env, monad)
|
244
|
+
{
|
245
|
+
"Content-Type" => "application/xml",
|
246
|
+
"Special-Header" => "special-value",
|
247
|
+
}
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
KeycloakRack::Middleware.prepend Patches::OverrideKeycloakFailureHeaders
|
253
|
+
```
|
254
|
+
|
255
|
+
In the future, this might be customizable, but it's low priority.
|
256
|
+
|
257
|
+
## History
|
258
|
+
|
259
|
+
What became this gem started out as a slight modification to [keycloak-api-rails](https://github.com/looorent/keycloak-api-rails)
|
260
|
+
by [looorent](https://github.com/looorent). For authenticating requests a Rails API that must _always_ have a token,
|
261
|
+
that gem works great and I would recommend it.
|
262
|
+
|
263
|
+
As I continued building my application, I had some needs that weren't met by it, namely:
|
264
|
+
|
265
|
+
- Anonymous user access—I just need to know if the user is authenticated or not without preventing
|
266
|
+
access to the application, I'll handle failures myself.
|
267
|
+
- Control over auth failures in general (this is still pending, though made easier to monkey-patch)
|
268
|
+
- Usage outside of Rails—I have some microservices that are rack applications.
|
269
|
+
- Easier role checking for rack middleware.
|
270
|
+
- Stricter auth: no query strings. I want my APIs to only support clients that send an `Authorization`
|
271
|
+
header with a bearer token.
|
272
|
+
|
273
|
+
I ended up rewriting it from scratch, but the logic in this owes a lot to the original author's design.
|
274
|
+
|
275
|
+
## Future extensions
|
276
|
+
|
277
|
+
- A way to extract custom attributes from the token besides the defaults Keycloak provides,
|
278
|
+
presently there's no way to get at those.
|
279
|
+
|
280
|
+
## Contributing
|
281
|
+
|
282
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/scryptmouse/keycloak_rack.
|
283
|
+
This project is intended to be a safe, welcoming space for collaboration, and contributors are
|
284
|
+
expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
285
|
+
|
286
|
+
## License
|
287
|
+
|
288
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/appraisal
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'appraisal' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("appraisal", "appraisal")
|
data/bin/console
ADDED
data/bin/fix-appraisals
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
root="$(realpath "$(dirname $0)/..")"
|
4
|
+
|
5
|
+
echo $root
|
6
|
+
|
7
|
+
for gemfile in $(find "$root/gemfiles" -iname "*.gemfile"); do
|
8
|
+
lockfile="${gemfile}.lock"
|
9
|
+
|
10
|
+
echo "Fixing $gemfile..." >&2
|
11
|
+
|
12
|
+
BUNDLE_GEMFILE="${gemfile}" bundle lock --lockfile="${lockfile}" --add-platform x86_64-linux > /dev/null
|
13
|
+
BUNDLE_GEMFILE="${gemfile}" bundle lock --lockfile="${lockfile}" --add-platform ruby > /dev/null
|
14
|
+
done
|
data/bin/rake
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rake", "rake")
|
data/bin/rspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rspec-core", "rspec")
|
data/bin/rubocop
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rubocop", "rubocop")
|