canvas-api 0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog +0 -0
- data/LICENSE +18 -0
- data/README.md +92 -0
- data/lib/canvas-api.rb +183 -0
- metadata +98 -0
data/Changelog
ADDED
File without changes
|
data/LICENSE
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2013 Instructure
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to use,
|
6
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
7
|
+
Software, and to permit persons to whom the Software is furnished to do so,
|
8
|
+
subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
17
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# Canvas API
|
2
|
+
|
3
|
+
This ruby library is to make it easier to use the
|
4
|
+
[Canvas API](http://api.instructure.com).
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
This is packaged as the `canvas-api` rubygem, so you can just add the dependency to
|
8
|
+
your Gemfile or install the gem on your system:
|
9
|
+
|
10
|
+
gem install canvas-api
|
11
|
+
|
12
|
+
To require the library in your project:
|
13
|
+
|
14
|
+
require 'canvas-api'
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
### OAuth Dance
|
19
|
+
|
20
|
+
Before you can make API calls you need an access token on behalf of the current user.
|
21
|
+
In order to get an access token you'll need to do the OAuth dance (and for that you'll
|
22
|
+
need a client_id and secret. Talk to the Canvas admin about getting these values):
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
canvas = Canvas::API.new(:host => "https://canvas.example.com", :client_id => 123, :secret => "abcdef")
|
26
|
+
url = canvas.oauth_url("https://my.site/oauth_success")
|
27
|
+
# => "https://canvas.example.com/login/oauth2/auth?client_id=123&response_type=code&redirect_uri=http%3A%2F%2Fmy.site%2Foauth_success
|
28
|
+
redirect to(url)
|
29
|
+
```
|
30
|
+
|
31
|
+
And then when the browser redirects to oauth_success:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
canvas = Canvas::API.new(:host => "https://canvas.example.com", :client_id => 123, :secret => "abcdef")
|
35
|
+
code = params['code']
|
36
|
+
canvas.retrieve_access_token(code, 'https://my.site/oauth_success') # this callback_url must match the one provided in the first step
|
37
|
+
# => {access_token: "qwert"}
|
38
|
+
```
|
39
|
+
### General API Calls
|
40
|
+
|
41
|
+
Once you've got an access token for a user you should save it (securely!) for future use. To use the API call:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
|
45
|
+
canvas.get("/api/v1/users/self/profile")
|
46
|
+
# => {id: 90210, name: "Annie Wilson", ... }
|
47
|
+
```
|
48
|
+
|
49
|
+
### Pagination
|
50
|
+
|
51
|
+
API endpoints that return lists are often paginated, meaning they will only return the first X results
|
52
|
+
(where X depends on the endpoint and, possibly, the per_page parameter you optionally set). To get more
|
53
|
+
results you'll need to make additional API calls:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
|
57
|
+
list = canvas.get("/api/v1/calendar_events?all_events=true")
|
58
|
+
list.length
|
59
|
+
# => 50
|
60
|
+
list.more?
|
61
|
+
# => true (if there's another page of results)
|
62
|
+
list.next_page!
|
63
|
+
# => [...] (returns the next page of results)
|
64
|
+
list.length
|
65
|
+
# => 100 (also concatenates the results on to the previous list, if that's more convenient)
|
66
|
+
list.next_page!
|
67
|
+
# => [...]
|
68
|
+
list.length
|
69
|
+
# => 150
|
70
|
+
```
|
71
|
+
|
72
|
+
### Additional Utilities
|
73
|
+
|
74
|
+
There are also some helper methods that can make some of the other tricky parts of the Canvas API a little more approachable.
|
75
|
+
|
76
|
+
#### File Uploads
|
77
|
+
|
78
|
+
TODO...
|
79
|
+
|
80
|
+
#### SIS ID Encoding
|
81
|
+
|
82
|
+
In addition to regular IDs, Canvas supports [SIS IDs](https://canvas.instructure.com/doc/api/file.object_ids.html) defined
|
83
|
+
by other systems. Sometimes these IDs contain non-standard characters, which can cause problems when
|
84
|
+
trying to use them via the API. In those cases you can do the following:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
sis_course_id = canvas.encode_id("sis_course_id", "r#-789")
|
88
|
+
# => "hex:sis_course_id:72232d373839"
|
89
|
+
canvas.get("/api/v1/courses/#{sis_course_id}/enrollments")
|
90
|
+
# => [...]
|
91
|
+
```
|
92
|
+
|
data/lib/canvas-api.rb
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'cgi'
|
3
|
+
require 'net/http'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Canvas
|
7
|
+
class API
|
8
|
+
def initialize(args={})
|
9
|
+
@host = args[:host] && args[:host].to_s
|
10
|
+
@token = args[:token] && args[:token].to_s
|
11
|
+
@client_id = args[:client_id] && args[:client_id].to_s
|
12
|
+
@secret = args[:secret] && args[:secret].to_s
|
13
|
+
raise "host required" unless @host
|
14
|
+
raise "invalid host, protocol required" unless @host.match(/^http/)
|
15
|
+
raise "invalid host" unless @host.match(/^https?:\/\/[^\/]+$/)
|
16
|
+
raise "token or client_id required" if !@token && !@client_id
|
17
|
+
raise "secret required for client_id configuration" if @client_id && !@secret
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :host
|
21
|
+
attr_accessor :token
|
22
|
+
attr_accessor :client_id
|
23
|
+
|
24
|
+
def masquerade_as(user_id)
|
25
|
+
@as_user_id = user_id && user_id.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def stop_masquerading
|
29
|
+
@as_user_id = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.encode_id(prefix, id)
|
33
|
+
return nil unless prefix && id
|
34
|
+
"hex:#{prefix}:" + id.to_s.unpack("H*")[0]
|
35
|
+
end
|
36
|
+
|
37
|
+
def encode_id(prefix, id)
|
38
|
+
Canvas::API.encode_id(prefix, id)
|
39
|
+
end
|
40
|
+
|
41
|
+
def oauth_url(callback_url, scopes="")
|
42
|
+
raise "client_id required for oauth flow" unless @client_id
|
43
|
+
raise "secret required for oauth flow" unless @secret
|
44
|
+
raise "callback_url required" unless callback_url
|
45
|
+
raise "invalid callback_url" unless (URI.parse(callback_url) rescue nil)
|
46
|
+
scopes ||= ""
|
47
|
+
scopes = scopes.length > 0 ? "&scopes=#{CGI.escape(scopes)}" : ""
|
48
|
+
"#{@host}/login/oauth2/auth?client_id=#{@client_id}&response_type=code&redirect_uri=#{CGI.escape(callback_url)}#{scopes}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def login_url(callback_url)
|
52
|
+
oauth_url(callback_url, "/auth/userinfo")
|
53
|
+
end
|
54
|
+
|
55
|
+
def retrieve_access_token(code, callback_url)
|
56
|
+
raise "client_id required for oauth flow" unless @client_id
|
57
|
+
raise "secret required for oauth flow" unless @secret
|
58
|
+
raise "code required" unless code
|
59
|
+
raise "callback_url required" unless callback_url
|
60
|
+
raise "invalid callback_url" unless (URI.parse(callback_url) rescue nil)
|
61
|
+
@token = "ignore"
|
62
|
+
res = post("/login/oauth2/token", :client_id => @client_id, :redirect_uri => callback_url, :client_secret => @secret, :code => code)
|
63
|
+
if res['access_token']
|
64
|
+
@token = res['access_token']
|
65
|
+
end
|
66
|
+
res
|
67
|
+
end
|
68
|
+
|
69
|
+
def logout
|
70
|
+
!!delete("/login/oauth2/token")['logged_out']
|
71
|
+
end
|
72
|
+
|
73
|
+
def validate_call(endpoint)
|
74
|
+
raise "token required for api calls" unless @token
|
75
|
+
raise "missing host" unless @host
|
76
|
+
raise "missing endpoint" unless endpoint
|
77
|
+
raise "missing leading slash on endpoint" unless endpoint.match(/^\//)
|
78
|
+
raise "invalid endpoint" unless endpoint.match(/^\/api\/v\d+\//) unless @token == 'ignore'
|
79
|
+
raise "invalid endpoint" unless (URI.parse(endpoint) rescue nil)
|
80
|
+
end
|
81
|
+
|
82
|
+
def generate_uri(endpoint)
|
83
|
+
validate_call(endpoint)
|
84
|
+
unless @token == "ignore"
|
85
|
+
endpoint += (endpoint.match(/\?/) ? "&" : "?") + "access_token=" + @token
|
86
|
+
endpoint += "&as_user_id=" + @as_user_id.to_s if @as_user_id
|
87
|
+
end
|
88
|
+
@uri = URI.parse(@host + endpoint)
|
89
|
+
@http = Net::HTTP.new(@uri.host, @uri.port)
|
90
|
+
@http.use_ssl = @uri.scheme == 'https'
|
91
|
+
@uri
|
92
|
+
end
|
93
|
+
|
94
|
+
def retrieve_response(request)
|
95
|
+
request['User-Agent'] = "CanvasAPI Ruby"
|
96
|
+
begin
|
97
|
+
response = @http.request(request)
|
98
|
+
rescue Timeout::Error => e
|
99
|
+
raise ApiError.new("request timed out")
|
100
|
+
end
|
101
|
+
raise ApiError.new("unexpected redirect to #{response.location}") if response.code.to_s.match(/3\d\d/)
|
102
|
+
json = JSON.parse(response.body) rescue {'error' => 'invalid JSON'}
|
103
|
+
if !json.is_a?(Array)
|
104
|
+
raise ApiError.new(json['error']) if json['error']
|
105
|
+
if !response.code.to_s.match(/2\d\d/)
|
106
|
+
json['message'] ||= "unexpected error"
|
107
|
+
raise ApiError.new("#{json['status']} #{json['message']}")
|
108
|
+
end
|
109
|
+
else
|
110
|
+
json = ResultSet.new(self, json)
|
111
|
+
if response['link']
|
112
|
+
json.link = response['link']
|
113
|
+
json.next_endpoint = response['link'].split(/,/).detect{|rel| rel.match(/rel="next"/) }.split(/;/).first.strip[1..-2].sub(/https?:\/\/[^\/]+/, '') rescue nil
|
114
|
+
end
|
115
|
+
end
|
116
|
+
json
|
117
|
+
end
|
118
|
+
|
119
|
+
# Semi-hack so I can write better specs
|
120
|
+
def get_request(endpoint)
|
121
|
+
Net::HTTP::Get.new(@uri.request_uri)
|
122
|
+
end
|
123
|
+
|
124
|
+
def get(endpoint)
|
125
|
+
generate_uri(endpoint)
|
126
|
+
request = get_request(endpoint)
|
127
|
+
retrieve_response(request)
|
128
|
+
end
|
129
|
+
|
130
|
+
def delete(endpoint)
|
131
|
+
generate_uri(endpoint)
|
132
|
+
request = Net::HTTP::Delete.new(@uri.request_uri)
|
133
|
+
retrieve_response(request)
|
134
|
+
end
|
135
|
+
|
136
|
+
def put(endpoint, params={})
|
137
|
+
generate_uri(endpoint)
|
138
|
+
request = Net::HTTP::Put.new(@uri.request_uri)
|
139
|
+
request.set_form_data(params)
|
140
|
+
retrieve_response(request)
|
141
|
+
end
|
142
|
+
|
143
|
+
def post(endpoint, params={})
|
144
|
+
generate_uri(endpoint)
|
145
|
+
request = Net::HTTP::Post.new(@uri.request_uri)
|
146
|
+
request.set_form_data(params)
|
147
|
+
retrieve_response(request)
|
148
|
+
end
|
149
|
+
|
150
|
+
def upload_file_from_local
|
151
|
+
# TODO
|
152
|
+
end
|
153
|
+
|
154
|
+
def upload_file_from_url
|
155
|
+
# TODO
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class ApiError < StandardError
|
160
|
+
end
|
161
|
+
|
162
|
+
class ResultSet < Array
|
163
|
+
def initialize(api, arr)
|
164
|
+
@api = api
|
165
|
+
super(arr)
|
166
|
+
end
|
167
|
+
attr_accessor :next_endpoint
|
168
|
+
attr_accessor :link
|
169
|
+
|
170
|
+
def more?
|
171
|
+
!!next_endpoint
|
172
|
+
end
|
173
|
+
|
174
|
+
def next_page!
|
175
|
+
ResultSet.new(@api, []) unless next_endpoint
|
176
|
+
more = @api.get(next_endpoint)
|
177
|
+
concat(more)
|
178
|
+
@next_endpoint = more.next_endpoint
|
179
|
+
@link = more.link
|
180
|
+
more
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: canvas-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.5'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Instructure
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-04-24 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: json
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: ruby-debug
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description:
|
63
|
+
email:
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files:
|
67
|
+
- LICENSE
|
68
|
+
files:
|
69
|
+
- lib/canvas-api.rb
|
70
|
+
- LICENSE
|
71
|
+
- README.md
|
72
|
+
- Changelog
|
73
|
+
homepage: http://github.com/whitmer/canvas-api
|
74
|
+
licenses: []
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ! '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 1.8.24
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: Ruby library for accessing the Canvas API
|
97
|
+
test_files: []
|
98
|
+
has_rdoc:
|