kinokero 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (187) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +44 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +49 -0
  7. data/Gemfile_mock +8 -0
  8. data/LICENSE +20 -0
  9. data/README.md +955 -0
  10. data/Rakefile +1 -0
  11. data/console/.ruby-gemset +1 -0
  12. data/console/.ruby-version +1 -0
  13. data/console/Gemfile +6 -0
  14. data/console/Gemfile.lock +63 -0
  15. data/console/README.md +34 -0
  16. data/console/config/application_configuration.rb +39 -0
  17. data/console/config/gcp_seed.yml +73 -0
  18. data/console/config/kinokero_initializer_template.rb +108 -0
  19. data/console/console +2 -0
  20. data/console/irb_console +2 -0
  21. data/console/lib/appliance_common.rb +73 -0
  22. data/console/twiga.rb +579 -0
  23. data/hp-check.log +244 -0
  24. data/kinokero.gemspec +35 -0
  25. data/lib/kinokero.rb +183 -0
  26. data/lib/kinokero/blank.rb +105 -0
  27. data/lib/kinokero/cloudprint.rb +1159 -0
  28. data/lib/kinokero/device.rb +6 -0
  29. data/lib/kinokero/jingle.rb +176 -0
  30. data/lib/kinokero/log.rb +157 -0
  31. data/lib/kinokero/printer.rb +313 -0
  32. data/lib/kinokero/proxy.rb +341 -0
  33. data/lib/kinokero/ruby_extensions.rb +21 -0
  34. data/lib/kinokero/sasl_xoauth2.rb +164 -0
  35. data/lib/kinokero/version.rb +3 -0
  36. data/lib/proto/cloud_device_description.proto +18 -0
  37. data/lib/proto/cloud_device_state.proto +30 -0
  38. data/lib/proto/cloud_device_state_type.proto +20 -0
  39. data/lib/proto/cloud_device_ui_state.proto +42 -0
  40. data/lib/proto/cloud_device_ui_state_severity.proto +12 -0
  41. data/lib/proto/cloud_job_ticket.proto +18 -0
  42. data/lib/proto/collate.proto +4 -0
  43. data/lib/proto/collate_ticket_item.proto +5 -0
  44. data/lib/proto/color.proto +42 -0
  45. data/lib/proto/color_ticket_item.proto +12 -0
  46. data/lib/proto/copies.proto +6 -0
  47. data/lib/proto/copies_ticket_item.proto +5 -0
  48. data/lib/proto/cover.proto +31 -0
  49. data/lib/proto/cover_state.proto +25 -0
  50. data/lib/proto/device_action_cause.proto +19 -0
  51. data/lib/proto/dpi.proto +27 -0
  52. data/lib/proto/dpi_ticket_item.proto +13 -0
  53. data/lib/proto/duplex.proto +15 -0
  54. data/lib/proto/duplex_ticket_item.proto +8 -0
  55. data/lib/proto/fit_to_page.proto +20 -0
  56. data/lib/proto/fit_to_page_ticket_item.proto +8 -0
  57. data/lib/proto/input_tray_state.proto +31 -0
  58. data/lib/proto/input_tray_unit.proto +35 -0
  59. data/lib/proto/job_state.proto +143 -0
  60. data/lib/proto/local_settings.proto +36 -0
  61. data/lib/proto/localized_string.proto +119 -0
  62. data/lib/proto/margins.proto +33 -0
  63. data/lib/proto/margins_ticket_item.proto +14 -0
  64. data/lib/proto/marker.proto +62 -0
  65. data/lib/proto/marker_state.proto +31 -0
  66. data/lib/proto/media_path.proto +6 -0
  67. data/lib/proto/media_path_state.proto +25 -0
  68. data/lib/proto/media_size.proto +216 -0
  69. data/lib/proto/media_size_ticket_item.proto +17 -0
  70. data/lib/proto/output_bin_state.proto +31 -0
  71. data/lib/proto/output_bin_unit.proto +32 -0
  72. data/lib/proto/page_orientation.proto +16 -0
  73. data/lib/proto/page_orientation_ticket_item.proto +9 -0
  74. data/lib/proto/page_range.proto +15 -0
  75. data/lib/proto/page_range_ticket_item.proto +7 -0
  76. data/lib/proto/print_job_state.proto +18 -0
  77. data/lib/proto/print_job_state_diff.proto +13 -0
  78. data/lib/proto/print_job_ui_state.proto +24 -0
  79. data/lib/proto/print_ticket_section.proto +30 -0
  80. data/lib/proto/printer_description_section.proto +107 -0
  81. data/lib/proto/printer_state_section.proto +49 -0
  82. data/lib/proto/printer_ui_state_section.proto +39 -0
  83. data/lib/proto/printing_speed.proto +35 -0
  84. data/lib/proto/pwg_raster_config.proto +176 -0
  85. data/lib/proto/range_capability.proto +14 -0
  86. data/lib/proto/reverse_order.proto +5 -0
  87. data/lib/proto/reverse_order_ticket_item.proto +5 -0
  88. data/lib/proto/scanner_description_section.proto +5 -0
  89. data/lib/proto/scanner_state_section.proto +25 -0
  90. data/lib/proto/select_capability.proto +31 -0
  91. data/lib/proto/supported_content_type.proto +12 -0
  92. data/lib/proto/typed_value_capability.proto +15 -0
  93. data/lib/proto/vendor_capability.proto +40 -0
  94. data/lib/proto/vendor_state.proto +26 -0
  95. data/lib/proto/vendor_ticket_item.proto +9 -0
  96. data/lib/proto_lib/cloud_device_state.pb.rb +21 -0
  97. data/lib/proto_lib/cloud_device_state_type.pb.rb +16 -0
  98. data/lib/proto_lib/cloud_device_ui_state.pb.rb +22 -0
  99. data/lib/proto_lib/cloud_device_ui_state_severity.pb.rb +17 -0
  100. data/lib/proto_lib/collate.pb.rb +11 -0
  101. data/lib/proto_lib/color.pb.rb +31 -0
  102. data/lib/proto_lib/copies.pb.rb +12 -0
  103. data/lib/proto_lib/cover.pb.rb +21 -0
  104. data/lib/proto_lib/cover_state.pb.rb +27 -0
  105. data/lib/proto_lib/device_action_cause.pb.rb +18 -0
  106. data/lib/proto_lib/dpi.pb.rb +27 -0
  107. data/lib/proto_lib/duplex.pb.rb +26 -0
  108. data/lib/proto_lib/fit_to_page.pb.rb +28 -0
  109. data/lib/proto_lib/input_tray_state.pb.rb +30 -0
  110. data/lib/proto_lib/input_tray_unit.pb.rb +25 -0
  111. data/lib/proto_lib/job_state.pb.rb +99 -0
  112. data/lib/proto_lib/localized_string.pb.rb +118 -0
  113. data/lib/proto_lib/margins.pb.rb +30 -0
  114. data/lib/proto_lib/marker.pb.rb +45 -0
  115. data/lib/proto_lib/marker_state.pb.rb +30 -0
  116. data/lib/proto_lib/media_path.pb.rb +11 -0
  117. data/lib/proto_lib/media_path_state.pb.rb +27 -0
  118. data/lib/proto_lib/media_size.pb.rb +198 -0
  119. data/lib/proto_lib/output_bin_state.pb.rb +30 -0
  120. data/lib/proto_lib/output_bin_unit.pb.rb +22 -0
  121. data/lib/proto_lib/page_orientation.pb.rb +26 -0
  122. data/lib/proto_lib/page_range.pb.rb +20 -0
  123. data/lib/proto_lib/print_job_state_diff.pb.rb +12 -0
  124. data/lib/proto_lib/printer_description_section.pb.rb +30 -0
  125. data/lib/proto_lib/printer_state_section.pb.rb +23 -0
  126. data/lib/proto_lib/printer_ui_state_section.pb.rb +28 -0
  127. data/lib/proto_lib/printing_speed.pb.rb +21 -0
  128. data/lib/proto_lib/pwg_raster_config.pb.rb +103 -0
  129. data/lib/proto_lib/range_capability.pb.rb +19 -0
  130. data/lib/proto_lib/reverse_order.pb.rb +11 -0
  131. data/lib/proto_lib/scanner_description_section.pb.rb +10 -0
  132. data/lib/proto_lib/scanner_state_section.pb.rb +17 -0
  133. data/lib/proto_lib/select_capability.pb.rb +22 -0
  134. data/lib/proto_lib/supported_content_type.pb.rb +13 -0
  135. data/lib/proto_lib/typed_value_capability.pb.rb +19 -0
  136. data/lib/proto_lib/vendor_capability.pb.rb +23 -0
  137. data/lib/proto_lib/vendor_state.pb.rb +27 -0
  138. data/test/.ruby-gemset +1 -0
  139. data/test/.ruby-version +1 -0
  140. data/test/Gemfile +68 -0
  141. data/test/Gemfile.lock +269 -0
  142. data/test/README.md +2 -0
  143. data/test/Rakefile +6 -0
  144. data/test/app/assets/javascripts/application.js +16 -0
  145. data/test/app/assets/stylesheets/application.css +13 -0
  146. data/test/app/controllers/application_controller.rb +13 -0
  147. data/test/app/controllers/home_controller.rb +10 -0
  148. data/test/app/helpers/application_helper.rb +2 -0
  149. data/test/app/views/home/index.html.erb +2 -0
  150. data/test/app/views/home/show.html.erb +2 -0
  151. data/test/app/views/layouts/application.html.erb +14 -0
  152. data/test/bin/bundle +3 -0
  153. data/test/bin/rails +4 -0
  154. data/test/bin/rake +4 -0
  155. data/test/config/application.rb +29 -0
  156. data/test/config/boot.rb +4 -0
  157. data/test/config/database.yml +25 -0
  158. data/test/config/environment.rb +5 -0
  159. data/test/config/environments/development.rb +48 -0
  160. data/test/config/environments/production.rb +95 -0
  161. data/test/config/environments/test.rb +42 -0
  162. data/test/config/initializers/backtrace_silencers.rb +7 -0
  163. data/test/config/initializers/filter_parameter_logging.rb +4 -0
  164. data/test/config/initializers/inflections.rb +16 -0
  165. data/test/config/initializers/mime_types.rb +5 -0
  166. data/test/config/initializers/secret_token.rb +12 -0
  167. data/test/config/initializers/session_store.rb +3 -0
  168. data/test/config/initializers/wrap_parameters.rb +14 -0
  169. data/test/config/locales/en.yml +23 -0
  170. data/test/config/routes.rb +65 -0
  171. data/test/db/development.sqlite3 +0 -0
  172. data/test/db/migrate/20111012050200_add_sessions_table.rb +12 -0
  173. data/test/db/schema.rb +26 -0
  174. data/test/db/seeds.rb +7 -0
  175. data/test/db/test.sqlite3 +0 -0
  176. data/test/log/development.log +0 -0
  177. data/test/log/test.log +0 -0
  178. data/test/test/controllers/home_controller_test.rb +133 -0
  179. data/test/test/ctlr_test_helper.rb +7 -0
  180. data/test/test/fixtures/gcp_seed.yml +51 -0
  181. data/test/test/models/cloudprint_test.rb +186 -0
  182. data/test/test/models/jingle_test.rb +44 -0
  183. data/test/test/models/printer_test.rb +99 -0
  184. data/test/test/models/proxy_test.rb +102 -0
  185. data/test/test/test_helper.rb +31 -0
  186. data/test/test/test_kinokero.rb +234 -0
  187. metadata +462 -0
@@ -0,0 +1,341 @@
1
+ # #########################################################################
2
+ # #########################################################################
3
+
4
+ module Kinokero
5
+
6
+ # #########################################################################
7
+
8
+ class Proxy
9
+
10
+ extend Forwardable
11
+ # require 'singleton'
12
+ # include Singleton
13
+
14
+ # #########################################################################
15
+
16
+ attr_reader :my_devices, :options
17
+
18
+ # #########################################################################
19
+
20
+ # -----------------------------------------------------------------------------
21
+ def initialize( gcp_hash, options = { verbose: true, auto_connect: true } )
22
+
23
+ @proxy_id = Kinokero.my_proxy_id
24
+ @options = options
25
+ @my_devices = {} # will hold the device objects from seed
26
+
27
+ Kinokero::Log.verbose_debug( options.inspect, options[:verbose] )
28
+
29
+ cups_list = Cups.show_destinations # current printer list
30
+
31
+ # convert seed data into device objects
32
+ gcp_hash.each_key do |item|
33
+
34
+ # make sure that the persistent printer is still in CUPS list
35
+ if cups_list.include?( gcp_hash[item][:cups_alias] )
36
+
37
+ # instantiate object and remember
38
+ @my_devices[ item ] =
39
+ Kinokero::Printer.new( gcp_hash[item] )
40
+
41
+ @my_devices[ item ].cloudprint = Kinokero::Cloudprint.new(
42
+ @my_devices[item].gcp_printer_control,
43
+ options
44
+ )
45
+
46
+ else # previously registered device no longer active
47
+ # TODO:
48
+ end # convert each seed to a device object
49
+
50
+ end # setting up each device
51
+
52
+ end
53
+
54
+ # -----------------------------------------------------------------------------
55
+ # do_connect --
56
+ # -----------------------------------------------------------------------------
57
+ def do_connect(item)
58
+ # establish a jingle connection
59
+ @my_devices[item].cloudprint.gtalk_start_connection do |printerid|
60
+ # NOTE: this execution takes place asynchronously
61
+ # upon callback from jingle notification
62
+
63
+ if printerid =~ /delete/
64
+ # jingle notified us of delete
65
+ # potentially reentreant; but delete anyway
66
+ # TODO: verify printerid for us?
67
+ do_delete( item, true )
68
+ else # print notification
69
+ do_print_jobs( printerid )
70
+ end # if..then..else notification type
71
+
72
+ end # block
73
+
74
+ # execution continues here BEFORE above block executes
75
+
76
+ # upon first connect, fetch & print any pending jobs in queue
77
+ fetch_and_print_queue_if_ready(
78
+ item,
79
+ @my_devices[item].gcp_printer_control[:gcp_printerid]
80
+ )
81
+
82
+ # begin polling the printer device status
83
+ @my_devices[item].start_poll_thread
84
+
85
+ end
86
+
87
+ # -----------------------------------------------------------------------------
88
+
89
+ def do_fetch_jobs( item )
90
+
91
+ @my_devices[item].cloudprint.gcp_get_printer_fetch(
92
+ @my_devices[item].gcp_printer_control[:gcp_printerid]
93
+ )
94
+
95
+ end
96
+
97
+ # -----------------------------------------------------------------------------
98
+ # do_delete --
99
+ # this is potentially reentreant:
100
+ # an initial do_delete spawns a jingle delete notification which
101
+ # then calls do delete again at an indeterminate point
102
+ # -----------------------------------------------------------------------------
103
+ def do_delete(item, skip_gcp=nil )
104
+
105
+ # forestall reentreant issues by checking our validity
106
+ unless (my_device = @my_devices[item]).nil?
107
+
108
+ # stop polling the printer device status
109
+ my_device.stop_poll_thread
110
+
111
+ # forestall reentreant issues by checking our validity
112
+ unless my_device.cloudprint.nil?
113
+ # do delete housekeeping & maybe issue GCP command
114
+ my_device.cloudprint.gcp_delete_printer( skip_gcp )
115
+ my_device.cloudprint = nil # release the reference to our object
116
+ end # unless cloudprint's been removed
117
+
118
+ end # unless device already removed
119
+
120
+ @my_devices.delete( item ) # remove device struct from our list
121
+
122
+ end
123
+
124
+ # -----------------------------------------------------------------------------
125
+ # do_register -- registers our default printer, prints claim info
126
+ # -----------------------------------------------------------------------------
127
+ def do_register( gcp_request, &block )
128
+
129
+ response = Kinokero::Cloudprint.register_anonymous_printer( gcp_request ) do |gcp_ctl|
130
+
131
+ # this block is called only if/when asynch polling completes
132
+ # in a separate process
133
+ puts Kinokero::Log.say_info("\n***** Printer successfully registered to GCP *****")
134
+ puts Kinokero::Log.say_warn "register gcp_control: #{@gcp_ctl.object_id}"
135
+ puts gcp_ctl.inspect
136
+
137
+ # wrap the newly registered printer in a device object
138
+ new_device = Kinokero::Printer.new( gcp_ctl, gcp_request)
139
+
140
+ # add it to our list of managed devices
141
+ @my_devices[ gcp_ctl[:item] ] = new_device
142
+
143
+ # create a cloudprint object to manage the protocols
144
+ new_device.cloudprint =
145
+ Kinokero::Cloudprint.new( gcp_ctl, @options )
146
+
147
+ Kinokero::Log.verbose_debug "my_devices has #{ @my_devices.size } devices [#{ @my_devices.object_id }]"
148
+
149
+ # this is the place to save anything we need to about the printers
150
+ # under swalapala control; info is in gcp_ctl
151
+ yield( gcp_ctl ) # persistence
152
+
153
+ end # block for register
154
+
155
+ # execution continues here AFTER registering but BEFORE polling completes
156
+ # this is our opportunity to tell the user to claim the printer via
157
+ # registration token at Google Cloud Print server
158
+
159
+ print_gcp_registration_info( response ) # output registration instructions
160
+
161
+ return response
162
+
163
+ end
164
+
165
+ # -----------------------------------------------------------------------------
166
+ # -----------------------------------------------------------------------------
167
+ def do_refresh(item)
168
+ @my_devices[item].cloudprint.gcp_refresh_tokens
169
+ # new token should be set up in the gcp_control area
170
+ end
171
+
172
+ # -----------------------------------------------------------------------------
173
+ # -----------------------------------------------------------------------------
174
+ def do_list(item)
175
+ @my_devices[item].cloudprint.gcp_get_printer_list
176
+ end
177
+
178
+ # -----------------------------------------------------------------------------
179
+ # -----------------------------------------------------------------------------
180
+ def do_ready_state(item)
181
+ @my_devices[item].cloudprint.gcp_ready_state_changed(
182
+ true, # shows ready for jobs
183
+ 0, # waiting for work
184
+ '' # no reason description needed
185
+ )
186
+ end
187
+
188
+
189
+ # -----------------------------------------------------------------------------
190
+ # -----------------------------------------------------------------------------
191
+
192
+ def item_from_printerid( printerid )
193
+
194
+ found = @my_devices.detect do |item, device|
195
+ break item if device.gcp_printer_control[:gcp_printerid] == printerid
196
+ false
197
+ end # each item
198
+
199
+ raise PrinteridNotFound, printerid if found.nil? # oops, not found!
200
+
201
+ return found
202
+
203
+ end
204
+
205
+ # -----------------------------------------------------------------------------
206
+
207
+ def fetch_and_print_queue_if_ready( item, printerid )
208
+
209
+ if @my_devices[item].is_printer_ready?
210
+
211
+ print_fetch_queue(
212
+ item, # find corresponding device item
213
+ printerid,
214
+ @my_devices[item].cloudprint.gcp_get_printer_fetch( printerid )
215
+ )
216
+
217
+ else # oops, printer is NOT ready
218
+ # TODO: shouldn't we tell GCP about this situation?
219
+
220
+ end # continue if printer ready
221
+
222
+ end
223
+
224
+ # -----------------------------------------------------------------------------
225
+
226
+ # do_print_jobs blends across the perfect protocol boundaries I'm trying to
227
+ # maintain with Cloudprint, mainly because there's a higher level process
228
+ # handling which it has to handle, thus involving multiple cloudprint
229
+ # interactions and Printer class interaction.
230
+ # -----------------------------------------------------------------------------
231
+ def do_print_jobs( printerid )
232
+
233
+ fetch_and_print_queue_if_ready(
234
+ item_from_printerid( printerid ),
235
+ printerid
236
+ )
237
+
238
+ end
239
+
240
+ # -----------------------------------------------------------------------------
241
+
242
+ # DRY work of printing a fetch queue of jobs
243
+ def print_fetch_queue(item, printerid, fetch_result)
244
+ if fetch_result['success']
245
+ Kinokero::Log.verbose_debug "#{ printerid } queue has #{ fetch_result['jobs'].size } jobs"
246
+
247
+ my_cloudprint = @my_devices[item].cloudprint # DRY access
248
+
249
+ # deal with each job fetched
250
+ fetch_result['jobs'].each do |job|
251
+
252
+ unless printerid == job['printerid'] # ? hmmm, different printer ref'd
253
+
254
+ item = item_from_printerid( printerid ) # find corresponding device item
255
+ printerid = job['printerid']
256
+ my_cloudprint = @my_devices[item].cloudprint # DRY access
257
+ print "\e[1;31m\n***** WARNING ***** differ printerid in fetch queue #{printerid}\n\e[0m"
258
+ end
259
+
260
+ # able to download the job file for printing?
261
+ if ( job_file = my_cloudprint.gcp_get_job_file( job["fileUrl"] ) )
262
+
263
+ # update printer status to IN PROGRESS
264
+ my_cloudprint.gcp_job_status(
265
+ job["id"],
266
+ ::Kinokero::Cloudprint::GCP_JOBSTATE_IN_PROGRESS,
267
+ 0
268
+ )
269
+
270
+ # write the file locally
271
+ File.open( job["id"], 'wb') { |fp| fp.write(job_file) }
272
+
273
+ status = @my_devices[item].print_file( job['id'] )
274
+
275
+ # TODO: do something intelligent with the status
276
+ # like report back to GCP
277
+
278
+ # poll printer job status & report back to GCP
279
+ my_cloudprint.gcp_job_status(
280
+ job["id"],
281
+ ::Kinokero::Cloudprint::GCP_JOBSTATE_DONE,
282
+ job["numberOfPages"]
283
+ )
284
+
285
+ # delete the file
286
+ File.delete( job["id"] )
287
+
288
+ else # failure to get file; tell GCP about the status
289
+ my_cloudprint.gcp_job_status_abort(
290
+ job["id"],
291
+ ::Kinokero::Cloudprint::GCP_USER_ACTION_OTHER,
292
+ 0
293
+ )
294
+
295
+ end # if..then..else get job file
296
+
297
+ end # do each job
298
+ end # pending job queue from fetch
299
+
300
+ end
301
+
302
+ # -----------------------------------------------------------------------------
303
+ # -----------------------------------------------------------------------------
304
+ def print_gcp_registration_info( response )
305
+ if response[:success]
306
+ Kinokero::Printer.print_gcp_registration_info(
307
+ response[:cups_alias], # actual printer to use
308
+ snippet_registration_info( response ) # crafted message to print
309
+ )
310
+ end
311
+ end
312
+
313
+ # -----------------------------------------------------------------------------
314
+ # -----------------------------------------------------------------------------
315
+ def snippet_registration_info( response )
316
+ <<RUBY10
317
+
318
+ ******************************************************************
319
+ *** Important instructions to complete CloudPrint registration ***
320
+ ******************************************************************
321
+ *** Go to the following url and claim printer with the given ***
322
+ *** registration token, or click the easy-claim url below. You ***
323
+ *** must do this within the next fifteen (15) minutes. Thanks! ***
324
+ ******************************************************************
325
+
326
+ Registration token: #{response[:gcp_printer_reg_token]}
327
+ Claim printer URL: #{response[:gcp_claim_token_url]}
328
+ Easy-claim URL: #{response[:gcp_easy_reg_url]}
329
+ Record id: #{response[:swalapala_printer_id]}
330
+ Printer name: #{response[:gcp_printer_name]}
331
+ GCP printer id: #{response[:gcp_printer_id]}
332
+
333
+ RUBY10
334
+ end
335
+
336
+ # -----------------------------------------------------------------------------
337
+ # -----------------------------------------------------------------------------
338
+
339
+ end # class Proxy
340
+
341
+ end # module
@@ -0,0 +1,21 @@
1
+ # commonly used extensions from Rails for Ruby
2
+ #
3
+ require 'kinokero/blank'
4
+
5
+ # ****************************************************************************
6
+ class Hash
7
+
8
+ # -----------------------------------------------------------------------------
9
+ # File activesupport/lib/active_support/core_ext/hash/keys.rb, line 48
10
+ # -----------------------------------------------------------------------------
11
+ def assert_valid_keys(*valid_keys)
12
+ valid_keys.flatten!
13
+ each_key do |k|
14
+ raise(ArgumentError, "Unknown key: #{k}") unless valid_keys.include?(k)
15
+ end
16
+ end
17
+ # -----------------------------------------------------------------------------
18
+ # -----------------------------------------------------------------------------
19
+
20
+ end # class Hash
21
+ # ****************************************************************************
@@ -0,0 +1,164 @@
1
+ # ****************************************************************************
2
+ # ***** addtions to XMPP4R to handle Google Talk X-OAUTH2 ********************
3
+ # ****************************************************************************
4
+ # ****************************************************************************
5
+ # ****************************************************************************
6
+
7
+ module Jabber
8
+
9
+ # ****************************************************************************
10
+
11
+ MECHANISM_XOAUTH2 = 'X-OAUTH2'
12
+ NS_GOOGLE_AUTH_PROTOCOL = "http://www.google.com/talk/protocol/auth"
13
+ NS_GOOGLE_AUTH_SERVICE = "chromiumsync"
14
+
15
+ # ****************************************************************************
16
+
17
+ class Client
18
+
19
+ # -----------------------------------------------------------------------------
20
+ # Authenticate with the server
21
+ #
22
+ # Throws ClientAuthenticationFailure
23
+ #
24
+ # Authentication mechanisms are used in the following preference:
25
+ # * SASL X-OAUTH2
26
+ # * SASL DIGEST-MD5
27
+ # * SASL PLAIN
28
+ # * Non-SASL digest
29
+ # password:: [String]
30
+ #
31
+ # THIS OVERRIDES XMPP4R method of the same
32
+ #
33
+ # -----------------------------------------------------------------------------
34
+ def auth(password)
35
+
36
+ begin
37
+ if @stream_mechanisms.include? MECHANISM_XOAUTH2
38
+ auth_sasl SASL.new(self, MECHANISM_XOAUTH2), password
39
+ elsif @stream_mechanisms.include? 'DIGEST-MD5'
40
+ auth_sasl SASL.new(self, 'DIGEST-MD5'), password
41
+ elsif @stream_mechanisms.include? 'PLAIN'
42
+ auth_sasl SASL.new(self, 'PLAIN'), password
43
+ else
44
+ auth_nonsasl(password)
45
+ end
46
+
47
+ rescue
48
+ Jabber::debuglog("#{$!.class}: #{$!}\n#{$!.backtrace.join("\n")}")
49
+ raise ClientAuthenticationFailure.new, $!.to_s
50
+ end
51
+
52
+ end
53
+
54
+ end # class Client
55
+
56
+ # ****************************************************************************
57
+ # Helpers for SASL authentication (RFC2222)
58
+ #
59
+ # You might not need to use them directly, they are
60
+ # invoked by Jabber::Client#auth
61
+ # ****************************************************************************
62
+
63
+ module SASL
64
+
65
+ # -----------------------------------------------------------------------------
66
+ # Factory function to obtain a SASL helper for the specified mechanism
67
+ # -----------------------------------------------------------------------------
68
+ def SASL.new(stream, mechanism)
69
+
70
+ case mechanism
71
+
72
+ when MECHANISM_XOAUTH2 # added for the override
73
+ Xoauth2.new(stream)
74
+
75
+ when 'DIGEST-MD5'
76
+ DigestMD5.new(stream)
77
+
78
+ when 'PLAIN'
79
+ Plain.new(stream)
80
+
81
+ when 'ANONYMOUS'
82
+ Anonymous.new(stream)
83
+
84
+ else
85
+ raise "Unknown SASL mechanism: #{mechanism}"
86
+
87
+ end # case
88
+
89
+ end
90
+
91
+ # -----------------------------------------------------------------------------
92
+ # cookie is an OAuth2 access token which you obtained from the anonymous registration flow.
93
+ # this is passed as the "password" to auth
94
+ # -----------------------------------------------------------------------------
95
+ class Xoauth2 < Base
96
+
97
+ def auth(password)
98
+
99
+ auth_text = "\x00#{@stream.jid.node}\x00#{password}"
100
+ error = nil
101
+
102
+ # ::Twiga.say_warn 'XOAUTH2: ' + auth_text.inspect + "\n"
103
+ # ::Twiga.say_warn 'ENCODED: ' + Base64::strict_encode64(auth_text).inspect + "\n"
104
+
105
+ @stream.send(
106
+ generate_auth(
107
+ MECHANISM_XOAUTH2,
108
+ Base64::strict_encode64(auth_text)
109
+ )
110
+ ) do |reply|
111
+ unless reply.name == 'success'
112
+ error = reply.first_element(nil).name
113
+ end
114
+ true
115
+ end # do reply handling
116
+
117
+ raise error unless error.nil?
118
+
119
+ end
120
+
121
+ # ****************************************************************************
122
+
123
+ private
124
+
125
+ # ****************************************************************************
126
+ # from the Jingle documentation for CloudPrint
127
+ #
128
+ # h) Outgoing stanza from Google Cloud Print proxy or printer
129
+ # ****************************************************************************
130
+ # <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl"
131
+ # mechanism="X-OAUTH2" auth:service="chromiumsync"
132
+ # auth:allow-generated-jid="true"
133
+ # auth:client-uses-full-bind-result="true"
134
+ # xmlns:auth="http://www.google.com/talk/protocol/auth">
135
+ # {Base-64 encoded authentication data}
136
+ # </auth>
137
+ # ****************************************************************************
138
+ def generate_auth(mechanism, text=nil)
139
+
140
+ auth = REXML::Element.new 'auth'
141
+ auth.add_namespace NS_SASL
142
+ auth.attributes['mechanism'] = mechanism
143
+ auth.attributes['auth:service'] = NS_GOOGLE_AUTH_SERVICE
144
+ auth.attributes['auth:allow-generated-jid'] = "true"
145
+ auth.attributes['auth:client-uses-full-bind-result'] = "true"
146
+ auth.attributes['xmlns:auth'] = NS_GOOGLE_AUTH_PROTOCOL
147
+ auth.text = text
148
+ auth
149
+ end
150
+
151
+ # -----------------------------------------------------------------------------
152
+ # -----------------------------------------------------------------------------
153
+
154
+ # ****************************************************************************
155
+
156
+ end # class Xoauth2
157
+
158
+ # ****************************************************************************
159
+
160
+ end # module Sasl
161
+
162
+ # ****************************************************************************
163
+ end # module Jabber
164
+