cased-rails 0.3.0 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|