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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/exe/terminalwire +1 -1
  3. data/exe/terminalwire-exec +1 -1
  4. metadata +9 -169
  5. data/.rspec +0 -3
  6. data/CHANGELOG.md +0 -5
  7. data/CODE_OF_CONDUCT.md +0 -132
  8. data/LICENSE.txt +0 -9
  9. data/README.md +0 -45
  10. data/Rakefile +0 -8
  11. data/examples/exec/localrails +0 -2
  12. data/lib/generators/terminalwire/install/USAGE +0 -9
  13. data/lib/generators/terminalwire/install/install_generator.rb +0 -38
  14. data/lib/generators/terminalwire/install/templates/application_terminal.rb.tt +0 -21
  15. data/lib/generators/terminalwire/install/templates/bin/terminalwire +0 -2
  16. data/lib/generators/terminalwire/install/templates/main_terminal.rb +0 -40
  17. data/lib/terminalwire/adapter.rb +0 -53
  18. data/lib/terminalwire/cache.rb +0 -114
  19. data/lib/terminalwire/client/entitlement/environment_variables.rb +0 -26
  20. data/lib/terminalwire/client/entitlement/paths.rb +0 -97
  21. data/lib/terminalwire/client/entitlement/policy.rb +0 -117
  22. data/lib/terminalwire/client/entitlement/schemes.rb +0 -26
  23. data/lib/terminalwire/client/entitlement.rb +0 -9
  24. data/lib/terminalwire/client/exec.rb +0 -44
  25. data/lib/terminalwire/client/handler.rb +0 -67
  26. data/lib/terminalwire/client/resource.rb +0 -204
  27. data/lib/terminalwire/client/server_license_verification.rb +0 -74
  28. data/lib/terminalwire/client.rb +0 -30
  29. data/lib/terminalwire/logging.rb +0 -8
  30. data/lib/terminalwire/rails.rb +0 -69
  31. data/lib/terminalwire/server/context.rb +0 -80
  32. data/lib/terminalwire/server/resource.rb +0 -120
  33. data/lib/terminalwire/server.rb +0 -63
  34. data/lib/terminalwire/thor.rb +0 -55
  35. data/lib/terminalwire/transport.rb +0 -58
  36. data/lib/terminalwire/version.rb +0 -5
  37. data/lib/terminalwire.rb +0 -56
  38. data/sig/terminalwire.rbs +0 -4
@@ -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