terminalwire 0.2.4 → 0.3.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/exe/terminalwire +1 -1
- data/exe/terminalwire-exec +1 -1
- metadata +9 -169
- data/.rspec +0 -3
- data/CHANGELOG.md +0 -5
- data/CODE_OF_CONDUCT.md +0 -132
- data/LICENSE.txt +0 -9
- data/README.md +0 -45
- data/Rakefile +0 -8
- data/examples/exec/localrails +0 -2
- data/lib/generators/terminalwire/install/USAGE +0 -9
- data/lib/generators/terminalwire/install/install_generator.rb +0 -38
- data/lib/generators/terminalwire/install/templates/application_terminal.rb.tt +0 -21
- data/lib/generators/terminalwire/install/templates/bin/terminalwire +0 -2
- data/lib/generators/terminalwire/install/templates/main_terminal.rb +0 -40
- data/lib/terminalwire/adapter.rb +0 -53
- data/lib/terminalwire/cache.rb +0 -114
- data/lib/terminalwire/client/entitlement/environment_variables.rb +0 -26
- data/lib/terminalwire/client/entitlement/paths.rb +0 -97
- data/lib/terminalwire/client/entitlement/policy.rb +0 -117
- data/lib/terminalwire/client/entitlement/schemes.rb +0 -26
- data/lib/terminalwire/client/entitlement.rb +0 -9
- data/lib/terminalwire/client/exec.rb +0 -44
- data/lib/terminalwire/client/handler.rb +0 -67
- data/lib/terminalwire/client/resource.rb +0 -204
- data/lib/terminalwire/client/server_license_verification.rb +0 -74
- data/lib/terminalwire/client.rb +0 -30
- data/lib/terminalwire/logging.rb +0 -8
- data/lib/terminalwire/rails.rb +0 -69
- data/lib/terminalwire/server/context.rb +0 -80
- data/lib/terminalwire/server/resource.rb +0 -120
- data/lib/terminalwire/server.rb +0 -63
- data/lib/terminalwire/thor.rb +0 -55
- data/lib/terminalwire/transport.rb +0 -58
- data/lib/terminalwire/version.rb +0 -5
- data/lib/terminalwire.rb +0 -56
- data/sig/terminalwire.rbs +0 -4
data/lib/terminalwire/cache.rb
DELETED
@@ -1,114 +0,0 @@
|
|
1
|
-
require "pathname"
|
2
|
-
require "msgpack"
|
3
|
-
require "base64"
|
4
|
-
require "time"
|
5
|
-
require "fileutils"
|
6
|
-
|
7
|
-
# Caches used on the client side for licesens, HTTP requests, etc.
|
8
|
-
module Terminalwire::Cache
|
9
|
-
module File
|
10
|
-
# Hoist the File class to avoid conflicts with the standard library.
|
11
|
-
File = ::File
|
12
|
-
|
13
|
-
class Store
|
14
|
-
include Enumerable
|
15
|
-
|
16
|
-
def initialize(path:)
|
17
|
-
@path = Pathname.new(path).expand_path
|
18
|
-
FileUtils.mkdir_p(@path) unless @path.directory?
|
19
|
-
end
|
20
|
-
|
21
|
-
def entry(key)
|
22
|
-
Entry.new(path: @path.join(Entry.key_path(key)))
|
23
|
-
end
|
24
|
-
alias :[] :entry
|
25
|
-
|
26
|
-
def evict
|
27
|
-
each(&:evict)
|
28
|
-
end
|
29
|
-
|
30
|
-
def destroy
|
31
|
-
each(&:destroy)
|
32
|
-
end
|
33
|
-
|
34
|
-
def each
|
35
|
-
@path.each_child do |path|
|
36
|
-
yield Entry.new(path:)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
class Entry
|
42
|
-
VERSION = "1.0"
|
43
|
-
|
44
|
-
def self.key_path(value)
|
45
|
-
Base64.urlsafe_encode64(value)
|
46
|
-
end
|
47
|
-
|
48
|
-
attr_accessor :value, :expires
|
49
|
-
|
50
|
-
def initialize(path:)
|
51
|
-
@path = path
|
52
|
-
deserialize if persisted?
|
53
|
-
end
|
54
|
-
|
55
|
-
def nil?
|
56
|
-
@value.nil?
|
57
|
-
end
|
58
|
-
|
59
|
-
def present?
|
60
|
-
not nil?
|
61
|
-
end
|
62
|
-
|
63
|
-
def persisted?
|
64
|
-
File.exist? @path
|
65
|
-
end
|
66
|
-
|
67
|
-
def expired?(time: Time.now)
|
68
|
-
@expires && @expires < time.utc
|
69
|
-
end
|
70
|
-
|
71
|
-
def fresh?(...)
|
72
|
-
not expired?(...)
|
73
|
-
end
|
74
|
-
|
75
|
-
def hit?
|
76
|
-
persisted? and fresh?
|
77
|
-
end
|
78
|
-
|
79
|
-
def miss?
|
80
|
-
not hit?
|
81
|
-
end
|
82
|
-
|
83
|
-
def save
|
84
|
-
File.write @path, serialize
|
85
|
-
end
|
86
|
-
|
87
|
-
def evict
|
88
|
-
destroy if expired?
|
89
|
-
end
|
90
|
-
|
91
|
-
def deserialize
|
92
|
-
case MessagePack.unpack(File.read(@path), symbolize_keys: true)
|
93
|
-
in { value:, expires:, version: VERSION }
|
94
|
-
@value = value
|
95
|
-
@expires = Time.parse(expires).utc if expires
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def destroy
|
100
|
-
File.delete(@path)
|
101
|
-
end
|
102
|
-
|
103
|
-
private
|
104
|
-
|
105
|
-
def serialize
|
106
|
-
MessagePack.pack(
|
107
|
-
value: @value,
|
108
|
-
expires: @expires&.utc&.iso8601,
|
109
|
-
version: VERSION
|
110
|
-
)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
module Terminalwire::Client::Entitlement
|
2
|
-
# ENV vars that the server can access on the client.
|
3
|
-
class EnvironmentVariables
|
4
|
-
include Enumerable
|
5
|
-
|
6
|
-
def initialize
|
7
|
-
@permitted = Set.new
|
8
|
-
end
|
9
|
-
|
10
|
-
def each(&)
|
11
|
-
@permitted.each(&)
|
12
|
-
end
|
13
|
-
|
14
|
-
def permit(variable)
|
15
|
-
@permitted << variable.to_s
|
16
|
-
end
|
17
|
-
|
18
|
-
def permitted?(key)
|
19
|
-
include? key.to_s
|
20
|
-
end
|
21
|
-
|
22
|
-
def serialize
|
23
|
-
map { |name| { name: } }
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,97 +0,0 @@
|
|
1
|
-
module Terminalwire::Client::Entitlement
|
2
|
-
# A list of paths and permissions that server has to write on the client workstation.
|
3
|
-
class Paths
|
4
|
-
class Permit
|
5
|
-
attr_reader :path, :mode
|
6
|
-
# Ensure the default file mode is read/write for owner only. This ensures
|
7
|
-
# that if the server tries uploading an executable file, it won't be when it
|
8
|
-
# lands on the client.
|
9
|
-
#
|
10
|
-
# Eventually we'll move this into entitlements so the client can set maximum
|
11
|
-
# permissions for files and directories.
|
12
|
-
MODE = 0o600 # rw-------
|
13
|
-
|
14
|
-
# Constants for permission bit masks
|
15
|
-
OWNER_PERMISSIONS = 0o700 # rwx------
|
16
|
-
GROUP_PERMISSIONS = 0o070 # ---rwx---
|
17
|
-
OTHERS_PERMISSIONS = 0o007 # ------rwx
|
18
|
-
|
19
|
-
# We'll validate that modes are within this range.
|
20
|
-
MODE_RANGE = 0o000..0o777
|
21
|
-
|
22
|
-
def initialize(path:, mode: MODE)
|
23
|
-
@path = Pathname.new(path)
|
24
|
-
@mode = convert(mode)
|
25
|
-
end
|
26
|
-
|
27
|
-
def permitted_path?(path)
|
28
|
-
# This MUST be done via File.fnmatch because Pathname#fnmatch does not work. If you
|
29
|
-
# try changing this 🚨 YOU MAY CIRCUMVENT THE SECURITY MEASURES IN PLACE. 🚨
|
30
|
-
File.fnmatch @path.to_s, path.to_s, File::FNM_PATHNAME
|
31
|
-
end
|
32
|
-
|
33
|
-
def permitted_mode?(value)
|
34
|
-
# Ensure the mode is at least as permissive as the permitted mode.
|
35
|
-
mode = convert(value)
|
36
|
-
|
37
|
-
# Extract permission bits for owner, group, and others
|
38
|
-
owner_bits = mode & OWNER_PERMISSIONS
|
39
|
-
group_bits = mode & GROUP_PERMISSIONS
|
40
|
-
others_bits = mode & OTHERS_PERMISSIONS
|
41
|
-
|
42
|
-
# Ensure that the mode doesn't grant more permissions than @mode in any class (owner, group, others)
|
43
|
-
(owner_bits <= @mode & OWNER_PERMISSIONS) &&
|
44
|
-
(group_bits <= @mode & GROUP_PERMISSIONS) &&
|
45
|
-
(others_bits <= @mode & OTHERS_PERMISSIONS)
|
46
|
-
end
|
47
|
-
|
48
|
-
def permitted?(path:, mode: @mode)
|
49
|
-
permitted_path?(path) && permitted_mode?(mode)
|
50
|
-
end
|
51
|
-
|
52
|
-
def serialize
|
53
|
-
{
|
54
|
-
location: @path.to_s,
|
55
|
-
mode: @mode
|
56
|
-
}
|
57
|
-
end
|
58
|
-
|
59
|
-
protected
|
60
|
-
def convert(value)
|
61
|
-
mode = Integer(value)
|
62
|
-
raise ArgumentError, "The mode #{format_octet value} must be an octet value between #{format_octet MODE_RANGE.first} and #{format_octet MODE_RANGE.last}" unless MODE_RANGE.cover?(mode)
|
63
|
-
mode
|
64
|
-
end
|
65
|
-
|
66
|
-
def format_octet(value)
|
67
|
-
format("0o%03o", value)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
include Enumerable
|
72
|
-
|
73
|
-
def initialize
|
74
|
-
@permitted = []
|
75
|
-
end
|
76
|
-
|
77
|
-
def each(&)
|
78
|
-
@permitted.each(&)
|
79
|
-
end
|
80
|
-
|
81
|
-
def permit(path, **)
|
82
|
-
@permitted.append Permit.new(path:, **)
|
83
|
-
end
|
84
|
-
|
85
|
-
def permitted?(path, mode: nil)
|
86
|
-
if mode
|
87
|
-
find { |it| it.permitted_path?(path) and it.permitted_mode?(mode) }
|
88
|
-
else
|
89
|
-
find { |it| it.permitted_path?(path) }
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def serialize
|
94
|
-
map(&:serialize)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
@@ -1,117 +0,0 @@
|
|
1
|
-
module Terminalwire::Client::Entitlement
|
2
|
-
module Policy
|
3
|
-
# A policy has the authority, paths, and schemes that the server is allowed to access.
|
4
|
-
class Base
|
5
|
-
attr_reader :paths, :authority, :schemes, :environment_variables
|
6
|
-
|
7
|
-
def initialize(authority:)
|
8
|
-
@authority = authority
|
9
|
-
@paths = Paths.new
|
10
|
-
|
11
|
-
# Permit the domain directory. This is necessary for basic operation of the client.
|
12
|
-
@paths.permit storage_path
|
13
|
-
@paths.permit storage_pattern
|
14
|
-
|
15
|
-
@schemes = Schemes.new
|
16
|
-
# Permit http & https by default.
|
17
|
-
@schemes.permit "http"
|
18
|
-
@schemes.permit "https"
|
19
|
-
|
20
|
-
@environment_variables = EnvironmentVariables.new
|
21
|
-
# Permit the HOME and TERMINALWIRE_HOME environment variables.
|
22
|
-
@environment_variables.permit "TERMINALWIRE_HOME"
|
23
|
-
end
|
24
|
-
|
25
|
-
def root_path
|
26
|
-
# TODO: This needs to be passed into the Policy so that it can be set by the client.
|
27
|
-
Terminalwire::Client.root_path
|
28
|
-
end
|
29
|
-
|
30
|
-
def authority_path
|
31
|
-
root_path.join("authorities/#{authority}")
|
32
|
-
end
|
33
|
-
|
34
|
-
def storage_path
|
35
|
-
authority_path.join("storage")
|
36
|
-
end
|
37
|
-
|
38
|
-
def storage_pattern
|
39
|
-
storage_path.join("**/*")
|
40
|
-
end
|
41
|
-
|
42
|
-
def serialize
|
43
|
-
{
|
44
|
-
authority: @authority,
|
45
|
-
schemes: @schemes.serialize,
|
46
|
-
paths: @paths.serialize,
|
47
|
-
environment_variables: @environment_variables.serialize
|
48
|
-
}
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
class Root < Base
|
53
|
-
AUTHORITY = "terminalwire.com".freeze
|
54
|
-
|
55
|
-
# Terminalwire checks these to install the binary stubs path.
|
56
|
-
SHELL_INITIALIZATION_FILE_PATHS = %w[
|
57
|
-
~/.bash_profile
|
58
|
-
~/.bashrc
|
59
|
-
~/.zprofile
|
60
|
-
~/.zshrc
|
61
|
-
~/.profile
|
62
|
-
~/.config/fish/config.fish
|
63
|
-
~/.bash_login
|
64
|
-
~/.cshrc
|
65
|
-
~/.tcshrc
|
66
|
-
].freeze
|
67
|
-
|
68
|
-
# Ensure the binary stubs are executable. This increases the
|
69
|
-
# file mode entitlement so that stubs created in ./bin are executable.
|
70
|
-
BINARY_PATH_FILE_MODE = 0o755
|
71
|
-
|
72
|
-
def initialize(*, **, &)
|
73
|
-
# Make damn sure the authority is set to Terminalwire.
|
74
|
-
super(*, authority: AUTHORITY, **, &)
|
75
|
-
|
76
|
-
# Now setup special permitted paths.
|
77
|
-
@paths.permit root_path
|
78
|
-
@paths.permit root_pattern
|
79
|
-
# Permit the dotfiles so terminalwire can install the binary stubs.
|
80
|
-
SHELL_INITIALIZATION_FILE_PATHS.each do |path|
|
81
|
-
@paths.permit path
|
82
|
-
end
|
83
|
-
|
84
|
-
# Permit terminalwire to grant execute permissions to the binary stubs.
|
85
|
-
@paths.permit binary_pattern, mode: BINARY_PATH_FILE_MODE
|
86
|
-
|
87
|
-
# Used to check if terminalwire is setup in the user's PATH environment variable.
|
88
|
-
@environment_variables.permit "PATH"
|
89
|
-
end
|
90
|
-
|
91
|
-
# Grant access to the `~/.terminalwire/**/*` path so users can install
|
92
|
-
# terminalwire apps via `terminalwire install svbtle`, etc.
|
93
|
-
def root_pattern
|
94
|
-
root_path.join("**/*").freeze
|
95
|
-
end
|
96
|
-
|
97
|
-
# Path where the terminalwire binary stubs are stored.
|
98
|
-
def binary_path
|
99
|
-
root_path.join("bin").freeze
|
100
|
-
end
|
101
|
-
|
102
|
-
# Pattern for the binary path.
|
103
|
-
def binary_pattern
|
104
|
-
binary_path.join("*").freeze
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def self.resolve(*, authority:, **, &)
|
109
|
-
case authority
|
110
|
-
when Policy::Root::AUTHORITY
|
111
|
-
Root.new(*, **, &)
|
112
|
-
else
|
113
|
-
Base.new *, authority:, **, &
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
module Terminalwire::Client::Entitlement
|
2
|
-
# URLs the server can open on the client.
|
3
|
-
class Schemes
|
4
|
-
include Enumerable
|
5
|
-
|
6
|
-
def initialize
|
7
|
-
@permitted = Set.new
|
8
|
-
end
|
9
|
-
|
10
|
-
def each(&)
|
11
|
-
@permitted.each(&)
|
12
|
-
end
|
13
|
-
|
14
|
-
def permit(scheme)
|
15
|
-
@permitted << scheme.to_s
|
16
|
-
end
|
17
|
-
|
18
|
-
def permitted?(url)
|
19
|
-
include? URI(url).scheme
|
20
|
-
end
|
21
|
-
|
22
|
-
def serialize
|
23
|
-
map { |scheme| { scheme: } }
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,9 +0,0 @@
|
|
1
|
-
require "pathname"
|
2
|
-
|
3
|
-
module Terminalwire::Client
|
4
|
-
# Entitlements are the security boundary between the server and the client that lives on the client.
|
5
|
-
# The server might request a file or directory from the client, and the client will check the entitlements
|
6
|
-
# to see if the server is authorized to access the requested resource.
|
7
|
-
module Entitlement
|
8
|
-
end
|
9
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
require "pathname"
|
2
|
-
require "yaml"
|
3
|
-
require "uri"
|
4
|
-
|
5
|
-
module Terminalwire::Client
|
6
|
-
# Called by the `terminalwire-exec` shebang in scripts. This makes it easy for people
|
7
|
-
# to create their own scripts that use Terminalwire that look like this:
|
8
|
-
#
|
9
|
-
# ```sh
|
10
|
-
# #!/usr/bin/env terminalwire-exec
|
11
|
-
# url: "https://terminalwire.com/terminal"
|
12
|
-
# ```
|
13
|
-
#
|
14
|
-
# These files are saved, then `chmod + x` is run on them and they become executable.
|
15
|
-
class Exec
|
16
|
-
attr_reader :arguments, :path, :configuration, :url
|
17
|
-
|
18
|
-
def initialize(path:, arguments:)
|
19
|
-
@arguments = arguments
|
20
|
-
@path = Pathname.new(path)
|
21
|
-
@configuration = YAML.safe_load_file(@path)
|
22
|
-
@url = URI(@configuration.fetch("url"))
|
23
|
-
rescue Errno::ENOENT => e
|
24
|
-
raise Terminalwire::Error, "File not found: #{@path}"
|
25
|
-
rescue URI::InvalidURIError => e
|
26
|
-
raise Terminalwire::Error, "Invalid URI: #{@url}"
|
27
|
-
rescue KeyError => e
|
28
|
-
raise Terminalwire::Error, "Missing key in configuration: #{e}"
|
29
|
-
end
|
30
|
-
|
31
|
-
def start
|
32
|
-
Terminalwire::Client.websocket(url:, arguments:)
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.start
|
36
|
-
case ARGV
|
37
|
-
in path, *arguments
|
38
|
-
new(path:, arguments:).start
|
39
|
-
end
|
40
|
-
rescue NoMatchingPatternError => e
|
41
|
-
raise Terminalwire::Error, "Launched with incorrect arguments: #{ARGV}"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,67 +0,0 @@
|
|
1
|
-
module Terminalwire::Client
|
2
|
-
# The handler is the main class that connects to the Terminalwire server and
|
3
|
-
# dispatches messages to the appropriate resources.
|
4
|
-
class Handler
|
5
|
-
VERSION = "0.1.0".freeze
|
6
|
-
|
7
|
-
include Terminalwire::Logging
|
8
|
-
|
9
|
-
attr_reader :adapter, :resources, :endpoint
|
10
|
-
attr_accessor :entitlement
|
11
|
-
|
12
|
-
def initialize(adapter, arguments: ARGV, program_name: $0, endpoint:)
|
13
|
-
@endpoint = endpoint
|
14
|
-
@adapter = adapter
|
15
|
-
@program_arguments = arguments
|
16
|
-
@program_name = program_name
|
17
|
-
@entitlement = Entitlement::Policy.resolve(authority: @endpoint.authority)
|
18
|
-
|
19
|
-
yield self if block_given?
|
20
|
-
|
21
|
-
@resources = Resource::Handler.new do |it|
|
22
|
-
it << Resource::STDOUT.new("stdout", @adapter, entitlement:)
|
23
|
-
it << Resource::STDIN.new("stdin", @adapter, entitlement:)
|
24
|
-
it << Resource::STDERR.new("stderr", @adapter, entitlement:)
|
25
|
-
it << Resource::Browser.new("browser", @adapter, entitlement:)
|
26
|
-
it << Resource::File.new("file", @adapter, entitlement:)
|
27
|
-
it << Resource::Directory.new("directory", @adapter, entitlement:)
|
28
|
-
it << Resource::EnvironmentVariable.new("environment_variable", @adapter, entitlement:)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def verify_license
|
33
|
-
# Connect to the Terminalwire license server to verify the URL endpoint
|
34
|
-
# and displays a message to the user, if any are present.
|
35
|
-
$stdout.print ServerLicenseVerification.new(url: @endpoint.to_url).message
|
36
|
-
rescue
|
37
|
-
$stderr.puts "Failed to verify server license."
|
38
|
-
end
|
39
|
-
|
40
|
-
def connect
|
41
|
-
verify_license
|
42
|
-
|
43
|
-
@adapter.write(
|
44
|
-
event: "initialization",
|
45
|
-
protocol: { version: VERSION },
|
46
|
-
entitlement: @entitlement.serialize,
|
47
|
-
program: {
|
48
|
-
name: @program_name,
|
49
|
-
arguments: @program_arguments
|
50
|
-
}
|
51
|
-
)
|
52
|
-
|
53
|
-
loop do
|
54
|
-
handle @adapter.read
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def handle(message)
|
59
|
-
case message
|
60
|
-
in { event: "resource", action: "command", name:, parameters: }
|
61
|
-
@resources.dispatch(**message)
|
62
|
-
in { event: "exit", status: }
|
63
|
-
exit Integer(status)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
@@ -1,204 +0,0 @@
|
|
1
|
-
require "fileutils"
|
2
|
-
require "io/console"
|
3
|
-
|
4
|
-
module Terminalwire::Client::Resource
|
5
|
-
# Dispatches messages from the Client::Handler to the appropriate resource.
|
6
|
-
class Handler
|
7
|
-
include Enumerable
|
8
|
-
|
9
|
-
def initialize
|
10
|
-
@resources = {}
|
11
|
-
yield self if block_given?
|
12
|
-
end
|
13
|
-
|
14
|
-
def each(&block)
|
15
|
-
@resources.values.each(&block)
|
16
|
-
end
|
17
|
-
|
18
|
-
def add(resource)
|
19
|
-
# Detect if the resource is already registered and throw an error
|
20
|
-
if @resources.key?(resource.name)
|
21
|
-
raise "Resource #{resource.name} already registered"
|
22
|
-
else
|
23
|
-
@resources[resource.name] = resource
|
24
|
-
end
|
25
|
-
end
|
26
|
-
alias :<< :add
|
27
|
-
|
28
|
-
def dispatch(**message)
|
29
|
-
case message
|
30
|
-
in { event:, action:, name:, command:, parameters: }
|
31
|
-
resource = @resources.fetch(name)
|
32
|
-
resource.command(command, **parameters)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
# Dispatcher, security, and response macros for resources.
|
38
|
-
class Base < Terminalwire::Resource::Base
|
39
|
-
def initialize(*, entitlement:, **)
|
40
|
-
super(*, **)
|
41
|
-
@entitlement = entitlement
|
42
|
-
connect
|
43
|
-
end
|
44
|
-
|
45
|
-
def command(command, **parameters)
|
46
|
-
begin
|
47
|
-
if permit(command, **parameters)
|
48
|
-
succeed self.public_send(command, **parameters)
|
49
|
-
else
|
50
|
-
fail "Client denied #{command}", command:, parameters:
|
51
|
-
end
|
52
|
-
rescue => e
|
53
|
-
fail e.message, command:, parameters:
|
54
|
-
raise
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
protected
|
59
|
-
|
60
|
-
def permit(...)
|
61
|
-
false
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
class EnvironmentVariable < Base
|
66
|
-
# Accepts a list of environment variables to permit.
|
67
|
-
def read(name:)
|
68
|
-
ENV[name]
|
69
|
-
end
|
70
|
-
|
71
|
-
# def write(name:, value:)
|
72
|
-
# ENV[name] = value
|
73
|
-
# end
|
74
|
-
|
75
|
-
protected
|
76
|
-
|
77
|
-
def permit(command, name:, **)
|
78
|
-
@entitlement.environment_variables.permitted? name
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
class STDOUT < Base
|
83
|
-
def connect
|
84
|
-
@io = $stdout
|
85
|
-
end
|
86
|
-
|
87
|
-
def print(data:)
|
88
|
-
@io.print(data)
|
89
|
-
end
|
90
|
-
|
91
|
-
def print_line(data:)
|
92
|
-
@io.puts(data)
|
93
|
-
end
|
94
|
-
|
95
|
-
protected
|
96
|
-
|
97
|
-
def permit(...)
|
98
|
-
true
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
class STDERR < STDOUT
|
103
|
-
def connect
|
104
|
-
@io = $stderr
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
class STDIN < Base
|
109
|
-
def connect
|
110
|
-
@io = $stdin
|
111
|
-
end
|
112
|
-
|
113
|
-
def read_line
|
114
|
-
@io.gets
|
115
|
-
end
|
116
|
-
|
117
|
-
def read_password
|
118
|
-
@io.getpass
|
119
|
-
end
|
120
|
-
|
121
|
-
protected
|
122
|
-
|
123
|
-
def permit(...)
|
124
|
-
true
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
class File < Base
|
129
|
-
File = ::File
|
130
|
-
|
131
|
-
def read(path:)
|
132
|
-
File.read File.expand_path(path)
|
133
|
-
end
|
134
|
-
|
135
|
-
def write(path:, content:, mode: nil)
|
136
|
-
File.open(File.expand_path(path), "w", mode) { |f| f.write(content) }
|
137
|
-
end
|
138
|
-
|
139
|
-
def append(path:, content:, mode: nil)
|
140
|
-
File.open(File.expand_path(path), "a", mode) { |f| f.write(content) }
|
141
|
-
end
|
142
|
-
|
143
|
-
def delete(path:)
|
144
|
-
File.delete File.expand_path(path)
|
145
|
-
end
|
146
|
-
|
147
|
-
def exist(path:)
|
148
|
-
File.exist? File.expand_path(path)
|
149
|
-
end
|
150
|
-
|
151
|
-
def change_mode(path:, mode:)
|
152
|
-
File.chmod mode, File.expand_path(path)
|
153
|
-
end
|
154
|
-
|
155
|
-
protected
|
156
|
-
|
157
|
-
def permit(command, path:, mode: nil, **)
|
158
|
-
@entitlement.paths.permitted? path, mode:
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
class Directory < Base
|
163
|
-
File = ::File
|
164
|
-
|
165
|
-
def list(path:)
|
166
|
-
Dir.glob path
|
167
|
-
end
|
168
|
-
|
169
|
-
def create(path:)
|
170
|
-
FileUtils.mkdir_p File.expand_path(path)
|
171
|
-
rescue Errno::EEXIST
|
172
|
-
# Do nothing
|
173
|
-
end
|
174
|
-
|
175
|
-
def exist(path:)
|
176
|
-
Dir.exist? path
|
177
|
-
end
|
178
|
-
|
179
|
-
def delete(path:)
|
180
|
-
Dir.delete path
|
181
|
-
end
|
182
|
-
|
183
|
-
protected
|
184
|
-
|
185
|
-
def permit(command, path:, **)
|
186
|
-
@entitlement.paths.permitted? path
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
class Browser < Base
|
191
|
-
def launch(url:)
|
192
|
-
Launchy.open(URI(url))
|
193
|
-
# TODO: This is a hack to get the `respond` method to work.
|
194
|
-
# Maybe explicitly call a `suceed` and `fail` method?
|
195
|
-
nil
|
196
|
-
end
|
197
|
-
|
198
|
-
protected
|
199
|
-
|
200
|
-
def permit(command, url:, **)
|
201
|
-
@entitlement.schemes.permitted? url
|
202
|
-
end
|
203
|
-
end
|
204
|
-
end
|