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