qalam_oauth_engine 2.2.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +28 -0
  3. data/README.md +110 -0
  4. data/Rakefile +28 -0
  5. data/app/controllers/canvas_oauth/canvas_controller.rb +26 -0
  6. data/app/controllers/canvas_oauth/oauth_application_controller.rb +5 -0
  7. data/app/models/canvas_oauth/authorization.rb +46 -0
  8. data/config/canvas.yml.example +12 -0
  9. data/config/routes.rb +3 -0
  10. data/db/migrate/20121121005358_create_canvas_oauth_authorizations.rb +16 -0
  11. data/lib/canvas_oauth.rb +26 -0
  12. data/lib/canvas_oauth/canvas_api.rb +331 -0
  13. data/lib/canvas_oauth/canvas_api_extensions.rb +9 -0
  14. data/lib/canvas_oauth/canvas_application.rb +73 -0
  15. data/lib/canvas_oauth/canvas_config.rb +34 -0
  16. data/lib/canvas_oauth/config.rb +3 -0
  17. data/lib/canvas_oauth/default_utf8_parser.rb +13 -0
  18. data/lib/canvas_oauth/engine.rb +15 -0
  19. data/lib/canvas_oauth/version.rb +3 -0
  20. data/lib/tasks/canvas_oauth_tasks.rake +1 -0
  21. data/spec/controllers/canvas_oauth/canvas_controller_spec.rb +90 -0
  22. data/spec/dummy/README.rdoc +261 -0
  23. data/spec/dummy/Rakefile +7 -0
  24. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  25. data/spec/dummy/app/controllers/welcome_controller.rb +5 -0
  26. data/spec/dummy/config.ru +4 -0
  27. data/spec/dummy/config/application.rb +57 -0
  28. data/spec/dummy/config/boot.rb +10 -0
  29. data/spec/dummy/config/canvas.yml +12 -0
  30. data/spec/dummy/config/database.yml +25 -0
  31. data/spec/dummy/config/environment.rb +5 -0
  32. data/spec/dummy/config/environments/development.rb +26 -0
  33. data/spec/dummy/config/environments/production.rb +69 -0
  34. data/spec/dummy/config/environments/test.rb +33 -0
  35. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  36. data/spec/dummy/config/initializers/inflections.rb +15 -0
  37. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  38. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  39. data/spec/dummy/config/initializers/session_store.rb +8 -0
  40. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  41. data/spec/dummy/config/locales/en.yml +5 -0
  42. data/spec/dummy/config/routes.rb +4 -0
  43. data/spec/dummy/db/development.sqlite3 +0 -0
  44. data/spec/dummy/db/migrate/20160711200737_create_canvas_oauth_authorizations.canvas_oauth.rb +16 -0
  45. data/spec/dummy/db/schema.rb +27 -0
  46. data/spec/dummy/db/test.sqlite3 +0 -0
  47. data/spec/dummy/log/development.log +1 -0
  48. data/spec/dummy/log/test.log +1630 -0
  49. data/spec/dummy/public/404.html +26 -0
  50. data/spec/dummy/public/422.html +26 -0
  51. data/spec/dummy/public/500.html +25 -0
  52. data/spec/dummy/public/favicon.ico +0 -0
  53. data/spec/dummy/public/robots.txt +5 -0
  54. data/spec/dummy/script/rails +6 -0
  55. data/spec/lib/canvas_oauth/canvas_api_extensions_spec.rb +13 -0
  56. data/spec/lib/canvas_oauth/canvas_api_spec.rb +364 -0
  57. data/spec/lib/canvas_oauth/default_utf8_parser_spec.rb +21 -0
  58. data/spec/models/canvas_oauth/authorization_spec.rb +47 -0
  59. data/spec/spec_helper.rb +64 -0
  60. metadata +377 -0
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/404.html -->
21
+ <div class="dialog">
22
+ <h1>The page you were looking for doesn't exist.</h1>
23
+ <p>You may have mistyped the address or the page may have moved.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/422.html -->
21
+ <div class="dialog">
22
+ <h1>The change you wanted was rejected.</h1>
23
+ <p>Maybe you tried to change something you didn't have access to.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,25 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/500.html -->
21
+ <div class="dialog">
22
+ <h1>We're sorry, but something went wrong.</h1>
23
+ </div>
24
+ </body>
25
+ </html>
File without changes
@@ -0,0 +1,5 @@
1
+ # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2
+ #
3
+ # To ban all spiders from the entire site uncomment the next two lines:
4
+ # User-Agent: *
5
+ # Disallow: /
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe CanvasOauth::CanvasApiExtensions do
4
+ describe "build" do
5
+ subject { CanvasOauth::CanvasApiExtensions.build('http://test.canvas', 1, 'abc123') }
6
+
7
+ before { allow(CanvasOauth::Authorization).to receive(:fetch_token).with(1, 'abc123').and_return('token') }
8
+
9
+ it { is_expected.to be_a CanvasOauth::CanvasApi }
10
+ its(:token) { is_expected.to eq 'token' }
11
+ its(:canvas_url) { is_expected.to eq 'http://test.canvas' }
12
+ end
13
+ end
@@ -0,0 +1,364 @@
1
+ require 'spec_helper'
2
+
3
+ describe CanvasOauth::CanvasApi do
4
+ let(:canvas) { CanvasOauth::CanvasApi.new('http://test.canvas', 1, 'token', 'refresh_token', 'key', 'secret') }
5
+
6
+ describe "initializer" do
7
+ subject { canvas }
8
+
9
+ its(:canvas_url) { is_expected.to eq 'http://test.canvas' }
10
+ its(:token) { is_expected.to eq 'token' }
11
+ its(:key) { is_expected.to eq 'key' }
12
+ its(:secret) { is_expected.to eq 'secret' }
13
+ end
14
+
15
+ describe "auth_url" do
16
+ subject { canvas.auth_url('http://localhost:3001/canvas/oauth', 'zzxxyy') }
17
+
18
+ it { is_expected.to eq "http://test.canvas/login/oauth2/auth?client_id=key&response_type=code&state=zzxxyy&redirect_uri=http://localhost:3001/canvas/oauth" }
19
+ end
20
+
21
+ describe "hash_csv" do
22
+ it "accepts a csv string and hashes it" do
23
+ csv = "canvas_course_id,course_id,short_name\n1,,Test Course"
24
+ expect(canvas.hash_csv(csv)).to eq([{
25
+ "canvas_course_id" => "1",
26
+ "course_id" => "",
27
+ "short_name" => "Test Course"
28
+ }])
29
+ end
30
+
31
+ it "accepts a parsed csv array and hashes it" do
32
+ csv = [["canvas_course_id", "course_id", "short_name"], ["1", nil, "Test Course"]]
33
+ expect(canvas.hash_csv(csv)).to eq([{
34
+ "canvas_course_id" => "1",
35
+ "course_id" => "",
36
+ "short_name" => "Test Course"
37
+ }])
38
+ end
39
+ end
40
+
41
+ describe "get_access_token" do
42
+ it "POSTs to /login/oauth2/token" do
43
+ expect(CanvasOauth::CanvasApi).to receive(:post).with('/login/oauth2/token', anything()).and_return({})
44
+ canvas.get_access_token('code')
45
+ end
46
+
47
+ it "returns the access token" do
48
+ allow(CanvasOauth::CanvasApi).to receive(:post).and_return({ 'access_token' => 'token' })
49
+ expect(canvas.get_access_token('code')).to eq 'token'
50
+ end
51
+
52
+ it "sends the key, secret, and code as params" do
53
+ params = {
54
+ body: {
55
+ client_id: 'key',
56
+ client_secret: 'secret',
57
+ code: 'code'
58
+ }
59
+ }
60
+
61
+ expect(CanvasOauth::CanvasApi).to receive(:post).with(anything(), params).and_return({})
62
+ canvas.get_access_token('code')
63
+ end
64
+ end
65
+
66
+ describe "requests" do
67
+ describe "authenticated_request" do
68
+ it "passes the params along as-is and adds an Authorization header" do
69
+ expect(CanvasOauth::CanvasApi).to receive(:get).with('/path', { query: 'stuff', headers: { 'Authorization' => 'Bearer token' } })
70
+ canvas.authenticated_request :get, '/path', { query: 'stuff' }
71
+ end
72
+
73
+ it "raises an authenticate error when the response is a 401 and WWW-Authenticate is set" do
74
+ allow(CanvasOauth::CanvasApi).to receive(:get).and_return(double(unauthorized?: true, headers: { 'WWW-Authenticate' => true }))
75
+ expect { canvas.authenticated_request :get, '/path' }.to raise_error CanvasOauth::CanvasApi::Authenticate
76
+ end
77
+
78
+ it "raises an unauthorized error when the response is a 401" do
79
+ allow(CanvasOauth::CanvasApi).to receive(:get).and_return(double(unauthorized?: true, headers: {}))
80
+ expect { canvas.authenticated_request :get, '/path' }.to raise_error CanvasOauth::CanvasApi::Unauthorized
81
+ end
82
+ end
83
+
84
+ describe "get_courses" do
85
+ it "queries /api/v1/courses" do
86
+ expect(CanvasOauth::CanvasApi).to receive(:get).with('/api/v1/courses', anything())
87
+ canvas.get_courses
88
+ end
89
+ end
90
+
91
+ describe "get_account_courses" do
92
+ it "queries /api/v1/accounts/:id/courses" do
93
+ expect(CanvasOauth::CanvasApi).to receive(:get).with('/api/v1/accounts/1/courses', anything())
94
+ canvas.get_account_courses(1)
95
+ end
96
+
97
+ it "paginates" do
98
+ expect(canvas).to receive(:paginated_get)
99
+ canvas.get_account_courses(1)
100
+ end
101
+ end
102
+
103
+ describe "get account users" do
104
+ it "queries /api/v1/accounts/:id/users" do
105
+ expect(CanvasOauth::CanvasApi).to receive(:get).with('/api/v1/accounts/1/users', anything())
106
+ canvas.get_account_users(1)
107
+ end
108
+
109
+ it "paginates" do
110
+ expect(canvas).to receive(:paginated_get)
111
+ canvas.get_account_users(1)
112
+ end
113
+ end
114
+
115
+ describe "get_course" do
116
+ it "queries /api/v1/courses/:id" do
117
+ expect(CanvasOauth::CanvasApi).to receive(:get).with('/api/v1/courses/123', anything())
118
+ canvas.get_course('123')
119
+ end
120
+ end
121
+
122
+ describe "get_course_students" do
123
+ it "queries /api/v1/courses/:id/students" do
124
+ expect(CanvasOauth::CanvasApi).to receive(:get).with('/api/v1/courses/123/students', anything())
125
+ canvas.get_course_students('123')
126
+ end
127
+
128
+ it "paginates" do
129
+ expect(canvas).to receive(:paginated_get)
130
+ canvas.get_course_students('123')
131
+ end
132
+ end
133
+
134
+ describe "get_sections" do
135
+ it "queries /api/v1/courses/:id/sections" do
136
+ expect(CanvasOauth::CanvasApi).to receive(:get).with('/api/v1/courses/123/sections', anything())
137
+ canvas.get_sections('123')
138
+ end
139
+
140
+ it "paginates" do
141
+ expect(canvas).to receive(:paginated_get)
142
+ canvas.get_sections('123')
143
+ end
144
+ end
145
+
146
+ describe "get_assignments" do
147
+ it "queries /api/v1/courses/:id/assignments" do
148
+ expect(CanvasOauth::CanvasApi).to receive(:get).with('/api/v1/courses/123/assignments', anything())
149
+ canvas.get_assignments('123')
150
+ end
151
+
152
+ it "paginates" do
153
+ expect(canvas).to receive(:paginated_get)
154
+ canvas.get_account_courses('123')
155
+ end
156
+ end
157
+
158
+ describe "get_user_profile" do
159
+ it "queries /api/v1/users/:id/profile" do
160
+ expect(CanvasOauth::CanvasApi).to receive(:get).with('/api/v1/users/123/profile', anything())
161
+ canvas.get_user_profile('123')
162
+ end
163
+ end
164
+
165
+ describe "create_assignment" do
166
+ it "posts to /api/v1/courses/:id/assignments" do
167
+ expect(CanvasOauth::CanvasApi).to receive(:post).with('/api/v1/courses/123/assignments', anything())
168
+ canvas.create_assignment('123', name: "Assignment")
169
+ end
170
+
171
+ it "sets the body of the request to the assignment params" do
172
+ expect(canvas).to receive(:authenticated_post).with(anything(), { body: { assignment: { name: "Assignment" }}})
173
+ canvas.create_assignment('123', name: "Assignment")
174
+ end
175
+ end
176
+
177
+ describe "update_assignment" do
178
+ it "puts to /api/v1/courses/:course_id/assignments/:id" do
179
+ expect(CanvasOauth::CanvasApi).to receive(:put).with('/api/v1/courses/123/assignments/345', anything())
180
+ canvas.update_assignment('123', '345', omit_from_final_grade: true)
181
+ end
182
+
183
+ it "sets the body of the request to the assignment params" do
184
+ expect(canvas).to receive(:authenticated_put).
185
+ with(anything(), { body: { assignment: { omit_from_final_grade: true }}})
186
+
187
+ canvas.update_assignment('123', '345', omit_from_final_grade: true)
188
+ end
189
+ end
190
+
191
+ describe "grade_assignment" do
192
+ it "puts to /api/v1/courses/:course_id/assignments/:assignment_id/submissions/:id" do
193
+ expect(CanvasOauth::CanvasApi).to receive(:put).with('/api/v1/courses/1/assignments/2/submissions/3', anything())
194
+ canvas.grade_assignment('1', '2', '3', {})
195
+ end
196
+
197
+ it "sets the body of the request to the grade params" do
198
+ expect(canvas).to receive(:authenticated_put).with(anything(), { body: { percentage: "80%" }})
199
+ canvas.grade_assignment('1', '2', '3', percentage: "80%")
200
+ end
201
+ end
202
+
203
+ describe 'get_submission' do
204
+ it 'queries /api/v1/courses/:course_id/assignments/:assignment_id/submissions/:user_id' do
205
+ expect(canvas).to receive(:authenticated_get).with('/api/v1/courses/1/assignments/2/submissions/3')
206
+ canvas.get_submission(1, 2, 3)
207
+ end
208
+ end
209
+
210
+ describe "get_report" do
211
+ context "from account level" do
212
+ let(:created) {
213
+ {
214
+ 'id' => '9',
215
+ 'status' => 'created'
216
+ }
217
+ }
218
+ let(:complete) {
219
+ {
220
+ 'id' => '10',
221
+ 'status' => 'complete',
222
+ 'file_url' => '/files/8/download/'
223
+ }
224
+ }
225
+ let(:running) {
226
+ {
227
+ 'id' => '11',
228
+ 'status' => 'running'
229
+ }
230
+ }
231
+ let(:aborted) {
232
+ {
233
+ 'id' => '12',
234
+ 'status' => 'aborted'
235
+ }
236
+ }
237
+ let(:file) { {'url': 'http://canvas.com'} }
238
+ let(:response) { double("response", :parsed_response => "1, 2, 3") }
239
+ let(:params) {
240
+ {
241
+ account_id: '1',
242
+ email: "foo@bar.com",
243
+ filters: {
244
+ start_date: '07/30/19',
245
+ end_date: '07/31/19'
246
+ }
247
+ }
248
+ }
249
+
250
+ before(:each) do
251
+ allow(canvas).to receive(:authenticated_post).and_return(created)
252
+ allow(canvas).to receive(:get_file).and_return(file)
253
+ allow(CanvasOauth::CanvasApi).to receive(:get).and_return(response)
254
+ allow(self).to receive(:sleep)
255
+ end
256
+
257
+ it "posts to /api/v1/accounts/:account_id/reports/:provisioning_csv" do
258
+ allow(canvas).to receive(:authenticated_get).and_return(complete)
259
+ expect(canvas).to receive(:authenticated_post).with("/api/v1/accounts/1/reports/provisioning_csv", { body: params })
260
+ canvas.get_report(1, :provisioning_csv, params)
261
+ end
262
+
263
+ it "queries /api/v1/accounts/:account_id/reports/:provisioning_csv/:report_id until report is 'complete'" do
264
+ allow(canvas).to receive(:authenticated_get?) { true }
265
+ expect(canvas).to receive(:authenticated_get).exactly(3).times.and_return(created, running, complete)
266
+ canvas.get_report(1, :provisioning_csv, params)
267
+ end
268
+
269
+ it "doesn't continue request reports upon 'aborted' status" do
270
+ allow(canvas).to receive(:authenticated_get?) { true }
271
+ expect(canvas).to receive(:authenticated_get).exactly(3).times.and_return(created, running, aborted)
272
+ canvas.get_report(1, :provisioning_csv, params)
273
+ end
274
+
275
+ it "uses the default UTF 8 parser it its get call" do
276
+ allow(canvas).to receive(:authenticated_get).and_return(complete)
277
+ expect(canvas).to receive(:authenticated_post).with("/api/v1/accounts/1/reports/provisioning_csv", { body: params })
278
+ expect(CanvasOauth::CanvasApi).to receive(:get).with(file['url'], hash_including(parser: CanvasOauth::DefaultUTF8Parser)).and_return(response)
279
+ canvas.get_report(1, :provisioning_csv, params)
280
+ end
281
+ end
282
+ end
283
+ end
284
+
285
+ describe "pagination" do
286
+ describe "valid_page?" do
287
+ let(:valid_page) { double(size: 2, nil?: false, body: '[{some:json}]') }
288
+ let(:same_page) { valid_page }
289
+ let(:blank_page) { double(size: 0, nil?: false, body: '[]') }
290
+
291
+ specify { expect(canvas.valid_page?(nil)).to be_falsey }
292
+ specify { expect(canvas.valid_page?(valid_page)).to be_truthy }
293
+ specify { expect(canvas.valid_page?(blank_page)).to be_falsey }
294
+ end
295
+
296
+ describe "paginated_get" do
297
+
298
+ let(:first_response_link) { {'link' => "<https://foobar.com/some/address?taco=tuesday&per_page=50&page=2>; rel=\"next\", <https://foobar.com/some/address?taco=tuesday&per_page=50&page=2>; rel=\"last\""} }
299
+ let(:query) { { query: { taco: 'tuesday' } } }
300
+
301
+ it "adds per_page parameters to the request query" do
302
+ expect(canvas).to receive(:authenticated_get).with("/some/address", query: { per_page: 50 })
303
+ canvas.paginated_get "/some/address"
304
+ end
305
+
306
+ it "requests the next link" do
307
+ allow(canvas).to receive(:valid_page?) { true }
308
+ first_response = []
309
+ second_response = []
310
+ allow(first_response).to receive(:headers).and_return(first_response_link)
311
+ allow(second_response).to receive(:headers).and_return({})
312
+ expect(canvas).to receive(:authenticated_get).
313
+ exactly(2).times.and_return(first_response,second_response)
314
+ canvas.paginated_get "/some/address", query
315
+ end
316
+
317
+ it "requests the next link without repeating query elements" do
318
+ query_expected = query.dup
319
+ query_expected[:query][:per_page] = 50
320
+
321
+ allow(canvas).to receive(:valid_page?) { true }
322
+
323
+ first_response = []
324
+ second_response = []
325
+ allow(first_response).to receive(:headers).and_return(first_response_link)
326
+ allow(second_response).to receive(:headers).and_return({})
327
+
328
+ expect(canvas).to receive(:authenticated_get).with("/some/address", query_expected).and_return(first_response)
329
+ expect(canvas).to receive(:authenticated_get).with('https://foobar.com/some/address?taco=tuesday&per_page=50&page=2', {query: nil}).and_return(second_response)
330
+
331
+ canvas.paginated_get "/some/address", query
332
+ end
333
+
334
+ it "sends only one request when no next link is in the response Link header" do
335
+ allow(canvas).to receive(:valid_page?) { true }
336
+ response = [{totally: "A real fake response"}]
337
+ allow(response).to receive(:headers).and_return({})
338
+ expect(canvas).to receive(:authenticated_get).once.and_return(response)
339
+ canvas.paginated_get "/some/address"
340
+ end
341
+
342
+ it "sends just one request when an invalid result is returned" do
343
+ allow(canvas).to receive(:valid_page?) { false }
344
+ response = []
345
+ allow(response).to receive(:headers).and_return({})
346
+ expect(canvas).to receive(:authenticated_get).once.and_return(response)
347
+ canvas.paginated_get "/some/address"
348
+ end
349
+ end
350
+ end
351
+
352
+ describe "course_account_id" do
353
+ it "returns the 'account_id' of a course" do
354
+ allow(canvas).to receive(:get_course).with(1).and_return('account_id' => 3)
355
+ expect(canvas.course_account_id(1)).to eq 3
356
+ end
357
+ end
358
+
359
+ describe "hex_sis_id" do
360
+ it "encodes the passed in ID and creates an SIS ID string" do
361
+ expect(canvas.hex_sis_id("sis_course_id", "101")).to eq "hex:sis_course_id:313031"
362
+ end
363
+ end
364
+ end