Add granular permissions for wiki pages

Note that we aren't doing any fancy grouping in the UI at this
point, that will be something added later as we coordinate with
the designers.

We will also need to re-run the data fixup when we are ready to
turn the feature flag on, to capture any differences made to
wiki page permissions between now and then.

Test Plan:
  - Run the migration and make sure there are no errors

  - With the granular_permissions_wiki_pages feature flag turned off
    the wiki page and REST API should work the same with this patchset
    checked out as it does in beta/production. Some things to check:

    * How it acts with Create/Edit/Update Page role override
      permission being enabled or disabled
    * How it acts with the `can edit course pages by default` setting
      in the course settings page
    * How it acts on an individual wiki when it is editable by
      only teachers, teachers and students, or anyone.
    * How it acts as a teachers, student, designer, and public user
      in the course with the various settings above toggled to
      different states.

  - With the granular_permissions_wiki_pages feature flag turned on
    the wiki page and REST api should work as expected. The same list
    checked above should be done so again, but this time:

    * Should only be able to create or duplicate pages if the
      create page permission is enabled for the users type  or the
      course level setting allows for the user to create pages
    * Should only be able to update or mark pages and front page if
      update page permission is enabled for the users type or the
      course level setting allows for the user to create pages
    * Should only be able to delete pages if the delete page
      permission is enabled
    * That the REST API works as expected
    * That the UI works as expected

fixes USERS-164

flag = granular_permissions_wiki_pages

Change-Id: I0b4ebeee425bf03c26101658fe4b95774df697ff
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/222848
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Product-Review: Landon Gilbert-Bland <lbland@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Reviewed-by: Charley Kline <ckline@instructure.com>
This commit is contained in:
Landon Gilbert-Bland 2020-01-09 08:24:10 -07:00
parent 1448526d63
commit 5687dd8dac
23 changed files with 681 additions and 64 deletions

View File

@ -77,7 +77,7 @@ export default class WikiPageContentView extends Backbone.View {
json.CAN = {
VIEW_ALL_PAGES: !!this.display_show_all_pages,
VIEW_PAGES: !!this.WIKI_RIGHTS.read,
PUBLISH: !!this.WIKI_RIGHTS.manage && json.contextName === 'courses',
PUBLISH: !!this.WIKI_RIGHTS.publish_page,
UPDATE_CONTENT: !!this.PAGE_RIGHTS.update || !!this.PAGE_RIGHTS.update_content,
DELETE: !!this.PAGE_RIGHTS.delete && !this.course_home,
READ_REVISIONS: !!this.PAGE_RIGHTS.read_revisions

View File

@ -104,7 +104,7 @@ export default class WikiPageEditView extends ValidatedFormView {
PUBLISH_NOW: !!this.WIKI_RIGHTS.publish_page && !this.model.get('published'),
DELETE: !!this.PAGE_RIGHTS.delete,
EDIT_TITLE: !!this.PAGE_RIGHTS.update || json.new_record,
EDIT_ROLES: !!this.WIKI_RIGHTS.manage
EDIT_ROLES: !!this.WIKI_RIGHTS.update
}
json.SHOW = {COURSE_ROLES: json.contextName === 'courses'}

View File

@ -65,10 +65,15 @@ export default class WikiPageIndexItemView extends Backbone.View {
toJSON() {
const json = super.toJSON(...arguments)
json.CAN = {
MANAGE: !!this.WIKI_RIGHTS.manage,
PUBLISH: !!this.WIKI_RIGHTS.manage && this.contextName === 'courses',
// TODO: Consider allowing duplicating pages in other contexts
DUPLICATE: !!this.WIKI_RIGHTS.manage && this.contextName === 'courses'
MANAGE:
!!this.WIKI_RIGHTS.create_page ||
!!this.WIKI_RIGHTS.delete_page ||
!!this.WIKI_RIGHTS.publish_page ||
!!this.WIKI_RIGHTS.update,
PUBLISH: !!this.WIKI_RIGHTS.publish_page,
DUPLICATE: !!this.WIKI_RIGHTS.create_page && this.contextName === 'courses',
UPDATE: !!this.WIKI_RIGHTS.update,
DELETE: !!this.WIKI_RIGHTS.delete_page
}
json.DIRECT_SHARE_ENABLED = ENV.DIRECT_SHARE_ENABLED

View File

@ -185,7 +185,7 @@ export default class WikiPageIndexView extends PaginatedCollectionView {
WIKI_RIGHTS: ENV.WIKI_RIGHTS,
PAGE_RIGHTS: {
update: ENV.WIKI_RIGHTS.update_page,
update_content: ENV.WIKI_RIGHTS.update_page_content,
update_content: ENV.WIKI_RIGHTS.update_page,
read_revisions: ENV.WIKI_RIGHTS.read_revisions
}
})
@ -291,8 +291,8 @@ export default class WikiPageIndexView extends PaginatedCollectionView {
const json = super.toJSON(...arguments)
json.CAN = {
CREATE: !!this.WIKI_RIGHTS.create_page,
MANAGE: !!this.WIKI_RIGHTS.manage,
PUBLISH: !!this.WIKI_RIGHTS.manage && this.contextName === 'courses'
MANAGE: !!this.WIKI_RIGHTS.update || !!this.WIKI_RIGHTS.delete_page,
PUBLISH: !!this.WIKI_RIGHTS.publish_page
}
json.CAN.VIEW_TOOLBAR = json.CAN.CREATE
json.fetched = !!this.fetched

View File

@ -249,7 +249,7 @@ export default class WikiPageView extends Backbone.View {
json.CAN = {
VIEW_ALL_PAGES: !!this.display_show_all_pages || !!this.WIKI_RIGHTS.manage,
VIEW_PAGES: !!this.WIKI_RIGHTS.read,
PUBLISH: !!this.WIKI_RIGHTS.manage && json.contextName === 'courses',
PUBLISH: !!this.WIKI_RIGHTS.publish_page,
VIEW_UNPUBLISHED: !!this.WIKI_RIGHTS.manage || !!this.WIKI_RIGHTS.view_unpublished_items,
UPDATE_CONTENT: !!this.PAGE_RIGHTS.update || !!this.PAGE_RIGHTS.update_content,
DELETE: !!this.PAGE_RIGHTS.delete && !this.course_home,

View File

@ -71,7 +71,7 @@ class CalendarsController < ApplicationController
:can_make_reservation => context.grants_right?(@current_user, :participate_as_student),
:can_update_todo_date => context.grants_right?(@current_user, session, :manage_content),
:can_update_discussion_topic => context.grants_right?(@current_user, session, :moderate_forum),
:can_update_wiki_page => context.grants_right?(@current_user, session, :manage_wiki),
:can_update_wiki_page => context.grants_right?(@current_user, session, :update),
:concluded => (context.is_a? Course) ? context.concluded? : false
}
if context.respond_to?("course_sections")

View File

@ -275,7 +275,7 @@ class WikiPagesApiController < ApplicationController
wiki_pages = Api.paginate(scope, self, pages_route)
if @context.wiki.grants_right?(@current_user, :manage)
if @context.wiki.grants_right?(@current_user, :update)
mc_status = setup_master_course_restrictions(wiki_pages, @context)
end
render :json => wiki_pages_json(wiki_pages, @current_user, session, :master_course_status => mc_status)
@ -391,13 +391,15 @@ class WikiPagesApiController < ApplicationController
perform_update = false
if @page.new_record?
perform_update = true if authorized_action(@page, @current_user, [:create])
allowed_fields = Set[:title, :body]
elsif authorized_action(@page, @current_user, [:update, :update_content])
perform_update = true
allowed_fields = Set[]
end
if perform_update
assign_todo_date
update_params = get_update_params
update_params = get_update_params(allowed_fields)
if !update_params.is_a?(Symbol) && @page.update(update_params) && process_front_page
log_asset_access(@page, "wiki", @wiki, 'participate')
@page.context_module_action(@current_user, @context, :contributed)
@ -541,8 +543,6 @@ class WikiPagesApiController < ApplicationController
@page.workflow_state = 'active'
@set_front_page = true
@set_as_front_page = true
else
@page.workflow_state = @wiki.grants_right?(@current_user, session, :manage) ? 'unpublished' : 'active'
end
end
end
@ -592,7 +592,7 @@ class WikiPagesApiController < ApplicationController
# check user permissions
rejected_fields = Set[]
if @wiki.grants_right?(@current_user, session, :manage)
if @wiki.grants_right?(@current_user, session, :update)
allowed_fields.clear
else
if workflow_state && workflow_state != @page.workflow_state

View File

@ -171,6 +171,7 @@ class WikiPagesController < ApplicationController
:STUDENT_PLANNER_ENABLED => context.root_account.feature_enabled?(:student_planner),
:CAN_SET_TODO_DATE => context.root_account.feature_enabled?(:student_planner) && context.grants_right?(@current_user, session, :manage_content),
:IMMERSIVE_READER_ENABLED => context.account&.feature_enabled?(:immersive_reader_wiki_pages),
:GRANULAR_PERMISSIONS_WIKI_PAGES => context.root_account.feature_enabled?(:granular_permissions_wiki_pages),
}
js_env(@wiki_pages_env)
@wiki_pages_env

View File

@ -485,6 +485,9 @@ class Group < ActiveRecord::Base
can :manage_content and
can :manage_files and
can :manage_wiki and
can :manage_wiki_create and
can :manage_wiki_delete and
can :manage_wiki_update and
can :post_to_forum and
can :create_collaborations and
can :create_forum
@ -543,6 +546,9 @@ class Group < ActiveRecord::Base
can :manage_files and
can :manage_students and
can :manage_wiki and
can :manage_wiki_create and
can :manage_wiki_delete and
can :manage_wiki_update and
can :moderate_forum and
can :post_to_forum and
can :create_forum and

View File

@ -685,7 +685,68 @@ class RoleOverride < ActiveRecord::Base
'TeacherEnrollment',
'DesignerEnrollment',
'AccountAdmin'
]
],
:account_allows => lambda {|a| !a.root_account.feature_enabled?(:granular_permissions_wiki_pages)}
},
:manage_wiki_create => {
:label => lambda { t("Create pages") },
:label_v2 => lambda { t("Pages - Create") },
:available_to => [
'TaEnrollment',
'TeacherEnrollment',
'DesignerEnrollment',
'TeacherlessStudentEnrollment',
'ObserverEnrollment',
'AccountAdmin',
'AccountMembership'
],
:true_for => [
'TaEnrollment',
'TeacherEnrollment',
'DesignerEnrollment',
'AccountAdmin'
],
:account_allows => lambda {|a| a.root_account.feature_enabled?(:granular_permissions_wiki_pages)},
},
:manage_wiki_delete => {
:label => lambda { t("Delete pages") },
:label_v2 => lambda { t("Pages - Delete") },
:available_to => [
'TaEnrollment',
'TeacherEnrollment',
'DesignerEnrollment',
'TeacherlessStudentEnrollment',
'ObserverEnrollment',
'AccountAdmin',
'AccountMembership'
],
:true_for => [
'TaEnrollment',
'TeacherEnrollment',
'DesignerEnrollment',
'AccountAdmin'
],
:account_allows => lambda {|a| a.root_account.feature_enabled?(:granular_permissions_wiki_pages)},
},
:manage_wiki_update => {
:label => lambda { t("Update pages") },
:label_v2 => lambda { t("Pages - Update") },
:available_to => [
'TaEnrollment',
'TeacherEnrollment',
'DesignerEnrollment',
'TeacherlessStudentEnrollment',
'ObserverEnrollment',
'AccountAdmin',
'AccountMembership'
],
:true_for => [
'TaEnrollment',
'TeacherEnrollment',
'DesignerEnrollment',
'AccountAdmin'
],
:account_allows => lambda {|a| a.root_account.feature_enabled?(:granular_permissions_wiki_pages)},
},
:moderate_forum => {
:label => lambda { t('permissions.moderate_form', "Moderate discussions ( delete / edit other's posts, lock topics)") },

View File

@ -151,13 +151,44 @@ class Wiki < ActiveRecord::Base
can :view_unpublished_items
given {|user, session| self.context.grants_right?(user, session, :participate_as_student) && self.context.respond_to?(:allow_student_wiki_edits) && self.context.allow_student_wiki_edits}
can :read and can :create_page and can :update_page and can :update_page_content
can :read and can :create_page and can :update_page
given {|user, session| self.context.grants_right?(user, session, :manage_wiki)}
can :manage and can :read and can :update and can :create_page and can :delete_page and can :delete_unpublished_page and can :update_page and can :update_page_content and can :view_unpublished_items
given do |user, session|
self.context.root_account.feature_enabled?(:granular_permissions_wiki_pages) &&
self.context.grants_right?(user, session, :manage_wiki_create)
end
can :read and can :create_page and can :view_unpublished_items
given do |user, session|
self.context.root_account.feature_enabled?(:granular_permissions_wiki_pages) &&
self.context.grants_right?(user, session, :manage_wiki_delete)
end
can :read and can :delete_page and can :view_unpublished_items
given do |user, session|
self.context.root_account.feature_enabled?(:granular_permissions_wiki_pages) &&
self.context.grants_right?(user, session, :manage_wiki_update)
end
can :read and can :update and can :update_page and can :view_unpublished_items
given {|user, session| self.context.grants_right?(user, session, :manage_wiki) && !self.context.is_a?(Group)}
# Pages created by a user without this permission will be automatically published
given do |user, session|
self.context.root_account.feature_enabled?(:granular_permissions_wiki_pages) &&
self.context.grants_right?(user, session, :manage_wiki_update) && !self.context.is_a?(Group)
end
can :publish_page
given do |user, session|
!self.context.root_account.feature_enabled?(:granular_permissions_wiki_pages) &&
self.context.grants_right?(user, session, :manage_wiki)
end
can :manage and can :read and can :update and can :create_page and can :delete_page and can :update_page and can :view_unpublished_items
# Pages created by a user without this permission will be automatically published
given do |user, session|
!self.context.root_account.feature_enabled?(:granular_permissions_wiki_pages) &&
self.context.grants_right?(user, session, :manage_wiki) && !self.context.is_a?(Group)
end
can :publish_page
end

View File

@ -274,19 +274,13 @@ class WikiPage < ActiveRecord::Base
given {|user| user && self.can_edit_page?(user)}
can :update_content and can :read_revisions
given {|user, session| user && self.can_edit_page?(user) && self.wiki.grants_right?(user, session, :create_page)}
given {|user, session| user && self.wiki.grants_right?(user, session, :create_page)}
can :create
given {|user, session| user && self.can_edit_page?(user) && self.wiki.grants_right?(user, session, :update_page)}
can :update and can :read_revisions
given {|user, session| user && self.can_edit_page?(user) && self.published? && self.wiki.grants_right?(user, session, :update_page_content)}
can :update_content and can :read_revisions
given {|user, session| user && self.can_edit_page?(user) && self.published? && self.wiki.grants_right?(user, session, :delete_page)}
can :delete
given {|user, session| user && self.can_edit_page?(user) && self.unpublished? && self.wiki.grants_right?(user, session, :delete_unpublished_page)}
given {|user, session| user && can_read_page?(user) && self.wiki.grants_right?(user, session, :delete_page)}
can :delete
end
@ -298,8 +292,8 @@ class WikiPage < ActiveRecord::Base
def can_edit_page?(user, session=nil)
return false unless can_read_page?(user, session)
# wiki managers are always allowed to edit
return true if wiki.grants_right?(user, session, :manage)
# wiki managers are always allowed to edit.
return true if wiki.grants_right?(user, session, :update)
roles = effective_roles
# teachers implies all course admins (teachers, TAs, etc)
@ -320,7 +314,7 @@ class WikiPage < ActiveRecord::Base
end
def available_for?(user, session=nil)
return true if wiki.grants_right?(user, session, :manage)
return true if wiki.grants_right?(user, session, :update)
return false unless published? || (unpublished? && wiki.grants_right?(user, session, :view_unpublished_items))
return false if locked_for?(user, :deep_check_if_needed => true)

View File

@ -56,7 +56,9 @@
<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}}
{{#unless front_page}}
<li><a href="#" class="icon-bookmark 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>
{{#if CAN.UPDATE_CONTENT}}
<li><a href="#" class="icon-bookmark 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>
{{/if}}
{{/unless}}
{{#if CAN.DIRECT_SHARE}}
<li><a href="#" class="icon-user direct-share-send-to-menu-item">{{#t}}Send To...{{/t}}</a></li>

View File

@ -22,35 +22,43 @@
</a>
<ul class="al-options">
{{#unless cannot_edit_by_master_course}}
<li><a href="#" class="icon-edit edit-menu-item" title="{{#t}}Edit{{/t}}">{{#t}}Edit{{/t}}</a></li>
{{#if CAN.UPDATE}}
<li><a href="#" class="icon-edit edit-menu-item" title="{{#t}}Edit{{/t}}">{{#t}}Edit{{/t}}</a></li>
{{/if}}
{{/unless}}
{{#unless cannot_delete_by_master_course}}
<li>
<a href="#" class="icon-trash delete-menu-item{{#unless deletable}} disabled{{/unless}}"
title="{{#t}}Delete {{title}}{{/t}}" {{#unless deletable}}aria-disabled="true"{{/unless}}>
{{#t}}Delete{{/t}}
</a>
</li>
{{#if CAN.DELETE}}
<li>
<a href="#" class="icon-trash delete-menu-item{{#unless deletable}} disabled{{/unless}}"
title="{{#t}}Delete {{title}}{{/t}}" {{#unless deletable}}aria-disabled="true"{{/unless}}>
{{#t}}Delete{{/t}}
</a>
</li>
{{/if}}
{{/unless}}
{{#if front_page}}
<li>
<a href="#" class="unset-as-front-page-menu-item{{#unless published}} disabled{{/unless}}"
title="{{#t}}Remove as Front Page{{/t}}" {{#unless published}}aria-disabled="true"{{/unless}}>
<i class="icon-remove-bookmark" aria-hidden="true">
{{#t}}Remove as Front Page{{/t}}
</i>
</a>
</li>
{{#if CAN.UPDATE}}
<li>
<a href="#" class="unset-as-front-page-menu-item{{#unless published}} disabled{{/unless}}"
title="{{#t}}Remove as Front Page{{/t}}" {{#unless published}}aria-disabled="true"{{/unless}}>
<i class="icon-remove-bookmark" aria-hidden="true">
{{#t}}Remove as Front Page{{/t}}
</i>
</a>
</li>
{{/if}}
{{/if}}
{{#unless front_page}}
<li>
<a href="#" class="use-as-front-page-menu-item{{#unless published}} disabled{{/unless}}"
title="{{#t}}Use as Front Page{{/t}}" {{#unless published}}aria-disabled="true"{{/unless}}>
<i class="icon-bookmark" aria-hidden="true">
{{#t}}Use as Front Page{{/t}}
</i>
</a>
</li>
{{#if CAN.UPDATE}}
<li>
<a href="#" class="use-as-front-page-menu-item{{#unless published}} disabled{{/unless}}"
title="{{#t}}Use as Front Page{{/t}}" {{#unless published}}aria-disabled="true"{{/unless}}>
<i class="icon-bookmark" aria-hidden="true">
{{#t}}Use as Front Page{{/t}}
</i>
</a>
</li>
{{/if}}
{{/unless}}
{{#if CAN.DUPLICATE}}
<li>

View File

@ -4,3 +4,8 @@ notify_for_manually_created_access_tokens:
display_name: Send notifications when manually created access tokens are made
description: Send notifications when manually created access tokens are made
applies_to: SiteAdmin
granular_permissions_wiki_pages:
applies_to: RootAccount
description: Granular permissions for managing wiki pages
display_name: Granular permissions for managing wiki pages
state: hidden

View File

@ -0,0 +1,26 @@
#
# Copyright (C) 2020 - present 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/>.
#
class GranularWikiPagePermissions < ActiveRecord::Migration[5.2]
tag :postdeploy
def up
DataFixup::AddRoleOverridesForNewPermission.run(:manage_wiki, :manage_wiki_create)
DataFixup::AddRoleOverridesForNewPermission.run(:manage_wiki, :manage_wiki_delete)
DataFixup::AddRoleOverridesForNewPermission.run(:manage_wiki, :manage_wiki_update)
end
end

View File

@ -27,6 +27,289 @@ describe WikiPagesApiController, type: :request do
include Api::V1::WikiPage
include LtiSpecHelper
['post', 'put'].each do |http_verb|
describe "creating a wiki page via #{http_verb}" do
before :once do
course_with_teacher(active_all: true)
student_in_course(active_all: true)
if http_verb == 'post'
@action = 'create'
@http_verb = :post
@url = "/api/v1/courses/#{@course.id}/pages"
else
@action = 'update'
@http_verb = :put
@url = "/api/v1/courses/#{@course.id}/pages/new-page"
end
end
def create_wiki_page(user, wiki_params, expected_status = 200)
path = {
controller: 'wiki_pages_api',
action: @action,
format: 'json',
course_id: @course.id.to_s,
}
path[:url] = 'new-page' if @http_verb == :put
params = { wiki_page: wiki_params }
api_call_as_user(user, @http_verb, @url, path, params, {}, {expected_status: expected_status})
end
context 'with granular permission disabled' do
before :once do
@course.root_account.disable_feature!(:granular_permissions_wiki_pages)
end
context 'with user having manage_wiki permission' do
it 'succeeds' do
create_wiki_page(@teacher, {title: 'New Page', body: 'banana'})
expect(WikiPage.last.title).to eq 'New Page'
expect(WikiPage.last.body).to eq 'banana'
end
it 'is not published by default when created' do
create_wiki_page(@teacher, {title: 'New Page'})
expect(WikiPage.last.workflow_state).to eq 'unpublished'
end
it 'can be explictly published when created' do
create_wiki_page(@teacher, {title: 'New Page', published: true})
expect(WikiPage.last.workflow_state).to eq 'active'
end
it 'allows the "editing_roles" field to be set' do
create_wiki_page(@teacher, {title: 'New Page', editing_roles: 'public'})
expect(WikiPage.last.editing_roles).to eq 'public'
end
end
context 'with user not having manage_wiki permission' do
it 'fails if the course does not grant create wiki page permission' do
create_wiki_page(@student, {title: 'New Page'}, 401)
expect(WikiPage.last).to be_nil
end
it 'succeeds if the course grants create wiki page permission' do
@course.update!({default_wiki_editing_roles: 'teachers,students'})
create_wiki_page(@student, {title: 'New Page', body: 'banana'})
expect(WikiPage.last.title).to eq 'New Page'
expect(WikiPage.last.body).to eq 'banana'
end
it 'does not allow the "who can edit" field to be set' do
@course.update!({default_wiki_editing_roles: 'teachers,students'})
create_wiki_page(@student, {title: 'New Page', editing_roles: 'public'}, 401)
expect(WikiPage.last).to be_nil
end
it 'is published automatically when created' do
@course.update!({default_wiki_editing_roles: 'teachers,students'})
create_wiki_page(@student, {title: 'New Page'})
expect(WikiPage.last.workflow_state).to eq 'active'
end
it 'cannot be set as unpublished when created' do
@course.update!({default_wiki_editing_roles: 'teachers,students'})
create_wiki_page(@student, {title: 'New Page', published: false}, 401)
expect(WikiPage.last).to be_nil
end
end
end
context 'with granular permission enabled' do
before :once do
@course.root_account.enable_feature!(:granular_permissions_wiki_pages)
end
context 'with the user having manage_wiki_create permission' do
it 'succeeds' do
create_wiki_page(@teacher, {title: 'New Page', body: 'banana'})
expect(WikiPage.last.title).to eq 'New Page'
expect(WikiPage.last.body).to eq 'banana'
end
context 'when the user also has manage_wiki_update permission' do
it 'is not published by default' do
create_wiki_page(@teacher, {title: 'New Page'})
expect(WikiPage.last.workflow_state).to eq 'unpublished'
end
it 'can be explictly published' do
create_wiki_page(@teacher, {title: 'New Page', published: true})
expect(WikiPage.last.workflow_state).to eq 'active'
end
it 'allows the "editing_roles" field to be set' do
create_wiki_page(@teacher, {title: 'New Page', editing_roles: 'public'})
expect(WikiPage.last.editing_roles).to eq 'public'
end
end
context 'when the user does not have manage_wiki_update permission' do
before :once do
teacher_role = Role.get_built_in_role('TeacherEnrollment')
RoleOverride.create!(
permission: 'manage_wiki_update',
enabled: false,
role: teacher_role,
account: @course.root_account
)
end
it 'is published by default when created' do
create_wiki_page(@teacher, {title: 'New Page'})
expect(WikiPage.last.workflow_state).to eq 'active'
end
it 'cannot be explictly unpublished when created' do
create_wiki_page(@teacher, {title: 'New Page', published: false}, 401)
expect(WikiPage.last).to be_nil
end
it 'does not allow the "editing_roles" field to be set' do
create_wiki_page(@teacher, {title: 'New Page', editing_roles: 'public'}, 401)
expect(WikiPage.last).to be_nil
end
end
end
context 'with the user not having manage_wiki_create permission' do
it 'fails if the course does not grant create wiki page permission' do
create_wiki_page(@student, {title: 'New Page'}, 401)
expect(WikiPage.last).to be_nil
end
it 'succeeds if the course grants create wiki page permission' do
@course.update!({default_wiki_editing_roles: 'teachers,students'})
create_wiki_page(@student, {title: 'New Page', body: 'banana'})
expect(WikiPage.last.title).to eq 'New Page'
expect(WikiPage.last.body).to eq 'banana'
end
it 'does not allow the "who can edit" field to be set' do
@course.update!({default_wiki_editing_roles: 'teachers,students'})
create_wiki_page(@student, {title: 'New Page', editing_roles: 'public'}, 401)
expect(WikiPage.last).to be_nil
end
it 'is published automatically when created' do
@course.update!({default_wiki_editing_roles: 'teachers,students'})
create_wiki_page(@student, {title: 'New Page'})
expect(WikiPage.last.workflow_state).to eq 'active'
end
it 'cannot be set as unpublished when created' do
@course.update!({default_wiki_editing_roles: 'teachers,students'})
create_wiki_page(@student, {title: 'New Page', published: false}, 401)
expect(WikiPage.last).to be_nil
end
end
end
end
end
describe 'DELETE' do
before :once do
course_with_teacher(active_all: true)
student_in_course(active_all: true)
wiki_page_model({ title: "Wiki Page" })
end
def delete_wiki_page(user, expected_status = 200)
url = "/api/v1/courses/#{@course.id}/pages/#{@page.url}"
path = {
controller: 'wiki_pages_api',
action: 'destroy',
format: 'json',
course_id: @course.id.to_s,
url: @page.url,
}
api_call_as_user(user, :delete, url, path, {}, {}, {expected_status: expected_status})
end
context 'with granular permission disabled' do
before :once do
@course.root_account.disable_feature!(:granular_permissions_wiki_pages)
end
it 'allows you to destroy a wiki page if you have the manage_wiki permission' do
delete_wiki_page(@teacher)
expect(@page.reload.workflow_state).to eq 'deleted'
end
it 'does not allow you to destroy a wiki page if you do not have the manage_wiki permission' do
delete_wiki_page(@student, 401)
expect(@page.reload.workflow_state).to eq 'active'
end
end
context 'with granular permission disabled' do
before :once do
@course.root_account.enable_feature!(:granular_permissions_wiki_pages)
end
it 'allows you to destroy a wiki page if you have the manage_wiki_delete permission' do
delete_wiki_page(@teacher)
expect(@page.reload.workflow_state).to eq 'deleted'
end
it 'does not allow you to destroy a wiki page if you do not have the manage_wiki_delete permission' do
teacher_role = Role.get_built_in_role('TeacherEnrollment')
RoleOverride.create!(
permission: 'manage_wiki_delete',
enabled: false,
role: teacher_role,
account: @course.root_account
)
delete_wiki_page(@teacher, 401)
expect(@page.reload.workflow_state).to eq 'active'
end
end
end
describe 'GET' do
before :once do
course_with_teacher(active_all: true)
student_in_course(active_all: true)
wiki_page_model({ title: "Wiki Page" })
end
def get_wiki_page(user, expected_status = 200)
url = "/api/v1/courses/#{@course.id}/pages/#{@page.url}"
path = {
controller: 'wiki_pages_api',
action: 'show',
format: 'json',
course_id: @course.id.to_s,
url: @page.url,
}
api_call_as_user(user, :get, url, path, {}, {}, {expected_status: expected_status})
end
it 'works for teachers' do
json = get_wiki_page(@teacher)
expect(json['url']).to eq @page.url
end
it 'works for students' do
json = get_wiki_page(@student)
expect(json['url']).to eq @page.url
end
it 'fails for a student if the wiki page is unpublished' do
@page.update!(workflow_state: 'unpublished')
json = get_wiki_page(@student, 401)
expect(json['url']).to be_nil
end
it 'fails if you do not have read permissions' do
user = User.create!
json = get_wiki_page(user, 401)
expect(json['url']).to be_nil
end
end
describe "POST 'duplicate'" do
before :once do
course_with_teacher(active_all: true)

View File

@ -67,7 +67,7 @@ test('only shows direct share menu items if enabled', () => {
const view = new WikiPageIndexItemView({
model: new WikiPage(),
collectionHasTodoDate: () => {},
WIKI_RIGHTS: {read: true, manage: true},
WIKI_RIGHTS: {read: true, manage: true, update: true},
CAN: {MANAGE: true}
})
view.render()
@ -101,7 +101,9 @@ testRights('CAN (manage course)', {
contextName: 'courses',
WIKI_RIGHTS: {
read: true,
manage: true
manage: true,
publish_page: true,
create_page: true
},
CAN: {
MANAGE: true,
@ -114,7 +116,9 @@ testRights('CAN (manage group)', {
contextName: 'groups',
WIKI_RIGHTS: {
read: true,
manage: true
manage: true,
publish_page: false,
create_page: true
},
CAN: {
MANAGE: true,
@ -138,3 +142,53 @@ testRights('CAN (null)', {
PUBLISH: false
}
})
// Tests for granular permissions, with manage permission removed
testRights('CAN (create page - course)', {
contextName: 'courses',
WIKI_RIGHTS: {create_page: true},
CAN: {
MANAGE: true,
PUBLISH: false,
DUPLICATE: true,
UPDATE: false,
DELETE: false
}
})
testRights('CAN (create page - group)', {
contextName: 'groups',
WIKI_RIGHTS: {create_page: true},
CAN: {
MANAGE: true,
PUBLISH: false,
DUPLICATE: false,
UPDATE: false,
DELETE: false
}
})
testRights('CAN (delete page)', {
contextName: 'courses',
WIKI_RIGHTS: {delete_page: true},
CAN: {
MANAGE: true,
PUBLISH: false,
DUPLICATE: false,
UPDATE: false,
DELETE: true
}
})
testRights('CAN (update page)', {
contextName: 'courses',
WIKI_RIGHTS: {update: true, publish_page: true},
CAN: {
MANAGE: true,
PUBLISH: true,
DUPLICATE: false,
UPDATE: true,
DELETE: false
}
})

View File

@ -221,6 +221,8 @@ testRights('CAN (manage course)', {
WIKI_RIGHTS: {
read: true,
create_page: true,
publish_page: true,
update: true,
manage: true
},
CAN: {
@ -235,6 +237,7 @@ testRights('CAN (manage group)', {
WIKI_RIGHTS: {
read: true,
create_page: true,
update: true,
manage: true
},
CAN: {
@ -261,3 +264,25 @@ testRights('CAN (null)', {
PUBLISH: false
}
})
// Granular permissions tests
testRights('CAN (read)', {
contextAssetString: 'course_73',
WIKI_RIGHTS: {update: true},
CAN: {
CREATE: false,
MANAGE: true,
PUBLISH: false
}
})
testRights('CAN (read)', {
contextAssetString: 'course_73',
WIKI_RIGHTS: {delete_page: true},
CAN: {
CREATE: false,
MANAGE: true,
PUBLISH: false
}
})

View File

@ -217,6 +217,7 @@ testRights('CAN (manage)', {
contextAssetString: 'course_73',
WIKI_RIGHTS: {
read: true,
publish_page: true,
manage: true
},
PAGE_RIGHTS: {
@ -288,6 +289,7 @@ testRights('CAN (manage, course home page)', {
course_home: true,
WIKI_RIGHTS: {
read: true,
publish_page: true,
manage: true
},
PAGE_RIGHTS: {

View File

@ -107,6 +107,43 @@ describe WikiPagesController do
end
end
describe ':granular_permissions_wiki_pages in js_env' do
before do
course_with_teacher(active_all: true)
wiki_page_model({ title: 'Wiki Page' })
user_session @teacher
end
it 'should be true if the feature flag is turned on' do
@course.root_account.enable_feature! :granular_permissions_wiki_pages
get 'show', params: {course_id: @course.id, id: @page.url}
expect(response).to be_successful
expect(controller.js_env[:GRANULAR_PERMISSIONS_WIKI_PAGES]).to be_truthy
end
it 'should be false if the feature flag is turned on' do
@course.root_account.disable_feature! :granular_permissions_wiki_pages
get 'show', params: {course_id: @course.id, id: @page.url}
expect(response).to be_successful
expect(controller.js_env[:GRANULAR_PERMISSIONS_WIKI_PAGES]).to be_falsey
end
it 'should be accurate for sub accounts' do
root_account = Account.create!
sub_account = root_account.sub_accounts.create!
root_account.enable_feature!(:granular_permissions_wiki_pages)
course = sub_account.courses.create!
course_with_teacher(course: course, active_all: true)
page = @course.wiki_pages.create!(title: "immersive reader", body: "")
user_session @teacher
get 'show', params: {course_id: @course.id, id: page.url}
expect(response).to be_successful
expect(controller.js_env[:GRANULAR_PERMISSIONS_WIKI_PAGES]).to be_truthy
end
end
describe "immersive reader" do
context 'in a sub account' do
before do

View File

@ -151,10 +151,6 @@ describe Wiki do
it 'should give update_page rights to students' do
expect(@course.wiki.grants_right?(@user, :update_page)).to be_truthy
end
it 'should give update_page_content rights to students' do
expect(@course.wiki.grants_right?(@user, :update_page_content)).to be_truthy
end
end
end

View File

@ -20,6 +20,10 @@ require_relative 'helpers/wiki_and_tiny_common'
require_relative 'helpers/public_courses_context'
require_relative 'helpers/files_common'
# We have the funky indenting here because we will remove this once the granular
# permission stuff is released, and I don't want to complicate the git history
# for this file
RSpec.shared_examples "wiki_pages" do
describe "Wiki Pages" do
include_context "in-process server selenium tests"
include FilesCommon
@ -35,6 +39,7 @@ describe "Wiki Pages" do
before do
account_model
course_with_teacher_logged_in :account => @account
set_granular_permission
end
it "should navigate to pages tab with no front page set", priority: "1", test_id: 126843 do
@ -128,6 +133,7 @@ describe "Wiki Pages" do
before do
account_model
course_with_teacher_logged_in
set_granular_permission
end
it "should edit page title from pages index", priority: "1", test_id: 126849 do
@ -154,6 +160,28 @@ describe "Wiki Pages" do
end
end
context "Index Page as a student" do
before do
course_with_student_logged_in
set_granular_permission
end
it "should display a warning alert to a student when accessing a deleted page", priority: "1", test_id: 126839 do
page = @course.wiki_pages.create!(title: 'delete_deux')
# sets the workflow_state = deleted to act as a deleted page
page.workflow_state = 'deleted'
page.save!
get "/courses/#{@course.id}/pages/delete_deux"
expect_flash_message :warning
end
it "should display a warning alert when accessing a non-existant page", priority: "1", test_id: 126841 do
skip('LA-373')
get "/courses/#{@course.id}/pages/non-existant"
expect_flash_message :warning
end
end
context "Accessibility" do
def check_header_focus(attribute)
f("[data-sort-field='#{attribute}']").click
@ -171,6 +199,7 @@ describe "Wiki Pages" do
before :each do
user_session(@user)
set_granular_permission
end
it "returns focus to the header item clicked while sorting" do
@ -442,9 +471,13 @@ describe "Wiki Pages" do
end
context "Insert RCE File" do
before do
course_with_teacher(user: @teacher, active_course: true, active_enrollment: true)
set_granular_permission
end
it "should insert a file using RCE in the wiki page", priority: "1", test_id: 126673 do
stub_rcs_config
course_with_teacher(user: @teacher, active_course: true, active_enrollment: true)
@course.wiki_pages.create!(:title => "Bar")
user_session(@user)
file = @course.attachments.create!(display_name: 'some test file', uploaded_data: default_uploaded_data)
@ -459,6 +492,7 @@ describe "Wiki Pages" do
before do
account_model
course_with_student_logged_in account: @account
set_granular_permission
end
it "should lock page based on module date", priority: "1", test_id: 126845 do
@ -507,6 +541,7 @@ describe "Wiki Pages" do
context "Permissions" do
before do
course_with_teacher
set_granular_permission
end
it "displays public content to unregistered users", priority: "1", test_id: 270035 do
@ -527,6 +562,7 @@ describe "Wiki Pages" do
context "menu tools" do
before do
course_with_teacher_logged_in
set_granular_permission
@tool = Account.default.context_external_tools.new(:name => "a", :domain => "google.com", :consumer_key => '12345', :shared_secret => 'secret')
@tool.wiki_page_menu = {:url => "http://www.example.com", :text => "Export Wiki Page"}
@tool.save!
@ -565,6 +601,8 @@ describe "Wiki Pages" do
include_context "public course as a logged out user"
it "should display wiki content", priority: "1", test_id: 270035 do
@coures = public_course
set_granular_permission
title = "foo"
public_course.wiki_pages.create!(:title => title, :body => "bar")
@ -576,6 +614,7 @@ describe "Wiki Pages" do
context "embed video in a Page" do
before :each do
course_with_teacher_logged_in :account => @account, :active_all => true
set_granular_permission
@course.wiki_pages.create!(title: 'Page1')
end
@ -602,4 +641,46 @@ describe "Wiki Pages" do
expect(f("iframe")).to be_present
end
end
context "MathML" do
include_context "public course as a logged out user"
it "should load mathjax in a page with <math>" do
skip('Unskip in ADMIN-2684')
title = "mathML"
@course = public_course
set_granular_permission
public_course.wiki_pages.create!(
:title => title,
:body => "<math><mi>&#x3C0;</mi> <msup> <mi>r</mi> <mn>2</mn> </msup></math>"
)
get "/courses/#{public_course.id}/wiki/#{title}"
is_mathjax_loaded = driver.execute_script("return (typeof MathJax == 'object')")
expect(is_mathjax_loaded).to match(true)
end
it "should not load mathjax without <math>" do
title = "not_mathML"
@course = public_course
set_granular_permission
public_course.wiki_pages.create!(:title => title, :body => "not mathML")
get "/courses/#{public_course.id}/wiki/#{title}"
is_mathjax_loaded = driver.execute_script("return (typeof MathJax == 'object')")
expect(is_mathjax_loaded).not_to match(true)
end
end
end
end # End shared_example block
RSpec.describe 'With granular permission on' do
it_behaves_like "wiki_pages" do
let(:set_granular_permission) { @course.root_account.enable_feature!(:granular_permissions_wiki_pages) }
end
end
RSpec.describe 'With granular permission off' do
it_behaves_like "wiki_pages" do
let(:set_granular_permission) { @course.root_account.disable_feature!(:granular_permissions_wiki_pages) }
end
end