terminalwire 0.2.5 → 0.3.0.alpha2

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/Rakefile +67 -5
  3. data/exe/terminalwire +1 -1
  4. data/exe/terminalwire-exec +1 -1
  5. metadata +9 -171
  6. data/.rspec +0 -3
  7. data/CHANGELOG.md +0 -5
  8. data/CODE_OF_CONDUCT.md +0 -132
  9. data/LICENSE.txt +0 -9
  10. data/README.md +0 -45
  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 -67
  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,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
@@ -1,74 +0,0 @@
1
- require "async/http/internet"
2
- require "base64"
3
- require "uri"
4
- require "fileutils"
5
-
6
- module Terminalwire::Client
7
- # Checkes the server for a license verification at `https://terminalwire.com/licenses/verifications/`
8
- # and displays the message to the user, if necessary.
9
- class ServerLicenseVerification
10
- include Terminalwire::Logging
11
-
12
- def initialize(url:)
13
- @url = URI(url)
14
- @internet = Async::HTTP::Internet.new
15
- @cache_store = Terminalwire::Cache::File::Store.new(path: Terminalwire::Client.root_path.join("cache/licenses/verifications"))
16
- end
17
-
18
- def key
19
- Base64.urlsafe_encode64 @url
20
- end
21
-
22
- def cache = @cache_store.entry key
23
-
24
- def payload
25
- if cache.miss?
26
- logger.debug "Stale verification. Requesting new verification."
27
- request do |response|
28
- # Set the expiry on the file cache for the header.
29
- if max_age = response.headers["cache-control"].max_age
30
- logger.debug "Caching for #{max_age}"
31
- cache.expires = Time.now + max_age
32
- end
33
-
34
- # Process based on the response code.
35
- case response.status
36
- in 200
37
- logger.debug "License for #{@url} found."
38
- data = self.class.unpack response.read
39
- cache.value = data
40
- return data
41
- in 404
42
- logger.debug "License for #{@url} not found."
43
- return self.class.unpack response.read
44
- end
45
- end
46
- else
47
- return cache.value
48
- end
49
- end
50
-
51
- def message
52
- payload.dig(:shell, :output)
53
- end
54
-
55
- protected
56
-
57
- def verification_url
58
- Terminalwire.url
59
- .path("/licenses/verifications", key)
60
- end
61
-
62
- def request(&)
63
- logger.debug "Requesting license verification from #{verification_url}."
64
- response = @internet.get verification_url, {
65
- "Accept" => "application/x-msgpack",
66
- "User-Agent" => "Terminalwire/#{Terminalwire::VERSION} Ruby/#{RUBY_VERSION} (#{RUBY_PLATFORM})",
67
- }, &
68
- end
69
-
70
- def self.unpack(pack)
71
- MessagePack.unpack(pack, symbolize_keys: true)
72
- end
73
- end
74
- end
@@ -1,30 +0,0 @@
1
- require 'fileutils'
2
- require 'launchy'
3
- require 'io/console'
4
- require 'pathname'
5
-
6
- module Terminalwire
7
- module Client
8
- ROOT_PATH = "~/.terminalwire".freeze
9
- def self.root_path = Pathname.new(ENV.fetch("TERMINALWIRE_HOME", ROOT_PATH))
10
-
11
- def self.websocket(url:, arguments: ARGV, &configuration)
12
- ENV["TERMINALWIRE_HOME"] ||= root_path.to_s
13
-
14
- url = URI(url)
15
-
16
- Async do |task|
17
- endpoint = Async::HTTP::Endpoint.parse(
18
- url,
19
- alpn_protocols: Async::HTTP::Protocol::HTTP11.names
20
- )
21
-
22
- Async::WebSocket::Client.connect(endpoint) do |adapter|
23
- transport = Terminalwire::Transport::WebSocket.new(adapter)
24
- adapter = Terminalwire::Adapter::Socket.new(transport)
25
- Terminalwire::Client::Handler.new(adapter, arguments:, endpoint:, &configuration).connect
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,8 +0,0 @@
1
- require 'logger'
2
-
3
- module Terminalwire
4
- module Logging
5
- DEVICE = Logger.new($stdout, level: ENV.fetch("TERMINALWIRE_LOG_LEVEL", "info"))
6
- def logger = DEVICE
7
- end
8
- end
@@ -1,69 +0,0 @@
1
- require 'jwt'
2
- require 'pathname'
3
- require 'forwardable'
4
-
5
- module Terminalwire::Rails
6
- class Session
7
- # JWT file name for the session file.
8
- FILENAME = "session.jwt"
9
-
10
- # Empty dictionary the user can stash all their session data into.
11
- EMPTY_SESSION = {}.freeze
12
-
13
- extend Forwardable
14
-
15
- # Delegate `dig` and `fetch` to the `read` method
16
- def_delegators :read,
17
- :dig, :fetch, :[]
18
-
19
- def initialize(context:, path: nil, secret_key: self.class.secret_key)
20
- @context = context
21
- @path = Pathname.new(path || context.storage_path)
22
- @config_file_path = @path.join(FILENAME)
23
- @secret_key = secret_key
24
-
25
- ensure_file
26
- end
27
-
28
- def read
29
- jwt_token = @context.file.read(@config_file_path)
30
- decoded_data = JWT.decode(jwt_token, @secret_key, true, algorithm: 'HS256')
31
- decoded_data[0] # JWT payload is the first element in the array
32
- rescue JWT::DecodeError => e
33
- raise "Invalid or tampered file: #{e.message}"
34
- end
35
-
36
- def reset
37
- @context.file.delete @config_file_path
38
- end
39
-
40
- def edit
41
- config = read
42
- yield config
43
- write(config)
44
- end
45
-
46
- def []=(key, value)
47
- edit { |config| config[key] = value }
48
- end
49
-
50
- def write(config)
51
- token = JWT.encode(config, @secret_key, 'HS256')
52
- @context.file.write(@config_file_path, token)
53
- end
54
-
55
- private
56
-
57
- def ensure_file
58
- return true if @context.file.exist? @config_file_path
59
- # Create the path if it doesn't exist on the client.
60
- @context.directory.create @path
61
- # Write an empty configuration on initialization
62
- write(EMPTY_SESSION)
63
- end
64
-
65
- def self.secret_key
66
- Rails.application.secret_key_base
67
- end
68
- end
69
- end
@@ -1,80 +0,0 @@
1
- require "fileutils"
2
-
3
- module Terminalwire::Server
4
- # Contains all of the resources that are accessible to the server on the client-side.
5
- # It's the primary interface for the server to interact with the client and is integrated
6
- # into other libraries like Thor, etc.
7
- class Context
8
- extend Forwardable
9
-
10
- attr_reader \
11
- :stdout, :stdin, :stderr,
12
- :browser,
13
- :file, :directory,
14
- :environment_variable,
15
- :authority,
16
- :root_path,
17
- :authority_path,
18
- :storage_path
19
-
20
- def_delegators :@stdout, :puts, :print
21
- def_delegators :@stdin, :gets, :getpass
22
-
23
- def initialize(adapter:, entitlement:)
24
- @adapter = adapter
25
- @entitlement = entitlement
26
-
27
- # Initialize resources
28
- @stdout = Resource::STDOUT.new("stdout", @adapter)
29
- @stdin = Resource::STDIN.new("stdin", @adapter)
30
- @stderr = Resource::STDERR.new("stderr", @adapter)
31
- @browser = Resource::Browser.new("browser", @adapter)
32
- @file = Resource::File.new("file", @adapter)
33
- @directory = Resource::Directory.new("directory", @adapter)
34
- @environment_variable = Resource::EnvironmentVariable.new("environment_variable", @adapter)
35
-
36
- # Authority is provided by the client.
37
- @authority = @entitlement.fetch(:authority)
38
- # The Terminalwire home path is provided by the client and set
39
- # as an environment variable.
40
- @root_path = Pathname.new(
41
- @environment_variable.read("TERMINALWIRE_HOME")
42
- )
43
- # Now derive the rest of the paths from the Terminalwire home path.
44
- @authority_path = @root_path.join("authorities", @authority)
45
- @storage_path = @authority_path.join("storage")
46
-
47
- if block_given?
48
- begin
49
- yield self
50
- ensure
51
- exit
52
- end
53
- end
54
- end
55
-
56
- # Wraps the environment variables in a hash-like object that can be accessed
57
- # from client#ENV. This makes it look and feel just like the ENV object in Ruby.
58
- class Env
59
- def initialize(context:)
60
- @context = context
61
- end
62
-
63
- def [](name)
64
- @context.environment_variable.read(name)
65
- end
66
- end
67
-
68
- def ENV
69
- @ENV ||= Env.new(context: self)
70
- end
71
-
72
- def exit(status = 0)
73
- @adapter.write(event: "exit", status: status)
74
- end
75
-
76
- def close
77
- @adapter.close
78
- end
79
- end
80
- end
@@ -1,120 +0,0 @@
1
- module Terminalwire::Server
2
- # Representation of the resources avilable to the server on the client-side. These
3
- # classes encapsulate the API alls to the client and provide a more Ruby-like interface.
4
- module Resource
5
- class Base < Terminalwire::Resource::Base
6
- private
7
-
8
- def command(command, **parameters)
9
- @adapter.write(
10
- event: "resource",
11
- name: @name,
12
- action: "command",
13
- command: command,
14
- parameters: parameters
15
- )
16
-
17
- response = @adapter.read
18
- case response.fetch(:status)
19
- when "success"
20
- response.fetch(:response)
21
- when "failure"
22
- raise Terminalwire::Error, response.inspect
23
- end
24
- end
25
- end
26
-
27
- class EnvironmentVariable < Base
28
- # Accepts a list of environment variables to permit.
29
- def read(name)
30
- command("read", name:)
31
- end
32
-
33
- # def write(name:, value:)
34
- # command("write", name:, value:)
35
- # end
36
- end
37
-
38
- class STDOUT < Base
39
- def puts(data)
40
- command("print_line", data: data)
41
- end
42
-
43
- def print(data)
44
- command("print", data: data)
45
- end
46
-
47
- def flush
48
- # Do nothing
49
- end
50
- end
51
-
52
- class STDERR < STDOUT
53
- end
54
-
55
- class STDIN < Base
56
- def getpass
57
- command("read_password")
58
- end
59
-
60
- def gets
61
- command("read_line")
62
- end
63
- end
64
-
65
- class File < Base
66
- def read(path)
67
- command("read", path: path.to_s)
68
- end
69
-
70
- def write(path, content)
71
- command("write", path: path.to_s, content:)
72
- end
73
-
74
- def append(path, content)
75
- command("append", path: path.to_s, content:)
76
- end
77
-
78
- def delete(path)
79
- command("delete", path: path.to_s)
80
- end
81
- alias :rm :delete
82
-
83
- def exist?(path)
84
- command("exist", path: path.to_s)
85
- end
86
-
87
- def change_mode(path, mode)
88
- command("change_mode", path: path.to_s, mode:)
89
- end
90
- alias :chmod :change_mode
91
- end
92
-
93
- class Directory < Base
94
- def list(path)
95
- command("list", path: path.to_s)
96
- end
97
- alias :ls :list
98
-
99
- def create(path)
100
- command("create", path: path.to_s)
101
- end
102
- alias :mkdir :create
103
-
104
- def exist?(path)
105
- command("exist", path: path.to_s)
106
- end
107
-
108
- def delete(path)
109
- command("delete", path: path.to_s)
110
- end
111
- alias :rm :delete
112
- end
113
-
114
- class Browser < Base
115
- def launch(url)
116
- command("launch", url: url)
117
- end
118
- end
119
- end
120
- end
@@ -1,63 +0,0 @@
1
- require "thor"
2
-
3
- module Terminalwire
4
- module Server
5
- class WebSocket
6
- include Logging
7
-
8
- def call(env)
9
- Async::WebSocket::Adapters::Rack.open(env, protocols: ['ws']) do |connection|
10
- handle(Adapter::Socket.new(Terminalwire::Transport::WebSocket.new(connection)))
11
- end or [200, { "Content-Type" => "text/plain" }, ["Connect via WebSockets"]]
12
- end
13
-
14
- def handle(adapter)
15
- while message = adapter.read
16
- puts message
17
- end
18
- end
19
- end
20
-
21
- class Thor < WebSocket
22
- Rails = ::Rails
23
-
24
- include Logging
25
-
26
- def initialize(cli_class)
27
- @cli_class = cli_class
28
-
29
- unless @cli_class.included_modules.include?(Terminalwire::Thor)
30
- raise 'Add `include Terminalwire::Thor` to the #{@cli_class.inspect} class.'
31
- end
32
- end
33
-
34
- def error_message
35
- "An error occurred. Please try again."
36
- end
37
-
38
- def handle(adapter)
39
- logger.info "ThorServer: Running #{@cli_class.inspect}"
40
- while message = adapter.read
41
- case message
42
- in { event: "initialization", protocol:, program: { arguments: }, entitlement: }
43
- context = Terminalwire::Server::Context.new(adapter:, entitlement:)
44
-
45
- begin
46
- @cli_class.start(arguments, context:)
47
- context.exit
48
- rescue StandardError => e
49
- if Rails.application.config.consider_all_requests_local
50
- # Show the full error message with stack trace in development
51
- context.stderr.puts "#{e.inspect}\n#{e.backtrace.join("\n")}"
52
- else
53
- # Show a generic message in production
54
- context.stderr.puts error_message
55
- end
56
- context.exit 1
57
- end
58
- end
59
- end
60
- end
61
- end
62
- end
63
- end