canvas-lms/spec/integration/security_spec.rb

1093 lines
40 KiB
Ruby

#
# Copyright (C) 2011 - 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/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../sharding_spec_helper')
require 'nokogiri'
describe "security" do
describe "session fixation" do
it "should change the cookie session id after logging in" do
u = user_with_pseudonym :active_user => true,
:username => "nobody@example.com",
:password => "asdfasdf"
u.save!
https!
get "/login"
follow_redirect! while response.redirect?
assert_response :success
cookie = cookies['_normandy_session']
expect(cookie).to be_present
expect(path).to eq "/login/canvas"
post "/login/canvas", params: {"pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf",
"pseudonym_session[remember_me]" => "1",
"redirect_to_ssl" => "1"}
follow_redirect! while response.redirect?
assert_response :success
expect(request.fullpath).to eql("/?login_success=1")
new_cookie = cookies['_normandy_session']
expect(new_cookie).to be_present
expect(cookie).not_to eql(new_cookie)
end
end
describe 'session cookies' do
it "should always set the primary cookie to session expiration" do
# whether they select "stay logged in" or not, the actual session cookie
# should go away with the user agent session. the secondary
# pseudonym_credentials cookie will stick around and authenticate them
# again (there's separate specs for that).
u = user_with_pseudonym :active_user => true,
:username => "nobody@example.com",
:password => "asdfasdf"
u.save!
https!
post "/login/canvas", params: {"pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf"}
assert_response 302
c = response['Set-Cookie'].lines.grep(/\A_normandy_session=/).first
expect(c).not_to match(/expires=/)
reset!
https!
post "/login/canvas", params: {"pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf",
"pseudonym_session[remember_me]" => "1"}
assert_response 302
c = response['Set-Cookie'].lines.grep(/\A_normandy_session=/).first
expect(c).not_to match(/expires=/)
end
it "should not return pseudonym_credentials when not remember_me" do
u = user_with_pseudonym :active_user => true,
:username => "nobody@example.com",
:password => "asdfasdf"
u.save!
https!
post "/login/canvas", params: {"pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf"}
assert_response 302
c1 = response['Set-Cookie'].lines.grep(/\Apseudonym_credentials=/).first
c2 = response['Set-Cookie'].lines.grep(/\A_normandy_session=/).first
expect(c1).not_to be_present
expect(c2).to be_present
end
end
it "should not prepend login json responses with protection" do
u = user_with_pseudonym :active_user => true,
:username => "nobody@example.com",
:password => "asdfasdf"
u.save!
post "/login/canvas", params: { "pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf",
"pseudonym_session[remember_me]" => "1" },
headers: { 'HTTP_ACCEPT' => 'application/json' }
expect(response).to be_successful
expect(response['Content-Type']).to match(%r"^application/json")
expect(response.body).not_to match(%r{^while\(1\);})
json = JSON.parse response.body
expect(json['pseudonym']['unique_id']).to eq "nobody@example.com"
end
it "should prepend GET JSON responses with protection" do
course_with_teacher_logged_in
get "/courses.json"
expect(response).to be_successful
expect(response['Content-Type']).to match(%r"^application/json")
expect(response.body).to match(%r{^while\(1\);})
end
it "should not prepend GET JSON responses to Accept application/json requests with protection" do
course_with_teacher_logged_in
get "/courses.json", headers: { 'HTTP_ACCEPT' => 'application/json' }
expect(response).to be_successful
expect(response['Content-Type']).to match(%r"^application/json")
expect(response.body).not_to match(%r{^while\(1\);})
end
it "should not prepend non-GET JSON responses with protection" do
course_with_teacher_logged_in
delete "/dashboard/ignore_stream_item/1"
expect(response).to be_successful
expect(response['Content-Type']).to match(%r"^application/json")
expect(response.body).not_to match(%r{^while\(1\);})
end
describe "remember me" do
before do
@u = user_with_pseudonym :active_all => true,
:username => "nobody@example.com",
:password => "asdfasdf"
@u.save!
@p = @u.pseudonym
https!
end
it "should not remember me when the wrong token is given" do
# plain persistence_token no longer works
get "/", headers: {"HTTP_COOKIE" => "pseudonym_credentials=#{@p.persistence_token}"}
expect(response).to redirect_to("https://www.example.com/login")
token = SessionPersistenceToken.generate(@p)
# correct token id, but nonsense uuid and persistence_token
get "/", headers: {"HTTP_COOKIE" => "pseudonym_credentials=#{token.id}::blah::blah"}
expect(response).to redirect_to("https://www.example.com/login")
# correct token id and persistence_token, but nonsense uuid
get "/", headers: {"HTTP_COOKIE" => "pseudonym_credentials=#{token.id}::#{@p.persistence_token}::blah"}
expect(response).to redirect_to("https://www.example.com/login")
end
it "should login via persistence token when no session exists" do
token = SessionPersistenceToken.generate(@p)
get "/", headers: {"HTTP_COOKIE" => "pseudonym_credentials=#{token.pseudonym_credentials}"}
expect(response).to be_successful
expect(cookies['_normandy_session']).to be_present
expect(session[:used_remember_me_token]).to be_truthy
# accessing sensitive areas of canvas require a fresh login
get "/profile/settings"
expect(response).to redirect_to login_url
expect(flash[:warning]).not_to be_empty
post "/login/canvas", params: {:pseudonym_session => { :unique_id => @p.unique_id, :password => 'asdfasdf' }}
expect(response).to redirect_to settings_profile_url
expect(session[:used_remember_me_token]).not_to be_truthy
follow_redirect!
expect(response).to be_successful
end
it "should not allow login via the same valid token twice" do
token = SessionPersistenceToken.generate(@p)
get "/", headers: {"HTTP_COOKIE" => "pseudonym_credentials=#{token.pseudonym_credentials}"}
expect(response).to be_successful
expect(SessionPersistenceToken.find_by_id(token.id)).to be_nil
reset!
https!
get "/", headers: {"HTTP_COOKIE" => "pseudonym_credentials=#{token.pseudonym_credentials}"}
expect(response).to redirect_to("https://www.example.com/login")
end
it "should generate a new valid token when a token is used" do
token = SessionPersistenceToken.generate(@p)
get "/", headers: {"HTTP_COOKIE" => "pseudonym_credentials=#{token.pseudonym_credentials}"}
expect(response).to be_successful
s1 = cookies['_normandy_session']
expect(s1).to be_present
cookie = cookies['pseudonym_credentials']
expect(cookie).to be_present
token2 = SessionPersistenceToken.find_by_pseudonym_credentials(CGI.unescape(cookie))
expect(token2).to be_present
expect(token2).not_to eq token
expect(token2.pseudonym).to eq @p
reset!
https!
# check that the new token is valid too
get "/", headers: {"HTTP_COOKIE" => "pseudonym_credentials=#{cookie}"}
expect(response).to be_successful
s2 = cookies['_normandy_session']
expect(s2).to be_present
expect(s2).not_to eq s1
end
it "should generate and return a token when remember_me is checked" do
post "/login/canvas", params: {"pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf",
"pseudonym_session[remember_me]" => "1"}
assert_response 302
cookie = cookies['pseudonym_credentials']
expect(cookie).to be_present
token = SessionPersistenceToken.find_by_pseudonym_credentials(CGI.unescape(cookie))
expect(token).to be_present
expect(token.pseudonym).to eq @p
# verify that the session is now persisting via the session cookie, not
# using and re-generating a one-time-use pseudonym_credentials token on each request
get "/"
expect(cookies['pseudonym_credentials']).to eq cookie
end
it "should destroy the token both user agent and server side on logout" do
expect {
post "/login/canvas", params: {"pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf",
"pseudonym_session[remember_me]" => "1"}
}.to change(SessionPersistenceToken, :count).by(1)
c = cookies['pseudonym_credentials']
expect(c).to be_present
expect {
delete "/logout"
}.to change(SessionPersistenceToken, :count).by(-1)
expect(cookies['pseudonym_credentials']).not_to be_present
expect(SessionPersistenceToken.find_by_pseudonym_credentials(CGI.unescape(c))).to be_nil
end
it "should allow multiple remember_me tokens for the same user" do
s1 = open_session
s1.https!
s2 = open_session
s2.https!
s1.post "/login/canvas", params: {"pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf",
"pseudonym_session[remember_me]" => "1"}
c1 = s1.cookies['pseudonym_credentials']
s2.post "/login/canvas", params: {"pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf",
"pseudonym_session[remember_me]" => "1"}
c2 = s2.cookies['pseudonym_credentials']
expect(c1).not_to eq c2
s3 = open_session
s3.https!
s3.get "/", headers: {"HTTP_COOKIE" => "pseudonym_credentials=#{c1}"}
expect(s3.response).to be_successful
s3.delete "/logout"
# make sure c2 can still work
s4 = open_session
s4.https!
s4.get "/", headers: {"HTTP_COOKIE" => "pseudonym_credentials=#{c2}"}
expect(s4.response).to be_successful
end
it "should not login if the pseudonym is deleted" do
token = SessionPersistenceToken.generate(@p)
@p.destroy
get "/", headers: {"HTTP_COOKIE" => "pseudonym_credentials=#{token.pseudonym_credentials}"}
expect(response).to redirect_to("https://www.example.com/login")
end
it "should not login if the pseudonym.persistence_token gets changed (pw change)" do
token = SessionPersistenceToken.generate(@p)
creds = token.pseudonym_credentials
pers1 = @p.persistence_token
@p.password = @p.password_confirmation = 'newpass1'
@p.save!
pers2 = @p.persistence_token
expect(pers1).not_to eq pers2
get "/", headers: {"HTTP_COOKIE" => "pseudonym_credentials=#{creds}"}
expect(response).to redirect_to("https://www.example.com/login")
end
context "sharding" do
specs_require_sharding
it "should work for an out-of-shard user" do
@shard1.activate do
account = Account.create!
user_with_pseudonym(:account => account)
end
token = SessionPersistenceToken.generate(@pseudonym)
get "/", headers: {"HTTP_COOKIE" => "pseudonym_credentials=#{token.pseudonym_credentials}"}
expect(response).to be_successful
expect(cookies['_normandy_session']).to be_present
expect(session[:used_remember_me_token]).to be_truthy
end
end
end
if Canvas.redis_enabled?
describe "max login attempts" do
before do
Setting.set('login_attempts_total', '2')
Setting.set('login_attempts_per_ip', '1')
u = user_with_pseudonym :active_user => true,
:username => "nobody@example.com",
:password => "asdfasdf"
u.save!
end
def bad_login(ip)
post "/login/canvas",
params: { "pseudonym_session[unique_id]" => "nobody@example.com", "pseudonym_session[password]" => "failboat" },
headers: { "REMOTE_ADDR" => ip }
follow_redirect! while response.redirect?
end
it "should be limited for the same ip" do
bad_login("5.5.5.5")
expect(response.body).to match(/Invalid username/)
bad_login("5.5.5.5")
expect(response.body).to match(/Too many failed login attempts/)
# should still fail
post "/login/canvas",
params: { "pseudonym_session[unique_id]" => "nobody@example.com", "pseudonym_session[password]" => "asdfasdf" },
headers: { "REMOTE_ADDR" => "5.5.5.5" }
follow_redirect! while response.redirect?
expect(response.body).to match(/Too many failed login attempts/)
end
it "should have a higher limit for other ips" do
bad_login("5.5.5.5")
expect(response.body).to match(/Invalid username/)
bad_login("5.5.5.6") # different IP, so allowed
expect(response.body).to match(/Invalid username/)
bad_login("5.5.5.7") # different IP, but too many total failures
expect(response.body).to match(/Too many failed login attempts/)
# should still fail
post "/login/canvas",
params: { "pseudonym_session[unique_id]" => "nobody@example.com", "pseudonym_session[password]" => "asdfasdf" },
headers: { "REMOTE_ADDR" => "5.5.5.7" }
follow_redirect! while response.redirect?
expect(response.body).to match(/Too many failed login attempts/)
end
it "should not block other users with the same ip" do
bad_login("5.5.5.5")
expect(response.body).to match(/Invalid username/)
# schools like to NAT hundreds of people to the same IP, so we don't
# ever block the IP address as a whole
user_with_pseudonym(:active_user => true, :username => "second@example.com", :password => "12341234").save!
post "/login/canvas",
params: { "pseudonym_session[unique_id]" => "second@example.com", "pseudonym_session[password]" => "12341234" },
headers: { "REMOTE_ADDR" => "5.5.5.5" }
follow_redirect! while response.redirect?
expect(request.fullpath).to eql("/?login_success=1")
end
it "should apply limitations correctly for cross-account logins" do
account = Account.create!
allow_any_instantiation_of(Account.default).to receive(:trusted_account_ids).and_return([account.id])
@pseudonym.account = account
@pseudonym.save!
bad_login("5.5.5.5")
expect(response.body).to match(/Invalid username/)
bad_login("5.5.5.6") # different IP, so allowed
expect(response.body).to match(/Invalid username/)
bad_login("5.5.5.7") # different IP, but too many total failures
expect(response.body).to match(/Too many failed login attempts/)
# should still fail
post "/login/canvas",
params: { "pseudonym_session[unique_id]" => "nobody@example.com", "pseudonym_session[password]" => "asdfasdf" },
headers: { "REMOTE_ADDR" => "5.5.5.5" }
follow_redirect! while response.redirect?
expect(response.body).to match(/Too many failed login attempts/)
end
end
end
it "should only allow user list username resolution if the current user has appropriate rights" do
u = User.create!(:name => 'test user')
u.pseudonyms.create!(:unique_id => "A1234567", :account => Account.default)
@course = Account.default.courses.create!
@course.offer!
@teacher = user_factory :active_all => true
@course.enroll_teacher(@teacher).tap do |e|
e.workflow_state = 'active'
e.save!
end
@student = user_factory :active_all => true
@course.enroll_student(@student).tap do |e|
e.workflow_state = 'active'
e.save!
end
@course.reload
user_session(@student)
post "/courses/#{@course.id}/user_lists.json", params: {:user_list => "A1234567, A345678"}
expect(response).not_to be_success
user_session(@teacher)
post "/courses/#{@course.id}/user_lists.json", params: {:user_list => "A1234567, A345678"}
assert_response :success
expect(json_parse).to eq({
"duplicates" => [],
"errored_users" => [{"address" => "A345678", "details" => "not_found", "type" => "pseudonym"}],
"users" => [{ "address" => "A1234567", "name" => "test user", "type" => "pseudonym", "user_id" => u.id }]
})
end
describe "user masquerading" do
before(:each) do
course_with_teacher
@teacher = @user
student_in_course
@student = @user
user_with_pseudonym :user => @student, :username => 'student@example.com', :password => 'password'
@student_pseudonym = @pseudonym
account_admin_user :account => Account.site_admin
@admin = @user
user_with_pseudonym :user => @admin, :username => 'admin@example.com', :password => 'password'
end
it "should require confirmation for becoming a user" do
user_session(@admin, @admin.pseudonyms.first)
get "/?become_user_id=#{@student.id}"
assert_response 302
expect(response.location).to match "/users/#{@student.id}/masquerade$"
expect(session[:masquerade_return_to]).to eq "/"
expect(session[:become_user_id]).to be_nil
expect(assigns['current_user'].id).to eq @admin.id
expect(assigns['real_current_user']).to be_nil
follow_redirect!
assert_response 200
expect(path).to eq "/users/#{@student.id}/masquerade"
expect(session[:become_user_id]).to be_nil
expect(assigns['current_user'].id).to eq @admin.id
expect(assigns['real_current_user']).to be_nil
post "/users/#{@student.id}/masquerade"
assert_response 302
expect(session[:become_user_id]).to eq @student.id.to_s
get "/"
assert_response 200
expect(session[:become_user_id]).to eq @student.id.to_s
expect(assigns['current_user'].id).to eq @student.id
expect(assigns['current_pseudonym']).to eq @student_pseudonym
expect(assigns['real_current_user'].id).to eq @admin.id
end
it "should not allow as_user_id for normal requests" do
user_session(@admin, @admin.pseudonyms.first)
get "/?as_user_id=#{@student.id}"
assert_response 200
expect(session[:become_user_id]).to be_nil
expect(assigns['current_user'].id).to eq @admin.id
expect(assigns['real_current_user']).to be_nil
end
it "should not allow non-admins to become other people" do
user_session(@student, @student.pseudonyms.first)
get "/?become_user_id=#{@teacher.id}"
assert_response 200
expect(session[:become_user_id]).to be_nil
expect(assigns['current_user'].id).to eq @student.id
expect(assigns['real_current_user']).to be_nil
post "/users/#{@teacher.id}/masquerade"
assert_response 401
expect(assigns['current_user'].id).to eq @student.id
expect(session[:become_user_id]).to be_nil
end
it "should record real user in page_views" do
Setting.set('enable_page_views', 'db')
user_session(@admin, @admin.pseudonyms.first)
get "/?become_user_id=#{@student.id}"
assert_response 302
expect(response.location).to match "/users/#{@student.id}/masquerade$"
expect(session[:masquerade_return_to]).to eq "/"
expect(session[:become_user_id]).to be_nil
expect(assigns['current_user'].id).to eq @admin.id
expect(assigns['real_current_user']).to be_nil
follow_redirect!
assert_response 200
expect(path).to eq "/users/#{@student.id}/masquerade"
expect(session[:become_user_id]).to be_nil
expect(assigns['current_user'].id).to eq @admin.id
expect(assigns['real_current_user']).to be_nil
pv1 = PageView.last
expect(pv1.user_id).to eq @admin.id
expect(pv1.real_user_id).to be_nil
post "/users/#{@student.id}/masquerade"
assert_response 302
expect(session[:become_user_id]).to eq @student.id.to_s
get "/"
assert_response 200
expect(session[:become_user_id]).to eq @student.id.to_s
expect(assigns['current_user'].id).to eq @student.id
expect(assigns['real_current_user'].id).to eq @admin.id
pv2 = PageView.all.detect{|pv| pv != pv1}
expect(pv2.user_id).to eq @student.id
expect(pv2.real_user_id).to eq @admin.id
end
it "should remember the destination with an intervening auth" do
token = SessionPersistenceToken.generate(@admin.pseudonyms.first)
get "/", headers: {"HTTP_COOKIE" => "pseudonym_credentials=#{token.pseudonym_credentials}"}
expect(response).to be_successful
expect(cookies['_normandy_session']).to be_present
expect(session[:used_remember_me_token]).to be_truthy
# accessing sensitive areas of canvas require a fresh login
get "/conversations?become_user_id=#{@student.id}"
expect(response).to redirect_to user_masquerade_url(@student)
follow_redirect!
expect(response).to redirect_to login_url
expect(flash[:warning]).not_to be_empty
post "/login/canvas", params: {:pseudonym_session => { :unique_id => @admin.pseudonyms.first.unique_id, :password => 'password' }}
expect(response).to redirect_to user_masquerade_url(@student)
expect(session[:used_remember_me_token]).not_to be_truthy
post "/users/#{@student.id}/masquerade"
expect(response).to redirect_to conversations_url
follow_redirect!
expect(response).to be_successful
expect(session[:become_user_id]).to eq @student.id.to_s
end
end
it "should not allow logins to safefiles domains" do
allow(HostUrl).to receive(:is_file_host?).and_return(true)
allow(HostUrl).to receive(:default_host).and_return('test.host')
get "http://files-test.host/login"
expect(response).to be_redirect
uri = URI.parse response['Location']
expect(uri.host).to eq 'test.host'
allow(HostUrl).to receive(:is_file_host?).and_return(false)
get "http://test.host/login"
expect(response).to redirect_to('http://test.host/login/canvas')
end
describe "admin permissions" do
before(:each) do
@role = custom_account_role('Limited Admin', :account => Account.site_admin)
account_admin_user(:account => Account.site_admin, :role => @role)
user_session(@admin)
end
def add_permission(permission)
Account.site_admin.role_overrides.create!(:permission => permission.to_s,
:role => @role,
:enabled => true)
end
def remove_permission(permission, role)
Account.default.role_overrides.create!(:permission => permission.to_s,
:role => role,
:enabled => false)
end
describe "site admin" do
it "role_overrides" do
get "/accounts/#{Account.site_admin.id}/settings"
expect(response).to be_successful
expect(response.body).not_to match /Permissions/
get "/accounts/#{Account.site_admin.id}/role_overrides"
assert_status(401)
add_permission :manage_role_overrides
get "/accounts/#{Account.site_admin.id}/role_overrides"
expect(response).to be_successful
get "/accounts/#{Account.site_admin.id}/settings"
expect(response).to be_successful
expect(response.body).to match /Permissions/
end
end
describe 'root account' do
it "read_roster" do
add_permission :view_statistics
get "/accounts/#{Account.default.id}/users"
assert_status(401)
get "/accounts/#{Account.default.id}/settings"
expect(response).to be_successful
get "/accounts/#{Account.default.id}/statistics"
expect(response).to be_successful
expect(response.body).not_to match /Recently Logged-In Users/
add_permission :read_roster
get "/accounts/#{Account.default.id}/users"
expect(response).to be_successful
get "/accounts/#{Account.default.id}/settings"
expect(response).to be_successful
get "/accounts/#{Account.default.id}/statistics"
expect(response).to be_successful
expect(response.body).to match /Recently Logged-In Users/
end
it "read_course_list" do
add_permission :view_statistics
course_factory
get "/accounts/#{Account.default.id}/settings"
expect(response).to be_successful
get "/accounts/#{Account.default.id}/statistics"
expect(response).to be_successful
expect(response.body).not_to match /Recently Started Courses/
expect(response.body).not_to match /Recently Ended Courses/
add_permission :read_course_list
get "/accounts/#{Account.default.id}"
expect(response).to be_successful
expect(response.body).to match /Courses/
get "/accounts/#{Account.default.id}/statistics"
expect(response).to be_successful
expect(response.body).to match /Recently Started Courses/
expect(response.body).to match /Recently Ended Courses/
end
it "view_statistics" do
get "/accounts/#{Account.default.id}/statistics"
assert_status(401)
get "/accounts/#{Account.default.id}/settings"
expect(response).to be_successful
expect(response.body).not_to match /Statistics/
add_permission :view_statistics
get "/accounts/#{Account.default.id}/statistics"
expect(response).to be_successful
get "/accounts/#{Account.default.id}/settings"
expect(response).to be_successful
expect(response.body).to match /Statistics/
end
it "manage_user_notes" do
Account.default.update_attribute(:enable_user_notes, true)
course_with_teacher
student_in_course
@student.update_account_associations
@user_note = UserNote.create!(:creator => @teacher, :user => @student)
get "/accounts/#{Account.default.id}/user_notes"
assert_status(401)
get "/accounts/#{Account.default.id}/settings"
expect(response).to be_successful
expect(response.body).not_to match /Faculty Journal/
get "/users/#{@student.id}/user_notes"
assert_status(401)
post "/users/#{@student.id}/user_notes"
assert_status(401)
get "/users/#{@student.id}/user_notes/#{@user_note.id}"
assert_status(401)
delete "/users/#{@student.id}/user_notes/#{@user_note.id}"
assert_status(401)
add_permission :manage_user_notes
get "/accounts/#{Account.default.id}/user_notes"
expect(response).to be_successful
get "/accounts/#{Account.default.id}/settings"
expect(response).to be_successful
expect(response.body).to match /Faculty Journal/
get "/users/#{@student.id}/user_notes"
expect(response).to be_successful
post "/users/#{@student.id}/user_notes.json"
expect(response).to be_successful
get "/users/#{@student.id}/user_notes/#{@user_note.id}.json"
expect(response).to be_successful
delete "/users/#{@student.id}/user_notes/#{@user_note.id}.json"
expect(response).to be_successful
end
it "view_jobs" do
get "/jobs"
expect(response).to be_redirect
add_permission :view_jobs
get "/jobs"
expect(response).to be_successful
end
end
describe 'course' do
before (:each) do
course_factory(active_all: true)
Account.default.update_attribute(:settings, { :no_enrollments_can_create_courses => false })
end
it 'read_as_admin' do
get "/courses/#{@course.id}"
expect(response).to be_redirect
get "/courses/#{@course.id}/details"
expect(response).to be_successful
html = Nokogiri::HTML(response.body)
expect(html.css('.edit_course_link')).to be_empty
expect(html.css('#tab-users')).to be_empty
expect(html.css('#tab-navigation')).to be_empty
@course.enroll_teacher(@admin).accept!
@admin.reload
get "/courses/#{@course.id}"
expect(response).to be_successful
get "/courses/#{@course.id}/details"
expect(response).to be_successful
html = Nokogiri::HTML(response.body)
expect(html.css('#course_form')).not_to be_empty
expect(html.css('#tab-navigation')).not_to be_empty
end
it 'read_roster' do
get "/courses/#{@course.id}/users"
assert_status(401)
get "/courses/#{@course.id}/users/prior"
assert_status(401)
get "/courses/#{@course.id}/groups"
assert_status(401)
get "/courses/#{@course.id}/details"
expect(response).to be_successful
expect(response.body).not_to match /People/
html = Nokogiri::HTML(response.body)
expect(html.css('#tab-users')).to be_empty
add_permission :read_roster
get "/courses/#{@course.id}/users"
expect(response).to be_successful
expect(response.body).to match /View User Groups/
expect(response.body).to match /View Prior Enrollments/
expect(response.body).not_to match /Manage Users/
get "/courses/#{@course.id}/users/prior"
expect(response).to be_successful
get "/courses/#{@course.id}/groups"
expect(response).to be_successful
get "/courses/#{@course.id}/details"
expect(response).to be_successful
expect(response.body).to match /People/
end
it "manage_students" do
get "/courses/#{@course.id}/users"
assert_status(401)
get "/courses/#{@course.id}/users/prior"
assert_status(401)
get "/courses/#{@course.id}/groups"
assert_status(401)
get "/courses/#{@course.id}/details"
expect(response).to be_successful
expect(response.body).not_to match /People/
add_permission :manage_students
get "/courses/#{@course.id}/users"
assert_status(401)
get "/courses/#{@course.id}/groups"
assert_status(401)
add_permission :read_roster
get "/courses/#{@course.id}/users"
expect(response).to be_successful
expect(response.body).to match /View User Groups/
expect(response.body).to match /View Prior Enrollments/
get "/courses/#{@course.id}/users/prior"
expect(response).to be_successful
get "/courses/#{@course.id}/groups"
expect(response).to be_successful
get "/courses/#{@course.id}/details"
expect(response).to be_successful
expect(response.body).to match /People/
@course.tab_configuration = [ { :id => Course::TAB_PEOPLE, :hidden => true } ]
@course.save!
# Should still be able to see People tab even if disabled, because we can
# manage stuff in it
get "/courses/#{@course.id}/details"
expect(response).to be_successful
expect(response.body).to match /People/
end
it 'view_all_grades' do
get "/courses/#{@course.id}/grades"
assert_status(401)
get "/courses/#{@course.id}/gradebook"
assert_status(401)
add_permission :view_all_grades
get "/courses/#{@course.id}/grades"
expect(response).to be_redirect
get "/courses/#{@course.id}/gradebook"
expect(response).to be_successful
end
it 'read_course_content' do
@course.assignments.create!
@course.wiki.set_front_page_url!("front-page")
@course.wiki.front_page.save!
@course.quizzes.create!
@course.attachments.create!(:uploaded_data => default_uploaded_data)
get "/courses/#{@course.id}"
expect(response).to be_redirect
get "/courses/#{@course.id}/assignments"
assert_status(401)
get "/courses/#{@course.id}/assignments/syllabus"
assert_status(401)
get "/courses/#{@course.id}/wiki"
assert_status(401)
get "/courses/#{@course.id}/quizzes"
assert_status(401)
get "/courses/#{@course.id}/discussion_topics"
assert_status(401)
get "/courses/#{@course.id}/files"
assert_status(401)
get "/courses/#{@course.id}/copy"
assert_status(401)
get "/courses/#{@course.id}/content_exports"
assert_status(401)
get "/courses/#{@course.id}/details"
expect(response).to be_successful
html = Nokogiri::HTML(response.body)
expect(html.css('.section .assignments')).to be_empty
expect(html.css('.section .syllabus')).to be_empty
expect(html.css('.section .pages')).to be_empty
expect(html.css('.section .quizzes')).to be_empty
expect(html.css('.section .discussions')).to be_empty
expect(html.css('.section .files')).to be_empty
expect(response.body).not_to match /Copy this Course/
expect(response.body).not_to match /Import Course Content/
expect(response.body).not_to match /Export this Course/
add_permission :read_course_content
add_permission :read_roster
add_permission :read_forum
get "/courses/#{@course.id}"
expect(response).to be_successful
expect(response.body).to match /People/
@course.tab_configuration = [ { :id => Course::TAB_PEOPLE, :hidden => true } ]
@course.save!
get "/courses/#{@course.id}/assignments"
expect(response).to be_successful
expect(response.body).to match /People/ # still has read_as_admin rights
get "/courses/#{@course.id}/assignments/syllabus"
expect(response).to be_successful
get "/courses/#{@course.id}/wiki"
expect(response).to be_successful
get "/courses/#{@course.id}/quizzes"
expect(response).to be_successful
get "/courses/#{@course.id}/discussion_topics"
expect(response).to be_successful
get "/courses/#{@course.id}/files"
expect(response).to be_successful
get "/courses/#{@course.id}/copy"
assert_status(401)
get "/courses/#{@course.id}/content_exports"
expect(response).to be_successful
get "/courses/#{@course.id}/details"
expect(response).to be_successful
html = Nokogiri::HTML(response.body)
expect(html.css('.section .assignments')).not_to be_empty
expect(html.css('.section .syllabus')).not_to be_empty
expect(html.css('.section .pages')).not_to be_empty
expect(html.css('.section .quizzes')).not_to be_empty
expect(html.css('.section .discussions')).not_to be_empty
expect(html.css('.section .files')).not_to be_empty
expect(response.body).not_to match /Copy this Course/
expect(response.body).not_to match /Import Course Content/
expect(response.body).to match /Export Course Content/
expect(response.body).not_to match /Delete this Course/
expect(response.body).not_to match /End this Course/
expect(html.css('input#course_account_id')).to be_empty
expect(html.css('input#course_enrollment_term_id')).to be_empty
delete "/courses/#{@course.id}"
assert_status(401)
delete "/courses/#{@course.id}", params: {:event => 'delete'}
assert_status(401)
add_permission :manage_courses
get "/courses/#{@course.id}/details"
expect(response).to be_successful
expect(response.body).to match /Copy this Course/
expect(response.body).not_to match /Import Course Content/
expect(response.body).to match /Export Course Content/
expect(response.body).to_not match /Delete this Course/
add_permission :change_course_state
get "/courses/#{@course.id}/details"
expect(response).to be_successful
expect(response.body).to match /Delete this Course/
html = Nokogiri::HTML(response.body)
expect(html.css('#course_account_id')).not_to be_empty
expect(html.css('#course_enrollment_term_id')).not_to be_empty
get "/courses/#{@course.id}/copy"
expect(response).to be_successful
delete "/courses/#{@course.id}", params: {:event => 'delete'}
expect(response).to be_redirect
expect(@course.reload).to be_deleted
end
it 'manage_content' do
get "/courses/#{@course.id}/details"
expect(response).to be_successful
expect(response.body).not_to match /Import Course Content/
get "/courses/#{@course.id}/content_migrations"
assert_status(401)
add_permission :manage_content
get "/courses/#{@course.id}/details"
expect(response).to be_successful
expect(response.body).to match /Import Course Content/
get "/courses/#{@course.id}/content_migrations"
expect(response).to be_successful
end
it 'read_reports' do
student_in_course(:active_all => 1)
add_permission :read_roster
get "/courses/#{@course.id}/users/#{@student.id}"
expect(response).to be_successful
expect(response.body).not_to match "Access Report"
get "/courses/#{@course.id}/users/#{@student.id}/usage"
assert_status(401)
add_permission :read_reports
get "/courses/#{@course.id}/users/#{@student.id}"
expect(response).to be_successful
expect(response.body).to match "Access Report"
get "/courses/#{@course.id}/users/#{@student.id}/usage"
expect(response).to be_successful
end
it 'manage_sections' do
course_with_teacher_logged_in(:active_all => 1)
remove_permission(:manage_sections, teacher_role)
get "/courses/#{@course.id}/settings"
expect(response).to be_successful
expect(response.body).not_to match 'Add Section'
post "/courses/#{@course.id}/sections"
assert_status(401)
get "/courses/#{@course.id}/sections/#{@course.default_section.id}"
expect(response).to be_successful
put "/courses/#{@course.id}/sections/#{@course.default_section.id}"
assert_status(401)
end
it 'change_course_state' do
course_with_teacher_logged_in(:active_all => 1)
remove_permission(:change_course_state, teacher_role)
get "/courses/#{@course.id}/settings"
expect(response).to be_successful
expect(response.body).not_to match 'End this Course'
delete "/courses/#{@course.id}", params: {:event => 'conclude'}
assert_status(401)
end
it 'view_statistics' do
course_with_teacher_logged_in(:active_all => 1)
@student = user_factory :active_all => true
@course.enroll_student(@student).tap do |e|
e.workflow_state = 'active'
e.save!
end
get "/courses/#{@course.id}/users/#{@student.id}"
expect(response).to be_successful
get "/users/#{@student.id}"
assert_status(401)
admin = account_admin_user :account => Account.site_admin
user_session(admin)
get "/users/#{@student.id}"
expect(response).to be_successful
end
end
end
end