canvas-api 0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/Changelog +0 -0
  2. data/LICENSE +18 -0
  3. data/README.md +92 -0
  4. data/lib/canvas-api.rb +183 -0
  5. metadata +98 -0
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.
@@ -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
+
@@ -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: