From a99c397662fc99b1e127c36b02907e6a68aa98cf Mon Sep 17 00:00:00 2001 From: Nathan Mills Date: Mon, 9 Feb 2015 15:13:53 -0700 Subject: [PATCH] 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 QA-Review: August Thornton Product-Review: Brad Horrocks --- Gemfile.d/development.rb | 2 +- app/controllers/application_controller.rb | 26 ++- app/controllers/assignments_controller.rb | 7 +- app/controllers/collaborations_controller.rb | 8 +- app/controllers/submissions_controller.rb | 4 +- app/controllers/users_controller.rb | 1 + app/models/collaboration.rb | 1 + app/models/google_docs_collaboration.rb | 126 ++++++++--- .../assignments/_submit_assignment.html.erb | 4 +- app/views/collaborations/_forms.html.erb | 27 ++- app/views/collaborations/index.html.erb | 15 +- config/initializers/google_docs.rb | 7 +- gems/google_docs/lib/google_docs.rb | 1 + .../google_docs/lib/google_docs/connection.rb | 8 +- .../lib/google_docs/drive_connection.rb | 214 ++++++++++++++++++ gems/google_drive/lib/google_drive/client.rb | 2 +- lib/canvas/plugins/default_plugins.rb | 2 +- .../application_controller_spec.rb | 74 +++++- .../collaborations_controller_spec.rb | 33 +-- .../submissions_controller_spec.rb | 2 +- spec/models/google_docs_collaboration_spec.rb | 4 +- 21 files changed, 484 insertions(+), 84 deletions(-) create mode 100644 gems/google_docs/lib/google_docs/drive_connection.rb diff --git a/Gemfile.d/development.rb b/Gemfile.d/development.rb index fc368f992ce..419adb0896e 100644 --- a/Gemfile.d/development.rb +++ b/Gemfile.d/development.rb @@ -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 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 00656356de0..f5343752cb8 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -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 diff --git a/app/controllers/assignments_controller.rb b/app/controllers/assignments_controller.rb index 7ca548bffbf..d981392afc1 100644 --- a/app/controllers/assignments_controller.rb +++ b/app/controllers/assignments_controller.rb @@ -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 diff --git a/app/controllers/collaborations_controller.rb b/app/controllers/collaborations_controller.rb index 4c1e7e8f2a5..fc6944d9075 100644 --- a/app/controllers/collaborations_controller.rb +++ b/app/controllers/collaborations_controller.rb @@ -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 diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 5fb20ad3372..0ff32622de9 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -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 diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index c0eba478b97..529cf65b1d7 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -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 diff --git a/app/models/collaboration.rb b/app/models/collaboration.rb index 4e06158fa2b..b91b72fba94 100644 --- a/app/models/collaboration.rb +++ b/app/models/collaboration.rb @@ -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 diff --git a/app/models/google_docs_collaboration.rb b/app/models/google_docs_collaboration.rb index b3bcf59007c..06babacd038 100644 --- a/app/models/google_docs_collaboration.rb +++ b/app/models/google_docs_collaboration.rb @@ -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 diff --git a/app/views/assignments/_submit_assignment.html.erb b/app/views/assignments/_submit_assignment.html.erb index 2f702b42f43..7a7bb655bfa 100644 --- a/app/views/assignments/_submit_assignment.html.erb +++ b/app/views/assignments/_submit_assignment.html.erb @@ -271,11 +271,13 @@
<%= 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:" %>
<% end %> <% end %> + <% if @assignment.submission_types && @assignment.submission_types.match(/media_recording/) %> <% if !feature_enabled?(:kaltura) %>
diff --git a/app/views/collaborations/_forms.html.erb b/app/views/collaborations/_forms.html.erb index 8dfa49ef81b..cda0bb3703e 100644 --- a/app/views/collaborations/_forms.html.erb +++ b/app/views/collaborations/_forms.html.erb @@ -19,7 +19,7 @@ <% end %> - + <%= 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
@@ -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?" %>
- - + +
<%= 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 %> diff --git a/app/views/collaborations/index.html.erb b/app/views/collaborations/index.html.erb index aff0e63a960..ee9e5a3ff88 100644 --- a/app/views/collaborations/index.html.erb +++ b/app/views/collaborations/index.html.erb @@ -57,7 +57,7 @@ HEREDOC
<% @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) %>
<% if can_do(collaboration, @current_user, :delete) %> + <% elsif @google_drive_upgrade %> +
+

<%= collaboration.title %>

+ + style="font-size: 0.8em;"> + <%= t :started_by, "Started by *%{user}*, %{at}", + :user => context_user_name(@context, collaboration.user), + :wrapper => "\\1", + :at => datetime_string(collaboration.created_at) %> + +
<% end %> <% end %>
diff --git a/config/initializers/google_docs.rb b/config/initializers/google_docs.rb index a8ed1f30702..1f742680df3 100644 --- a/config/initializers/google_docs.rb +++ b/config/initializers/google_docs.rb @@ -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 \ No newline at end of file +GoogleDocs::Entry.extension_looker_upper = ScribdMimeType diff --git a/gems/google_docs/lib/google_docs.rb b/gems/google_docs/lib/google_docs.rb index 5a5d0d454e3..4de8fdff3a1 100644 --- a/gems/google_docs/lib/google_docs.rb +++ b/gems/google_docs/lib/google_docs.rb @@ -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" diff --git a/gems/google_docs/lib/google_docs/connection.rb b/gems/google_docs/lib/google_docs/connection.rb index 24429329555..b0e6161bbd1 100644 --- a/gems/google_docs/lib/google_docs/connection.rb +++ b/gems/google_docs/lib/google_docs/connection.rb @@ -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 diff --git a/gems/google_docs/lib/google_docs/drive_connection.rb b/gems/google_docs/lib/google_docs/drive_connection.rb new file mode 100644 index 00000000000..94edb52304f --- /dev/null +++ b/gems/google_docs/lib/google_docs/drive_connection.rb @@ -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 . +# + +# 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 + diff --git a/gems/google_drive/lib/google_drive/client.rb b/gems/google_drive/lib/google_drive/client.rb index 7b4b32ffbd9..bc82d95617e 100644 --- a/gems/google_drive/lib/google_drive/client.rb +++ b/gems/google_drive/lib/google_drive/client.rb @@ -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'] diff --git a/lib/canvas/plugins/default_plugins.rb b/lib/canvas/plugins/default_plugins.rb index 6a262caed96..43a1912b034 100644 --- a/lib/canvas/plugins/default_plugins.rb +++ b/lib/canvas/plugins/default_plugins.rb @@ -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', diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 5fa4391d1a0..7a744312bf5 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -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 diff --git a/spec/controllers/collaborations_controller_spec.rb b/spec/controllers/collaborations_controller_spec.rb index ea1e63a2efb..f4f822cbab8 100644 --- a/spec/controllers/collaborations_controller_spec.rb +++ b/spec/controllers/collaborations_controller_spec.rb @@ -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 diff --git a/spec/controllers/submissions_controller_spec.rb b/spec/controllers/submissions_controller_spec.rb index a5925c3a481..032c80ed59f 100644 --- a/spec/controllers/submissions_controller_spec.rb +++ b/spec/controllers/submissions_controller_spec.rb @@ -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 diff --git a/spec/models/google_docs_collaboration_spec.rb b/spec/models/google_docs_collaboration_spec.rb index da10380788c..52fd096b64d 100644 --- a/spec/models/google_docs_collaboration_spec.rb +++ b/spec/models/google_docs_collaboration_spec.rb @@ -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: ""), 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