cased-rails 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d0e3655a9bc7ff8cce27657993146a0eda10999481d2c89194c88bd5f601262
4
- data.tar.gz: 78a0974005fe163ff98c154a8848bbdeb280af5d757c2f6330b6de7a482b8b5e
3
+ metadata.gz: 15e27ebfe2d7455bd3cee960128de6ff27626fc9ad4a229e6f6670ee272e9222
4
+ data.tar.gz: c1ffe4197af92bc672e6d0cdfa9063707d5735b81f2c1f4a44c863c4b9aaf6d7
5
5
  SHA512:
6
- metadata.gz: 4deb9a511c4079537c25af408aebac3342ce39ed8706874e99e513cb1c66e05ef141d5927dabda1cd2f100930a991dbb4bef37dda340de3604456ccaf74b6aad
7
- data.tar.gz: 61e3279ca41dab08c8a92cbc47876bf4f147976283e83f7c68798f637efacce07d79ecc25aacbd9fa3d5f3f2ad9517f90b472a234e2fd5ef0442454fc64792ab
6
+ metadata.gz: c00b195a048a091dbf64bc85c3c27401aa749bd206939cdfaf25e3e76e7e711e2d4c89056a80a9ef44dd27998ab812fe8c1c88c3ad4eafe14ee4675894c0ed46
7
+ data.tar.gz: a2f7344896353de1ed38c1602b97d7badb5ee1d438753ade7d3f8c63538a7d691ee9768e16f21af19b19061c554a8c4e0de3bc6c3fb526d9eed3c3a6524c74fb
@@ -0,0 +1,2 @@
1
+ //= link_tree ../../images/cased
2
+ //= link_directory ../../javascripts/cased .js
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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cased
4
+ class AuthorizationsController < ApplicationController
5
+ def create
6
+ self.cased_authorization = params[:token]
7
+ end
8
+
9
+ def destroy
10
+ self.cased_authorization = nil
11
+ end
12
+ end
13
+ end
@@ -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: 'none', 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' %>
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>
@@ -0,0 +1,3 @@
1
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
2
+ inflect.acronym 'CLI'
3
+ end
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
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'cased-ruby'
4
+ require 'cased/rails/config'
4
5
  require 'cased/rails/railtie'
5
6
  require 'cased/rails/engine'
6
7
  require 'cased/model/automatic'
@@ -0,0 +1,58 @@
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 skip_recording_console=(should_skip_recording_console)
22
+ @skip_recording_console = should_skip_recording_console
23
+ end
24
+
25
+ def skip_recording_console?
26
+ return @skip_recording_console if defined?(@skip_recording_console)
27
+
28
+ @skip_recording_console = ::Rails.env.development? || ::Rails.env.test?
29
+ end
30
+
31
+ def filter_parameters=(new_filter_parameters)
32
+ @filter_parameters = new_filter_parameters
33
+ end
34
+
35
+ def filter_parameters?
36
+ return @filter_parameters if defined?(@filter_parameters)
37
+
38
+ @filter_parameters = if ENV['CASED_FILTER_PARAMETERS']
39
+ parse_bool(ENV['CASED_FILTER_PARAMETERS'])
40
+ else
41
+ true
42
+ end
43
+ end
44
+
45
+ def url=(url)
46
+ @url = url
47
+ end
48
+
49
+ def url
50
+ return @url if defined?(@url)
51
+
52
+ @url = ENV.fetch('CASED_URL', 'https://app.cased.com')
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ Cased::Config.prepend(Cased::Rails::Config)
@@ -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,22 @@ module Cased
43
56
  # :nocov:
44
57
  console do
45
58
  Cased.console
59
+
60
+ # We only want to record any non-development or test console sessions.
61
+ next if Cased.config.skip_recording_console?
62
+
63
+ session = Cased::CLI::InteractiveSession.start(command: "#{Dir.pwd}/bin/rails console")
64
+ Cased.context.merge(guard_session: session)
65
+ # If the session does not need its output recorded, we can bypass any
66
+ # forced exits.
67
+ next unless session.record_output?
68
+
69
+ # If we reach this line inside of the recorded session we don't want to
70
+ # exit but instead proceed to the `rails console` as usual.
71
+ #
72
+ # We don't want to enter the parent `rails console` so we exit right
73
+ # away as we know the child `rails console` completed successfully.
74
+ exit unless Cased::CLI::Session.current&.approved?
46
75
  end
47
76
  end
48
77
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cased
4
4
  module Rails
5
- VERSION = '0.3.1'
5
+ VERSION = '0.4.0'
6
6
  end
7
7
  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.1
4
+ version: 0.4.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-01-21 00:00:00.000000000 Z
11
+ date: 2021-03-03 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.3.3
19
+ version: 0.4.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.3.3
26
+ version: 0.4.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,10 +117,32 @@ 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