Module: PluginTool
- Defined in:
- lib/auth.rb,
lib/misc.rb,
lib/check.rb,
lib/custom.rb,
lib/plugin.rb,
lib/server.rb,
lib/packing.rb,
lib/manifest.rb,
lib/minimise.rb,
lib/watchers.rb,
lib/new_plugin.rb,
lib/local_config.rb,
lib/notifications.rb,
lib/syntax_checking.rb,
lib/schema_requirements.rb
Defined Under Namespace
Modules: LocalConfig Classes: CustomBehaviour, Minimiser, Plugin, SchemaRequirements, WatcherPoll
Constant Summary collapse
- LOCAL_CUSTOM_BEHAVIOUR_FILENAME =
"server.behaviour.rb"
- TRUSTED_CODE_DIGESTS_FILENAME =
Digests of trusted code are stored outside the source code repo, so it can’t be written by the repo contents
"~/.haplo-plugin-tool-trusted.json"
- PACKING_ACCEPTABLE_FILENAME =
/\A(js|static|template|test|data)\/([a-z0-9_-]+\/)*[a-z0-9_-]+\.[a-z0-9]+\z/
- PACKING_ACCEPTABLE_EXCEPTIONS =
['plugin.json', 'requirements.schema', 'global.js', 'certificates-temp-http-api.pem']
- ALLOWED_PLUGIN_DIRS =
NOTE: Also update packing.rb if this changes
['js', 'static', 'template', 'test', 'data']
- Context =
Java::OrgMozillaJavascript::Context
- @@keys_pathname =
"#{Dir.getwd}/.server.json"
- @@custom =
CustomBehaviour.new
- @@http =
nil
- @@notification_options =
nil
- @@notification_have_suppressed_first_system_audit_entry =
false
- @@notification_announce_reconnect =
false
- @@syntax_check_queue =
[]
- @@syntax_check_queue_lock =
Mutex.new
- @@syntax_check_queue_semaphore =
Mutex.new
- @@syntax_check_queue_semaphore_var =
ConditionVariable.new
Class Method Summary collapse
- .beep ⇒ Object
- .check_file_result(plugin, name, result) ⇒ Object
- .check_for_certificate_file ⇒ Object
- .check_plugin(plugin) ⇒ Object
- .check_plugins(plugins) ⇒ Object
-
.cmd_auth(options) ⇒ Object
———————————————————————————————————.
-
.cmd_server(options) ⇒ Object
———————————————————————————————————.
- .custom_behaviour ⇒ Object
- .decode_and_handle_notifications(encoded) ⇒ Object
- .determine_manifest_changes(from, to) ⇒ Object
- .do_notifications ⇒ Object
- .do_syntax_checking ⇒ Object
- .finish_with_connection ⇒ Object
- .generate_manifest(directory) ⇒ Object
- .get(path) ⇒ Object
- .get_application_info ⇒ Object
- .get_http ⇒ Object
- .get_server_hostname ⇒ Object
- .get_with_json_response(path) ⇒ Object
- .handle_notification(type, data) ⇒ Object
- .init_syntax_checking ⇒ Object
-
.load_keys_file ⇒ Object
———————————————————————————————————.
- .make_http_connection ⇒ Object
- .make_new_plugin(plugin_name) ⇒ Object
- .make_watcher(dirs) ⇒ Object
- .pack_plugin(plugin_name, output_directory) ⇒ Object
-
.parse_hostname_with_port(hostname_with_port) ⇒ Object
———————————————————————————————————.
- .post(path, params = nil, files = nil) ⇒ Object
- .post_with_json_response(path, params = nil, files = nil) ⇒ Object
- .report_errors_from_server(r) ⇒ Object
- .save_keys_file(keys) ⇒ Object
- .select_server(keys, substring) ⇒ Object
- .set_custom_behaviour(custom) ⇒ Object
- .set_server(hostname, port, key) ⇒ Object
-
.setup_auth(options) ⇒ Object
———————————————————————————————————.
- .setup_request(req) ⇒ Object
- .start_notifications(options) ⇒ Object
- .start_syntax_check ⇒ Object
- .syntax_check(plugin, filename) ⇒ Object
- .syntax_check_one_file(plugin, file) ⇒ Object
- .try_load_custom ⇒ Object
Class Method Details
.beep ⇒ Object
4 5 6 |
# File 'lib/misc.rb', line 4 def self.beep $stdout.write("\07") end |
.check_file_result(plugin, name, result) ⇒ Object
48 49 50 51 52 53 54 55 |
# File 'lib/check.rb', line 48 def self.check_file_result(plugin, name, result) line = " #{plugin.plugin_dir}/#{name}: #{result}" @@check_report << line @@check_report << "\n" @@check_ok = false if result == :FAIL @@check_warn = true if result == :WARN puts line end |
.check_for_certificate_file ⇒ Object
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/server.rb', line 14 def self.check_for_certificate_file @@server_ca = nil hostname_parts = (@@server_hostname || '').split('.') search_list = (0..hostname_parts.length).map do |n| filename = "server" search_parts = hostname_parts[n..hostname_parts.length] filename << '.' unless search_parts.empty? filename << search_parts.join('.') filename << ".crt" end @@server_ca = search_list.find { |f| File.file?(f) } if @@server_ca puts "NOTICE: Using alternative CAs for SSL from #{@@server_ca}" else # Use build in certificate bundle @@server_ca = "#{File.dirname(__FILE__)}/CertificateBundle.pem" end end |
.check_plugin(plugin) ⇒ Object
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/check.rb', line 27 def self.check_plugin(plugin) plugin_dir = plugin.plugin_dir Dir.glob("#{plugin_dir}/**/*").each do |pathname| next unless File.file?(pathname) plugin_relative_name = pathname[plugin_dir.length+1, pathname.length] if pathname =~ /\.js\z/ # Check JavaScript report = syntax_check_one_file(plugin, plugin_relative_name) if report == nil check_file_result(plugin, plugin_relative_name, :OK) else puts "**** #{plugin_relative_name} has errors:\n#{report}\n" check_file_result(plugin, plugin_relative_name, (plugin_relative_name =~ /\Ajs\//) ? :FAIL : :WARN) end else # TODO: Checks for other file types, including the plugin.json check_file_result(plugin, plugin_relative_name, :OK) end end end |
.check_plugins(plugins) ⇒ Object
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# File 'lib/check.rb', line 4 def self.check_plugins(plugins) init_syntax_checking @@check_report = '' @@check_ok = true @@check_warn = false plugins.each { |p| check_plugin(p) } if @@check_warn || !(@@check_ok) # Output the report again because it may have been obscured by the errors puts "--------------------" puts @@check_report end # Output the verdict puts if @@check_ok && !@@check_warn puts "PASSED" elsif @@check_warn puts "PASSED WITH WARNINGS" else puts "FAILED" exit 1 end end |
.cmd_auth(options) ⇒ Object
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/auth.rb', line 39 def self.cmd_auth() end_on_error "No hostname given on command line" if .args.empty? hostname, port, url_base, server_name = parse_hostname_with_port(.args.first) keys = load_keys_file() if keys['keys'].has_key?(server_name) && !.force puts puts "Already authorised with #{server_name}" puts "Use the --force argument to reauthorise with the server." return end set_server(hostname, port, nil) check_for_certificate_file() http = get_http() this_hostname = java.net.InetAddress.getLocalHost().getHostName() || "unknown" puts "Requesting token from #{url_base} ..." start_auth_path = "/api/plugin-tool-auth/start-auth?name=#{URI.encode(this_hostname)}" request = Net::HTTP::Get.new(start_auth_path) setup_request(request) token = nil begin response = http.request(request) end_on_error "Server returned an error. Check hostname and port." unless response.code == "200" parsed_json = JSON.parse(response.body) token = parsed_json['token'] end_on_error "Server doesn't look like a ONEIS server with plugin debugging enabled" unless parsed_json['ONEIS'] == 'plugin-tool-auth' && token rescue => e end_on_error "Failed to start authorisation process. Check hostname and port." end # Check token looks OK so we don't form dodgy URLs end_on_error "Bad token" unless token =~ /\A[a-z0-9A-Z_-]+\z/ user_url = "#{url_base}/do/plugin-tool-auth/create/#{token}" poll_path = "/api/plugin-tool-auth/poll/#{token}" puts if java.lang.System.getProperty("os.name") == 'Mac OS X' puts "Attempting to open the following URL in your browser." puts "If the browser does not open, please visit this URL in your browser." system "open #{user_url}" else puts "Please visit this URL in your browser, and authenticate if necessary." end puts " #{user_url}" puts # Poll for a few minutes, waiting for the user to authenticate puts "Waiting for server to authorise..." poll_count = 0 key = nil while poll_count < 60 && !key delay = if poll_count < 10 2 elsif poll_count < 20 4 else 8 end sleep delay begin request = Net::HTTP::Get.new(poll_path) setup_request(request) response = http.request(request) parsed_json = JSON.parse(response.body) case parsed_json['status'] when 'wait' # poll again when 'available' key = parsed_json['key'] else end_on_error "Authorisation process failed." end rescue => e end_on_error "Error communicating with server" end end finish_with_connection() end_on_error "Didn't managed to authorise with server." unless key puts "Successfully authorised with server." keys['default'] = server_name keys['keys'][server_name] = key save_keys_file(keys) puts puts "Key stored in #{@@keys_pathname}" puts "#{server_name} selected as default server." end |
.cmd_server(options) ⇒ Object
27 28 29 30 31 32 33 34 35 |
# File 'lib/auth.rb', line 27 def self.cmd_server() end_on_error "No server name substring given on command line" if .args.empty? keys = load_keys_file() server = select_server(keys, .args.first) end_on_error "No server found for substring '#{.args.first}" unless server keys['default'] = server puts "Selected server #{server}" save_keys_file(keys) end |
.custom_behaviour ⇒ Object
22 23 24 |
# File 'lib/custom.rb', line 22 def self.custom_behaviour @@custom end |
.decode_and_handle_notifications(encoded) ⇒ Object
49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/notifications.rb', line 49 def self.decode_and_handle_notifications(encoded) size = encoded.length pos = 0 while pos < (size - 12) type = encoded[pos, 4] data_size = encoded[pos + 4, 8].to_i(16) data = encoded[pos + 12, data_size] pos += 12 + data_size handle_notification(type, data) end end |
.determine_manifest_changes(from, to) ⇒ Object
32 33 34 35 36 37 38 39 40 41 |
# File 'lib/manifest.rb', line 32 def self.determine_manifest_changes(from, to) changes = [] from.each_key do |name| changes << [name, :delete] unless to.has_key?(name) end to.each do |name,hash| changes << [name, hash] unless from[name] == hash end changes end |
.do_notifications ⇒ Object
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/notifications.rb', line 24 def self.do_notifications http = make_http_connection puts "NOTICE: Notification connection established." if @@notification_announce_reconnect while true sleep(0.25) # small throttle of requests path = '/api/development-plugin-loader/get-notifications' path << "?queue=#{@@notification_queue_name}" if @@notification_queue_name request = Net::HTTP::Get.new(path) setup_request(request) # Server uses long-polling, and will respond after a long timeout, or when a notification # has been queued for sending to this process. response = http.request(request) if response.kind_of?(Net::HTTPOK) @@notification_queue_name = response['X-Queue-Name'] begin decode_and_handle_notifications response.body rescue puts "NOTICE: Error handling notification from server." end else raise "Bad response" end end end |
.do_syntax_checking ⇒ Object
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/syntax_checking.rb', line 80 def self.do_syntax_checking init_syntax_checking while(true) plugin, file = @@syntax_check_queue_lock.synchronize do @@syntax_check_queue.shift end if file == nil # Wait for another file @@syntax_check_queue_semaphore.synchronize { @@syntax_check_queue_semaphore_var.wait(@@syntax_check_queue_semaphore) } else report = syntax_check_one_file(plugin, file) if report != nil puts "\nJavaScript file #{plugin.plugin_dir}/#{file} has syntax errors:\n#{report}\n\n" beep end end end end |
.finish_with_connection ⇒ Object
56 57 58 59 60 61 |
# File 'lib/server.rb', line 56 def self.finish_with_connection if @@http != nil @@http.finish @@http = nil end end |
.generate_manifest(directory) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/manifest.rb', line 7 def self.generate_manifest(directory) manifest = Hash.new Dir.glob("#{directory}/**/*").sort.each do |pathname| # Ignore directories next unless File.file? pathname # Ignore Emacs backup files next if pathname =~ /\~\z/ # Check file filename = pathname.slice(directory.length + 1, pathname.length) raise "Bad filename for #{filename}" unless filename =~ /\A([a-zA-Z0-9_\/\-]+\/)?([a-z0-9_-]+\.[a-z0-9]+)\z/ dir = $1 name = $2 if dir != nil dir = dir.gsub(/\/\z/,'') raise "Bad directory #{dir}" unless dir =~ /\A([a-zA-Z0-9_\-]+)[a-zA-Z0-9_\/\-]*\z/ raise "Bad root directory #{$1}" unless ALLOWED_PLUGIN_DIRS.include?($1) end # Get hash of file digest = File.open(pathname) { |f| Digest::SHA256.hexdigest(f.read) } # And add to manifest manifest[filename] = digest end manifest end |
.get(path) ⇒ Object
68 69 70 71 72 73 |
# File 'lib/server.rb', line 68 def self.get(path) http = get_http request = Net::HTTP::Get.new(path) setup_request(request) http.request(request).body end |
.get_application_info ⇒ Object
139 140 141 |
# File 'lib/server.rb', line 139 def self.get_application_info @@server_application_info ||= JSON.parse(get("/api/development-plugin-loader/application-info")) end |
.get_http ⇒ Object
52 53 54 |
# File 'lib/server.rb', line 52 def self.get_http @@http ||= make_http_connection end |
.get_server_hostname ⇒ Object
10 11 12 |
# File 'lib/server.rb', line 10 def self.get_server_hostname @@server_hostname end |
.get_with_json_response(path) ⇒ Object
75 76 77 |
# File 'lib/server.rb', line 75 def self.get_with_json_response(path) report_errors_from_server(JSON.parse(get(path))) end |
.handle_notification(type, data) ⇒ Object
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/notifications.rb', line 61 def self.handle_notification(type, data) case type when 'log ' # Output from console.log() puts "LOG:#{data}" when 'audt' decoded = JSON.parse(data) kind = decoded.find { |name,value| name == 'auditEntryType' }.last if kind =~ /\A[A-Z\-]+\z/ # System audit entry - suppressed by default unless @@notification_options.show_system_audit unless @@notification_have_suppressed_first_system_audit_entry @@notification_have_suppressed_first_system_audit_entry = true puts "NOTICE: System audit trail entries are not being shown. Run with --show-system-audit to display." end return end end puts "AUDIT -------------------------------------------" decoded.each do |key, value| puts sprintf("%22s: %s", key, value.to_s) end else puts "WARNING: Unknown notification received from server. Upgrade the plugin tool using 'jgem update oneis'." sleep(5) # throttle problematic responses end end |
.init_syntax_checking ⇒ Object
22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/syntax_checking.rb', line 22 def self.init_syntax_checking # Set up interpreter and get a syntax checker function raise "Another JS Context is active" unless nil == Context.getCurrentContext() @@cx = Context.enter(); @@javascript_scope = @@cx.initStandardObjects(); jshint = File.open("#{File.dirname(__FILE__)}/jshint.js") { |f| f.read } @@cx.evaluateString(@@javascript_scope, jshint, "<jshint.js>", 1, nil); testerfn = File.open("#{File.dirname(__FILE__)}/js_syntax_test.js") { |f| f.read } @@cx.evaluateString(@@javascript_scope, testerfn, "<js_syntax_test.js>", 1, nil); @@syntax_tester = @@javascript_scope.get("syntax_tester", @@javascript_scope); end |
.load_keys_file ⇒ Object
149 150 151 152 153 154 155 |
# File 'lib/auth.rb', line 149 def self.load_keys_file if File.exist?(@@keys_pathname) File.open(@@keys_pathname) { |f| JSON.parse(f.read) } else {"_" => "Contains server keys. DO NOT COMMIT TO SOURCE CONTROL.", "default" => nil, "keys" => {}} end end |
.make_http_connection ⇒ Object
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/server.rb', line 33 def self.make_http_connection http = Net::HTTP.new(@@server_hostname, @@server_port) ssl_ca = OpenSSL::X509::Store.new unless ssl_ca.respond_to? :add_file puts puts "jruby-openssl gem is not installed or bouncy castle crypto not available on CLASSPATH." puts "See installation instructions." puts exit 1 end ssl_ca.add_file(@@server_ca) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_PEER http.cert_store = ssl_ca http.start http end |
.make_new_plugin(plugin_name) ⇒ Object
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/new_plugin.rb', line 4 def self.make_new_plugin(plugin_name) unless plugin_name =~ /\A[a-z0-9_]+\z/ && plugin_name.length > 8 end_on_error "Bad plugin name - must use a-z0-9_ only, and more than 8 characters." end if File.exist?(plugin_name) end_on_error "File or directory #{plugin_name} already exists" end FileUtils.mkdir(plugin_name) ['js', 'static', 'template', 'test'].each do |dir| FileUtils.mkdir("#{plugin_name}/#{dir}") end random = java.security.SecureRandom.new() rbytes = Java::byte[20].new random.nextBytes(rbytes) install_secret = String.from_java_bytes(rbytes).unpack('H*').join plugin_url_fragment = plugin_name.gsub('_','-') File.open("#{plugin_name}/plugin.json",'w') do |file| file.write(<<__E) { "pluginName": "#{plugin_name}", "pluginAuthor": "TODO Your Company", "pluginVersion": 1, "displayName": "#{plugin_name.split('_').map {|e| e.capitalize} .join(' ')}", "displayDescription": "TODO Longer description of plugin", "installSecret": "#{install_secret}", "apiVersion": 4, "load": [ "js/#{plugin_name}.js" ], "respond": ["/do/#{plugin_url_fragment}"] } __E end File.open("#{plugin_name}/js/#{plugin_name}.js",'w') do |file| file.write(<<__E) P.respond("GET", "/do/#{plugin_url_fragment}/example", [ ], function(E) { E.render({ pageTitle: "Example page" }); }); __E end File.open("#{plugin_name}/template/example.html",'w') do |file| file.write(<<__E) <p>This is an example template.</p> __E end File.open("#{plugin_name}/test/#{plugin_name}_test1.js",'w') do |file| file.write(<<__E) t.test(function() { // For documentation, see // http://docs.oneis.co.uk/dev/plugin/tests t.assert(true); }); __E end File.open("#{plugin_name}/requirements.schema",'w') do |file| file.write("\n\n\n") end puts <<__E Plugin #{plugin_name} has been created. Run oneis-plugin -p #{plugin_name} to upload it to the server, then visit https://<HOSTNAME>/do/#{plugin_url_fragment}/example to see a sample page. See http://docs.oneis.co.uk/dev/plugin for more information. __E end |
.make_watcher(dirs) ⇒ Object
32 33 34 35 36 37 |
# File 'lib/watchers.rb', line 32 def self.make_watcher(dirs) # TODO: Option to use external watcher task # pipe = IO.popen(watcher_cmd) # wait with pipe.read WatcherPoll.new(dirs) end |
.pack_plugin(plugin_name, output_directory) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/packing.rb', line 7 def self.pack_plugin(plugin_name, output_directory) # Get filenames and sort files = Dir.glob("#{plugin_name}/**/*").map do |filename| if File.file? filename filename[plugin_name.length+1, filename.length] else nil end end .compact.sort # Check each filename is acceptable files.each do |filename| unless filename =~ PACKING_ACCEPTABLE_FILENAME || PACKING_ACCEPTABLE_EXCEPTIONS.include?(filename) puts "File '#{filename}' has an unacceptable filename" exit 1 end end # Clean output directory output_plugin_dir = "#{output_directory}/#{plugin_name}" puts "Output directory: #{output_plugin_dir}" if File.exist? output_plugin_dir puts "Removing old output directory #{output_plugin_dir}" FileUtils.rm_r(output_plugin_dir) end # Make file structure FileUtils.mkdir(output_plugin_dir) # Process each file, building a manifest puts "Processing files:" manifest = '' minimiser = PluginTool::Minimiser.new files.each do |filename| puts " #{filename}" data = File.open("#{plugin_name}/#{filename}") { |f| f.read } # Minimise file? unless filename =~ /\Ajs\// data = minimiser.process(data, filename) end hash = Digest::SHA256.hexdigest(data) # Make sure output directory exists, write file output_pathname = "#{output_plugin_dir}/#{filename}" output_directory = File.dirname(output_pathname) FileUtils.mkdir_p(output_directory) unless File.directory?(output_directory) File.open(output_pathname, "w") { |f| f.write data } # Filename entry in Manifest manifest << "F #{hash} #{filename}\n" end minimiser.finish # Write manifest and version File.open("#{output_plugin_dir}/manifest", "w") { |f| f.write manifest } version = Digest::SHA256.hexdigest(manifest) File.open("#{output_plugin_dir}/version", "w") { |f| f.write "#{version}\n" } # All done puts "Version: #{version}\nPlugin packed." end |
.parse_hostname_with_port(hostname_with_port) ⇒ Object
136 137 138 139 140 141 142 143 144 145 |
# File 'lib/auth.rb', line 136 def self.parse_hostname_with_port(hostname_with_port) hostname_with_port = hostname_with_port.downcase.strip unless hostname_with_port =~ /\A([a-z0-9\.-]+)(:(\d+))?\z/ end_on_error "Bad hostname #{hostname_with_port}" end hostname = $1 port = $3 ? $3.to_i : 443 server_name = "#{hostname}#{$3 ? ":#{$3}" : ''}" [hostname, port, "https://#{server_name}", server_name] end |
.post(path, params = nil, files = nil) ⇒ Object
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/server.rb', line 79 def self.post(path, params = nil, files = nil) http = get_http request = Net::HTTP::Post.new(path) setup_request(request) if files == nil request.set_form_data(params) if params != nil else boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1" request["Content-Type"] = "multipart/form-data; boundary=#{boundary}" body = '' if params != nil params.each do |key,value| body << <<-EOF --#{boundary}\r Content-Disposition: form-data; name="#{key}"\r \r #{value}\r EOF end end files.each do |key,info| pathname, data = info body << <<-EOF --#{boundary}\r Content-Disposition: form-data; name="#{key}"; filename="#{pathname}"\r Content-Type: application/octet-stream\r Content-Length: #{data.length}\r \r #{data}\r EOF end body << "--#{boundary}--\r" request.body = body end http.request(request).body end |
.post_with_json_response(path, params = nil, files = nil) ⇒ Object
116 117 118 |
# File 'lib/server.rb', line 116 def self.post_with_json_response(path, params = nil, files = nil) report_errors_from_server(JSON.parse(post(path, params, files))) end |
.report_errors_from_server(r) ⇒ Object
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/server.rb', line 120 def self.report_errors_from_server(r) unless r.kind_of? Hash r = { "result" => 'error', "protocol_error" => true, "message" => "Unknown error. Either the server is not enabled for development, or the credentials in #{SERVER_INFO} are not valid." } end if r["result"] != 'success' && r.has_key?("message") puts "\n\n**************************************************************" puts " ERROR REPORTED BY SERVER" puts "**************************************************************\n\n" puts r["message"] puts "\n**************************************************************\n\n" beep end r end |
.save_keys_file(keys) ⇒ Object
157 158 159 160 161 |
# File 'lib/auth.rb', line 157 def self.save_keys_file(keys) pn = "#{@@keys_pathname}.n" File.open(pn, "w") { |f| f.write(JSON.pretty_generate(keys)) } File.rename(pn, @@keys_pathname) end |
.select_server(keys, substring) ⇒ Object
163 164 165 166 |
# File 'lib/auth.rb', line 163 def self.select_server(keys, substring) s = substring.downcase.strip keys['keys'].keys.sort { |a,b| (a.length == b.length) ? (a <=> b) : (a.length <=> b.length) } .find { |a| a.include? s } end |
.set_custom_behaviour(custom) ⇒ Object
18 19 20 |
# File 'lib/custom.rb', line 18 def self.set_custom_behaviour(custom) @@custom = custom end |
.set_server(hostname, port, key) ⇒ Object
4 5 6 7 8 |
# File 'lib/server.rb', line 4 def self.set_server(hostname, port, key) @@server_hostname = hostname @@server_port = port @@server_key = key end |
.setup_auth(options) ⇒ Object
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# File 'lib/auth.rb', line 8 def self.setup_auth() keys = load_keys_file() server = keys['default'] if .server_substring server = select_server(keys, .server_substring) end_on_error "No server found for substring '#{.server_substring}" unless server end key = keys['keys'][server] if key hostname, port, url_base = parse_hostname_with_port(server) set_server(hostname, port, key) puts "Application: #{url_base}" else end_on_error "No server authorised. Run oneis-plugin auth SERVER_NAME" end end |
.setup_request(req) ⇒ Object
63 64 65 66 |
# File 'lib/server.rb', line 63 def self.setup_request(req) req['User-Agent'] = 'plugin-tool' req['X-ONEIS-Key'] = @@server_key if @@server_key end |
.start_notifications(options) ⇒ Object
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# File 'lib/notifications.rb', line 8 def self.start_notifications() @@notification_options = @@notification_queue_name = nil Thread.new do while true begin self.do_notifications rescue => e puts "NOTICE: Lost notification connection, will attempt to reconnect soon." @@notification_announce_reconnect = true sleep(5) # throttle errors end end end end |
.start_syntax_check ⇒ Object
11 12 13 14 15 16 17 18 19 20 |
# File 'lib/syntax_checking.rb', line 11 def self.start_syntax_check Thread.new do begin do_syntax_checking rescue => e puts "EXCEPTION IN SYNTAX CHECKER\n#{e.inspect}" exit 1 end end end |
.syntax_check(plugin, filename) ⇒ Object
99 100 101 102 103 104 105 |
# File 'lib/syntax_checking.rb', line 99 def self.syntax_check(plugin, filename) @@syntax_check_queue_lock.synchronize do @@syntax_check_queue << [plugin, filename] @@syntax_check_queue.uniq! end @@syntax_check_queue_semaphore.synchronize { @@syntax_check_queue_semaphore_var.signal } end |
.syntax_check_one_file(plugin, file) ⇒ Object
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/syntax_checking.rb', line 34 def self.syntax_check_one_file(plugin, file) # Load the plugin.json file plugin_dir = plugin.plugin_dir plugin_json = File.open("#{plugin_dir}/plugin.json") { |f| JSON.parse(f.read) } api_version = (plugin_json["apiVersion"] || '0').to_i # Determine kind of file kind = (file =~ /\A(\w+)\//) ? $1 : file # Is this file referenced? report = nil unless kind != 'js' || plugin_json['load'].include?(file) puts "\nWARNING: #{plugin_dir}/plugin.json doesn't mention #{file} in the load directive.\n" report = "Couldn't check file - not mentioned in plugin.json" else schema_requirements = (api_version < 4) ? nil : SchemaRequirements.new("#{plugin_dir}/requirements.schema") extra_globals = schema_requirements ? schema_requirements.locals.dup : [] # Load the file js = File.open("#{plugin_dir}/#{file}") { |f| f.read } if api_version < 3 # If it's not the first file, need to tell JSHint about the extra var unless plugin_json['load'].first == file extra_globals << plugin_json["pluginName"]; end else if kind == 'js' || kind == 'test' extra_globals << plugin_json["pluginName"] extra_globals << 'P' locals = plugin_json["locals"] if locals locals.each_key { |local| extra_globals << local } end end if kind == 'test' extra_globals << 't' end # global.js requires server side syntax checking, but doesn't have any extra globals, not even the plugin name. kind = 'js' if kind == 'global.js' end # Do syntax checking lint_report = @@syntax_tester.call(@@cx, @@javascript_scope, @@javascript_scope, [js, kind, JSON.generate(extra_globals)]) schema_report = schema_requirements ? schema_requirements.report_usage(js) : nil report = [lint_report, schema_report].compact.join("\n\n") report = nil if report.empty? end report end |
.try_load_custom ⇒ Object
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/custom.rb', line 26 def self.try_load_custom return unless File.exist?(LOCAL_CUSTOM_BEHAVIOUR_FILENAME) trusted_code = nil untrusted_code = File.open(LOCAL_CUSTOM_BEHAVIOUR_FILENAME) { |f| f.read } untrusted_code_digest = Digest::SHA256.hexdigest(untrusted_code) trusted_code_digests = {"trust" => []} trusted_code_filename = File.(TRUSTED_CODE_DIGESTS_FILENAME) if File.exist?(trusted_code_filename) trusted_code_digests = JSON.parse(File.open(trusted_code_filename) { |f| f.read }) end unless trusted_code_digests["trust"].include?(untrusted_code_digest) # Make sure the user wants to run this code. Otherwise running the plugin tool in a repo you've just # downloaded could unexpectedly execute code on your local machine. if ARGV.length == 2 && ARGV[0] == 'trust' && ARGV[1] =~ /\A[0-9a-z]{64}\z/ && ARGV[1] == untrusted_code_digest trusted_code_digests["trust"].push(untrusted_code_digest) File.open(trusted_code_filename,"w") { |f| f.write JSON.pretty_generate(trusted_code_digests) } puts "Stored trust for #{LOCAL_CUSTOM_BEHAVIOUR_FILENAME} with contents #{untrusted_code_digest}." exit 0 else puts puts "-------------------------------------------------------------------------------------------" puts " Do you trust the code in #{LOCAL_CUSTOM_BEHAVIOUR_FILENAME} to be run every time you run the" puts " plugin tool? If yes, run" puts " oneis-plugin trust #{untrusted_code_digest}" puts " to permanently trust this version of #{LOCAL_CUSTOM_BEHAVIOUR_FILENAME}" puts "-------------------------------------------------------------------------------------------" puts PluginTool.beep exit 1 end end if ARGV.length > 0 && ARGV[0] == "trust" puts "Unexpected trust command." exit 1 end # User trusts the code, run it # There is a race condition here, but we're trying to protect against code in repositories, not # against software running on the local machine. load LOCAL_CUSTOM_BEHAVIOUR_FILENAME end |