cased-rails 0.3.1 → 0.5.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 +4 -4
- data/README.md +155 -19
- data/app/assets/config/cased/manifest.js +2 -0
- data/app/assets/images/cased/favicon.ico +0 -0
- data/app/assets/images/cased/logo.png +0 -0
- data/app/assets/javascripts/cased/index.js +75 -0
- data/app/controllers/cased/authorizations_controller.rb +13 -0
- data/app/controllers/cased/cli/sessions_controller.rb +55 -0
- data/app/helpers/cased_helper.rb +23 -0
- data/app/models/cased/authorization.rb +65 -0
- data/app/views/cased/authorizations/create.html.erb +10 -0
- data/app/views/cased/cli/sessions/_form.html.erb +15 -0
- data/app/views/cased/cli/sessions/_guard_session.json.jbuilder +27 -0
- data/app/views/cased/cli/sessions/new.html.erb +24 -0
- data/app/views/cased/cli/sessions/steps/_canceled.html.erb +17 -0
- data/app/views/cased/cli/sessions/steps/_create.html.erb +15 -0
- data/app/views/cased/cli/sessions/steps/_denied.html.erb +17 -0
- data/app/views/cased/cli/sessions/steps/_reason_required.html.erb +16 -0
- data/app/views/cased/cli/sessions/steps/_requested.html.erb +49 -0
- data/app/views/cased/cli/sessions/steps/_timed_out.html.erb +17 -0
- data/app/views/layouts/cased/cli.html.erb +13 -0
- data/config/initializers/inflections.rb +3 -0
- data/config/routes.rb +6 -0
- data/lib/cased/controller_helpers.rb +97 -0
- data/lib/cased/rails.rb +1 -0
- data/lib/cased/rails/config.rb +38 -0
- data/lib/cased/rails/railtie.rb +30 -0
- data/lib/cased/rails/version.rb +1 -1
- data/lib/tasks/cased.rake +11 -0
- metadata +41 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ac8f2d72b0bd9acb03474b7903c2d7d061c6a95d99beb5ea46d8b18dfa2fe37
|
4
|
+
data.tar.gz: 17c1ea4d0eaa1c44a365d51b1a88b04a3c5a3c479f6318ab33bdd03c5163ea02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 00c9c0d383329eb6e53432458a578db0c795107822bafcdcb8249bcc5432541ac3b5c7609b288110c8e6b9d531c6ac211399a4b43aeb1988115a28ad03b1bab6
|
7
|
+
data.tar.gz: b9d292a6ea549455cd9f595fe4d764910abfaa9e0f5331595359fb2a3520deec48479c5bf6591606df1e74e40958825b34d3c2b98af8cd7c2a4ec37a3544258b
|
data/README.md
CHANGED
@@ -7,15 +7,19 @@ A Cased client for Ruby on Rails applications in your organization to control an
|
|
7
7
|
- [Installation](#installation)
|
8
8
|
- [Configuration](#configuration)
|
9
9
|
- [Usage](#usage)
|
10
|
-
- [
|
11
|
-
|
12
|
-
|
13
|
-
- [
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
- [Cased CLI](#cased-cli)
|
11
|
+
- [Recording console sessions](#recording-console-sessions)
|
12
|
+
- [Approval workflows for sensitive operations](#approval-workflows-for-sensitive-operations)
|
13
|
+
- [Audit trails](#audit-trails)
|
14
|
+
- [Publishing events to Cased](#publishing-events-to-cased)
|
15
|
+
- [Publishing audit events for all record creation, updates, and deletions automatically](#publishing-audit-events-for-all-record-creation-updates-and-deletions-automatically)
|
16
|
+
- [Retrieving events from a Cased audit trail](#retrieving-events-from-a-cased-audit-trail)
|
17
|
+
- [Retrieving events from multiple Cased audit trails](#retrieving-events-from-multiple-cased-audit-trails)
|
18
|
+
- [Exporting events](#exporting-events)
|
19
|
+
- [Masking & filtering sensitive information](#masking-and-filtering-sensitive-information)
|
20
|
+
- [Disable publishing events](#disable-publishing-events)
|
21
|
+
- [Context](#context)
|
22
|
+
- [Testing](#testing)
|
19
23
|
- [Customizing cased-rails](#customizing-cased-rails)
|
20
24
|
- [Contributing](#contributing)
|
21
25
|
|
@@ -41,6 +45,26 @@ All configuration options available in cased-rails are available to be configure
|
|
41
45
|
|
42
46
|
```ruby
|
43
47
|
Cased.configure do |config|
|
48
|
+
# GUARD_APPLICATION_KEY=guard_application_1ntKX0P4vUbKoc0lMWGiSbrBHcH
|
49
|
+
config.guard_application_key = 'guard_application_1ntKX0P4vUbKoc0lMWGiSbrBHcH'
|
50
|
+
|
51
|
+
# GUARD_USER_TOKEN=user_1oFqlROLNRGVLOXJSsHkJiVmylr
|
52
|
+
config.guard_user_token = 'user_1oFqlROLNRGVLOXJSsHkJiVmylr'
|
53
|
+
|
54
|
+
# DENY_IF_UNREACHABLE=1
|
55
|
+
config.guard_deny_if_unreachable = true
|
56
|
+
|
57
|
+
# Attach metadata to all CLI requests. This metadata will appear in Cased and
|
58
|
+
# any notification source such as email or Slack.
|
59
|
+
#
|
60
|
+
# You are limited to 20 properties and cannot be a nested dictionary. Metadata
|
61
|
+
# specified in the CLI request overrides any configured globally.
|
62
|
+
config.cli.metadata = {
|
63
|
+
rails_env: ENV['RAILS_ENV'],
|
64
|
+
heroku_application: ENV['HEROKU_APP_NAME'],
|
65
|
+
git_commit: ENV['GIT_COMMIT'],
|
66
|
+
}
|
67
|
+
|
44
68
|
# CASED_POLICY_KEY=policy_live_1dQpY5JliYgHSkEntAbMVzuOROh
|
45
69
|
config.policy_key = 'policy_live_1dQpY5JliYgHSkEntAbMVzuOROh'
|
46
70
|
|
@@ -76,7 +100,119 @@ end
|
|
76
100
|
|
77
101
|
## Usage
|
78
102
|
|
79
|
-
###
|
103
|
+
### Cased CLI
|
104
|
+
|
105
|
+
#### Playback console sessions
|
106
|
+
|
107
|
+
Having visibility into production terminal sessions is essential to providing
|
108
|
+
access to sensitive data and critical systems. `cased-rails` can provide complete
|
109
|
+
command line session recordings with minimal configuration.
|
110
|
+
|
111
|
+
First, enable the "Record output" option in your application's settings page on Cased.
|
112
|
+
|
113
|
+
Next grab the application's key from the same settings page and configure
|
114
|
+
`cased-rails` with it either by using an environment variable or manually.
|
115
|
+
|
116
|
+
**Environment variable**
|
117
|
+
|
118
|
+
```
|
119
|
+
GUARD_APPLICATION_KEY=guard_application_1rBCh8o3YMaI1eAKxbrNvnLki3x rails console
|
120
|
+
```
|
121
|
+
|
122
|
+
**Manually**
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
Cased.configure do |config|
|
126
|
+
config.guard_application_key = 'guard_application_1rBCh8o3YMaI1eAKxbrNvnLki3x'
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
By default playback will be saved only when a Rails console is started outside
|
131
|
+
of development and test. When the playback is being saved, by default all
|
132
|
+
parameters other than `id`, `action`, and `controller` will be filtered out.
|
133
|
+
For example:
|
134
|
+
|
135
|
+
```
|
136
|
+
#<User id: "user_1qwkKB8IGxQFlu3C4lI53tCIyZI", organization: "Enterprise">
|
137
|
+
```
|
138
|
+
|
139
|
+
Would become:
|
140
|
+
|
141
|
+
```
|
142
|
+
#<User id: "user_1qwkKB8IGxQFlu3C4lI53tCIyZI", organization: [FILTERED]>
|
143
|
+
```
|
144
|
+
|
145
|
+
If you'd like to configure if filtering is enabled or specify which attributes
|
146
|
+
are not filtered you can do so with:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
Cased.configure do |config|
|
150
|
+
config.unfiltered_parameters = ['id', 'action', 'controller']
|
151
|
+
config.filter_parameters = Rails.env.production?
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
#### Approval workflows for sensitive operations
|
156
|
+
|
157
|
+
Adding approval workflows to your controllers is a two step process in your
|
158
|
+
Rails applications.
|
159
|
+
|
160
|
+
First, mount the Rails engine in your routes. The included Rails engine in
|
161
|
+
cased-rails is necessary for the approval workflow to know whether or not it has
|
162
|
+
been requested, approved, denied, canceled or timed out.
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
Rails.application.routes.draw do
|
166
|
+
mount Cased::Rails::Engine => '/cased'
|
167
|
+
|
168
|
+
root to: 'home#show'
|
169
|
+
end
|
170
|
+
```
|
171
|
+
|
172
|
+
To control the requirements for an approval workflow, that must be configured
|
173
|
+
within your CLI application settings on Cased. Some controls include restricting
|
174
|
+
which users or groups can approve the request, if a reason is required, how long
|
175
|
+
until the request times out, and more.
|
176
|
+
|
177
|
+
To start an your approval workflow all that is needed is to call the `guard`
|
178
|
+
method before a request using `before_action`.
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
class AccountsController < ApplicationController
|
182
|
+
before_action :guard, only: %i[update destroy]
|
183
|
+
|
184
|
+
def update
|
185
|
+
if current_account.update(account_params)
|
186
|
+
redirect_to current_account
|
187
|
+
else
|
188
|
+
render :edit
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def destroy
|
193
|
+
if current_account.destroy
|
194
|
+
redirect_to accounts_path
|
195
|
+
else
|
196
|
+
redirect_to current_account
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
def account_params
|
203
|
+
params.require(:account).permit(:name, :description, :email)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
```
|
207
|
+
|
208
|
+
Approval workflows are best started just before data is about to be created,
|
209
|
+
updated, or destroyed. Approval workflows are not intended to control permission
|
210
|
+
to view resources. The actions we recommend guarding are `create`, `update`, and
|
211
|
+
`destroy` based on your needs.
|
212
|
+
|
213
|
+
### Audit trails
|
214
|
+
|
215
|
+
#### Publishing events to Cased
|
80
216
|
|
81
217
|
Once Cased is setup there are two ways to publish your first audit trail event.
|
82
218
|
The first is using the `cased` helper method included in all ActiveRecord models.
|
@@ -151,7 +287,7 @@ end
|
|
151
287
|
|
152
288
|
By publishing the `team.create` audit event within the controller directly as shown you risk not having a complete and comprehensive audit trail for each team created in your application as it may happen in your API, model callbacks, and more.
|
153
289
|
|
154
|
-
|
290
|
+
#### Publishing audit events for all record creation, updates, and deletions automatically
|
155
291
|
|
156
292
|
Cased provides a mixin you can include in your models or in `ApplicationRecord` to automatically publish when new models are created, updated, or destroyed.
|
157
293
|
|
@@ -173,7 +309,7 @@ end
|
|
173
309
|
|
174
310
|
This mixin is intended to get you up and running quickly. You'll likely need to configure your own callbacks to control what exactly gets published to Cased.
|
175
311
|
|
176
|
-
|
312
|
+
#### Retrieving events from a Cased audit trail
|
177
313
|
|
178
314
|
If you plan on retrieving events from your audit trails to power a user facing audit trail or API you must use a Cased API key.
|
179
315
|
|
@@ -200,7 +336,7 @@ class AuditTrailController < ApplicationController
|
|
200
336
|
end
|
201
337
|
```
|
202
338
|
|
203
|
-
|
339
|
+
#### Retrieving events from multiple Cased audit trails
|
204
340
|
|
205
341
|
To retrieve events from one or more Cased audit trails you can configure multiple Cased API keys and retrieve events for each one by fetching their respective clients.
|
206
342
|
|
@@ -227,7 +363,7 @@ results.each do |event|
|
|
227
363
|
end
|
228
364
|
```
|
229
365
|
|
230
|
-
|
366
|
+
#### Exporting events
|
231
367
|
|
232
368
|
Exporting events from Cased allows you to provide users with exports of their own data or to respond to data requests.
|
233
369
|
|
@@ -243,7 +379,7 @@ export = Cased.policy.exports.create(
|
|
243
379
|
export.download_url # => https://api.cased.com/exports/export_1dSHQSNtAH90KA8zGTooMnmMdiD/download?token=eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidXNlcl8xZFFwWThiQmdFd2RwbWRwVnJydER6TVg0ZkgiLCJ
|
244
380
|
```
|
245
381
|
|
246
|
-
|
382
|
+
#### Masking & filtering sensitive information
|
247
383
|
|
248
384
|
If you are handling sensitive information on behalf of your users you should consider masking or filtering any sensitive information.
|
249
385
|
|
@@ -258,7 +394,7 @@ Cased.publish(
|
|
258
394
|
)
|
259
395
|
```
|
260
396
|
|
261
|
-
|
397
|
+
#### Console Usage
|
262
398
|
|
263
399
|
Most Cased events will be created by users from actions on the website from
|
264
400
|
custom defined events or lifecycle callbacks. The exception is any console
|
@@ -275,7 +411,7 @@ Rails.application.console do
|
|
275
411
|
end
|
276
412
|
```
|
277
413
|
|
278
|
-
|
414
|
+
#### Disable publishing events
|
279
415
|
|
280
416
|
Although rare, there may be times where you wish to disable publishing events to Cased. To do so wrap your transaction inside of a `Cased.disable` block:
|
281
417
|
|
@@ -291,7 +427,7 @@ Or you can configure the entire process to disable publishing events.
|
|
291
427
|
CASED_DISABLE_PUBLISHING=1 bundle exec ruby crawl.rb
|
292
428
|
```
|
293
429
|
|
294
|
-
|
430
|
+
#### Context
|
295
431
|
|
296
432
|
When you include `cased-rails` in your application your Ruby on Rails application is configures a [Rack middleware](https://github.com/cased/cased-ruby/blob/master/lib/cased/rack_middleware.rb) that populates `Cased.context` with the following information for each request:
|
297
433
|
|
@@ -364,7 +500,7 @@ To clear/reset the context:
|
|
364
500
|
Cased.context.clear
|
365
501
|
```
|
366
502
|
|
367
|
-
|
503
|
+
#### Testing
|
368
504
|
|
369
505
|
`cased-rails` provides a Cased::TestHelper test helper class that you can use to test events are being published to Cased.
|
370
506
|
|
Binary file
|
Binary file
|
@@ -0,0 +1,75 @@
|
|
1
|
+
//= require rails-ujs
|
2
|
+
|
3
|
+
let windowReference = null;
|
4
|
+
let previousUrl = null;
|
5
|
+
let casedCreateSession = null;
|
6
|
+
let casedLoggedInContainer = null;
|
7
|
+
let casedLoggedOutContainer = null;
|
8
|
+
let casedUser = null;
|
9
|
+
|
10
|
+
// receiveMessage is the callback that is triggered when an authentication
|
11
|
+
// response is received from the new window we opened.
|
12
|
+
//
|
13
|
+
// We use this callback to update the user information in the UI and show the
|
14
|
+
// logged in container.
|
15
|
+
const receiveMessage = (event) => {
|
16
|
+
if (!event.isTrusted) {
|
17
|
+
return;
|
18
|
+
}
|
19
|
+
|
20
|
+
const { user } = event.data;
|
21
|
+
casedUser.innerText = user;
|
22
|
+
if (casedCreateSession) {
|
23
|
+
casedCreateSession.submit();
|
24
|
+
} else {
|
25
|
+
casedLoggedInContainer.classList.remove("hidden");
|
26
|
+
casedLoggedOutContainer.classList.add("hidden");
|
27
|
+
}
|
28
|
+
};
|
29
|
+
|
30
|
+
// openSignInWindow is used to present the Cased sign in window.
|
31
|
+
const openSignInWindow = (url) => {
|
32
|
+
window.removeEventListener("message", receiveMessage);
|
33
|
+
const windowFeatures =
|
34
|
+
"toolbar=no, menubar=no, width=600, height=700, top=50, left=200";
|
35
|
+
|
36
|
+
if (windowReference === null || windowReference.closed) {
|
37
|
+
windowReference = window.open(url, "Cased", windowFeatures);
|
38
|
+
} else if (previousUrl !== url) {
|
39
|
+
// If the window is already open and the previous URL was different, we need
|
40
|
+
// to load a new URL and refocus.
|
41
|
+
windowReference = window.open(url, "Cased", windowFeatures);
|
42
|
+
windowReference.focus();
|
43
|
+
} else {
|
44
|
+
windowReference.focus();
|
45
|
+
}
|
46
|
+
|
47
|
+
window.addEventListener("message", (event) => receiveMessage(event), false);
|
48
|
+
previousUrl = url;
|
49
|
+
};
|
50
|
+
|
51
|
+
window.addEventListener("DOMContentLoaded", (event) => {
|
52
|
+
// Global elements
|
53
|
+
casedCreateSession = document.getElementById("cased-create-session");
|
54
|
+
casedLoggedInContainer = document.getElementById("cased-logged-in");
|
55
|
+
casedLoggedOutContainer = document.getElementById("cased-logged-out");
|
56
|
+
casedUser = document.getElementById("cased-user");
|
57
|
+
|
58
|
+
// Local elements
|
59
|
+
const casedAuthenticate = document.getElementById("cased-authenticate");
|
60
|
+
if (casedAuthenticate) {
|
61
|
+
casedAuthenticate.addEventListener("click", (event) => {
|
62
|
+
event.preventDefault();
|
63
|
+
|
64
|
+
openSignInWindow(event.currentTarget.href);
|
65
|
+
});
|
66
|
+
}
|
67
|
+
|
68
|
+
const casedLogout = document.getElementById("cased-logout");
|
69
|
+
if (casedLogout) {
|
70
|
+
casedLogout.addEventListener("ajax:success", (_event) => {
|
71
|
+
casedLoggedInContainer.classList.add("hidden");
|
72
|
+
casedLoggedOutContainer.classList.remove("hidden");
|
73
|
+
});
|
74
|
+
}
|
75
|
+
});
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cased
|
4
|
+
module CLI
|
5
|
+
class SessionsController < ApplicationController
|
6
|
+
def show
|
7
|
+
guard_session = Cased::CLI::Session.find(params[:guard_session_id])
|
8
|
+
|
9
|
+
respond_to do |format|
|
10
|
+
format.html do
|
11
|
+
render partial: 'cased/cli/sessions/form', locals: { guard_session: guard_session }
|
12
|
+
end
|
13
|
+
|
14
|
+
format.json do
|
15
|
+
render partial: 'cased/cli/sessions/guard_session', locals: { guard_session: guard_session }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def cancel
|
21
|
+
guard_session = Cased::CLI::Session.find(params[:guard_session_id])
|
22
|
+
guard_session.cancel
|
23
|
+
|
24
|
+
respond_to do |format|
|
25
|
+
format.html do
|
26
|
+
safe_redirect_back
|
27
|
+
end
|
28
|
+
|
29
|
+
format.json do
|
30
|
+
render partial: 'cased/cli/sessions/guard_session', locals: { guard_session: guard_session }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def safe_redirect_back(allow_other_host: false, **args)
|
38
|
+
referer = params[:referer]
|
39
|
+
redirect_to_referer = referer && (allow_other_host || url_host_allowed?(referer))
|
40
|
+
redirect_to redirect_to_referer ? referer : guard_fallback_location, **args
|
41
|
+
end
|
42
|
+
|
43
|
+
def url_host_allowed?(url)
|
44
|
+
uri = URI(url.to_s)
|
45
|
+
|
46
|
+
# We're redirecting to a path on app.cased.com, that is okay.
|
47
|
+
return true if uri.host.blank?
|
48
|
+
|
49
|
+
uri.host == request.host
|
50
|
+
rescue ArgumentError, URI::Error
|
51
|
+
false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CasedHelper
|
4
|
+
# Guarded parameters are the original parameters when the form was first
|
5
|
+
# submitted. These parameters need to be preserved.
|
6
|
+
def guarded_parameters(form)
|
7
|
+
form_params = params.except(:authenticity_token, :controller, :action)
|
8
|
+
|
9
|
+
safe_join render_guarded_parameters(form, form_params.to_unsafe_h)
|
10
|
+
end
|
11
|
+
|
12
|
+
def render_guarded_parameters(form, form_params, prefix = nil)
|
13
|
+
form_params.collect do |key, value|
|
14
|
+
case value
|
15
|
+
when Hash
|
16
|
+
render_guarded_parameters(form, value, key)
|
17
|
+
else
|
18
|
+
name = prefix ? "#{prefix}[#{key}]" : key
|
19
|
+
hidden_field_tag(name, value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jwt'
|
4
|
+
|
5
|
+
module Cased
|
6
|
+
class Authorization
|
7
|
+
class MissingApplicationKey < StandardError
|
8
|
+
MESSAGE = <<~MSG
|
9
|
+
Missing GUARD_APPLICATION_KEY or Cased.config.guard_application_key.
|
10
|
+
MSG
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
super(MESSAGE)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
ALGORITHM = 'HS256'
|
18
|
+
|
19
|
+
def self.load!(token)
|
20
|
+
raise MissingApplicationKey if Cased.config.guard_application_key.blank?
|
21
|
+
|
22
|
+
# JWT.decode will raise here if the token has expired or the application
|
23
|
+
# key does not match meaning it has been tampered with.
|
24
|
+
data, = JWT.decode(token, Cased.config.guard_application_key, true, algorithm: ALGORITHM)
|
25
|
+
|
26
|
+
new(
|
27
|
+
user: data.fetch('user'),
|
28
|
+
user_id: data.fetch('user_id'),
|
29
|
+
expires_at: data.fetch('exp'),
|
30
|
+
issuer: data.fetch('iss'),
|
31
|
+
issued_at: data.fetch('iat'),
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.validate!(token)
|
36
|
+
load!(token)
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :user
|
40
|
+
attr_reader :user_id
|
41
|
+
attr_reader :issued_at
|
42
|
+
attr_reader :expires_at
|
43
|
+
attr_reader :issuer
|
44
|
+
|
45
|
+
def initialize(user:, user_id:, issued_at:, expires_at:, issuer:)
|
46
|
+
@user = user
|
47
|
+
@user_id = user_id
|
48
|
+
@issued_at = Time.at(issued_at)
|
49
|
+
@expires_at = Time.at(expires_at)
|
50
|
+
@issuer = issuer
|
51
|
+
end
|
52
|
+
|
53
|
+
def token
|
54
|
+
user_id
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
user
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_param
|
62
|
+
user_id
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<script>
|
2
|
+
// We need to let the originating window know about the new logged in user.
|
3
|
+
// Once the originating window is notified we can close this window.
|
4
|
+
if (window.opener) {
|
5
|
+
window.opener.postMessage({
|
6
|
+
user: "<%= cased_authorization.user %>"
|
7
|
+
})
|
8
|
+
window.close()
|
9
|
+
}
|
10
|
+
</script>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<div id="guard-session-container">
|
2
|
+
<% if guard_session.requested? %>
|
3
|
+
<%= render 'cased/cli/sessions/steps/requested', guard_session: guard_session %>
|
4
|
+
<% elsif guard_session.denied? %>
|
5
|
+
<%= render 'cased/cli/sessions/steps/denied', guard_session: guard_session %>
|
6
|
+
<% elsif guard_session.canceled? %>
|
7
|
+
<%= render 'cased/cli/sessions/steps/canceled', guard_session: guard_session %>
|
8
|
+
<% elsif guard_session.timed_out? %>
|
9
|
+
<%= render 'cased/cli/sessions/steps/timed_out', guard_session: guard_session %>
|
10
|
+
<% elsif guard_session.reason_required? %>
|
11
|
+
<%= render 'cased/cli/sessions/steps/reason_required', guard_session: guard_session %>
|
12
|
+
<% else %>
|
13
|
+
<%= render 'cased/cli/sessions/steps/create', guard_session: guard_session %>
|
14
|
+
<% end %>
|
15
|
+
</div>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
json.id guard_session.id
|
2
|
+
json.url guard_session.url
|
3
|
+
json.api_url guard_session.api_url
|
4
|
+
json.state guard_session.state
|
5
|
+
json.command guard_session.command
|
6
|
+
json.metadata guard_session.metadata
|
7
|
+
json.reason guard_session.reason
|
8
|
+
json.ip_address guard_session.ip_address
|
9
|
+
json.requester do |requester|
|
10
|
+
requester.id guard_session.requester['id']
|
11
|
+
requester.email guard_session.requester['email']
|
12
|
+
end
|
13
|
+
|
14
|
+
json.responded_at guard_session.responded_at
|
15
|
+
json.responder do |responder|
|
16
|
+
responder.id guard_session.responder['id']
|
17
|
+
responder.email guard_session.responder['email']
|
18
|
+
end
|
19
|
+
|
20
|
+
json.guard_application do |guard_application|
|
21
|
+
guard_application.id guard_session.guard_application['id']
|
22
|
+
guard_application.name guard_session.guard_application['name']
|
23
|
+
guard_application.settings do |settings|
|
24
|
+
settings.message_of_the_day guard_session.guard_application.dig('settings', 'message_of_the_day')
|
25
|
+
settings.reason_required guard_session.guard_application.dig('settings', 'reason_required')
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<div class="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
2
|
+
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
3
|
+
<%= image_tag 'cased/logo.png', class: 'mx-auto h-12 w-auto' %>
|
4
|
+
</div>
|
5
|
+
|
6
|
+
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
7
|
+
<div id="cased-logged-in" class="<%= 'hidden' unless cased_authorization? %>">
|
8
|
+
<div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
9
|
+
<%= render 'cased/cli/sessions/form', guard_session: current_guard_session %>
|
10
|
+
</div>
|
11
|
+
<div class="text-center py-4 flex justify-center space-x-2">
|
12
|
+
<div>
|
13
|
+
Logged in as <span id="cased-user"><%= cased_authorization %></span>.
|
14
|
+
</div>
|
15
|
+
<%= button_to 'Logout', cased.logout_path, form: { id: 'cased-logout' }, method: :delete, remote: true, class: 'p-0 border-none bg-transparent text-blue-500 shadow-none cursor-pointer' %>
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
<div id="cased-logged-out" class="<%= 'hidden' if cased_authorization? %>">
|
19
|
+
<div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
20
|
+
<%= link_to 'Sign in to Cased', "#{Cased.config.url}/login/connect/#{Cased.config.guard_application_key}?return_to=#{cased.authorizations_url}", id: 'cased-authenticate', class: 'w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gray-600 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500' %>
|
21
|
+
</div>
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
</div>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<div class="sm:flex sm:items-start">
|
2
|
+
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
3
|
+
<svg class="h-6 w-6 text-red-600" x-description="Heroicon name: outline/exclamation" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
4
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
|
5
|
+
</svg>
|
6
|
+
</div>
|
7
|
+
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
8
|
+
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-headline">
|
9
|
+
Request Canceled
|
10
|
+
</h3>
|
11
|
+
<div class="mt-2">
|
12
|
+
<p class="text-sm text-gray-500">
|
13
|
+
You canceled the request.
|
14
|
+
</p>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
</div>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<%= form_with method: request.request_method_symbol, local: true, id: 'cased-create-session' do |form| %>
|
2
|
+
<%= guarded_parameters form %>
|
3
|
+
|
4
|
+
<div class="flex justify-center">
|
5
|
+
<div>
|
6
|
+
<svg class="animate-spin -ml-1 mr-3 h-6 w-6 text-gray" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
7
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
8
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
9
|
+
</svg>
|
10
|
+
</div>
|
11
|
+
<div>
|
12
|
+
Loading…
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
<% end %>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<div class="sm:flex sm:items-start">
|
2
|
+
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
3
|
+
<svg class="h-6 w-6 text-red-600" x-description="Heroicon name: outline/exclamation" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
4
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
|
5
|
+
</svg>
|
6
|
+
</div>
|
7
|
+
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
8
|
+
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-headline">
|
9
|
+
Request Denied
|
10
|
+
</h3>
|
11
|
+
<div class="mt-2">
|
12
|
+
<p class="text-sm text-gray-500">
|
13
|
+
<strong><%= guard_session.responder['email'] %></strong> denied your request.
|
14
|
+
</p>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
</div>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<%= form_with method: request.request_method_symbol, class: 'space-y-6', local: true, id: 'guard-reason-required-form' do |form| %>
|
2
|
+
<%= guarded_parameters form %>
|
3
|
+
|
4
|
+
<div>
|
5
|
+
<%= form.label 'guard_session[reason]', 'Reason', class: 'block text-sm font-medium text-gray-700' %>
|
6
|
+
<div class="mt-1">
|
7
|
+
<%= form.text_field 'guard_session[reason]', required: true, autocomplete: 'off', placeholder: 'Provide a reason', class: 'appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm' %>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div>
|
12
|
+
<button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gray-600 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
|
13
|
+
Submit
|
14
|
+
</button>
|
15
|
+
</div>
|
16
|
+
<% end %>
|
@@ -0,0 +1,49 @@
|
|
1
|
+
<%= form_with method: request.request_method_symbol, local: true, id: 'guard-session-form' do |form| %>
|
2
|
+
<%= guarded_parameters form %>
|
3
|
+
|
4
|
+
<div class="flex justify-center">
|
5
|
+
<div>
|
6
|
+
<svg class="animate-spin -ml-1 mr-3 h-6 w-6 text-gray" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
7
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
8
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
9
|
+
</svg>
|
10
|
+
</div>
|
11
|
+
<div>
|
12
|
+
Waiting for approval…
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
<%= form.hidden_field 'guard_session[id]', value: guard_session.id %>
|
16
|
+
<%= form.hidden_field 'guard_session[referer]', value: request.referer %>
|
17
|
+
<% end %>
|
18
|
+
|
19
|
+
<script>
|
20
|
+
const checkState = () => {
|
21
|
+
fetch("<%= cased.guard_session_url(guard_session, format: :json) %>")
|
22
|
+
.then((response) => response.json())
|
23
|
+
.then((json) => {
|
24
|
+
switch (json.state) {
|
25
|
+
case 'approved':
|
26
|
+
document.getElementById('guard-session-form').submit()
|
27
|
+
break;
|
28
|
+
|
29
|
+
case 'requested':
|
30
|
+
setTimeout(checkState, 1000)
|
31
|
+
break;
|
32
|
+
|
33
|
+
default:
|
34
|
+
refresh()
|
35
|
+
break;
|
36
|
+
}
|
37
|
+
})
|
38
|
+
}
|
39
|
+
|
40
|
+
const refresh = () => {
|
41
|
+
fetch("<%= cased.guard_session_url(guard_session) %>")
|
42
|
+
.then((response) => response.text())
|
43
|
+
.then((html) => {
|
44
|
+
document.getElementById('guard-session-container').innerHTML = html
|
45
|
+
})
|
46
|
+
}
|
47
|
+
|
48
|
+
checkState()
|
49
|
+
</script>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<div class="sm:flex sm:items-start">
|
2
|
+
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-yellow-100 sm:mx-0 sm:h-10 sm:w-10">
|
3
|
+
<svg class="h-6 w-6 text-yellow-600" x-description="Heroicon name: outline/exclamation" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
4
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
|
5
|
+
</svg>
|
6
|
+
</div>
|
7
|
+
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
8
|
+
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-headline">
|
9
|
+
Request Timed Out
|
10
|
+
</h3>
|
11
|
+
<div class="mt-2">
|
12
|
+
<p class="text-sm text-gray-500">
|
13
|
+
Your request timed out.
|
14
|
+
</p>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
</div>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Cased</title>
|
5
|
+
<%= csp_meta_tag %>
|
6
|
+
<%= favicon_link_tag 'cased/favicon.ico' %>
|
7
|
+
<%= javascript_include_tag 'cased/index' %>
|
8
|
+
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
|
9
|
+
</head>
|
10
|
+
<body>
|
11
|
+
<%= yield %>
|
12
|
+
</body>
|
13
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
Cased::Rails::Engine.routes.draw do
|
2
|
+
get '/authorizations/callback' => 'cased/authorizations#create', as: :authorizations
|
3
|
+
delete '/logout' => 'cased/authorizations#destroy', as: :logout
|
4
|
+
get '/cli/sessions/:guard_session_id' => 'cased/cli/sessions#show', as: :guard_session
|
5
|
+
post '/cli/sessions/:guard_session_id/cancel' => 'cased/cli/sessions#cancel', as: :cancel_guard_session
|
6
|
+
end
|
@@ -6,10 +6,107 @@ module Cased
|
|
6
6
|
|
7
7
|
included do
|
8
8
|
before_action :cased_setup_request_context
|
9
|
+
if respond_to?(:helper_method)
|
10
|
+
helper_method :current_guard_session
|
11
|
+
helper_method :cased_authorization
|
12
|
+
helper_method :cased_authorization?
|
13
|
+
end
|
9
14
|
end
|
10
15
|
|
11
16
|
private
|
12
17
|
|
18
|
+
def guard_required?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def cased_authorization
|
23
|
+
@cased_authorization ||= begin
|
24
|
+
if cookies[:cased_authorization]
|
25
|
+
Cased::Authorization.load!(cookies[:cased_authorization])
|
26
|
+
end
|
27
|
+
rescue JWT::ExpiredSignature
|
28
|
+
cookies.delete(:cased_authorization)
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def cased_authorization?
|
34
|
+
cased_authorization.present?
|
35
|
+
end
|
36
|
+
|
37
|
+
def cased_authorization=(token)
|
38
|
+
if token.nil?
|
39
|
+
cookies.delete(:cased_authorization)
|
40
|
+
else
|
41
|
+
Cased::Authorization.validate!(token)
|
42
|
+
|
43
|
+
cookies[:cased_authorization] = token
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def current_guard_session
|
48
|
+
@current_guard_session ||= Cased::CLI::Session.new(
|
49
|
+
reason: params.dig(:guard_session, :reason),
|
50
|
+
metadata: guard_session_metadata,
|
51
|
+
authentication: cased_authorization,
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def guard_session_approved?
|
56
|
+
guard_session_id = params.dig(:guard_session, :id)
|
57
|
+
return false unless guard_session_id.present?
|
58
|
+
|
59
|
+
session = Cased::CLI::Session.find(guard_session_id)
|
60
|
+
session.approved?
|
61
|
+
end
|
62
|
+
|
63
|
+
def guard
|
64
|
+
# TODO: Cancel previous session if not used
|
65
|
+
return true unless guard_required?
|
66
|
+
|
67
|
+
if guard_session_approved?
|
68
|
+
Cased.context.merge(guard_session: current_guard_session)
|
69
|
+
return true
|
70
|
+
end
|
71
|
+
|
72
|
+
if cased_authorization? && current_guard_session.create && current_guard_session.approved?
|
73
|
+
Cased.context.merge(guard_session: current_guard_session)
|
74
|
+
return true
|
75
|
+
end
|
76
|
+
|
77
|
+
render_guard
|
78
|
+
end
|
79
|
+
|
80
|
+
def guard_fallback_location
|
81
|
+
if respond_to?(:root_path)
|
82
|
+
root_path
|
83
|
+
else
|
84
|
+
'/'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def render_guard
|
89
|
+
respond_to do |format|
|
90
|
+
format.html do
|
91
|
+
render template: 'cased/cli/sessions/new', layout: 'cased/cli'
|
92
|
+
end
|
93
|
+
|
94
|
+
format.json do
|
95
|
+
render json: { error: true }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def guard_session_metadata
|
101
|
+
{
|
102
|
+
location: request.remote_ip,
|
103
|
+
request_http_method: request.method,
|
104
|
+
request_user_agent: request.headers['User-Agent'],
|
105
|
+
request_url: request.original_url,
|
106
|
+
request_id: request.request_id,
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
13
110
|
def cased_setup_request_context
|
14
111
|
Cased.context.merge(cased_initial_request_context)
|
15
112
|
end
|
data/lib/cased/rails.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cased
|
4
|
+
module Rails
|
5
|
+
module Config
|
6
|
+
def unfiltered_parameters=(new_unfiltered_parameters)
|
7
|
+
@unfiltered_parameters = Array.wrap(new_unfiltered_parameters)
|
8
|
+
end
|
9
|
+
|
10
|
+
def unfiltered_parameters
|
11
|
+
@unfiltered_parameters ||= [
|
12
|
+
# Database record ID's
|
13
|
+
'id',
|
14
|
+
# Controller actions
|
15
|
+
'action',
|
16
|
+
# Controller names
|
17
|
+
'controller',
|
18
|
+
].freeze
|
19
|
+
end
|
20
|
+
|
21
|
+
def filter_parameters=(new_filter_parameters)
|
22
|
+
@filter_parameters = new_filter_parameters
|
23
|
+
end
|
24
|
+
|
25
|
+
def filter_parameters?
|
26
|
+
return @filter_parameters if defined?(@filter_parameters)
|
27
|
+
|
28
|
+
@filter_parameters = if ENV['CASED_FILTER_PARAMETERS']
|
29
|
+
parse_bool(ENV['CASED_FILTER_PARAMETERS'])
|
30
|
+
else
|
31
|
+
::Rails.env.staging? || ::Rails.env.production?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
Cased::Config.prepend(Cased::Rails::Config)
|
data/lib/cased/rails/railtie.rb
CHANGED
@@ -5,6 +5,19 @@ require 'rails/railtie'
|
|
5
5
|
module Cased
|
6
6
|
module Rails
|
7
7
|
class Railtie < ::Rails::Railtie
|
8
|
+
initializer 'cased.parameter_filter' do |app|
|
9
|
+
app.config.filter_parameters << proc do |key, value, _original_params|
|
10
|
+
next unless Cased.config.filter_parameters?
|
11
|
+
next if Cased.config.unfiltered_parameters.include?(key) || !value.respond_to?(:replace)
|
12
|
+
|
13
|
+
value.replace(ActiveSupport::ParameterFilter::FILTERED)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
initializer 'cased.assets.precompile' do |app|
|
18
|
+
app.config.assets.precompile << 'cased/manifest.js'
|
19
|
+
end
|
20
|
+
|
8
21
|
initializer 'cased.include_controller_helpers' do
|
9
22
|
ActiveSupport.on_load(:action_controller) do
|
10
23
|
require 'cased/controller_helpers'
|
@@ -43,6 +56,23 @@ module Cased
|
|
43
56
|
# :nocov:
|
44
57
|
console do
|
45
58
|
Cased.console
|
59
|
+
|
60
|
+
# We only want to start an interactive session if Cased CLI is
|
61
|
+
# configured.
|
62
|
+
next if Cased.config.guard_application_key.blank?
|
63
|
+
|
64
|
+
session = Cased::CLI::InteractiveSession.start(command: "#{Dir.pwd}/bin/rails console")
|
65
|
+
Cased.context.merge(guard_session: session)
|
66
|
+
# If the session does not need its output recorded, we can bypass any
|
67
|
+
# forced exits.
|
68
|
+
next unless session.record_output?
|
69
|
+
|
70
|
+
# If we reach this line inside of the recorded session we don't want to
|
71
|
+
# exit but instead proceed to the `rails console` as usual.
|
72
|
+
#
|
73
|
+
# We don't want to enter the parent `rails console` so we exit right
|
74
|
+
# away as we know the child `rails console` completed successfully.
|
75
|
+
exit unless Cased::CLI::Session.current&.approved?
|
46
76
|
end
|
47
77
|
end
|
48
78
|
end
|
data/lib/cased/rails/version.rb
CHANGED
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
desc 'Enforce your Cased CLI controls before the Rake task is executed'
|
4
|
+
task :guard do
|
5
|
+
next if Cased.config.guard_application_key.blank?
|
6
|
+
|
7
|
+
session = Cased::CLI::InteractiveSession.start
|
8
|
+
next unless session.record_output?
|
9
|
+
|
10
|
+
exit unless Cased::CLI::Session.current&.approved?
|
11
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cased-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Garrett Bjerkhoel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cased-ruby
|
@@ -16,14 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.5.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.5.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: jbuilder
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rails
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -103,14 +117,37 @@ extra_rdoc_files: []
|
|
103
117
|
files:
|
104
118
|
- README.md
|
105
119
|
- Rakefile
|
120
|
+
- app/assets/config/cased/manifest.js
|
121
|
+
- app/assets/images/cased/favicon.ico
|
122
|
+
- app/assets/images/cased/logo.png
|
123
|
+
- app/assets/javascripts/cased/index.js
|
124
|
+
- app/controllers/cased/authorizations_controller.rb
|
125
|
+
- app/controllers/cased/cli/sessions_controller.rb
|
126
|
+
- app/helpers/cased_helper.rb
|
127
|
+
- app/models/cased/authorization.rb
|
128
|
+
- app/views/cased/authorizations/create.html.erb
|
129
|
+
- app/views/cased/cli/sessions/_form.html.erb
|
130
|
+
- app/views/cased/cli/sessions/_guard_session.json.jbuilder
|
131
|
+
- app/views/cased/cli/sessions/new.html.erb
|
132
|
+
- app/views/cased/cli/sessions/steps/_canceled.html.erb
|
133
|
+
- app/views/cased/cli/sessions/steps/_create.html.erb
|
134
|
+
- app/views/cased/cli/sessions/steps/_denied.html.erb
|
135
|
+
- app/views/cased/cli/sessions/steps/_reason_required.html.erb
|
136
|
+
- app/views/cased/cli/sessions/steps/_requested.html.erb
|
137
|
+
- app/views/cased/cli/sessions/steps/_timed_out.html.erb
|
138
|
+
- app/views/layouts/cased/cli.html.erb
|
139
|
+
- config/initializers/inflections.rb
|
140
|
+
- config/routes.rb
|
106
141
|
- lib/cased/controller_helpers.rb
|
107
142
|
- lib/cased/model/automatic.rb
|
108
143
|
- lib/cased/rails.rb
|
109
144
|
- lib/cased/rails/active_job.rb
|
145
|
+
- lib/cased/rails/config.rb
|
110
146
|
- lib/cased/rails/engine.rb
|
111
147
|
- lib/cased/rails/model.rb
|
112
148
|
- lib/cased/rails/railtie.rb
|
113
149
|
- lib/cased/rails/version.rb
|
150
|
+
- lib/tasks/cased.rake
|
114
151
|
homepage: https://github.com/cased/cased-rails
|
115
152
|
licenses:
|
116
153
|
- MIT
|