mediawiki-gateway 0.6.2 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/COPYING +22 -0
  3. data/ChangeLog +16 -0
  4. data/README.md +80 -21
  5. data/Rakefile +28 -34
  6. data/bin/mediawiki-gateway +203 -0
  7. data/lib/media_wiki.rb +4 -9
  8. data/lib/media_wiki/exception.rb +11 -8
  9. data/lib/media_wiki/fake_wiki.rb +636 -0
  10. data/lib/media_wiki/gateway.rb +105 -940
  11. data/lib/media_wiki/gateway/files.rb +173 -0
  12. data/lib/media_wiki/gateway/pages.rb +400 -0
  13. data/lib/media_wiki/gateway/query.rb +98 -0
  14. data/lib/media_wiki/gateway/site.rb +101 -0
  15. data/lib/media_wiki/gateway/users.rb +182 -0
  16. data/lib/media_wiki/utils.rb +47 -13
  17. data/lib/media_wiki/version.rb +27 -0
  18. data/lib/mediawiki-gateway.rb +1 -0
  19. data/spec/{import-test-data.xml → data/import.xml} +0 -0
  20. data/spec/media_wiki/gateway/files_spec.rb +34 -0
  21. data/spec/media_wiki/gateway/pages_spec.rb +390 -0
  22. data/spec/media_wiki/gateway/query_spec.rb +84 -0
  23. data/spec/media_wiki/gateway/site_spec.rb +122 -0
  24. data/spec/media_wiki/gateway/users_spec.rb +171 -0
  25. data/spec/media_wiki/gateway_spec.rb +129 -0
  26. data/spec/{live_gateway_spec.rb → media_wiki/live_gateway_spec.rb} +31 -35
  27. data/spec/{utils_spec.rb → media_wiki/utils_spec.rb} +41 -39
  28. data/spec/spec_helper.rb +17 -16
  29. metadata +77 -135
  30. data/.ruby-version +0 -1
  31. data/.rvmrc +0 -34
  32. data/Gemfile +0 -19
  33. data/Gemfile.lock +0 -77
  34. data/LICENSE +0 -21
  35. data/config/hosts.yml +0 -17
  36. data/lib/media_wiki/config.rb +0 -69
  37. data/mediawiki-gateway.gemspec +0 -113
  38. data/samples/README +0 -18
  39. data/samples/create_page.rb +0 -13
  40. data/samples/delete_batch.rb +0 -14
  41. data/samples/download_batch.rb +0 -15
  42. data/samples/email_user.rb +0 -14
  43. data/samples/export_xml.rb +0 -14
  44. data/samples/get_page.rb +0 -11
  45. data/samples/import_xml.rb +0 -14
  46. data/samples/run_fake_media_wiki.rb +0 -8
  47. data/samples/search_content.rb +0 -12
  48. data/samples/semantic_query.rb +0 -17
  49. data/samples/upload_commons.rb +0 -45
  50. data/samples/upload_file.rb +0 -13
  51. data/spec/fake_media_wiki/api_pages.rb +0 -135
  52. data/spec/fake_media_wiki/app.rb +0 -360
  53. data/spec/fake_media_wiki/query_handling.rb +0 -136
  54. data/spec/gateway_spec.rb +0 -888
@@ -1,9 +1,4 @@
1
- require 'thread' # bizarre workaround for Rails 2.3.x/RubyGems incompatibility
2
- require File.dirname(__FILE__) + '/media_wiki/config'
3
- require File.dirname(__FILE__) + '/media_wiki/exception'
4
- require File.dirname(__FILE__) + '/media_wiki/utils'
5
- require File.dirname(__FILE__) + '/media_wiki/gateway'
6
-
7
- module MediaWiki
8
- VERSION = "0.6.2"
9
- end
1
+ require_relative 'media_wiki/version'
2
+ require_relative 'media_wiki/exception'
3
+ require_relative 'media_wiki/utils'
4
+ require_relative 'media_wiki/gateway'
@@ -1,26 +1,29 @@
1
1
  module MediaWiki
2
+
2
3
  # General exception occurred within MediaWiki::Gateway, and parent class for MediaWiki::APIError, MediaWiki::Unauthorized.
3
- class Exception < Exception
4
+ class Exception < ::Exception
4
5
  end
5
6
 
6
7
  # Wrapper for errors returned by MediaWiki API. Possible codes are defined in http://www.mediawiki.org/wiki/API:Errors_and_warnings.
7
8
  #
8
9
  # Warnings also throw errors with code 'warning', unless MediaWiki::Gateway#new was called with :ignorewarnings.
9
- class APIError < MediaWiki::Exception
10
+ class APIError < Exception
11
+
10
12
  attr_reader :code, :info, :message
11
-
13
+
12
14
  def initialize(code, info)
13
- @code = code
14
- @info = info
15
- @message = "API error: code '#{code}', info '#{info}'"
15
+ @code, @info, @message = code, info,
16
+ "API error: code '#{code}', info '#{info}'"
16
17
  end
17
18
 
18
19
  def to_s
19
- "#{self.class.to_s}: #{@message}"
20
+ "#{self.class}: #{@message}"
20
21
  end
22
+
21
23
  end
22
24
 
23
25
  # User is not authorized to perform this operation. Also thrown if MediaWiki::Gateway#login fails.
24
- class Unauthorized < MediaWiki::Exception
26
+ class Unauthorized < Exception
25
27
  end
28
+
26
29
  end
@@ -0,0 +1,636 @@
1
+ require 'sinatra/base'
2
+ require 'sham_rack'
3
+ require 'nokogiri'
4
+
5
+ module MediaWiki
6
+
7
+ # A simple Rack app that stubs out a web service, for testing.
8
+
9
+ module FakeWiki
10
+
11
+ class App < Sinatra::Base
12
+
13
+ set :show_exceptions, false
14
+ set :environment, :development
15
+
16
+ def initialize
17
+ reset
18
+ super
19
+ end
20
+
21
+ def reset
22
+ @sequence_id = 0
23
+
24
+ @users = {}
25
+ add_user('atlasmw', 'wombat', 'local', true)
26
+ add_user('nonadmin', 'sekrit', 'local', false)
27
+ add_user('ldapuser', 'ldappass', 'ldapdomain', false)
28
+
29
+ @pages = ApiPages.new
30
+ @pages.add('Main Page', 'Content')
31
+ @pages.add('Main 2', 'Content')
32
+ @pages.add('Empty', '')
33
+ @pages.add('Level/Level/Index', '{{#include:Foo}} {{#include:Bar}}')
34
+ @pages.add_namespace(100, "Book")
35
+ @pages.add('Book:Italy', 'Introduction')
36
+ @pages.add_namespace(200, "Sandbox")
37
+ @pages.add('Foopage', 'Content')
38
+ @pages.add('Redirect', '#REDIRECT', true)
39
+
40
+ @extensions = { 'FooExtension' => 'r1', 'BarExtension' => 'r2', 'Semantic MediaWiki' => '1.5' }
41
+
42
+ @logged_in_users = []
43
+ end
44
+
45
+ def next_id
46
+ @sequence_id += 1
47
+ end
48
+
49
+ def add_user(username, password, domain, is_admin)
50
+ @users[username] = {
51
+ :userid => next_id,
52
+ :username => username,
53
+ :password => password,
54
+ :domain => domain,
55
+ :is_admin => is_admin
56
+ }
57
+ end
58
+
59
+ def logged_in(username)
60
+ @logged_in_users.include?(username)
61
+ end
62
+
63
+ get "/w/api.php" do
64
+ handle_request if params[:action] == 'query'
65
+ end
66
+
67
+ post "/w/api.php" do
68
+ handle_request
69
+ end
70
+
71
+ def handle_request
72
+ begin
73
+ halt(503, "Maxlag exceeded") if params[:maxlag].to_i < 0
74
+
75
+ @token = ApiToken.new(params)
76
+ action = params[:action]
77
+ if respond_to?(action)
78
+ content_type "application/xml"
79
+ return send(action)
80
+ end
81
+
82
+ halt(404, "Page not found")
83
+ rescue ApiError => e
84
+ return api_error_response(e.code, e.message)
85
+ end
86
+ end
87
+
88
+ def import
89
+ @token.validate_admin
90
+
91
+ api_response do |_|
92
+ _.import do
93
+ _.page(nil, :title => "Main Page", :ns => 0, :revisions => 0)
94
+ _.page(nil, :title => "Template:Header", :ns => 10, :revisions => 1)
95
+ end
96
+ end
97
+ end
98
+
99
+ def validate_page_overwrite(current_page)
100
+ if current_page && params[:createonly]
101
+ raise ApiError.new("articleexists", "The article you tried to create has been created already")
102
+ end
103
+ end
104
+
105
+ def edit
106
+ @token.validate
107
+
108
+ title = params[:title]
109
+ current_page = @pages.get(title)
110
+ validate_page_overwrite(current_page)
111
+
112
+ new_page = @pages.add(title, params[:text])
113
+ page_info = {:result => "Success", :pageid => new_page[:pageid], :title => new_page[:title], :newrevid => new_page[:pageid]}
114
+ if current_page
115
+ page_info.merge!(:oldrevid => current_page[:pageid])
116
+ else
117
+ page_info.merge!(:new => "", :oldrevid => 0)
118
+ end
119
+
120
+ api_response do |_|
121
+ _.edit(nil, page_info)
122
+ end
123
+ end
124
+
125
+ def delete
126
+ @token.validate_admin
127
+
128
+ title = params[:title]
129
+ raise ApiError.new("missingtitle", "The page you requested doesn't exist") unless @pages.get(title)
130
+ @pages.delete(title)
131
+
132
+ api_response do |_|
133
+ _.delete(nil, {:title => title, :reason => "Default reason"})
134
+ end
135
+ end
136
+
137
+ def undelete
138
+ @token.validate_admin
139
+
140
+ title = params[:title]
141
+ revisions = @pages.undelete(title)
142
+ api_response do |_|
143
+ _.undelete(nil, {:title => title, :revisions => revisions})
144
+ end
145
+ end
146
+
147
+ def upload
148
+ @token.validate
149
+
150
+ filename = params[:filename]
151
+ @pages.add(filename, params[:file])
152
+ api_response do |_|
153
+ _.upload(nil, {:filename => filename, :result => "Success"})
154
+ end
155
+ end
156
+
157
+ def parse
158
+ page = @pages.get(params[:page])
159
+ api_response do |_|
160
+ _.parse({ :revid => page ? page[:pageid] : 0}) do
161
+ if params[:page] == "Foopage"
162
+ _.text!('Sample <B>HTML</B> content.' \
163
+ '<img width="150" height="150" class="thumbimage" src="http://upload.wikimedia.org/foo/Ruby_logo.svg" alt="Ruby logo.svg"/>' \
164
+ '<span class="editsection">[<a title="Edit section: Nomenclature" href="/w/index.php?title=Seat_of_local_government&amp;action=edit&amp;section=1">edit</a>]</span>' \
165
+ '<a title="Interpreted language" href="/wiki/Interpreted_language">interpreted language</a>'
166
+ )
167
+ else
168
+ _.text!('Sample <B>HTML</B> content.')
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ def action
175
+ [:userrights].each do |action_type|
176
+ return send(action_type)
177
+ end
178
+ halt(404, "Page not found")
179
+ end
180
+
181
+ def query
182
+ [:prop, :export, :list, :meta].each do |query_type|
183
+ return send(query_type) if params[query_type]
184
+ end
185
+ halt(404, "Page not found")
186
+ end
187
+
188
+ def prop
189
+ return get_revisions if params[:prop] == "revisions"
190
+ return get_undelete_token if params[:drprop] == 'token'
191
+ return get_token if params[:intoken]
192
+ return get_info if params[:prop] == "info"
193
+ end
194
+
195
+ def export
196
+ Nokogiri::XML::Builder.new do |_|
197
+ _.mediawiki do
198
+ requested_page_titles.each do |requested_title|
199
+ page = @pages.get(requested_title)
200
+ _.page do
201
+ _.title(page[:title])
202
+ _.id(page[:pageid])
203
+ _.revision do
204
+ _.id(page[:pageid])
205
+ _.text!(page[:content])
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end.to_xml
211
+ end
212
+
213
+ def list
214
+ list_type = params[:list].to_sym
215
+
216
+ # api.php?action=query&list=users&ususers=Bob&ustoken=userrights
217
+ if list_type == :users && params[:ustoken] && params[:ususers]
218
+ # This "list" is actually a request for a user rights token
219
+ return get_userrights_token(params[:ususers])
220
+ end
221
+
222
+ # This is a real list
223
+ return send(list_type) if respond_to?(list_type)
224
+ halt(404, "Page not found")
225
+ end
226
+
227
+ def allpages
228
+ api_response do |_|
229
+ _.query do
230
+ _.allpages do
231
+ prefix = params[:apprefix]
232
+ namespace = @pages.namespaces_by_id[params[:apnamespace].to_i]
233
+ prefix = "#{namespace}:#{prefix}" unless namespace.empty?
234
+ @pages.list(prefix).each do |key, page|
235
+ _.p(nil, { :title => page[:title], :ns => page[:namespace], :id => page[:pageid] })
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
241
+
242
+ def search
243
+ api_response do |_|
244
+ _.query do
245
+ _.search do
246
+ namespaces = params[:srnamespace] ? params[:srnamespace].split('|') : [ "0" ]
247
+ @pages.search(params[:srsearch], namespaces).first(params[:srlimit].to_i).each do |key, page|
248
+ _.p(nil, { :title => page[:title], :ns => page[:namespace], :id => page[:pageid] })
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
254
+
255
+ def meta
256
+ meta_type = params[:meta].to_sym
257
+ return send(meta_type) if respond_to?(meta_type)
258
+ halt(404, "Page not found")
259
+ end
260
+
261
+ def siteinfo
262
+ if siteinfo_type = params[:siprop]
263
+ return send(siteinfo_type) if respond_to?(siteinfo_type)
264
+ halt(404, "Page not found")
265
+ else
266
+ api_response do |_|
267
+ _.query do
268
+ _.general(generator: "MediaWiki #{MediaWiki::VERSION}")
269
+ end
270
+ end
271
+ end
272
+ end
273
+
274
+ def namespaces
275
+ api_response do |_|
276
+ _.query do
277
+ _.namespaces do
278
+ @pages.namespaces_by_prefix.each do |prefix, id|
279
+ attr = { :id => id }
280
+ attr[:canonical] = prefix unless prefix.empty?
281
+ _.ns(prefix, attr)
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+ def extensions
289
+ api_response do |_|
290
+ _.query do
291
+ _.extensions do
292
+ @extensions.each do |name, version|
293
+ attr = { :version => version }
294
+ attr[:name] = name unless name.empty?
295
+ _.ext(name, attr)
296
+ end
297
+ end
298
+ end
299
+ end
300
+ end
301
+
302
+ def api_response(api_attr = {}, &block)
303
+ Nokogiri::XML::Builder.new do |_|
304
+ _.api(api_attr, &block)
305
+ end.to_xml
306
+ end
307
+
308
+ def api_error_response(code, info)
309
+ api_response do |_|
310
+ _.error(nil,
311
+ :code => code,
312
+ :info => info)
313
+ end
314
+ end
315
+
316
+ def query_pages
317
+ api_response do |_|
318
+ _.query do
319
+ _.pages do
320
+ requested_page_titles.each do |title|
321
+ yield(_, title, @pages.get(title))
322
+ end
323
+ end
324
+ end
325
+ end
326
+ end
327
+
328
+ def get_info
329
+ query_pages do |_, title, page|
330
+ attributes = { :title => title, :ns => '0'}
331
+ if page.nil?
332
+ attributes[:missing] = ""
333
+ else
334
+ attributes[:redirect] = "" if page[:redirect]
335
+ end
336
+ _.page(nil, attributes)
337
+ end
338
+ end
339
+
340
+ def get_revisions
341
+ query_pages do |_, title, page|
342
+ if page.nil?
343
+ _.page(nil, { :title => title, :ns => '0', :missing => "" })
344
+ else
345
+ page = page.dup
346
+ content = page.delete(:content)
347
+ _.page(page.merge({ :ns => 0 })) do
348
+ _.revisions do
349
+ _.rev(content)
350
+ end
351
+ end
352
+ end
353
+ end
354
+ end
355
+
356
+ def user
357
+ username = request.cookies['login']
358
+ @users[username] if logged_in(username)
359
+ end
360
+
361
+ def requested_page_titles
362
+ params[:titles].split("|")
363
+ end
364
+
365
+ def tokens
366
+ @token.request(user)
367
+
368
+ api_response do |_|
369
+ _.tokens(:optionstoken => @token.optionstoken)
370
+ end
371
+ end
372
+
373
+ def get_token
374
+ token_str = @token.request(user)
375
+ query_pages do |_, title, page|
376
+ page = page ? page.dup : {}
377
+ page[params[:intoken] + "token"] = token_str if token_str
378
+ _.page(nil, page.merge({ :ns => 0 }))
379
+ end
380
+ end
381
+
382
+ def get_undelete_token
383
+ @token.set_type 'undelete'
384
+ token_str = @token.request(user)
385
+ api_response do |_|
386
+ _.query do
387
+ _.deletedrevs do
388
+ requested_page_titles.select {|title| ! @pages.get(title) }.each do |title|
389
+ _.page(nil, { :title => title, :token => token_str })
390
+ end
391
+ end
392
+ end
393
+ end
394
+ end
395
+
396
+ def get_userrights_token(username)
397
+ @token.set_type 'userrights'
398
+ token_str = @token.request(user)
399
+
400
+ user_to_manage = @users[username]
401
+
402
+ if user_to_manage
403
+ api_response do |_|
404
+ _.query do
405
+ _.users do
406
+ _.user(nil, { :name => user_to_manage[:username], :userrightstoken => token_str })
407
+ end
408
+ end
409
+ end
410
+ else
411
+ api_response do |_|
412
+ _.error(nil, { :code => 'nosuchuser', :info => "The user '#{params[:ususer].to_s}' does not exist"} )
413
+ end
414
+ end
415
+ end
416
+
417
+ def login
418
+ user = @users[params[:lgname]]
419
+ if user and user[:domain] == params[:lgdomain]
420
+ if params[:lgpassword] == user[:password]
421
+ @logged_in_users << user[:username]
422
+ response.set_cookie('login', user[:username])
423
+ result = { :result => "Success", :lguserid => "1", :lgusername => "Atlasmw"}
424
+ else
425
+ result = { :result => "WrongPass" }
426
+ end
427
+ else
428
+ result = { :result => "NotExists" }
429
+ end
430
+
431
+ api_response do |_|
432
+ _.login(nil, result)
433
+ end
434
+ end
435
+
436
+ def createaccount
437
+ api_response do |_|
438
+ @token.request(user)
439
+
440
+ if params[:token] && !params[:token].empty?
441
+ @token.validate_admin
442
+ add_user(params[:name], params[:password], 'local', false)
443
+ _.createaccount(:token => @token.createusertoken, :userid => @users.length, :username => params[:name], :result => 'success')
444
+ else
445
+ _.createaccount(:token => @token.createusertoken, :result => 'needtoken')
446
+ end
447
+ end
448
+ end
449
+
450
+ def options
451
+ api_response(:options => 'success')
452
+ end
453
+
454
+ def userrights
455
+ api_response do |_|
456
+ _.userrights({:user => params[:user]}) do
457
+ _.removed do
458
+ params[:remove].split('|').each do |removed_group|
459
+ _.group(removed_group)
460
+ end
461
+ end
462
+ _.added do
463
+ params[:add].split('|').each do |added_group|
464
+ _.group(added_group)
465
+ end
466
+ end
467
+ end
468
+ end
469
+ end
470
+
471
+ end
472
+
473
+ class WikiPage
474
+
475
+ def initialize(options={})
476
+ options.each { |k, v| send("#{k}=", v) }
477
+ end
478
+
479
+ attr_accessor :content, :author
480
+
481
+ end
482
+
483
+ class ApiPages
484
+
485
+ def initialize
486
+ @page_id = 0
487
+ @pages = {}
488
+ @namespaces = { "" => 0 }
489
+ end
490
+
491
+ def add_namespace(id, prefix)
492
+ @namespaces[prefix] = id
493
+ end
494
+
495
+ def namespaces_by_prefix
496
+ @namespaces
497
+ end
498
+
499
+ def namespaces_by_id
500
+ @namespaces.invert
501
+ end
502
+
503
+ def add(title, content, redirect=false)
504
+ @page_id += 1
505
+ dummy, prefix = title.split(":", 2).reverse
506
+ @pages[title] = {
507
+ :pageid => @page_id,
508
+ :namespace => namespaces_by_prefix[prefix || ""],
509
+ :title => title,
510
+ :content => content,
511
+ :redirect => redirect
512
+ }
513
+ end
514
+
515
+ def get(title)
516
+ @pages[title]
517
+ end
518
+
519
+ def list(prefix)
520
+ @pages.select do |key, page|
521
+ key =~ /^#{prefix}/
522
+ end
523
+ end
524
+
525
+ def search(searchkey, namespaces)
526
+ raise ApiError.new("srparam-search", "empty search string is not allowed") if searchkey.empty?
527
+ @pages.select do |key, page|
528
+ page[:content] =~ /#{searchkey}/ and namespaces.include? page[:namespace].to_s
529
+ end
530
+ end
531
+
532
+ def delete(title)
533
+ @pages.delete(title)
534
+ end
535
+
536
+ def undelete(title)
537
+ if @pages[title]
538
+ 0
539
+ else
540
+ add(title, "Undeleted content")
541
+ 1
542
+ end
543
+ end
544
+ end
545
+
546
+ class ApiToken
547
+
548
+ ADMIN_TOKEN = "admin_token+\\"
549
+ REGULAR_TOKEN = "regular_token+\\"
550
+ BLANK_TOKEN = "+\\"
551
+
552
+ def initialize(params)
553
+ @token_str = params[:token]
554
+ @token_in = params[:intoken]
555
+ end
556
+
557
+ def set_type(type)
558
+ @token_in = type
559
+ end
560
+
561
+ def validate
562
+ unless @token_str
563
+ raise ApiError.new("notoken", "The token parameter must be set")
564
+ end
565
+ end
566
+
567
+ def validate_admin
568
+ validate
569
+ if @token_str != ADMIN_TOKEN
570
+ raise ApiError.new("badtoken", "Invalid token")
571
+ end
572
+ end
573
+
574
+ def request(user)
575
+ @user = user
576
+ respond_to?(requested_token_type) ? send(requested_token_type) : nil
577
+ end
578
+
579
+ def requested_token_type
580
+ "#{@token_in}token".to_sym
581
+ end
582
+
583
+ def importtoken
584
+ if @user && @user[:is_admin]
585
+ ADMIN_TOKEN
586
+ else
587
+ nil
588
+ end
589
+ end
590
+
591
+ alias_method :deletetoken, :importtoken
592
+ alias_method :undeletetoken, :importtoken
593
+ alias_method :userrightstoken, :importtoken
594
+ alias_method :createusertoken, :importtoken
595
+
596
+ def edittoken
597
+ if @user
598
+ REGULAR_TOKEN
599
+ else
600
+ BLANK_TOKEN
601
+ end
602
+ end
603
+
604
+ alias_method :optionstoken, :edittoken
605
+
606
+ end
607
+
608
+ class ApiError < StandardError
609
+
610
+ attr_reader :code, :message
611
+
612
+ def initialize(code, message)
613
+ @code = code
614
+ @message = message
615
+ end
616
+
617
+ end
618
+
619
+ module RSpecAdapter
620
+
621
+ ADDRESS = 'dummy-wiki.example'
622
+
623
+ def self.enhance(config, *args)
624
+ ShamRack.mount($fake_media_wiki = App.new!, ADDRESS)
625
+
626
+ config.before(*args) {
627
+ @gateway = Gateway.new("http://#{ADDRESS}/w/api.php")
628
+ $fake_media_wiki.reset
629
+ }
630
+ end
631
+
632
+ end
633
+
634
+ end
635
+
636
+ end