Google Drive Collaborations & Submissions
force users to auth google drive when the plugin is enabled Add support for google drive in collaborations and homework submissions Everything still looks like google docs to the user. fixes PLAT-892 fixes PLAT-893 fixes PLAT-894 Test plan: create a google docs integration and enable the google drive plugin when you visit the colaborations page it should ask you to authorize canvas to use google drive Regression test homework submissions with google doc and drive Regression test collaborations with google doc and drive Change-Id: I79bdbdcae915b08a19cc9a078a64b49ef5f34796 Reviewed-on: https://gerrit.instructure.com/48583 Tested-by: Jenkins Reviewed-by: Brad Humphrey <brad@instructure.com> QA-Review: August Thornton <august@instructure.com> Product-Review: Brad Horrocks <bhorrocks@instructure.com>
This commit is contained in:
parent
c1b82356ab
commit
a99c397662
|
@ -16,7 +16,7 @@ group :development do
|
|||
|
||||
unless ENV['DISABLE_RUBY_DEBUGGING']
|
||||
gem 'byebug', '3.5.1', :platforms => [:ruby_20, :ruby_21, :ruby_22]
|
||||
gem 'columnize', '0.9.0', :platforms => [:ruby_20, :ruby_21, :ruby_22]
|
||||
gem 'columnize', '0.9.0', :platforms => [:ruby_20, :ruby_21, :ruby_22]
|
||||
gem 'debugger', '1.6.6', :platforms => :ruby_19
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1819,14 +1819,36 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
def google_drive_connection
|
||||
return unless Canvas::Plugin.find(:google_drive).try(:settings)
|
||||
## @real_current_user first ensures that a masquerading user never sees the
|
||||
## masqueradee's files, but in general you may want to block access to google
|
||||
## docs for masqueraders earlier in the request
|
||||
if logged_in_user
|
||||
refresh_token, access_token = Rails.cache.fetch(['google_drive_tokens', logged_in_user].cache_key) do
|
||||
service = logged_in_user.user_services.where(service: "google_drive").first
|
||||
service && [service.token, service.secret]
|
||||
end
|
||||
else
|
||||
refresh_token = session[:oauth_gdrive_refresh_token]
|
||||
access_token = session[:oauth_gdrive_access_token]
|
||||
end
|
||||
|
||||
GoogleDocs::DriveConnection.new(refresh_token, access_token) if refresh_token && access_token
|
||||
end
|
||||
|
||||
def google_service_connection
|
||||
google_drive_connection || google_docs_connection
|
||||
end
|
||||
|
||||
def google_drive_user_client
|
||||
if logged_in_user
|
||||
refresh_token, access_token = Rails.cache.fetch(['google_drive_tokens', logged_in_user].cache_key) do
|
||||
service = logged_in_user.user_services.where(service: "google_drive").first
|
||||
service && [service.token, service.access_token]
|
||||
end
|
||||
else
|
||||
refresh_token = session[:oauth_gdrive_access_token]
|
||||
access_token = session[:oauth_gdrive_refresh_token]
|
||||
refresh_token = session[:oauth_gdrive_refresh_token]
|
||||
access_token = session[:oauth_gdrive_access_token]
|
||||
end
|
||||
google_drive_client(refresh_token, access_token)
|
||||
end
|
||||
|
|
|
@ -129,12 +129,14 @@ class AssignmentsController < ApplicationController
|
|||
end
|
||||
|
||||
begin
|
||||
google_docs = google_docs_connection
|
||||
google_docs = google_service_connection
|
||||
@google_service = google_docs.service_type
|
||||
@google_docs_token = google_docs.retrieve_access_token
|
||||
rescue GoogleDocs::NoTokenError
|
||||
#do nothing
|
||||
end
|
||||
|
||||
|
||||
add_crumb(@assignment.title, polymorphic_url([@context, @assignment]))
|
||||
log_asset_access(@assignment, "assignments", @assignment.assignment_group)
|
||||
|
||||
|
@ -164,8 +166,7 @@ class AssignmentsController < ApplicationController
|
|||
if assignment.allow_google_docs_submission? && @real_current_user.blank?
|
||||
docs = {}
|
||||
begin
|
||||
google_docs = google_docs_connection
|
||||
docs = google_docs.list_with_extension_filter(assignment.allowed_extensions)
|
||||
docs = google_service_connection.list_with_extension_filter(assignment.allowed_extensions)
|
||||
rescue GoogleDocs::NoTokenError
|
||||
#do nothing
|
||||
rescue => e
|
||||
|
|
|
@ -67,8 +67,11 @@ class CollaborationsController < ApplicationController
|
|||
|
||||
@collaborations = @context.collaborations.active
|
||||
log_asset_access("collaborations:#{@context.asset_string}", "collaborations", "other")
|
||||
@google_drive_upgrade = logged_in_user && Canvas::Plugin.find(:google_drive).try(:settings) &&
|
||||
(!logged_in_user.user_services.where(service: 'google_drive').first || !(google_drive_connection.verify_access_token rescue false))
|
||||
|
||||
@google_docs_authorized = !@google_drive_upgrade && google_service_connection.verify_access_token rescue false
|
||||
|
||||
@google_docs_authorized = google_docs_connection.verify_access_token rescue false
|
||||
js_env :TITLE_MAX_LEN => Collaboration::TITLE_MAX_LENGTH,
|
||||
:collaboration_types => Collaboration.collaboration_types
|
||||
end
|
||||
|
@ -135,7 +138,7 @@ class CollaborationsController < ApplicationController
|
|||
def destroy
|
||||
@collaboration = @context.collaborations.find(params[:id])
|
||||
if authorized_action(@collaboration, @current_user, :delete)
|
||||
@collaboration.delete_document if params[:delete_doc]
|
||||
@collaboration.delete_document if value_to_boolean(params[:delete_doc])
|
||||
@collaboration.destroy
|
||||
respond_to do |format|
|
||||
format.html { redirect_to named_context_url(@context, :collaborations_url) }
|
||||
|
@ -180,5 +183,6 @@ class CollaborationsController < ApplicationController
|
|||
return false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -476,11 +476,11 @@ class SubmissionsController < ApplicationController
|
|||
|
||||
def submit_google_doc(document_id)
|
||||
# fetch document from google
|
||||
google_docs = google_docs_connection
|
||||
google_docs = google_service_connection
|
||||
document_response, display_name, file_extension = google_docs.download(document_id)
|
||||
|
||||
# error handling
|
||||
unless document_response.try(:is_a?, Net::HTTPOK)
|
||||
unless document_response.try(:is_a?, Net::HTTPOK) || document_response.status == 200
|
||||
flash[:error] = t('errors.assignment_submit_fail', 'Assignment failed to submit')
|
||||
end
|
||||
|
||||
|
|
|
@ -238,6 +238,7 @@ class UsersController < ApplicationController
|
|||
if logged_in_user
|
||||
service = UserService.where(user_id: @current_user, service: 'google_drive', service_domain: 'drive.google.com').first_or_initialize
|
||||
service.service_user_id = user_info['permissionId']
|
||||
service.service_user_name = user_info['emailAddress']
|
||||
service.token = client.authorization.refresh_token
|
||||
service.secret = client.authorization.access_token
|
||||
service.save
|
||||
|
|
|
@ -308,6 +308,7 @@ class Collaboration < ActiveRecord::Base
|
|||
users_to_remove.uniq!
|
||||
end
|
||||
# make real user objects, instead of just ids, cause that's what this code expects
|
||||
users_to_remove.reject! {|id| id == self.user.id}
|
||||
users_to_remove = users_to_remove.map { |id| User.send(:instantiate, 'id' => id) }
|
||||
remove_users_from_document(users_to_remove)
|
||||
end
|
||||
|
|
|
@ -17,84 +17,120 @@
|
|||
#
|
||||
|
||||
class GoogleDocsCollaboration < Collaboration
|
||||
GOOGLE_DOC_SERVICE = "google.com"
|
||||
GOOGLE_DRIVE_SERVICE = "drive.google.com"
|
||||
|
||||
def style_class
|
||||
'google_docs'
|
||||
end
|
||||
|
||||
|
||||
def service_name
|
||||
"Google Docs"
|
||||
end
|
||||
|
||||
|
||||
def delete_document
|
||||
if !self.document_id && self.user
|
||||
google_docs = google_docs_for_user
|
||||
google_docs.delete_doc(GoogleDocEntry.new(self.data))
|
||||
if self.document_id && self.user
|
||||
# google docs expected an object
|
||||
# drive just wants an id
|
||||
doc = is_google_drive ? self.document_id : GoogleDocs::Entry.new(self.data)
|
||||
google_adapter_for_user.delete_doc(doc)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def initialize_document
|
||||
if !self.document_id && self.user
|
||||
|
||||
name = self.title
|
||||
name = nil if name && name.empty?
|
||||
name ||= I18n.t('lib.google_docs.default_document_name', "Instructure Doc")
|
||||
|
||||
google_docs = google_docs_for_user
|
||||
file = google_docs.create_doc(name, google_docs.retrieve_access_token)
|
||||
self.document_id = file.document_id
|
||||
self.data = file.entry.to_xml
|
||||
self.url = file.alternate_url.to_s
|
||||
result = google_adapter_for_user.create_doc(name)
|
||||
if result
|
||||
if is_google_drive
|
||||
self.document_id = result.data.id
|
||||
self.data = result.data.to_json
|
||||
|
||||
self.url = result.data.alternateLink
|
||||
else
|
||||
self.document_id = result.document_id
|
||||
self.data = result.entry.to_xml
|
||||
self.url = result.alternate_url.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def user_can_access_document_type?(user)
|
||||
if self.user && user
|
||||
google_services = user.user_services.where(service_domain: "google.com").to_a
|
||||
!!google_services.find{|s| s.service_user_id}
|
||||
else
|
||||
false
|
||||
end
|
||||
return !!google_user_service(user) || !!google_user_service(user, GOOGLE_DRIVE_SERVICE) if self.user && user
|
||||
false
|
||||
end
|
||||
|
||||
|
||||
def authorize_user(user)
|
||||
return unless self.document_id
|
||||
google_services = user.user_services.where(service_domain: "google.com").to_a
|
||||
service_user_id = google_services.find{|s| s.service_user_id}.service_user_id rescue nil
|
||||
service_user_id = google_adapter_user_service(user).service_user_id rescue nil
|
||||
collaborator = self.collaborators.where(user_id: user).first
|
||||
|
||||
if collaborator && collaborator.authorized_service_user_id != service_user_id
|
||||
google_docs = google_docs_for_user
|
||||
google_docs.acl_remove(self.document_id, [collaborator.authorized_service_user_id]) if collaborator.authorized_service_user_id
|
||||
google_docs.acl_add(self.document_id, [user])
|
||||
google_adapter_for_user.acl_remove(self.document_id, [collaborator.authorized_service_user_id]) if collaborator.authorized_service_user_id
|
||||
|
||||
user_param = is_google_drive ? service_user_id : user
|
||||
google_adapter_for_user.acl_add(self.document_id, [user_param])
|
||||
collaborator.update_attributes(:authorized_service_user_id => service_user_id)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def remove_users_from_document(users_to_remove)
|
||||
google_docs = google_docs_for_user
|
||||
google_docs.acl_remove(self.document_id, users_to_remove) if self.document_id
|
||||
if is_google_drive
|
||||
users_to_remove = users_to_remove.map do |user|
|
||||
user_service = google_user_service(user, GOOGLE_DRIVE_SERVICE) and user_service.service_user_id
|
||||
end
|
||||
end
|
||||
|
||||
google_adapter_for_user.acl_remove(self.document_id, users_to_remove) if self.document_id
|
||||
end
|
||||
|
||||
def add_users_to_document(new_users)
|
||||
domain = if context.root_account.feature_enabled?(:google_docs_domain_restriction)
|
||||
context.root_account.settings[:google_docs_domain]
|
||||
else
|
||||
nil
|
||||
end
|
||||
if document_id
|
||||
google_docs = google_docs_for_user
|
||||
google_docs.acl_add(self.document_id, new_users, domain)
|
||||
domain = if context.root_account.feature_enabled?(:google_docs_domain_restriction)
|
||||
context.root_account.settings[:google_docs_domain]
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
if is_google_drive
|
||||
user_ids = new_users.map do |user|
|
||||
google_adapter_user_service(user).service_user_id rescue nil
|
||||
end.compact
|
||||
else
|
||||
user_ids = new_users
|
||||
end
|
||||
|
||||
google_adapter_for_user.acl_add(self.document_id, user_ids, domain)
|
||||
end
|
||||
end
|
||||
|
||||
def parse_data
|
||||
@entry_data ||= Atom::Entry.load_entry(self.data)
|
||||
end
|
||||
|
||||
|
||||
def self.config
|
||||
GoogleDocs::Connection.config
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
##
|
||||
# Check to see if this collaboration can use google drive
|
||||
def is_google_drive(user=self.user)
|
||||
return unless Canvas::Plugin.find(:google_drive).try(:settings)
|
||||
@google_drive ||= {}
|
||||
@google_drive[user.id] ||= !!google_user_service(user, GOOGLE_DRIVE_SERVICE)
|
||||
end
|
||||
|
||||
def google_user_service(user, service_domain=GOOGLE_DOC_SERVICE)
|
||||
google_services = user.user_services.where(service_domain: service_domain).to_a
|
||||
google_services.find{|s| s.service_user_id}
|
||||
end
|
||||
|
||||
def google_docs_for_user
|
||||
service_token, service_secret = Rails.cache.fetch(['google_docs_tokens', self.user].cache_key) do
|
||||
service = self.user.user_services.where(service: "google_docs").first
|
||||
|
@ -103,4 +139,22 @@ class GoogleDocsCollaboration < Collaboration
|
|||
raise GoogleDocs::NoTokenError unless service_token && service_secret
|
||||
GoogleDocs::Connection.new(service_token, service_secret)
|
||||
end
|
||||
|
||||
def google_drive_for_user
|
||||
refresh_token, access_token = Rails.cache.fetch(['google_drive_tokens', self.user].cache_key) do
|
||||
service = self.user.user_services.where(service: "google_drive").first
|
||||
service && [service.token, service.secret]
|
||||
end
|
||||
raise GoogleDocs::NoTokenError unless refresh_token && access_token
|
||||
GoogleDocs::DriveConnection.new(refresh_token, access_token)
|
||||
end
|
||||
|
||||
def google_adapter_for_user
|
||||
return google_drive_for_user if is_google_drive
|
||||
google_docs_for_user
|
||||
end
|
||||
|
||||
def google_adapter_user_service(user)
|
||||
google_user_service(user, GOOGLE_DRIVE_SERVICE) || google_user_service(user)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -271,11 +271,13 @@
|
|||
<div id="submit_google_doc_form">
|
||||
<%= t 'messages.google_docs_auth_required', "Before you can submit assignments directly from Google Docs you need to authorize Canvas to access your Google Docs account:" %>
|
||||
<div style="font-size: 1.1em; text-align: center; margin: 10px;">
|
||||
<a class="btn" href="<%= oauth_url(:service => :google_docs, :return_to => (request.url + "#submit_google_doc_form")) %>"><%= t 'links.authorize_google_docs', "Authorize Google Docs Access" %></a>
|
||||
<%# TODO: do the right stuff %>
|
||||
<a class="btn" href="<%= oauth_url(:service => :google_drive, :return_to => (request.url + "#submit_google_doc_form")) %>"><%= t 'links.authorize_google_docs', "Authorize Google Docs Access" %></a>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if @assignment.submission_types && @assignment.submission_types.match(/media_recording/) %>
|
||||
<% if !feature_enabled?(:kaltura) %>
|
||||
<div id="submit_media_recording_form">
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<% end %>
|
||||
</select>
|
||||
</td>
|
||||
</tr><tr id="google_docs_description" style="display: none;" class="collaboration_type <%= 'unauthorized' unless @google_docs_authorized %>">
|
||||
</tr><tr id="google_docs_description" style="display: none;" class="collaboration_type <%= 'unauthorized' unless @google_docs_authorized%>">
|
||||
<td colspan="2" style="padding: 5px 20px 10px">
|
||||
<%= image_tag "google_docs_icon.png", :style => "float: right; margin-left: 15px;" %>
|
||||
<%= mt 'descriptions.google_docs', "Google Docs is a great place to collaborate on a group project. It's like Microsoft Word, but lets you work together with others on the same file at the same time without having to email it around. \n \n**Warning**: you (and all your collaborators) will need a Google account in order to participate in any Google Docs collaborations." %>
|
||||
|
@ -56,12 +56,21 @@ HEREDOC
|
|||
</div>
|
||||
</div>
|
||||
<div id="collaborate_authorize_google_docs" class="collaboration_authorization" style="display: none; margin: 20px;">
|
||||
<%= t '#instructions.authorize_google_docs', "Before you can collaborate on documents, you need to authorize Canvas
|
||||
to access your Google Docs account:" %>
|
||||
<div class="button-container">
|
||||
<a class="btn button-default-action" href="<%= oauth_url(:service => :google_docs, :return_to => (request.url + "#add_collaboration")) %>"><%= t '#buttons.authorize_google_docs', "Authorize Google Docs Access" %></a>
|
||||
<button type="button" class="btn button-secondary cancel_button"><%= t '#buttons.cancel', "Cancel" %></button>
|
||||
</div>
|
||||
<% if @google_drive_upgrade %>
|
||||
<%= t '#instructions.authorize_google_drive', "Before you can collaborate on documents, you need to authorize Canvas
|
||||
to access your Google Drive account:" %>
|
||||
<div class="button-container">
|
||||
<a class="btn button-default-action" href="<%= oauth_url(:service => :google_drive, :return_to => (request.url + "#add_collaboration")) %>"><%= t '#buttons.authorize_google_drive', "Authorize Google Drive Access" %></a>
|
||||
<button type="button" class="btn button-secondary cancel_button"><%= t '#buttons.cancel', "Cancel" %></button>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= t '#instructions.authorize_google_docs', "Before you can collaborate on documents, you need to authorize Canvas
|
||||
to access your Google Docs account:" %>
|
||||
<div class="button-container">
|
||||
<a class="btn button-default-action" href="<%= oauth_url(:service => :google_docs, :return_to => (request.url + "#add_collaboration")) %>"><%= t '#buttons.authorize_google_docs', "Authorize Google Docs Access" %></a>
|
||||
<button type="button" class="btn button-secondary cancel_button"><%= t '#buttons.cancel', "Cancel" %></button>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -72,8 +81,8 @@ HEREDOC
|
|||
<%= t 'messages.delete_google_doc_as_well', "This collaboration is being stored as a Google Doc. Did you want to delete it just
|
||||
from Canvas, or remove it from Google Docs as well?" %>
|
||||
<div style="margin-top: 15px;">
|
||||
<button type="button" class="btn delete_button delete_document_button"><%= mt 'buttons.delete_from_canvas', "Just Delete \nfrom Canvas" %></button>
|
||||
<button type="button" class="btn delete_button"><%= mt 'buttons.delete_from_google_docs', "Also Delete From \nGoogle Docs" %></button>
|
||||
<button type="button" class="btn delete_button"><%= mt 'buttons.delete_from_canvas', "Just Delete \nfrom Canvas" %></button>
|
||||
<button type="button" class="btn delete_button delete_document_button"><%= mt 'buttons.delete_from_google_docs', "Also Delete From \nGoogle Docs" %></button>
|
||||
</div>
|
||||
</div>
|
||||
<%= form_tag(context_url(@context, :context_collaboration_url, "{{ id }}"), {:id => "edit_collaboration_form", :method => :put, :style => "display: none; margin-top: 10px;", :class => 'collaboration communication_message'}) do %>
|
||||
|
|
|
@ -57,7 +57,7 @@ HEREDOC
|
|||
</div>
|
||||
<div id="collaborations">
|
||||
<% @collaborations.each do |collaboration| %>
|
||||
<% if can_do(collaboration, @current_user, :read) %>
|
||||
<% if can_do(collaboration, @current_user, :read) && !collaboration.is_a?(GoogleDocsCollaboration) || (collaboration.is_a?(GoogleDocsCollaboration) && !@google_drive_upgrade) %>
|
||||
<div class="collaboration <%= collaboration.style_class %> collaboration_<%= collaboration.id %>" data-id="<%= collaboration.id %>">
|
||||
<% if can_do(collaboration, @current_user, :delete) %>
|
||||
<div class="links">
|
||||
|
@ -76,6 +76,19 @@ HEREDOC
|
|||
:at => datetime_string(collaboration.created_at) %>
|
||||
</span>
|
||||
</div>
|
||||
<% elsif @google_drive_upgrade %>
|
||||
<div class="collaboration <%= collaboration.style_class %> collaboration_<%= collaboration.id %>" data-id="<%= collaboration.id %>">
|
||||
<h3><%= collaboration.title %></h3>
|
||||
<div style="margin-bottom: 5px;" class="description">
|
||||
<a href="<%= oauth_url(:service => :google_drive, :return_to => (request.url)) %>"><%= t 'google.drive.upgrade.description', "To access this collaboration you must authorize Canvas to access your Google Drive account" %></a>
|
||||
</div>
|
||||
<span <%= context_sensitive_datetime_title(collaboration.created_at, @context) %> style="font-size: 0.8em;">
|
||||
<%= t :started_by, "Started by *%{user}*, %{at}",
|
||||
:user => context_user_name(@context, collaboration.user),
|
||||
:wrapper => "<a href=\"#{context_url(@context, :context_user_url, collaboration.user_id)}\" class=\"collaborator_link\">\\1</a>",
|
||||
:at => datetime_string(collaboration.created_at) %>
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
|
||||
GoogleDocs::DriveConnection.config = Proc.new do
|
||||
Canvas::Plugin.find(:google_drive).try(:settings) || ConfigFile.load('google_drive')
|
||||
end
|
||||
|
||||
GoogleDocs::Connection.config = Proc.new do
|
||||
Canvas::Plugin.find(:google_docs).try(:settings) || ConfigFile.load('google_docs')
|
||||
end
|
||||
|
||||
GoogleDocs::Entry.extension_looker_upper = ScribdMimeType
|
||||
GoogleDocs::Entry.extension_looker_upper = ScribdMimeType
|
||||
|
|
|
@ -4,6 +4,7 @@ require 'uri'
|
|||
|
||||
module GoogleDocs
|
||||
require "google_docs/connection"
|
||||
require "google_docs/drive_connection"
|
||||
require "google_docs/entry"
|
||||
require "google_docs/folder"
|
||||
require "google_docs/no_token_error"
|
||||
|
|
|
@ -31,7 +31,11 @@ module GoogleDocs
|
|||
@access_token ||= OAuth::AccessToken.new(consumer, @oauth_gdocs_access_token, @oauth_gdocs_access_token_secret)
|
||||
end
|
||||
|
||||
def get_service_user_info(access_token)
|
||||
def service_type
|
||||
:google_docs
|
||||
end
|
||||
|
||||
def get_service_user_info(access_token=retrieve_access_token)
|
||||
doc = create_doc("Temp Doc: #{Time.now.strftime("%d %b %Y, %I:%M %p")}", access_token)
|
||||
delete_doc(doc, access_token)
|
||||
service_user_id = doc.entry.authors[0].email rescue nil
|
||||
|
@ -211,7 +215,7 @@ module GoogleDocs
|
|||
add_extension_namespace :gAcl, 'http://schemas.google.com/acl/2007'
|
||||
end
|
||||
|
||||
def create_doc(name, access_token)
|
||||
def create_doc(name, access_token=retrieve_access_token)
|
||||
url = "https://docs.google.com/feeds/documents/private/full"
|
||||
entry = Atom::Entry.new do |entry|
|
||||
entry.title = name
|
||||
|
|
|
@ -0,0 +1,214 @@
|
|||
#
|
||||
# Copyright (C) 2011 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# See Google Docs API documentation here:
|
||||
# http://code.google.com/apis/documents/docs/2.0/developers_guide_protocol.html
|
||||
module GoogleDocs
|
||||
class DriveConnectionException < RuntimeError
|
||||
end
|
||||
|
||||
class DriveConnection
|
||||
def initialize(refresh_token, access_token)
|
||||
@refresh_token = refresh_token
|
||||
@access_token = access_token
|
||||
end
|
||||
|
||||
def retrieve_access_token
|
||||
@access_token || api_client
|
||||
end
|
||||
|
||||
def service_type
|
||||
:google_drive
|
||||
end
|
||||
|
||||
def download(document_id)
|
||||
response = api_client.execute!(
|
||||
:api_method => drive.files.get,
|
||||
:parameters => { :fileId => document_id }
|
||||
)
|
||||
|
||||
file = response.data
|
||||
file_info = get_file_info(file)
|
||||
result = api_client.execute(:uri => file_info[:url])
|
||||
if result.status == 200
|
||||
|
||||
# hack to make it seem like the old object
|
||||
result.define_singleton_method(:content_type) do
|
||||
result.headers['Content-Type']
|
||||
end
|
||||
|
||||
# TODO: get extension from response header
|
||||
[result, file.title, file_info[:ext]]
|
||||
else
|
||||
raise DriveConnectionException
|
||||
end
|
||||
end
|
||||
|
||||
def list_with_extension_filter(extensions)
|
||||
list extensions
|
||||
end
|
||||
|
||||
def create_doc(name)
|
||||
file_data = {
|
||||
:title => name,
|
||||
:mimeType => 'application/vnd.google-apps.document'
|
||||
}
|
||||
|
||||
api_client.authorization.update_token!
|
||||
file = drive.files.insert.request_schema.new(file_data)
|
||||
|
||||
result = api_client.execute(
|
||||
:api_method => drive.files.insert,
|
||||
:body_object => file
|
||||
)
|
||||
|
||||
if result.status == 200
|
||||
result
|
||||
else
|
||||
raise DriveConnectionException
|
||||
end
|
||||
end
|
||||
|
||||
def delete_doc(document_id)
|
||||
api_client.authorization.update_token!
|
||||
result = api_client.execute(
|
||||
:api_method => drive.files.delete,
|
||||
:parameters => { :fileId => document_id })
|
||||
if result.error? && !result.error_message.include?('File not found')
|
||||
raise DriveConnectionException
|
||||
end
|
||||
end
|
||||
|
||||
def acl_remove(document_id, users)
|
||||
api_client.authorization.update_token!
|
||||
users.each do |user_id|
|
||||
next if user_id.blank?
|
||||
result = api_client.execute(
|
||||
:api_method => drive.permissions.delete,
|
||||
:parameters => {
|
||||
:fileId => document_id,
|
||||
:permissionId => user_id })
|
||||
if result.error?
|
||||
raise DriveConnectionException, result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Add users to a Google Doc ACL list.
|
||||
#
|
||||
# document_id - The id of the Google Doc to add users to.
|
||||
# users - An array of user objects.
|
||||
# domain - The string domain to restrict additions to (e.g. "example.com").
|
||||
# Accounts not on this domain will be ignored.
|
||||
#
|
||||
# Returns nothing.
|
||||
def acl_add(document_id, users, domain = nil)
|
||||
# TODO: support domain
|
||||
api_client.authorization.update_token!
|
||||
users.each do |user_id|
|
||||
new_permission = drive.permissions.insert.request_schema.new({
|
||||
:id => user_id,
|
||||
:type => 'user',
|
||||
:role => 'writer'
|
||||
})
|
||||
result = api_client.execute(
|
||||
:api_method => drive.permissions.insert,
|
||||
:body_object => new_permission,
|
||||
:parameters => { :fileId => document_id }
|
||||
)
|
||||
if result.error?
|
||||
raise DriveConnectionException
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def verify_access_token
|
||||
api_client.authorization.update_token!
|
||||
return api_client.execute(:api_method => drive.about.get).status == 200
|
||||
end
|
||||
|
||||
def self.config_check(settings)
|
||||
raise DriveConnectionException("No config check")
|
||||
end
|
||||
|
||||
def self.config=(config)
|
||||
unless config.is_a?(Proc)
|
||||
raise 'Config must be a Proc'
|
||||
end
|
||||
@config = config
|
||||
end
|
||||
|
||||
def self.config
|
||||
@config.call
|
||||
end
|
||||
|
||||
private
|
||||
def list(extensions)
|
||||
documents = api_client.execute!(:api_method => drive.files.list).data.to_hash
|
||||
{
|
||||
:name => '/',
|
||||
:folders => [],
|
||||
:files => documents['items'].map do |doc|
|
||||
doc_info = get_file_info(doc)
|
||||
|
||||
if extensions.include?(doc_info[:ext])
|
||||
{
|
||||
:name => doc['title'],
|
||||
:document_id => doc['id'],
|
||||
:extension => doc_info[:ext],
|
||||
:alternate_url => {
|
||||
:href => doc_info[:url]
|
||||
}
|
||||
}
|
||||
end
|
||||
end.compact!
|
||||
}
|
||||
end
|
||||
|
||||
def api_client
|
||||
return nil if GoogleDocs::DriveConnection.config.nil?
|
||||
@api_client ||= GoogleDrive::Client.create(GoogleDocs::DriveConnection.config, @refresh_token, @access_token)
|
||||
end
|
||||
|
||||
def drive
|
||||
api_client.discovered_api('drive', 'v2')
|
||||
end
|
||||
|
||||
def get_file_info(file)
|
||||
file_ext = {
|
||||
".docx" => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
".xlsx" => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
".pdf" => 'application/pdf',
|
||||
}
|
||||
|
||||
e = file_ext.find {|extension, mime_type| file['exportLinks'] && file['exportLinks'][mime_type] }
|
||||
if e.present?
|
||||
{
|
||||
url: file['exportLinks'][e.last],
|
||||
ext: e.first
|
||||
}
|
||||
else
|
||||
{
|
||||
url: file['downloadUrl'],
|
||||
ext: ".none"
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -10,7 +10,7 @@ module GoogleDrive
|
|||
# @param [String] access_token
|
||||
# Optional access_token
|
||||
def self.create(client_secrets, refresh_token = nil, access_token = nil)
|
||||
client = Google::APIClient.new
|
||||
client = Google::APIClient.new(application_name: "Instructure Google Drive", application_version: "0.0.1")
|
||||
client.authorization.client_id = client_secrets['client_id']
|
||||
client.authorization.client_secret = client_secrets['client_secret']
|
||||
client.authorization.redirect_uri = client_secrets['redirect_uri']
|
||||
|
|
|
@ -82,7 +82,7 @@ Canvas::Plugin.register('google_docs', :collaborations, {
|
|||
:settings_partial => 'plugins/google_docs_settings',
|
||||
:validator => 'GoogleDocsValidator'
|
||||
})
|
||||
Canvas::Plugin.register('google_drive', :collaborations, {
|
||||
Canvas::Plugin.register('google_drive', nil, {
|
||||
:name => lambda{ t :name, 'Google Drive' },
|
||||
:description => lambda{ t :description, 'Google Drive file sharing' },
|
||||
:website => 'http://drive.google.com',
|
||||
|
|
|
@ -132,6 +132,69 @@ describe ApplicationController do
|
|||
|
||||
end
|
||||
|
||||
it "uses @real_current_user first" do
|
||||
mock_real_current_user = mock()
|
||||
mock_current_user = mock()
|
||||
controller.instance_variable_set(:@real_current_user, mock_real_current_user)
|
||||
controller.instance_variable_set(:@current_user, mock_current_user)
|
||||
session[:oauth_gdrive_refresh_token] = "session_token"
|
||||
session[:oauth_gdrive_access_token] = "sesion_secret"
|
||||
|
||||
Rails.cache.expects(:fetch).with(['google_drive_tokens', mock_real_current_user].cache_key).returns(["real_current_user_token", "real_current_user_secret"])
|
||||
|
||||
GoogleDocs::DriveConnection.expects(:new).with("real_current_user_token", "real_current_user_secret")
|
||||
|
||||
controller.send(:google_drive_connection)
|
||||
end
|
||||
|
||||
it "uses @current_user second" do
|
||||
mock_current_user = mock()
|
||||
controller.instance_variable_set(:@real_current_user, nil)
|
||||
controller.instance_variable_set(:@current_user, mock_current_user)
|
||||
session[:oauth_gdrive_refresh_token] = "session_token"
|
||||
session[:oauth_gdrive_access_token] = "sesion_secret"
|
||||
|
||||
Rails.cache.expects(:fetch).with(['google_drive_tokens', mock_current_user].cache_key).returns(["current_user_token", "current_user_secret"])
|
||||
|
||||
GoogleDocs::DriveConnection.expects(:new).with("current_user_token", "current_user_secret")
|
||||
controller.send(:google_drive_connection)
|
||||
end
|
||||
|
||||
it "queries user services if token isn't in the cache" do
|
||||
mock_current_user = mock()
|
||||
controller.instance_variable_set(:@real_current_user, nil)
|
||||
controller.instance_variable_set(:@current_user, mock_current_user)
|
||||
session[:oauth_gdrive_refresh_token] = "session_token"
|
||||
session[:oauth_gdrive_access_token] = "sesion_secret"
|
||||
|
||||
mock_user_services = mock("mock_user_services")
|
||||
mock_current_user.expects(:user_services).returns(mock_user_services)
|
||||
mock_user_services.expects(:where).with(service: "google_drive").returns(stub(first: mock(token: "user_service_token", secret: "user_service_secret")))
|
||||
|
||||
GoogleDocs::DriveConnection.expects(:new).with("user_service_token", "user_service_secret")
|
||||
controller.send(:google_drive_connection)
|
||||
end
|
||||
|
||||
it "uses the session values if no users are set" do
|
||||
controller.instance_variable_set(:@real_current_user, nil)
|
||||
controller.instance_variable_set(:@current_user, nil)
|
||||
session[:oauth_gdrive_refresh_token] = "session_token"
|
||||
session[:oauth_gdrive_access_token] = "sesion_secret"
|
||||
|
||||
GoogleDocs::DriveConnection.expects(:new).with("session_token", "sesion_secret")
|
||||
|
||||
controller.send(:google_drive_connection)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#google_drive_user_client" do
|
||||
before :each do
|
||||
settings_mock = mock()
|
||||
settings_mock.stubs(:settings).returns({})
|
||||
Canvas::Plugin.stubs(:find).returns(settings_mock)
|
||||
|
||||
end
|
||||
|
||||
it "uses @real_current_user first" do
|
||||
mock_real_current_user = mock()
|
||||
mock_current_user = mock()
|
||||
|
@ -140,7 +203,7 @@ describe ApplicationController do
|
|||
|
||||
Rails.cache.expects(:fetch).with(['google_drive_tokens', mock_real_current_user].cache_key).returns(["real_current_user_refresh_token", "real_current_user_access_token"])
|
||||
GoogleDrive::Client.expects(:create).with({},"real_current_user_refresh_token", "real_current_user_access_token")
|
||||
controller.send(:google_drive_connection)
|
||||
controller.send(:google_drive_user_client)
|
||||
end
|
||||
|
||||
it "uses @current_user second" do
|
||||
|
@ -149,7 +212,7 @@ describe ApplicationController do
|
|||
controller.instance_variable_set(:@current_user, mock_current_user)
|
||||
Rails.cache.expects(:fetch).with(['google_drive_tokens', mock_current_user].cache_key).returns(["current_user_refresh_token", "current_user_access_token"])
|
||||
GoogleDrive::Client.expects(:create).with({},"current_user_refresh_token", "current_user_access_token")
|
||||
controller.send(:google_drive_connection)
|
||||
controller.send(:google_drive_user_client)
|
||||
end
|
||||
|
||||
it "queries user services if token isn't in the cache" do
|
||||
|
@ -165,7 +228,7 @@ describe ApplicationController do
|
|||
|
||||
GoogleDrive::Client.expects(:create).with({}, "user_refresh_token", "user_access_token")
|
||||
|
||||
controller.send(:google_drive_connection)
|
||||
controller.send(:google_drive_user_client)
|
||||
end
|
||||
|
||||
it "uses the session values if no users are set" do
|
||||
|
@ -174,9 +237,8 @@ describe ApplicationController do
|
|||
session[:oauth_gdrive_access_token] = "access_token"
|
||||
session[:oauth_gdrive_refresh_token] = "refresh_token"
|
||||
|
||||
GoogleDrive::Client.expects(:create).with({}, "access_token", "refresh_token")
|
||||
|
||||
controller.send(:google_drive_connection)
|
||||
GoogleDrive::Client.expects(:create).with({}, "refresh_token", "access_token")
|
||||
controller.send(:google_drive_user_client)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -42,10 +42,7 @@ describe CollaborationsController do
|
|||
|
||||
it "should assign variables" do
|
||||
user_session(@student)
|
||||
mock_user_service = mock()
|
||||
@user.expects(:user_services).returns(mock_user_service)
|
||||
mock_user_service.expects(:where).with(service: "google_docs").returns(stub(first: mock(token: "token", secret: "secret")))
|
||||
GoogleDocs::Connection.any_instance.expects(:verify_access_token).returns(true)
|
||||
controller.stubs(:google_docs_connection).returns(mock(verify_access_token:true))
|
||||
|
||||
get 'index', :course_id => @course.id
|
||||
|
||||
|
@ -55,9 +52,7 @@ describe CollaborationsController do
|
|||
|
||||
it "should handle users without google authorized" do
|
||||
user_session(@student)
|
||||
mock_user_service = mock()
|
||||
@user.expects(:user_services).returns(mock_user_service)
|
||||
mock_user_service.expects(:where).with(service: "google_docs").returns(stub(first: mock(token: nil, secret: nil)))
|
||||
controller.stubs(:google_docs_connection).returns(mock(verify_access_token:false))
|
||||
|
||||
get 'index', :course_id => @course.id
|
||||
|
||||
|
@ -67,10 +62,9 @@ describe CollaborationsController do
|
|||
|
||||
it "should assign variables when verify raises" do
|
||||
user_session(@student)
|
||||
mock_user_service = mock()
|
||||
@user.expects(:user_services).returns(mock_user_service)
|
||||
mock_user_service.expects(:where).with(service: "google_docs").returns(stub(first: mock(token: "token", secret: "secret")))
|
||||
GoogleDocs::Connection.any_instance.expects(:verify_access_token).raises("Error")
|
||||
google_docs_connection_mock = mock()
|
||||
google_docs_connection_mock.expects(:verify_access_token).raises("Error")
|
||||
controller.stubs(:google_docs_connection).returns(google_docs_connection_mock)
|
||||
|
||||
get 'index', :course_id => @course.id
|
||||
|
||||
|
@ -78,6 +72,19 @@ describe CollaborationsController do
|
|||
expect(assigns(:google_docs_authorized)).to eq false
|
||||
end
|
||||
|
||||
it 'handles users that need to upgrade to google_drive' do
|
||||
user_session(@student)
|
||||
plugin = Canvas::Plugin.find(:google_drive)
|
||||
plugin_setting = PluginSetting.find_by_name(plugin.id) || PluginSetting.new(:name => plugin.id, :settings => plugin.default_settings)
|
||||
plugin_setting.posted_settings = {}
|
||||
plugin_setting.save!
|
||||
get 'index', :course_id => @course.id
|
||||
|
||||
expect(response).to be_success
|
||||
expect(assigns(:google_docs_authorized)).to be_falsey
|
||||
expect(assigns(:google_drive_upgrade)).to be_truthy
|
||||
end
|
||||
|
||||
it "should not allow the student view student to access collaborations" do
|
||||
course_with_teacher_logged_in(:active_user => true)
|
||||
expect(@course).not_to be_available
|
||||
|
@ -94,9 +101,7 @@ describe CollaborationsController do
|
|||
group = gc.groups.create!(:context => @course)
|
||||
group.add_user(@student, 'accepted')
|
||||
|
||||
mock_user_service = mock()
|
||||
@user.expects(:user_services).returns(mock_user_service)
|
||||
mock_user_service.expects(:where).with(service: "google_docs").returns(stub(first: mock(token: "token", secret: "secret")))
|
||||
#controller.stubs(:google_docs_connection).returns(mock(verify_access_token:false))
|
||||
|
||||
get 'index', :group_id => group.id
|
||||
expect(response).to be_success
|
||||
|
|
|
@ -208,7 +208,7 @@ describe SubmissionsController do
|
|||
flag.state = 'on'
|
||||
flag.save!
|
||||
mock_user_service = mock()
|
||||
@user.expects(:user_services).returns(mock_user_service)
|
||||
@user.stubs(:user_services).returns(mock_user_service)
|
||||
mock_user_service.expects(:where).with(service: "google_docs").returns(stub(first: mock(token: "token", secret: "secret")))
|
||||
end
|
||||
|
||||
|
|
|
@ -26,9 +26,11 @@ describe GoogleDocsCollaboration do
|
|||
google_docs_collaboration.title = "title"
|
||||
google_docs_collaboration.user = user
|
||||
google_doc_connection = stub(retrieve_access_token: "asdf123")
|
||||
|
||||
Canvas::Plugin.stubs(:find).with(:google_drive).returns(nil)
|
||||
GoogleDocs::Connection.expects(:new).returns(google_doc_connection)
|
||||
file = stub(document_id: 1, entry: stub(to_xml: "<xml></xml>"), alternate_url: "http://google.com")
|
||||
google_doc_connection.expects(:create_doc).with("title", "asdf123").returns(file)
|
||||
google_doc_connection.expects(:create_doc).with("title").returns(file)
|
||||
Rails.cache.expects(:fetch).returns(["token", "secret"])
|
||||
|
||||
google_docs_collaboration.initialize_document
|
||||
|
|
Loading…
Reference in New Issue