terminalwire 0.2.5 → 0.3.0.alpha2

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.
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,53 +0,0 @@
1
- require 'msgpack'
2
-
3
- module Terminalwire::Adapter
4
- # Works with Test, TCP, Unix, WebSocket, and other socket-like abstractions.
5
- class Socket
6
- include Terminalwire::Logging
7
-
8
- attr_reader :transport
9
-
10
- def initialize(transport)
11
- @transport = transport
12
- end
13
-
14
- def write(data)
15
- logger.debug "Adapter: Sending #{data.inspect}"
16
- packed_data = MessagePack.pack(data, symbolize_keys: true)
17
- @transport.write(packed_data)
18
- end
19
-
20
- def read
21
- logger.debug "Adapter: Reading"
22
- packed_data = @transport.read
23
- return nil if packed_data.nil?
24
- data = MessagePack.unpack(packed_data, symbolize_keys: true)
25
- logger.debug "Adapter: Received #{data.inspect}"
26
- data
27
- end
28
-
29
- def close
30
- @transport.close
31
- end
32
- end
33
-
34
- # This is a test adapter that can be used for testing purposes.
35
- class Test
36
- attr_reader :responses
37
-
38
- def initialize(responses = [])
39
- @responses = responses
40
- end
41
-
42
- def write(**data)
43
- @responses << data
44
- end
45
-
46
- def response
47
- @responses.pop
48
- end
49
-
50
- def close
51
- end
52
- end
53
- end
@@ -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