cased-rails 0.3.0 → 0.4.3
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/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 +45 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49f90bda9670e4c31103308b7140637c601f769112a8f94873f48b2f8e957a75
|
4
|
+
data.tar.gz: 50788a3192675057b100135a72c8b33b6e263961c181683de574585663a7e494
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f960a6c6bbaad726b8efae2dc34bec84c2f8cf45389bcc1785c50f5efc2b463f4b703c1560c1ab1bdd3baf3626cca80f00671050cb134d3d959c3f82ae708ce
|
7
|
+
data.tar.gz: 0dba75ffb963563d4122aa473dbfb0aca765b0fbc5d970eb3876e77a1a05720f7e5bb22a2d0484bd1ae6bf2e96d6d719af43e5d6fefcc54ec7c8e74c157e1256
|
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.3
|
4
|
+
version: 0.4.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Garrett Bjerkhoel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cased-ruby
|
@@ -16,28 +16,42 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.4.3
|
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.4.3
|
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
|
30
44
|
requirements:
|
31
|
-
- - "
|
45
|
+
- - ">="
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version: 6.0
|
47
|
+
version: '6.0'
|
34
48
|
type: :runtime
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
|
-
- - "
|
52
|
+
- - ">="
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version: 6.0
|
54
|
+
version: '6.0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: mocha
|
43
57
|
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
|