canvas_connect 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ # CanvasConnect
2
+
3
+ CanvasConnect is an Adobe Connect plugin for [Instructure
4
+ Canvas](http://instructure.com). It allows users to create and join Adobe
5
+ Connect conferences directly from their courses.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'canvas_connect'
12
+
13
+ ## Usage
14
+
15
+ CanvasConnect registers itself as a Canvas plugin and can be configured by
16
+ going to /plugins in your Canvas instance and entering your Connect information.
17
+
18
+ CanvasConnect assumes that it's being used inside of a Canvas instance, and
19
+ will explode in strange and beautiful ways if you try to run anything in it
20
+ outside of Canvas.
21
+
22
+ ## Contributing
23
+
24
+ 1. Fork it
25
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
26
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
27
+ 4. Push to the branch (`git push origin my-new-feature`)
28
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,190 @@
1
+ #
2
+ # Copyright (C) 2012 Instructure, Inc.
3
+ #
4
+ # This file is part of Canvas.
5
+ #
6
+ # Canvas is free software: you can redistribute it and/or modify it under
7
+ # the terms of the GNU Affero General Public License as published by the Free
8
+ # Software Foundation, version 3 of the License.
9
+ #
10
+ # Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
11
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
+ # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13
+ # details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License along
16
+ # with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ class AdobeConnectConference < WebConference
20
+ # Public: Start a new conference and return its key. (required by WebConference)
21
+ #
22
+ # Returns a conference key string.
23
+ def initiate_conference
24
+ unless @conference_key.present?
25
+ create_meeting unless meeting_exists?
26
+ save
27
+ end
28
+
29
+ find_conference_key
30
+ end
31
+
32
+ # Public: Determine the status of the conference (required by WebConference).
33
+ #
34
+ # Returns conference status as a symbol (either :active or :closed).
35
+ def conference_status
36
+ if meeting_exists? && end_at.present? && Time.now < end_at
37
+ :active
38
+ elsif meeting_exists?
39
+ :active
40
+ else
41
+ :closed
42
+ end
43
+ end
44
+
45
+ # Public: Add an admin to the conference and create a meeting URL (required by WebConference).
46
+ #
47
+ # admin - The user to add to the conference as an admin.
48
+ # _ - Included for compatibility w/ web_conference.rb
49
+ #
50
+ # Returns a meeting URL string.
51
+ def admin_join_url(admin, _ = nil)
52
+ user = add_host(admin)
53
+ key = CanvasConnect::Service.user_session(user, config[:domain])
54
+ "#{meeting_url}?session=#{key}"
55
+ end
56
+
57
+ # Public: Add a participant to the conference and create a meeting URL.
58
+ # Make the user a conference admin if they have permissions to create
59
+ # a conference (required by WebConference).
60
+ #
61
+ # user - The user to add to the conference as an admin.
62
+ # _ - Included for compatibility w/ web_conference.rb
63
+ #
64
+ # Returns a meeting URL string.
65
+ def participant_join_url(user, _ = nil)
66
+ if grants_right?(user, nil, :initiate)
67
+ admin_join_url(user)
68
+ else
69
+ "#{meeting_url}?guestName=#{URI.escape(user.name)}"
70
+ end
71
+ end
72
+
73
+ protected
74
+ # Internal: Retrieve the SCO-ID for this meeting.
75
+ #
76
+ # Returns an SCO-ID string.
77
+ def find_conference_key
78
+ unless @conference_key.present?
79
+ response = connect_service.sco_by_url(:url_path => meeting_url_suffix)
80
+ @conference_key = response.body.xpath('//sco[@sco-id]').attr('sco-id').value
81
+ end
82
+
83
+ @conference_key
84
+ end
85
+
86
+ # Internal: Register a participant as a host.
87
+ #
88
+ # user - The user to add as a conference admin.
89
+ #
90
+ # Returns the CanvasConnect::ConnectUser.
91
+ def add_host(user)
92
+ connect_user = CanvasConnect::ConnectUser.find_or_create(user)
93
+ connect_service.permissions_update(
94
+ :acl_id => find_conference_key,
95
+ :principal_id => connect_user.id,
96
+ :permission_id => 'host')
97
+
98
+ connect_user
99
+ end
100
+
101
+ # Internal: Create a new Connect meeting.
102
+ #
103
+ # Returns nothing.
104
+ def create_meeting
105
+ params = { :type => 'meeting',
106
+ :name => meeting_name,
107
+ :folder_id => meeting_folder.id,
108
+ :date_begin => start_at.iso8601,
109
+ :url_path => meeting_url_suffix }
110
+ params[:end_at] = end_at.iso8601 if end_at.present?
111
+
112
+ result = connect_service.sco_update(params)
113
+ if result.body.xpath('//status[@code="ok"]').empty?
114
+ error = result.body.at_xpath('//invalid')
115
+ Rails.logger.error "Adobe Connect error on meeting create. Field: #{error['field']}, Value: #{error['subcode']}"
116
+
117
+ if error['field'] == 'folder-id'
118
+ throw CanvasConnect::MeetingFolderError.new("Folder '#{CanvasConnect.config[:meeting_folder]}' doesn't exist!")
119
+ end
120
+
121
+ return nil
122
+ end
123
+
124
+ sco_id = result.body.at_xpath('//sco')['sco-id']
125
+ make_meeting_public(sco_id)
126
+ end
127
+
128
+ # Internal: Make a given meeting publicly accessible.
129
+ #
130
+ # sco_id - The meeting's SCO-ID string.
131
+ #
132
+ # Returns the request object.
133
+ def make_meeting_public(sco_id)
134
+ connect_service.permissions_update(:acl_id => sco_id,
135
+ :principal_id => 'public-access',
136
+ :permission_id => 'view-hidden')
137
+ end
138
+
139
+ # Internal: Determine if this meeting exists in Adobe Connect.
140
+ #
141
+ # Returns a boolean.
142
+ def meeting_exists?
143
+ result = connect_service.sco_by_url(:url_path => meeting_url_suffix)
144
+ result.body.xpath('//status[@code="ok"]').present?
145
+ end
146
+
147
+ # Internal: Create a unique meeting name from the course and conference IDs.
148
+ #
149
+ # Returns a meeting name string.
150
+ def meeting_name
151
+ course_code = if self.context.respond_to?(:course_code)
152
+ self.context.course_code
153
+ elsif self.context.context.respond_to?(:course_code)
154
+ self.context.context.course_code
155
+ else
156
+ 'Canvas'
157
+ end
158
+ "#{course_code}: #{self.title} [#{self.id}]"
159
+ end
160
+ memoize :meeting_name
161
+
162
+ # Internal: Generate the base URL for the meeting.
163
+ def meeting_url
164
+ "#{config[:domain]}/#{meeting_url_suffix}"
165
+ end
166
+ memoize :meeting_url
167
+
168
+ # Internal: Generate a URL suffix for this conference.
169
+ #
170
+ # Returns a URL suffix string of format "canvas-meeting-:id".
171
+ def meeting_url_suffix
172
+ "canvas-meeting-#{self.id}"
173
+ end
174
+ memoize :meeting_url_suffix
175
+
176
+ # Internal: Get and cache a reference to the remote folder.
177
+ #
178
+ # Returns a CanvasConnect::MeetingFolder.
179
+ def meeting_folder
180
+ @meeting_folder ||= CanvasConnect::MeetingFolder.new(config[:meeting_container])
181
+ end
182
+
183
+ # Internal: Manage a connection to an Adobe Connect API.
184
+ #
185
+ # Returns a CanvasConnect::Service object.
186
+ def connect_service
187
+ CanvasConnect.client
188
+ end
189
+ end
190
+
@@ -0,0 +1,37 @@
1
+ <% fields_for :settings, OpenObject.new(settings) do |f| %>
2
+ <table class="formtable" style="width: 500px;">
3
+ <tbody>
4
+ <tr>
5
+ <td><%= f.blabel :domain, :en => "Domain" %></td>
6
+ <td>
7
+ <%= f.text_field :domain %><br>
8
+ <small class="help-text">
9
+ <%= t(:domain_description, "Protocol and hostname or IP address of your Adobe Connect installation (e.g. https://connect.mycompany.com)") %>
10
+ </small>
11
+ </td>
12
+ </tr>
13
+
14
+ <tr>
15
+ <td><%= f.blabel :meeting_container, :en => "Meeting Folder" %></td>
16
+ <td>
17
+ <%= f.text_field :meeting_container %><br>
18
+ <small class="help-text">
19
+ <%= t(:meeting_container_description, "The name of a folder, inside the \"User Meetings\" folder, where Canvas meetings will be stored. Create this folder before creating any meetings.") %>
20
+ </small>
21
+ </td>
22
+ </tr>
23
+ <tr>
24
+ <td><%= f.blabel :login, "Login ID" %></td>
25
+ <td>
26
+ <%= f.text_field :login, :autocomplete => false %><br>
27
+ </td>
28
+ </tr>
29
+ <tr>
30
+ <td><%= f.label :password, "Password" %></td>
31
+ <td>
32
+ <%= f.password_field :password, :autocomplete => false %><br>
33
+ </td>
34
+ </tr>
35
+ </tbody>
36
+ </table>
37
+ <% end %>
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'canvas_connect/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'canvas_connect'
8
+ gem.version = CanvasConnect::VERSION
9
+ gem.authors = ['Zach Pendleton']
10
+ gem.email = ['zachp@instructure.com']
11
+ gem.description = %q{Canvas Connect is an Adobe Connect plugin for the Instructure Canvas LMS. It allows teachers and administrators to create and launch Connect conferences directly from their courses.}
12
+ gem.summary = %q{Adobe Connect integration for Instructure Canvas (http://instructure.com).}
13
+ gem.homepage = 'http://instructure.com'
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = %w{app lib}
19
+ end
20
+
@@ -0,0 +1,44 @@
1
+ #
2
+ # Copyright (C) 2012 Instructure, Inc.
3
+ #
4
+ # This file is part of Canvas.
5
+ #
6
+ # Canvas is free software: you can redistribute it and/or modify it under
7
+ # the terms of the GNU Affero General Public License as published by the Free
8
+ # Software Foundation, version 3 of the License.
9
+ #
10
+ # Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
11
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
+ # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13
+ # details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License along
16
+ # with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ module Canvas
20
+ module Plugins
21
+ class AdobeConnect
22
+ # Public: Bootstrap the gem on app load.
23
+ #
24
+ # Returns nothing.
25
+ def initialize; register; end
26
+
27
+ protected
28
+ # Internal: Register as a plugin with Canvas.
29
+ #
30
+ # Returns a Canvas plugin object.
31
+ def register
32
+ Canvas::Plugin.register('adobe_connect', :web_conferencing, {
33
+ :name => lambda { t(:name, 'Adobe Connect') },
34
+ :description => lambda { t(:description, 'Adobe Connect web conferencing support.') },
35
+ :author => 'OCAD University',
36
+ :author_website => 'http://www.ocadu.ca',
37
+ :version => CanvasConnect::VERSION,
38
+ :settings_partial => 'plugins/connect_settings',
39
+ :validator => 'AdobeConnectValidator',
40
+ :encrypted_settings => [:password] })
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,78 @@
1
+ #
2
+ # Copyright (C) 2012 Instructure, Inc.
3
+ #
4
+ # This file is part of Canvas.
5
+ #
6
+ # Canvas is free software: you can redistribute it and/or modify it under
7
+ # the terms of the GNU Affero General Public License as published by the Free
8
+ # Software Foundation, version 3 of the License.
9
+ #
10
+ # Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
11
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
+ # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13
+ # details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License along
16
+ # with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ module Canvas
20
+ module Plugins
21
+ module Validators
22
+ module AdobeConnectValidator
23
+ # Public: An array of allowed plugin settings.
24
+ REQUIRED_KEYS = %w{domain login password meeting_container}
25
+
26
+ # Public: Validate setting input for this plugin.
27
+ #
28
+ # settings - A hash of settings params.
29
+ # plugin_setting - A plugin setting object.
30
+ #
31
+ # Returns false on error or a hash of settings options.
32
+ def self.validate(settings, plugin_setting)
33
+ filtered_settings = settings.slice(*REQUIRED_KEYS)
34
+ if all_empty?(filtered_settings)
35
+ # Allow no settings.
36
+ {}
37
+ else
38
+ if valid?(filtered_settings)
39
+ filtered_settings
40
+ else
41
+ plugin_setting.errors.add_to_base(I18n.t('canvas.plugins.errors.all_fields_required', 'All fields are required'))
42
+ false
43
+ end
44
+ end
45
+ end
46
+
47
+ protected
48
+ # Internal: Determine if a given settings hash is empty.
49
+ #
50
+ # settings - A hash of setting params.
51
+ #
52
+ # Returns a boolean.
53
+ def self.all_empty?(settings)
54
+ settings.values.all?(&:blank?)
55
+ end
56
+
57
+ # Internal: Determine if any settings are missing from the given hash.
58
+ #
59
+ # settings - A hash of setting params.
60
+ #
61
+ # Returns a boolean.
62
+ def self.any_empty?(settings)
63
+ settings.values.any?(&:blank?)
64
+ end
65
+
66
+ # Internal: Validate that all required settings are present.
67
+ #
68
+ # settings - The hash of settings to validate.
69
+ #
70
+ # Returns boolean.
71
+ def self.valid?(settings)
72
+ !(any_empty?(settings) || (REQUIRED_KEYS & settings.keys) != REQUIRED_KEYS)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+
@@ -0,0 +1,67 @@
1
+ #
2
+ # Copyright (C) 2012 Instructure, Inc.
3
+ #
4
+ # This file is part of Canvas.
5
+ #
6
+ # Canvas is free software: you can redistribute it and/or modify it under
7
+ # the terms of the GNU Affero General Public License as published by the Free
8
+ # Software Foundation, version 3 of the License.
9
+ #
10
+ # Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
11
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
+ # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13
+ # details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License along
16
+ # with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ require "canvas_connect/version"
20
+ require "canvas/plugins/validators/adobe_connect_validator"
21
+ require "canvas/plugins/adobe_connect"
22
+ require "canvas_connect/response"
23
+ require "canvas_connect/meeting_folder"
24
+ require "canvas_connect/connect_user"
25
+ require "canvas_connect/service"
26
+
27
+ module CanvasConnect
28
+ class ConnectionError < StandardError; end
29
+ class MeetingFolderError < StandardError; end
30
+
31
+ # Public: Configure as a Canvas plugin.
32
+ #
33
+ # Returns nothing.
34
+ def self.register
35
+ Rails.configuration.to_prepare do
36
+ view_path = File.dirname(__FILE__) + '/../app/views'
37
+ unless ApplicationController.view_paths.include?(view_path)
38
+ ApplicationController.view_paths.unshift(view_path)
39
+ end
40
+
41
+ require "models/adobe_connect_conference"
42
+
43
+ Canvas::Plugins::AdobeConnect.new
44
+ end
45
+ end
46
+
47
+ # Public: Find the plugin configuration.
48
+ #
49
+ # Returns a settings hash.
50
+ def self.config
51
+ Canvas::Plugin.find('adobe_connect').settings || {}
52
+ end
53
+
54
+ # Return a cached Connect Service object to make requests with.
55
+ #
56
+ # Returns a CanvasConnect::Service.
57
+ def self.client
58
+ unless @client
59
+ @client = Service.new(*self.config.values_at(:login, :password_dec, :domain))
60
+ @client.log_in
61
+ end
62
+
63
+ @client
64
+ end
65
+ end
66
+
67
+ CanvasConnect.register