cased-ruby 0.3.3 → 0.4.4
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/Gemfile.lock +20 -15
- data/README.md +135 -54
- data/bin/cli +14 -0
- data/cased-ruby.gemspec +2 -0
- data/lib/cased.rb +1 -0
- data/lib/cased/cli.rb +13 -0
- data/lib/cased/cli/asciinema/file.rb +108 -0
- data/lib/cased/cli/asciinema/writer.rb +69 -0
- data/lib/cased/cli/authentication.rb +31 -0
- data/lib/cased/cli/identity.rb +43 -0
- data/lib/cased/cli/interactive_session.rb +121 -0
- data/lib/cased/cli/log.rb +27 -0
- data/lib/cased/cli/recorder.rb +58 -0
- data/lib/cased/cli/session.rb +292 -0
- data/lib/cased/clients.rb +7 -3
- data/lib/cased/config.rb +58 -0
- data/lib/cased/http/client.rb +13 -8
- data/lib/cased/http/error.rb +5 -2
- data/lib/cased/query.rb +6 -3
- data/lib/cased/version.rb +1 -1
- data/vendor/cache/activesupport-6.1.3.gem +0 -0
- data/vendor/cache/concurrent-ruby-1.1.8.gem +0 -0
- data/vendor/cache/faraday-1.3.0.gem +0 -0
- data/vendor/cache/faraday-net_http-1.0.1.gem +0 -0
- data/vendor/cache/i18n-1.8.9.gem +0 -0
- data/vendor/cache/json-2.5.1.gem +0 -0
- data/vendor/cache/jwt-2.2.2.gem +0 -0
- data/vendor/cache/ruby2_keywords-0.0.4.gem +0 -0
- data/vendor/cache/subprocess-1.5.4.gem +0 -0
- data/vendor/cache/tzinfo-2.0.4.gem +0 -0
- data/vendor/cache/zeitwerk-2.4.2.gem +0 -0
- metadata +51 -11
- data/vendor/cache/activesupport-6.0.3.4.gem +0 -0
- data/vendor/cache/concurrent-ruby-1.1.7.gem +0 -0
- data/vendor/cache/faraday-1.1.0.gem +0 -0
- data/vendor/cache/i18n-1.8.5.gem +0 -0
- data/vendor/cache/json-2.3.1.gem +0 -0
- data/vendor/cache/ruby2_keywords-0.0.2.gem +0 -0
- data/vendor/cache/thread_safe-0.3.6.gem +0 -0
- data/vendor/cache/tzinfo-1.2.7.gem +0 -0
- data/vendor/cache/zeitwerk-2.4.0.gem +0 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Cased
|
6
|
+
module CLI
|
7
|
+
# Spec: https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v2.md
|
8
|
+
module Asciinema
|
9
|
+
class Writer
|
10
|
+
VERSION = 2
|
11
|
+
|
12
|
+
attr_accessor :width
|
13
|
+
attr_accessor :height
|
14
|
+
attr_reader :command
|
15
|
+
attr_reader :stream
|
16
|
+
attr_reader :started_at
|
17
|
+
attr_reader :finished_at
|
18
|
+
|
19
|
+
def initialize(command: [], width: 80, height: 24)
|
20
|
+
@command = command
|
21
|
+
@width = width
|
22
|
+
@height = height
|
23
|
+
@stream = []
|
24
|
+
@started_at = Time.now
|
25
|
+
end
|
26
|
+
|
27
|
+
def <<(output)
|
28
|
+
stream << [Time.now - started_at, 'o', output]
|
29
|
+
end
|
30
|
+
|
31
|
+
def time
|
32
|
+
@started_at = Time.now
|
33
|
+
ret = yield
|
34
|
+
@finished_at = Time.now
|
35
|
+
ret
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_cast
|
39
|
+
# In the event we didn't run the writer in a #time block, we should
|
40
|
+
# set the finished time if it isn't set.
|
41
|
+
@finished_at ||= Time.now
|
42
|
+
|
43
|
+
File.new(header, stream).to_cast
|
44
|
+
end
|
45
|
+
|
46
|
+
def header
|
47
|
+
{
|
48
|
+
'version' => VERSION,
|
49
|
+
'env' => {
|
50
|
+
'SHELL' => ENV['SHELL'],
|
51
|
+
'TERM' => ENV['TERM'],
|
52
|
+
},
|
53
|
+
'width' => width,
|
54
|
+
'height' => height,
|
55
|
+
'command' => command.join(' '),
|
56
|
+
}.tap do |h|
|
57
|
+
if started_at
|
58
|
+
h['timestamp'] = started_at.to_i
|
59
|
+
end
|
60
|
+
|
61
|
+
if started_at && finished_at
|
62
|
+
h['duration'] = finished_at - started_at
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module Cased
|
6
|
+
module CLI
|
7
|
+
class Authentication
|
8
|
+
attr_reader :directory
|
9
|
+
attr_reader :credentials_path
|
10
|
+
attr_writer :token
|
11
|
+
|
12
|
+
def initialize(token: nil)
|
13
|
+
@token = token || Cased.config.guard_user_token
|
14
|
+
@directory = Pathname.new(File.expand_path('~/.cguard'))
|
15
|
+
@credentials_path = @directory.join('credentials')
|
16
|
+
end
|
17
|
+
|
18
|
+
def exists?
|
19
|
+
!token.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def token
|
23
|
+
@token ||= begin
|
24
|
+
credentials_path.read
|
25
|
+
rescue Errno::ENOENT
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cased
|
4
|
+
module CLI
|
5
|
+
class Identity
|
6
|
+
def initialize
|
7
|
+
@timeout = 30
|
8
|
+
end
|
9
|
+
|
10
|
+
def identify
|
11
|
+
response = Cased.clients.cli.post('cli/applications/users/identify')
|
12
|
+
case response.status
|
13
|
+
when 201 # Created
|
14
|
+
url = response.body.fetch('url')
|
15
|
+
Cased::CLI::Log.log 'To login, please visit:'
|
16
|
+
puts url
|
17
|
+
poll(response.body['api_url'])
|
18
|
+
when 401 # Unauthorized
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def poll(poll_url)
|
24
|
+
count = 0
|
25
|
+
user_id = nil
|
26
|
+
ip_address = nil
|
27
|
+
|
28
|
+
while user_id.nil?
|
29
|
+
count += 1
|
30
|
+
response = Cased.clients.cli.get(poll_url)
|
31
|
+
if response.success?
|
32
|
+
user_id = response.body.dig('user', 'id')
|
33
|
+
ip_address = response.body.fetch('ip_address')
|
34
|
+
else
|
35
|
+
sleep 1
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
[user_id, ip_address]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cased/cli/session'
|
4
|
+
|
5
|
+
module Cased
|
6
|
+
module CLI
|
7
|
+
# InteractiveSession is responsible for initiating a Cased CLI session and
|
8
|
+
# responding to all its possible states.
|
9
|
+
#
|
10
|
+
# InteractiveSession is intended to be used where a TTY is present to handle
|
11
|
+
# the entire flow from authentication, reason required, waiting for
|
12
|
+
# approval, canceled, or timed out.
|
13
|
+
class InteractiveSession
|
14
|
+
def self.start(reason: nil, command: nil, metadata: {})
|
15
|
+
return Cased::CLI::Session.current if Cased::CLI::Session.current&.approved?
|
16
|
+
|
17
|
+
Cased::CLI::Log.log 'Running under Cased CLI.'
|
18
|
+
|
19
|
+
new(reason: reason, command: command, metadata: metadata).create
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :session
|
23
|
+
|
24
|
+
def initialize(reason: nil, command: nil, metadata: {})
|
25
|
+
@session = Cased::CLI::Session.new(
|
26
|
+
reason: reason,
|
27
|
+
command: command,
|
28
|
+
metadata: metadata,
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def create
|
33
|
+
signal_handler = Signal.trap('SIGINT') do
|
34
|
+
if session.requested?
|
35
|
+
Cased::CLI::Log.log 'Exiting and canceling request…'
|
36
|
+
session.cancel
|
37
|
+
exit 0
|
38
|
+
elsif signal_handler.respond_to?(:call)
|
39
|
+
# We need to call the original handler if we exit this interactive
|
40
|
+
# session successfully
|
41
|
+
signal_handler.call
|
42
|
+
else
|
43
|
+
raise Interrupt
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if session.create
|
48
|
+
handle_state(session.state)
|
49
|
+
elsif session.reauthenticate?
|
50
|
+
Cased::CLI::Log.log "You must re-authenticate with Cased due to recent changes to this application's settings."
|
51
|
+
|
52
|
+
identity = Cased::CLI::Identity.new
|
53
|
+
token, ip_address = identity.identify
|
54
|
+
session.authentication.token = token
|
55
|
+
session.forwarded_ip_address = ip_address
|
56
|
+
|
57
|
+
create
|
58
|
+
elsif session.unauthorized?
|
59
|
+
if session.authentication.exists?
|
60
|
+
Cased::CLI::Log.log "Existing credentials at #{session.authentication.credentials_path} are not valid."
|
61
|
+
else
|
62
|
+
Cased::CLI::Log.log "Could not find credentials at #{session.authentication.credentials_path}, looking up now…"
|
63
|
+
end
|
64
|
+
|
65
|
+
identity = Cased::CLI::Identity.new
|
66
|
+
token, ip_address = identity.identify
|
67
|
+
session.authentication.token = token
|
68
|
+
session.forwarded_ip_address = ip_address
|
69
|
+
|
70
|
+
create
|
71
|
+
elsif session.reason_required?
|
72
|
+
reason_prompt && create
|
73
|
+
else
|
74
|
+
Cased::CLI::Log.log 'Could not start CLI session.'
|
75
|
+
exit 1 if Cased.config.guard_deny_if_unreachable?
|
76
|
+
end
|
77
|
+
|
78
|
+
session
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def reason_prompt
|
84
|
+
print Cased::CLI::Log.string 'Please enter a reason for access: '
|
85
|
+
session.reason = STDIN.gets.chomp
|
86
|
+
end
|
87
|
+
|
88
|
+
def wait_for_approval
|
89
|
+
sleep 1
|
90
|
+
session.refresh && handle_state(session.state)
|
91
|
+
end
|
92
|
+
|
93
|
+
def waiting_for_approval_message
|
94
|
+
return if defined?(@waiting_for_approval_message_displayed)
|
95
|
+
|
96
|
+
motd = session.guard_application.dig('settings', 'message_of_the_day')
|
97
|
+
waiting_message = motd.blank? ? 'Approval request sent…' : motd
|
98
|
+
Cased::CLI::Log.log "#{waiting_message} (id: #{session.id})"
|
99
|
+
@waiting_for_approval_message_displayed = true
|
100
|
+
end
|
101
|
+
|
102
|
+
def handle_state(state)
|
103
|
+
case state
|
104
|
+
when 'approved'
|
105
|
+
Cased::CLI::Log.log 'CLI session has been approved'
|
106
|
+
session.record
|
107
|
+
when 'requested'
|
108
|
+
waiting_for_approval_message
|
109
|
+
wait_for_approval
|
110
|
+
when 'denied'
|
111
|
+
Cased::CLI::Log.log 'CLI session has been denied'
|
112
|
+
exit 1
|
113
|
+
when 'timed_out'
|
114
|
+
Cased::CLI::Log.log 'CLI session has timed out'
|
115
|
+
when 'canceled'
|
116
|
+
Cased::CLI::Log.log 'CLI session has been canceled'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cased
|
4
|
+
module CLI
|
5
|
+
module Log
|
6
|
+
CLEAR = "\e[0m"
|
7
|
+
YELLOW = "\e[33m"
|
8
|
+
BOLD = "\e[1m"
|
9
|
+
|
10
|
+
def self.string(text)
|
11
|
+
[color('[cased]', YELLOW, true), text].join(' ')
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.log(text)
|
15
|
+
puts string(text)
|
16
|
+
ensure
|
17
|
+
STDOUT.flush
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.color(text, color, bold = false)
|
21
|
+
color = self.class.const_get(color.upcase) if color.is_a?(Symbol)
|
22
|
+
bold = bold ? BOLD : ''
|
23
|
+
"#{bold}#{color}#{text}#{CLEAR}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'subprocess'
|
4
|
+
|
5
|
+
module Cased
|
6
|
+
module CLI
|
7
|
+
class Recorder
|
8
|
+
KEY = 'CASED_CLI_RECORDING'
|
9
|
+
TRUE = '1'
|
10
|
+
|
11
|
+
attr_reader :command
|
12
|
+
attr_reader :events
|
13
|
+
attr_reader :started_at
|
14
|
+
attr_reader :width
|
15
|
+
attr_reader :height
|
16
|
+
attr_reader :options
|
17
|
+
attr_accessor :writer
|
18
|
+
|
19
|
+
# @return [Boolean] if CLI session is being recorded.
|
20
|
+
def self.recording?
|
21
|
+
ENV[KEY] == TRUE
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(command, env: {})
|
25
|
+
@command = command
|
26
|
+
@events = []
|
27
|
+
@width = Subprocess.check_output(%w[tput cols]).strip.to_i
|
28
|
+
@height = Subprocess.check_output(%w[tput lines]).strip.to_i
|
29
|
+
|
30
|
+
subprocess_env = ENV.to_h.dup
|
31
|
+
subprocess_env[KEY] = TRUE
|
32
|
+
subprocess_env.merge!(env)
|
33
|
+
@writer = Cased::CLI::Asciinema::Writer.new(
|
34
|
+
command: command,
|
35
|
+
width: width,
|
36
|
+
height: height,
|
37
|
+
)
|
38
|
+
|
39
|
+
@options = {
|
40
|
+
stdout: Subprocess::PIPE,
|
41
|
+
env: subprocess_env,
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def start
|
46
|
+
writer.time do
|
47
|
+
Subprocess.check_call(command, options) do |t|
|
48
|
+
t.communicate do |stdout, _stderr|
|
49
|
+
STDOUT.write(stdout)
|
50
|
+
|
51
|
+
writer << stdout.gsub("\n", "\r\n")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,292 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cased/cli/authentication'
|
4
|
+
require 'cased/cli/identity'
|
5
|
+
require 'cased/cli/recorder'
|
6
|
+
require 'cased/model'
|
7
|
+
|
8
|
+
module Cased
|
9
|
+
module CLI
|
10
|
+
class Session
|
11
|
+
include Cased::Model
|
12
|
+
|
13
|
+
def self.find(guard_session_id)
|
14
|
+
authentication = Cased::CLI::Authentication.new
|
15
|
+
|
16
|
+
response = Cased.clients.cli.get("cli/sessions/#{guard_session_id}", user_token: authentication.token)
|
17
|
+
new.tap do |session|
|
18
|
+
session.session = response.body
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# If we're inside of a recorded session we can lookup the session
|
23
|
+
# we're in.
|
24
|
+
def self.current
|
25
|
+
@current ||= if ENV['GUARD_SESSION_ID']
|
26
|
+
Cased::CLI::Session.find(ENV['GUARD_SESSION_ID'])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.current?
|
31
|
+
current.present?
|
32
|
+
end
|
33
|
+
|
34
|
+
class << self
|
35
|
+
attr_writer :current
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Cased::CLI::Authentication]
|
39
|
+
attr_reader :authentication
|
40
|
+
|
41
|
+
# Public: The CLI session ID
|
42
|
+
# @example
|
43
|
+
# session.id #=> "guard_session_1oFqm5GBQYwhH8pfIpnS0A5QgFJ"
|
44
|
+
# @return [String, nil]
|
45
|
+
attr_reader :id
|
46
|
+
|
47
|
+
# Public: The CLI session web URL
|
48
|
+
# @example
|
49
|
+
# session.url #=> "https://api.cased.com/cli/programs/ruby/sessions/guard_session_1oFqm5GBQYwhH8pfIpnS0A5QgFJ"
|
50
|
+
# @return [String, nil]
|
51
|
+
attr_reader :url
|
52
|
+
|
53
|
+
# Public: The CLI session API URL
|
54
|
+
# @example
|
55
|
+
# session.api_url #=> "https://api.cased.com/cli/sessions/guard_session_1oFqm5GBQYwhH8pfIpnS0A5QgFJ"
|
56
|
+
# @return [String, nil]
|
57
|
+
attr_reader :api_url
|
58
|
+
|
59
|
+
# Public: The CLI session record API URL
|
60
|
+
# @example
|
61
|
+
# session.api_record_url #=> "https://api.cased.com/cli/sessions/guard_session_1oFqm5GBQYwhH8pfIpnS0A5QgFJ/record"
|
62
|
+
# @return [String, nil]
|
63
|
+
attr_reader :api_record_url
|
64
|
+
|
65
|
+
# Public: The current state the CLI session is in
|
66
|
+
# @example
|
67
|
+
# session.api_url #=> "approved"
|
68
|
+
# @return [String, nil]
|
69
|
+
attr_reader :state
|
70
|
+
|
71
|
+
# Public: Command that invoked CLI session.
|
72
|
+
# @example
|
73
|
+
# session.command #=> "/usr/local/bin/rails console"
|
74
|
+
# @return [String]
|
75
|
+
attr_accessor :command
|
76
|
+
|
77
|
+
# Public: Additional user supplied metadata about the CLI session.
|
78
|
+
# @example
|
79
|
+
# session.metadata #=> {"hostname" => "Mac.local"}
|
80
|
+
# @return [Hash]
|
81
|
+
attr_accessor :metadata
|
82
|
+
|
83
|
+
# Public: The user supplied reason for the CLI session for taking place.
|
84
|
+
# @example
|
85
|
+
# session.reason #=> "Investigating customer support ticket."
|
86
|
+
# @return [String, nil]
|
87
|
+
attr_accessor :reason
|
88
|
+
|
89
|
+
# Public: The forwarded IP V4 or IP V6 address of the user that initiated
|
90
|
+
# the CLI session.
|
91
|
+
#
|
92
|
+
# @example
|
93
|
+
# session.forwarded_ip_address #=> "1.1.1.1"
|
94
|
+
# @return [String, nil]
|
95
|
+
attr_accessor :forwarded_ip_address
|
96
|
+
|
97
|
+
# Public: The client's IP V4 or IP V6 address that initiated the CLI session.
|
98
|
+
# @example
|
99
|
+
# session.ip_address #=> "1.1.1.1"
|
100
|
+
# @return [String, nil]
|
101
|
+
attr_reader :ip_address
|
102
|
+
|
103
|
+
# Public: The Cased user that requested the CLI session.
|
104
|
+
# @example
|
105
|
+
# session.requester #=> {"id" => "user_1oFqlROLNRGVLOXJSsHkJiVmylr"}
|
106
|
+
# @return [Hash, nil]
|
107
|
+
attr_reader :requester
|
108
|
+
|
109
|
+
# Public: The Cased user that requested the CLI session.
|
110
|
+
# @example
|
111
|
+
# session.responded_at #=> "2021-02-10 12:08:44 -0800"
|
112
|
+
# @return [Time, nil]
|
113
|
+
attr_reader :responded_at
|
114
|
+
|
115
|
+
# Public: The Cased user that responded to the CLI session.
|
116
|
+
# @example
|
117
|
+
# session.responder #=> {"id" => "user_1oFqlROLNRGVLOXJSsHkJiVmylr"}
|
118
|
+
# @return [Hash, nil]
|
119
|
+
attr_reader :responder
|
120
|
+
|
121
|
+
# Public: The CLI application that the CLI session belongs to.
|
122
|
+
# @example
|
123
|
+
# session.guard_application #=> {"id" => "guard_application_1oFqltbMqSEtJQKRCAYQNrQoXsS"}
|
124
|
+
# @return [Hash, nil]
|
125
|
+
attr_reader :guard_application
|
126
|
+
|
127
|
+
def initialize(reason: nil, command: nil, metadata: {}, authentication: nil)
|
128
|
+
@authentication = authentication || Cased::CLI::Authentication.new
|
129
|
+
@reason = reason
|
130
|
+
@command = command || [$PROGRAM_NAME, *ARGV].join(' ')
|
131
|
+
@metadata = metadata
|
132
|
+
@requester = {}
|
133
|
+
@responder = {}
|
134
|
+
@guard_application = {}
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_s
|
138
|
+
command
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_param
|
142
|
+
id
|
143
|
+
end
|
144
|
+
|
145
|
+
def session=(session)
|
146
|
+
@error = nil
|
147
|
+
@id = session.fetch('id')
|
148
|
+
@api_url = session.fetch('api_url')
|
149
|
+
@api_record_url = session.fetch('api_record_url')
|
150
|
+
@url = session.fetch('url')
|
151
|
+
@state = session.fetch('state')
|
152
|
+
@command = session.fetch('command')
|
153
|
+
@metadata = session.fetch('metadata')
|
154
|
+
@reason = session.fetch('reason')
|
155
|
+
@forwarded_ip_address = session.fetch('forwarded_ip_address')
|
156
|
+
@ip_address = session.fetch('ip_address')
|
157
|
+
@requester = session.fetch('requester')
|
158
|
+
@responded_at = session['responded_at']
|
159
|
+
@responder = session['responder'] || {}
|
160
|
+
@guard_application = session.fetch('guard_application')
|
161
|
+
end
|
162
|
+
|
163
|
+
def requested?
|
164
|
+
state == 'requested'
|
165
|
+
end
|
166
|
+
|
167
|
+
def approved?
|
168
|
+
state == 'approved'
|
169
|
+
end
|
170
|
+
|
171
|
+
def denied?
|
172
|
+
state == 'denied'
|
173
|
+
end
|
174
|
+
|
175
|
+
def canceled?
|
176
|
+
state == 'canceled'
|
177
|
+
end
|
178
|
+
|
179
|
+
def timed_out?
|
180
|
+
state == 'timed_out'
|
181
|
+
end
|
182
|
+
|
183
|
+
def refresh
|
184
|
+
return false unless api_url
|
185
|
+
|
186
|
+
response = Cased.clients.cli.get(api_url, user_token: authentication.token)
|
187
|
+
self.session = response.body
|
188
|
+
end
|
189
|
+
|
190
|
+
def error?
|
191
|
+
!error.nil?
|
192
|
+
end
|
193
|
+
|
194
|
+
def success?
|
195
|
+
id && !error?
|
196
|
+
end
|
197
|
+
|
198
|
+
def reason_required?
|
199
|
+
error == :reason_required || guard_application.dig('settings', 'reason_required')
|
200
|
+
end
|
201
|
+
|
202
|
+
def unauthorized?
|
203
|
+
error == :unauthorized
|
204
|
+
end
|
205
|
+
|
206
|
+
def reauthenticate?
|
207
|
+
error == :reauthenticate
|
208
|
+
end
|
209
|
+
|
210
|
+
def record_output?
|
211
|
+
guard_application.dig('settings', 'record_output') || false
|
212
|
+
end
|
213
|
+
|
214
|
+
def record
|
215
|
+
return false unless recordable? && record_output?
|
216
|
+
|
217
|
+
Cased::CLI::Log.log 'CLI session is now recording'
|
218
|
+
|
219
|
+
recorder = Cased::CLI::Recorder.new(command.split(' '), env: {
|
220
|
+
'GUARD_SESSION_ID' => id,
|
221
|
+
'GUARD_APPLICATION_ID' => guard_application.fetch('id'),
|
222
|
+
'GUARD_USER_TOKEN' => requester.fetch('id'),
|
223
|
+
})
|
224
|
+
recorder.start
|
225
|
+
|
226
|
+
Cased.clients.cli.put(api_record_url,
|
227
|
+
recording: recorder.writer.to_cast,
|
228
|
+
user_token: authentication.token)
|
229
|
+
|
230
|
+
Cased::CLI::Log.log 'CLI session recorded'
|
231
|
+
end
|
232
|
+
|
233
|
+
def create
|
234
|
+
return false unless id.nil?
|
235
|
+
|
236
|
+
response = Cased.clients.cli.post('cli/sessions',
|
237
|
+
user_token: authentication.token,
|
238
|
+
forwarded_ip_address: forwarded_ip_address,
|
239
|
+
reason: reason,
|
240
|
+
metadata: metadata,
|
241
|
+
command: command)
|
242
|
+
if response.success?
|
243
|
+
self.session = response.body
|
244
|
+
else
|
245
|
+
case response.body['error']
|
246
|
+
when 'reason_required'
|
247
|
+
@error = :reason_required
|
248
|
+
when 'unauthorized'
|
249
|
+
@error = :unauthorized
|
250
|
+
when 'reauthenticate'
|
251
|
+
@error = :reauthenticate
|
252
|
+
else
|
253
|
+
@error = true
|
254
|
+
return false
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
response.success?
|
259
|
+
end
|
260
|
+
|
261
|
+
def cancel
|
262
|
+
response = Cased.clients.cli.post("#{api_url}/cancel", user_token: authentication.token)
|
263
|
+
self.session = response.body
|
264
|
+
|
265
|
+
canceled?
|
266
|
+
end
|
267
|
+
|
268
|
+
def cased_category
|
269
|
+
:cli
|
270
|
+
end
|
271
|
+
|
272
|
+
def cased_id
|
273
|
+
id
|
274
|
+
end
|
275
|
+
|
276
|
+
def cased_context(category: cased_category)
|
277
|
+
{
|
278
|
+
"#{category}_id".to_sym => cased_id,
|
279
|
+
category.to_sym => to_s,
|
280
|
+
}
|
281
|
+
end
|
282
|
+
|
283
|
+
def recordable?
|
284
|
+
STDOUT.isatty
|
285
|
+
end
|
286
|
+
|
287
|
+
private
|
288
|
+
|
289
|
+
attr_reader :error
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|