iated 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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