master courses: hide edit/delete for restricted wiki pages

proof of concept for first-pass all-or-nothing restrictions

closes #MC-50

Change-Id: I937aeb24892e4fca0b31d5cd2d05058e7ea01679
Reviewed-on: https://gerrit.instructure.com/98553
Tested-by: Jenkins
Reviewed-by: Jeremy Stanley <jeremy@instructure.com>
Product-Review: James Williams  <jamesw@instructure.com>
QA-Review: James Williams  <jamesw@instructure.com>
This commit is contained in:
James Williams 2016-12-28 14:45:20 -07:00
parent 1e14794c0b
commit 84494e877d
10 changed files with 94 additions and 25 deletions

View File

@ -1985,7 +1985,8 @@ class ApplicationController < ActionController::Base
end end
if @page if @page
hash[:WIKI_PAGE] = wiki_page_json(@page, @current_user, session, true, :deep_check_if_needed => true) check_for_restrictions = master_courses? && @context.wiki.grants_right?(@current_user, :manage)
hash[:WIKI_PAGE] = wiki_page_json(@page, @current_user, session, true, :deep_check_if_needed => true, :include_master_course_restrictions => check_for_restrictions)
hash[:WIKI_PAGE_REVISION] = (current_version = @page.versions.current) ? StringifyIds.stringify_id(current_version.number) : nil hash[:WIKI_PAGE_REVISION] = (current_version = @page.versions.current) ? StringifyIds.stringify_id(current_version.number) : nil
hash[:WIKI_PAGE_SHOW_PATH] = named_context_url(@context, :context_wiki_page_path, @page) hash[:WIKI_PAGE_SHOW_PATH] = named_context_url(@context, :context_wiki_page_path, @page)
hash[:WIKI_PAGE_EDIT_PATH] = named_context_url(@context, :edit_context_wiki_page_path, @page) hash[:WIKI_PAGE_EDIT_PATH] = named_context_url(@context, :edit_context_wiki_page_path, @page)

View File

@ -250,7 +250,10 @@ class WikiPagesApiController < ApplicationController
scope = scope.order(order_clause) scope = scope.order(order_clause)
wiki_pages = Api.paginate(scope, self, pages_route) wiki_pages = Api.paginate(scope, self, pages_route)
render :json => wiki_pages_json(wiki_pages, @current_user, session)
check_for_restrictions = master_courses? && @context.wiki.grants_right?(@current_user, :manage)
MasterCourses::Restrictor.preload_restrictions(wiki_pages) if check_for_restrictions
render :json => wiki_pages_json(wiki_pages, @current_user, session, :include_master_course_restrictions => check_for_restrictions)
end end
end end
@ -391,6 +394,7 @@ class WikiPagesApiController < ApplicationController
# @returns Page # @returns Page
def destroy def destroy
if authorized_action(@page, @current_user, :delete) if authorized_action(@page, @current_user, :delete)
return render_unauthorized_action if editing_restricted?(@page)
if !@was_front_page if !@was_front_page
@page.destroy @page.destroy
process_front_page process_front_page

View File

@ -110,6 +110,8 @@ class WikiPagesController < ApplicationController
def edit def edit
if @page.grants_any_right?(@current_user, session, :update, :update_content) if @page.grants_any_right?(@current_user, session, :update, :update_content)
return render_unauthorized_action if editing_restricted?(@page)
js_env ConditionalRelease::Service.env_for @context js_env ConditionalRelease::Service.env_for @context
if !ConditionalRelease::Service.enabled_in_context?(@context) || if !ConditionalRelease::Service.enabled_in_context?(@context) ||
enforce_assignment_visible(@page) enforce_assignment_visible(@page)

View File

@ -58,9 +58,9 @@ module MasterCourses::Restrictor
case edit_type case edit_type
when :all when :all
MasterCourses::LOCK_TYPES.all?{|type| restrictions[type]} self.class.base_class.restricted_column_settings.keys.all?{|type| restrictions[type]} # make it possible to only have restrictions on one type
when :any when :any
MasterCourses::LOCK_TYPES.any?{|type| restrictions[type]} self.class.base_class.restricted_column_settings.keys.any?{|type| restrictions[type]}
when *MasterCourses::LOCK_TYPES when *MasterCourses::LOCK_TYPES
!!restrictions[edit_type] !!restrictions[edit_type]
else else
@ -137,6 +137,7 @@ module MasterCourses::Restrictor
locked_types << type locked_types << type
end end
end end
locked_types locked_types
end end

View File

@ -32,9 +32,11 @@
{{/if}} {{/if}}
{{/if}} {{/if}}
{{/unless}} {{/unless}}
{{#if CAN.UPDATE_CONTENT}} {{#unless restricted_by_master_course}}
<a href="{{wiki_page_edit_path}}" class="btn edit-wiki"><i class="icon-edit"></i> {{#t 'buttons.edit'}}Edit{{/t}}</a> {{#if CAN.UPDATE_CONTENT}}
{{/if}} <a href="{{wiki_page_edit_path}}" class="btn edit-wiki"><i class="icon-edit"></i> {{#t 'buttons.edit'}}Edit{{/t}}</a>
{{/if}}
{{/unless}}
{{#if CAN.ACCESS_GEAR_MENU}} {{#if CAN.ACCESS_GEAR_MENU}}
<div class="inline-block"> <div class="inline-block">
<a class="btn al-trigger" tabindex="0" role="button" href="#"> <a class="btn al-trigger" tabindex="0" role="button" href="#">
@ -42,9 +44,11 @@
<span class="screenreader-only">{{#t 'toolbar_menu.settings'}}Settings{{/t}}</span> <span class="screenreader-only">{{#t 'toolbar_menu.settings'}}Settings{{/t}}</span>
</a> </a>
<ul class="al-options"> <ul class="al-options">
{{#if CAN.DELETE}} {{#unless restricted_by_master_course}}
<li><a href="#" class="icon-trash delete_page{{#unless deletable}} disabled{{/unless}}" {{#unless deletable}}aria-disabled="true"{{/unless}}>{{#t "delete_wiki"}}Delete{{/t}}</a></li> {{#if CAN.DELETE}}
{{/if}} <li><a href="#" class="icon-trash delete_page{{#unless deletable}} disabled{{/unless}}" {{#unless deletable}}aria-disabled="true"{{/unless}}>{{#t "delete_wiki"}}Delete{{/t}}</a></li>
{{/if}}
{{/unless}}
{{#if CAN.READ_REVISIONS}} {{#if CAN.READ_REVISIONS}}
<li><a href="{{wiki_page_history_path}}" class="icon-clock view_page_history">{{#t "view_page_history_wiki"}}View Page History{{/t}}</a></li> <li><a href="{{wiki_page_history_path}}" class="icon-clock view_page_history">{{#t "view_page_history_wiki"}}View Page History{{/t}}</a></li>
{{/if}} {{/if}}

View File

@ -15,8 +15,10 @@
<span class="screenreader-only">{{#t 'menu.settings'}}Settings{{/t}}</span> <span class="screenreader-only">{{#t 'menu.settings'}}Settings{{/t}}</span>
</a> </a>
<ul class="al-options"> <ul class="al-options">
<li><a href="#" class="icon-edit edit-menu-item" title="{{#t 'menu.edit'}}Edit{{/t}}">{{#t 'menu.edit'}}Edit{{/t}}</a></li> {{#unless restricted_by_master_course}}
<li><a href="#" class="icon-trash delete-menu-item{{#unless deletable}} disabled{{/unless}}" title="{{#t 'menu.delete'}}Delete{{/t}}" {{#unless deletable}}aria-disabled="true"{{/unless}}>{{#t 'menu.delete'}}Delete{{/t}}</a></li> <li><a href="#" class="icon-edit edit-menu-item" title="{{#t 'menu.edit'}}Edit{{/t}}">{{#t 'menu.edit'}}Edit{{/t}}</a></li>
<li><a href="#" class="icon-trash delete-menu-item{{#unless deletable}} disabled{{/unless}}" title="{{#t 'menu.delete'}}Delete{{/t}}" {{#unless deletable}}aria-disabled="true"{{/unless}}>{{#t 'menu.delete'}}Delete{{/t}}</a></li>
{{/unless}}
{{#unless front_page}} {{#unless front_page}}
<li><a href="#" class="icon-document use-as-front-page-menu-item{{#unless published}} disabled{{/unless}}" title="{{#t 'menu.use_front_page'}}Use as Front Page{{/t}}" {{#unless published}}aria-disabled="true"{{/unless}}>{{#t 'menu.use_front_page'}}Use as Front Page{{/t}}</a></li> <li><a href="#" class="icon-document use-as-front-page-menu-item{{#unless published}} disabled{{/unless}}" title="{{#t 'menu.use_front_page'}}Use as Front Page{{/t}}" {{#unless published}}aria-disabled="true"{{/unless}}>{{#t 'menu.use_front_page'}}Use as Front Page{{/t}}</a></li>
{{/unless}} {{/unless}}

View File

@ -49,11 +49,14 @@ module Api::V1::WikiPage
hash['body'] = api_user_content(wiki_page.body) hash['body'] = api_user_content(wiki_page.body)
wiki_page.increment_view_count(current_user, wiki_page.context) wiki_page.increment_view_count(current_user, wiki_page.context)
end end
if opts[:include_master_course_restrictions]
hash['restricted_by_master_course'] = wiki_page.editing_restricted?
end
hash hash
end end
def wiki_pages_json(wiki_pages, current_user, session) def wiki_pages_json(wiki_pages, current_user, session, opts={})
wiki_pages.map { |page| wiki_page_json(page, current_user, session, false) } wiki_pages.map { |page| wiki_page_json(page, current_user, session, false, opts) }
end end
def wiki_page_revision_json(version, current_user, current_session, include_content = true, latest_version = nil) def wiki_page_revision_json(version, current_user, current_session, include_content = true, latest_version = nil)

View File

@ -479,8 +479,8 @@ END
}, },
'master_courses' => 'master_courses' =>
{ {
display_name: -> { I18n.t('Master Courses') }, display_name: -> { I18n.t('Blueprint Courses') }, # this won't be confusing at all
description: -> { I18n.t('Enable the creation of Master Courses') }, description: -> { I18n.t('Enable the creation of Blueprint Courses') },
applies_to: 'RootAccount', applies_to: 'RootAccount',
state: 'hidden', state: 'hidden',
beta: true, beta: true,

View File

@ -59,15 +59,7 @@ describe MasterCourses::CollectionRestrictor do
expect(@aq.editing_restricted?(:content)).to be_truthy expect(@aq.editing_restricted?(:content)).to be_truthy
expect(@aq.editing_restricted?(:settings)).to be_falsey expect(@aq.editing_restricted?(:settings)).to be_falsey
expect(@aq.editing_restricted?(:any)).to be_truthy expect(@aq.editing_restricted?(:any)).to be_truthy
expect(@aq.editing_restricted?(:all)).to be_falsey expect(@aq.editing_restricted?(:all)).to be_truthy # in retrospect - if we're only classifying content as restricted then this should probably be true
end
it "should return true if fully locked" do
@tag.update_attribute(:restrictions, {:content => true, :settings => true})
expect(@aq.editing_restricted?(:content)).to be_truthy
expect(@aq.editing_restricted?(:settings)).to be_truthy
expect(@aq.editing_restricted?(:any)).to be_truthy
expect(@aq.editing_restricted?(:all)).to be_truthy
end end
end end
end end

View File

@ -0,0 +1,60 @@
require_relative '../common'
describe "master courses - child courses - wiki page locking" do
include_context "in-process server selenium tests"
before :once do
Account.default.enable_feature!(:master_courses)
@copy_from = course_factory(:active_all => true)
@template = MasterCourses::MasterTemplate.set_as_master_course(@copy_from)
@original_page = @copy_from.wiki.wiki_pages.create!(:title => "blah", :body => "bloo")
@tag = @template.create_content_tag_for!(@original_page)
course_with_teacher(:active_all => true)
@copy_to = @course
@page_copy = @copy_to.wiki.wiki_pages.new(:title => "blah", :body => "bloo") # just create a copy directly instead of doing a real migraiton
@page_copy.migration_id = @tag.migration_id
@page_copy.save!
end
before :each do
user_session(@teacher)
end
it "should not show the edit/delete cog-menu options on the index when locked" do
@tag.update_attribute(:restrictions, {:content => true, :settings => true})
get "/courses/#{@copy_to.id}/pages"
f('.al-trigger').click
expect(f('.al-options')).to_not contain_css('.edit-menu-item')
expect(f('.al-options')).to_not contain_css('.delete-menu-item')
end
it "should show the edit/delete cog-menu options on the index when not locked" do
get "/courses/#{@copy_to.id}/pages"
f('.al-trigger').click
expect(f('.al-options')).to contain_css('.edit-menu-item')
expect(f('.al-options')).to contain_css('.delete-menu-item')
end
it "should not show the edit/delete options on the show page when locked" do
@tag.update_attribute(:restrictions, {:content => true, :settings => true})
get "/courses/#{@copy_to.id}/pages/#{@page_copy.url}"
expect(f('#content')).to_not contain_css('.edit-wiki')
f('.al-trigger').click
expect(f('.al-options')).to_not contain_css('.delete_page')
end
it "should show the edit/delete cog-menu options on the index when not locked" do
get "/courses/#{@copy_to.id}/pages/#{@page_copy.url}"
expect(f('#content')).to contain_css('.edit-wiki')
f('.al-trigger').click
expect(f('.al-options')).to contain_css('.delete_page')
end
end