iated 0.0.1

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 (54) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +3 -0
  3. data/Guardfile +24 -0
  4. data/LICENSE +23 -0
  5. data/Makefile +72 -0
  6. data/README.md +87 -0
  7. data/Rakefile +10 -0
  8. data/bin/iated +13 -0
  9. data/config.ru +5 -0
  10. data/extensions/chrome/background.html +5 -0
  11. data/extensions/chrome/background.js +105 -0
  12. data/extensions/chrome/contentscript.css +0 -0
  13. data/extensions/chrome/contentscript.js +110 -0
  14. data/extensions/chrome/jquery-ui.js +45 -0
  15. data/extensions/chrome/jquery.js +16 -0
  16. data/extensions/chrome/jquery.updater.js +46 -0
  17. data/extensions/chrome/manifest.json +19 -0
  18. data/extensions/chrome/yaml.js +489 -0
  19. data/extensions/tests/simple.html +23 -0
  20. data/features/extension_authenticates.feature +30 -0
  21. data/features/extension_edits.feature +45 -0
  22. data/features/step_definitions/extension_steps.rb +134 -0
  23. data/features/support/env.rb +47 -0
  24. data/features/support/hooks.rb +11 -0
  25. data/iated.gemspec +48 -0
  26. data/lib/iated.rb +111 -0
  27. data/lib/iated/browser_token_db.rb +76 -0
  28. data/lib/iated/edit_session.rb +221 -0
  29. data/lib/iated/helpers.rb +9 -0
  30. data/lib/iated/mcp.rb +144 -0
  31. data/lib/iated/page_helpers.rb +33 -0
  32. data/lib/iated/public/jquery-ui.js +101 -0
  33. data/lib/iated/public/jquery.js +2 -0
  34. data/lib/iated/public/robots.txt +5 -0
  35. data/lib/iated/server.rb +162 -0
  36. data/lib/iated/sys_pref.rb +201 -0
  37. data/lib/iated/version.rb +3 -0
  38. data/lib/iated/views/hello.haml +13 -0
  39. data/lib/iated/views/preferences.haml +27 -0
  40. data/lib/iated/views/reference.coffee +79 -0
  41. data/lib/iated/views/reference.haml +94 -0
  42. data/lib/iated/views/reference.scss +36 -0
  43. data/lib/iated/views/root.haml +13 -0
  44. data/spec/lib/iated/browser_token_db_spec.rb +68 -0
  45. data/spec/lib/iated/edit_session_spec.rb +157 -0
  46. data/spec/lib/iated/mcp_spec.rb +86 -0
  47. data/spec/lib/iated/sys_pref_spec.rb +40 -0
  48. data/spec/protocol/edit_spec.rb +88 -0
  49. data/spec/protocol/hello_spec.rb +18 -0
  50. data/spec/protocol/notfound_spec.rb +11 -0
  51. data/spec/protocol/ping_spec.rb +10 -0
  52. data/spec/protocol/preferences_spec.rb +35 -0
  53. data/spec/spec_helper.rb +21 -0
  54. metadata +460 -0
@@ -0,0 +1,76 @@
1
+ require 'yaml'
2
+ require 'pathname'
3
+ require 'digest/md5'
4
+
5
+ module Iated
6
+ ## The database for storing authorized browser tokens
7
+ class BrowserTokenDB
8
+
9
+ def initialize fname
10
+ @fname = Pathname.new fname
11
+ @tokens = {}
12
+
13
+ # Write an empty file.
14
+ if @fname.exist?
15
+ read
16
+ else
17
+ write
18
+ end
19
+ end
20
+
21
+ ## Add a token to the database.
22
+ # This stores it persistently.
23
+ # @return [String] the new token
24
+ def add user_agent
25
+ token = generate_token
26
+ @tokens[token] = user_agent.to_s
27
+ write
28
+ return token
29
+ end
30
+
31
+ ## Does the token exist in the db?
32
+ # @return [Boolean] Does this token exist in the store?
33
+ def has_token? token
34
+ @tokens.key? token
35
+ end
36
+
37
+ ## List of all tokens in DB.
38
+ # @return [Array] All tokens stored in DB.
39
+ def tokens
40
+ @tokens.keys
41
+ end
42
+
43
+ ## What is the user_agent for the token?
44
+ # @return [String] The user agent
45
+ def user_agent token
46
+ @tokens[token]
47
+ end
48
+
49
+ private
50
+
51
+ ## Generate a token
52
+ # @return [String] a hex string, 32 characters long
53
+ def generate_token
54
+ # FIXME this could be better.
55
+ digest = Digest::MD5.new
56
+ digest << Time.now.to_s
57
+ digest << rand.to_s
58
+ return digest.hexdigest
59
+ end
60
+
61
+ ## Write the data to the store
62
+ # @return [nil]
63
+ def write
64
+ @fname.dirname.mkpath unless @fname.dirname.directory?
65
+ @fname.open('w') do |f|
66
+ f.puts @tokens.to_yaml
67
+ end
68
+ end
69
+
70
+ ## Read the data from the store
71
+ # @return [nil]
72
+ def read
73
+ @tokens = YAML::load_file @fname.to_s
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,221 @@
1
+ require 'digest/md5'
2
+ require 'set'
3
+ require 'addressable/uri'
4
+ require 'fileutils'
5
+ require 'pathname'
6
+ require 'digest/md5'
7
+
8
+ module Iated
9
+ ## An Edit Session
10
+ #
11
+ # A single session for editing a textarea. It tracks the editor, etc.
12
+ class EditSession
13
+ # @return [String,nil] The URL the textarea came from
14
+ attr_reader :url
15
+ # @return [String,nil] The textarea id
16
+ attr_reader :tid
17
+ # @return [String,nil] The extension the text file should use
18
+ attr_reader :extension
19
+ # @return [String] The session id
20
+ attr_reader :sid
21
+
22
+ ##
23
+ # @param [Hash] options Various optional arguments (:url, :tid, :extension)
24
+ def initialize options=nil
25
+ normalized_options = normalize_keys options
26
+ @url = normalized_options[:url]
27
+ @tid = normalized_options[:tid]
28
+ @extension = normalized_options[:extension]
29
+ @change_id = 0
30
+ @sid = self.class.calculate_sid normalized_options
31
+ @last_checksum = :nochecksum
32
+
33
+ # Save session.
34
+ Iated::sessions[@sid] = self
35
+
36
+ # Save the text, if passed in the original options
37
+ self.text = options[:text] if options.key?(:text)
38
+ end
39
+
40
+ ## Finds an existing session
41
+ # @return [EditSession]
42
+ def self.find search=nil
43
+ # TODO Once the sid is random, then find needs to work via a data store.
44
+ if search.is_a? String
45
+ tok = search
46
+ else
47
+ tok = calculate_sid search
48
+ end
49
+ Iated::sessions[tok]
50
+ end
51
+
52
+ ## Finds an existing session or creates a new one
53
+ # @return [EditSession]
54
+ def self.find_or_create search=nil
55
+ sess = find search
56
+ sess.nil? ? self.new(search) : sess
57
+ end
58
+
59
+ ## Calculate a sid based on url, id, ext
60
+ # @return [String] A session id
61
+ def self.calculate_sid options
62
+ options = normalize_keys options
63
+
64
+ # TODO Should retrieve sid from datastore if it exists.
65
+ # TODO The sid calculation needs a random number
66
+ # TODO The sid calculation needs the current time or date
67
+ digest = Digest::MD5.new
68
+ digest << "url: #{options[:url]}"
69
+ digest << "tid: #{options[:tid]}"
70
+ digest << "extension: #{options[:extension]}"
71
+ return digest.hexdigest
72
+ end
73
+
74
+ ## Increment the change_id
75
+ # @return [Integer] The new number of changes
76
+ def increment_change_id
77
+ @change_id = @change_id + 1
78
+ end
79
+
80
+ ## Return the change_id
81
+ # @returns [Integer] The new number of changes
82
+ def change_id
83
+ if filename.exist?
84
+ new_checksum = filename.open('r') do |f|
85
+ Digest::MD5.hexdigest(f.read)
86
+ end
87
+
88
+ if @last_checksum != new_checksum
89
+ increment_change_id
90
+ @last_checksum = new_checksum
91
+ end
92
+ end
93
+ @change_id
94
+ end
95
+
96
+ ## Returns true if the editor is running.
97
+ # @return [Boolean] True if the editor is running
98
+ def running?
99
+ # TODO This should check to see if a process is running or not.
100
+ return true
101
+ end
102
+
103
+ ## Opens the user's configured editor.
104
+ def edit
105
+ editor = Iated.mcp.prefs.editor.to_s
106
+ if Iated::environment == :test
107
+ # We don't want to fire up real editors in testing mode.
108
+ #$stderr.puts "I would have edited #{filename.to_s.inspect} with #{editor.inspect}"
109
+ return
110
+ else
111
+ if RUBY_ENGINE == "jruby"
112
+ edit_jruby
113
+ else
114
+ edit_ruby
115
+ end
116
+ end
117
+ end
118
+
119
+ ## The Ruby version of edit
120
+ def edit_ruby
121
+ editor = Iated.mcp.prefs.editor.to_s
122
+ cmd = []
123
+ if RUBY_PLATFORM =~ /darwin/ && editor =~ /\.app$/
124
+ cmd << "/usr/bin/open"
125
+ cmd << "-a"
126
+ end
127
+ cmd << editor
128
+ cmd << filename.to_s
129
+ # TODO This doesn't get the exit code...
130
+ system *cmd
131
+ end
132
+
133
+ ## The JRuby version of edit
134
+ def edit_jruby
135
+ editor = Iated.mcp.prefs.editor.to_s
136
+
137
+ cmd = nil # We will store the CommandLine object here.
138
+ java_import org.apache.commons.exec.OS
139
+ java_import org.apache.commons.exec.CommandLine
140
+ java_import org.apache.commons.exec.DefaultExecutor
141
+
142
+ if OS.is_family_mac && editor =~ /\.app$/
143
+ # It's a Mac .app
144
+ cmd = CommandLine.new "/usr/bin/open"
145
+ cmd.add_argument("-a").add_argument(editor)
146
+ else
147
+ cmd = CommandLine.new editor
148
+ end
149
+ cmd.add_argument(filename.to_s)
150
+
151
+ executor = DefaultExecutor.new
152
+ executor.execute(cmd)
153
+ end
154
+
155
+ ## Returns the file where the session is saved.
156
+ # @return [Pathname] The filename and path the text was saved to.
157
+ def filename
158
+ if @filename.nil?
159
+ bad_chars = /[^a-zA-Z0-9._-]+/
160
+ url = Addressable::URI.parse(@url)
161
+ config_dir = Iated::mcp.prefs.config_dir
162
+ if url.host
163
+ # TODO Handle case where url is the root and filename is empty.
164
+ @filename = config_dir + url.host.gsub(bad_chars, '') +
165
+ "#{url.basename.gsub(bad_chars, '')}#{@extension}"
166
+ else
167
+ @filename = config_dir + "#{@url.gsub(bad_chars, '')}#{@extension}"
168
+ end
169
+ end
170
+ return @filename
171
+ end
172
+
173
+ ## Returns the text of the filename
174
+ def text
175
+ if filename.exist?
176
+ filename.read
177
+ else
178
+ nil
179
+ end
180
+ end
181
+
182
+ def text= value
183
+ increment_change_id if filename.exist?
184
+ filename.dirname.mkpath
185
+ # TODO: locking
186
+ filename.open('w') do |f|
187
+ f.write value
188
+ end
189
+ @last_checksum = Digest::MD5.hexdigest(value)
190
+ end
191
+
192
+ ## Normalizes the search options (`:url`, `:tid`, `:extension`)
193
+ #
194
+ # Used by other functions
195
+ # @return [Hash]
196
+ def self.normalize_keys hash=nil
197
+ norm_hash = {}
198
+ hash = {} if hash.nil?
199
+
200
+ [:url, :tid, :extension].each do |key|
201
+ norm_hash[key] = hash[key] || hash[key.to_s] || (:extension == key ? '.txt' : nil)
202
+ end
203
+
204
+ # Verify the keys are sane. We ignore :text.
205
+ accepted_keys = Set.new((hash.keys + [:text]).map {|x| [x, x.to_s]}.flatten)
206
+ unexpected_keys = Set.new(hash.keys) - accepted_keys
207
+ raise "Invalid keys: #{unexpected_keys.inspect}" if unexpected_keys.count > 0
208
+ return norm_hash
209
+ end
210
+
211
+ ## Alias for the class-method `normalize_keys`
212
+ # @see Iated::EditSession.normalize_keys
213
+ # @return [Hash]
214
+ def normalize_keys hash
215
+ self.class.normalize_keys hash
216
+ end
217
+
218
+ private
219
+
220
+ end
221
+ end
@@ -0,0 +1,9 @@
1
+
2
+ require 'sinatra'
3
+
4
+ helpers do
5
+ def require_authtoken params
6
+ cache_control :no_cache, :private
7
+ halt 403 if params[:auth].nil? or params[:auth] == 'magictoken'
8
+ end
9
+ end
@@ -0,0 +1,144 @@
1
+ ## Iated Master Controller Package
2
+ # This is the coordination point for
3
+ # all the data stored, and the UI.
4
+
5
+ require 'sinatra'
6
+ require 'pathname'
7
+ require 'iated/sys_pref'
8
+ require 'iated/browser_token_db'
9
+
10
+ module Iated
11
+ ## The Master Control Progrom
12
+ #
13
+ # Yes, I've been watching Tron lately, why do ask?
14
+ class MCP
15
+
16
+ attr_accessor :debug
17
+
18
+ attr_reader :browser_token_db
19
+
20
+ # @return [SysPref] The current System Preferencs object
21
+ attr_reader :prefs
22
+
23
+ def initialize
24
+ @debug = false
25
+ @prefs = SysPref.new
26
+ @browser_token_db = Iated::BrowserTokenDB.new(@prefs.config_dir + 'browser_tokens.yml')
27
+ end
28
+
29
+ ## Called by the main program to get everything started.
30
+ def begin!
31
+ start_server
32
+ end
33
+
34
+ # @return [String,Symbol] The currently showing secret or the symbol `:notset`
35
+ def secret
36
+ showing_secret? ? @secret : :notset
37
+ end
38
+
39
+ ## Confirm the secret
40
+ #
41
+ # @param [String] guess The string to check against the secret
42
+ # @return [Boolean] Was the guess right?
43
+ def confirm_secret guess
44
+ success = (guess == secret)
45
+ hide_secret
46
+ return success
47
+ end
48
+
49
+ # @return [Boolean] Is the secret being shown to the user?
50
+ def showing_secret?
51
+ @is_showing_secret
52
+ end
53
+
54
+ ## Show the secret to the user
55
+ # @return [nil]
56
+ def show_secret
57
+ generate_secret
58
+ # TODO This should do something different for text, vs. gui.
59
+ puts " ** A browser requested authorization: #{@secret}" unless :test == ui
60
+ ## require 'java'
61
+ ## Thread.new do
62
+ ## puts " ** A browser requested authorization: #{@secret}"
63
+ ### TODO This needs to be abstracted. There is no reason a purely command line version can't be done. Also, loading swing will break CI.
64
+ ### import javax.swing.JOptionPane
65
+ ### JOptionPane.showMessageDialog(nil,
66
+ ### "A browser has requested authorization: #{@secret}\nDo not press 'OK' until after you enter the number",
67
+ ### "Authorize Browser",
68
+ ### JOptionPane::INFORMATION_MESSAGE)
69
+ ## @is_showing_secret = false
70
+ ## end
71
+ end
72
+
73
+ ## Hide the secret again
74
+ # @return [nil]
75
+ def hide_secret
76
+ @is_showing_secret = false
77
+ # TODO secret should be using a JPanel or something, not a dialog.
78
+ end
79
+
80
+ ## The magic value shown to user to confirm a connection
81
+ # @return [String] The string
82
+ def generate_secret
83
+ @secret = (1..4).map{ rand(10).to_s }.join ""
84
+ @is_showing_secret = true
85
+ return @secret
86
+ end
87
+
88
+ ## The magic value registering a browser.
89
+ # @return [String] The token
90
+ def generate_token user_agent
91
+ @browser_token_db.add user_agent
92
+ end
93
+
94
+ ## Is the auth token a valid one?
95
+ # @return [Boolean] True if the token is valid and exist
96
+ def is_token_valid? token
97
+ @browser_token_db.has_token? token
98
+ end
99
+
100
+ def ui= ui_type
101
+ if [:text, :test, :gui].include? ui_type
102
+ @ui = ui_type
103
+ else
104
+ raise "Invalid UI type: #{ui_type.inspect}"
105
+ end
106
+ end
107
+
108
+ def ui
109
+ # Default UI
110
+ @ui ||= (:test == Iated.environment ? :test : :text)
111
+ end
112
+
113
+ def start_server
114
+ # TODO this needs to be in a separate thread.
115
+ set :server, %w[webrick]
116
+ set :bind, 'localhost'
117
+ set :port, prefs.port
118
+ if @debug
119
+ set :show_exceptions, true
120
+ set :environment, :development
121
+ else
122
+ set :show_exceptions, false
123
+ set :environment, :production
124
+ end
125
+
126
+ @is_running = true
127
+ Sinatra::Application.run!
128
+ end
129
+
130
+ def stop_server
131
+ @is_running = false
132
+ Sinatra::Application.quit!
133
+ end
134
+
135
+ def cucumber_coverage_check
136
+ @cucumber_check = true
137
+ end
138
+
139
+ def rspec_coverage_check
140
+ @rspec_check = true
141
+ end
142
+
143
+ end
144
+ end