canvas-lms/spec/integration/security_spec.rb

1141 lines
40 KiB
Ruby

#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../sharding_spec_helper')
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_via_redirect "/login"
assert_response :success
cookie = cookies['_normandy_session']
cookie.should be_present
path.should == "/login"
post_via_redirect "/login", "pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf",
"pseudonym_session[remember_me]" => "1",
"redirect_to_ssl" => "1"
assert_response :success
request.fullpath.should eql("/?login_success=1")
new_cookie = cookies['_normandy_session']
new_cookie.should be_present
cookie.should_not eql(new_cookie)
end
end
describe "permissions" do
# if we end up moving the permissions cache to memcache, this test won't be
# valid anymore and we need some more extensive tests for actual cache
# invalidation. right now, though, this is the only really valid way to
# test that we're actually flushing on every request.
it "should flush the role_override caches on every request" do
course_with_teacher_logged_in
get "/courses/#{@course.to_param}/users"
assert_response :success
RoleOverride.send(:instance_variable_get, '@cached_permissions').should_not be_empty
RoleOverride.send(:class_variable_get, '@@role_override_chain').should_not be_empty
get "/dashboard"
assert_response 301
# verify the cache is emptied on every request
RoleOverride.send(:instance_variable_get, '@cached_permissions').should be_empty
RoleOverride.send(:class_variable_get, '@@role_override_chain').should be_empty
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", "pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf"
assert_response 302
c = response['Set-Cookie'].lines.grep(/\A_normandy_session=/).first
c.should_not match(/expires=/)
reset!
https!
post "/login", "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
c.should_not 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", "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
c1.should_not be_present
c2.should be_present
end
# these specs aren't needed in rails3, where we use a newer authlogic that
# has built-in support for the httponly/secure options
if CANVAS_RAILS2
it "should make both cookies httponly" do
u = user_with_pseudonym :active_user => true,
:username => "nobody@example.com",
:password => "asdfasdf"
u.save!
https!
post "/login", "pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf",
"pseudonym_session[remember_me]" => "1"
assert_response 302
c1 = response['Set-Cookie'].lines.grep(/\Apseudonym_credentials=/).first
c2 = response['Set-Cookie'].lines.grep(/\A_normandy_session=/).first
c1.should match(/; *HttpOnly/)
c2.should match(/; *HttpOnly/)
c1.should_not match(/; *secure/)
c2.should_not match(/; *secure/)
end
it "should make both cookies secure only if configured" do
ActionController::Base.session_options[:secure] = true
u = user_with_pseudonym :active_user => true,
:username => "nobody@example.com",
:password => "asdfasdf"
u.save!
https!
post "/login", "pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf",
"pseudonym_session[remember_me]" => "1"
assert_response 302
c1 = response['Set-Cookie'].lines.grep(/\Apseudonym_credentials=/).first
c2 = response['Set-Cookie'].lines.grep(/\A_normandy_session=/).first
c1.should match(/; *secure/)
c2.should match(/; *secure/)
ActionController::Base.session_options[:secure] = nil
end
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", { "pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf",
"pseudonym_session[remember_me]" => "1" }, { 'HTTP_ACCEPT' => 'application/json' }
response.should be_success
response['Content-Type'].should match(%r"^application/json")
response.body.should_not match(%r{^while\(1\);})
json = JSON.parse response.body
json['pseudonym']['unique_id'].should == "nobody@example.com"
end
it "should prepend GET JSON responses with protection" do
course_with_teacher_logged_in
get "/courses.json"
response.should be_success
response['Content-Type'].should match(%r"^application/json")
response.body.should 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", nil, { 'HTTP_ACCEPT' => 'application/json' }
response.should be_success
response['Content-Type'].should match(%r"^application/json")
response.body.should_not 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"
response.should be_success
response['Content-Type'].should match(%r"^application/json")
response.body.should_not 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 "/", {}, "HTTP_COOKIE" => "pseudonym_credentials=#{@p.persistence_token}"
response.should redirect_to("https://www.example.com/login")
token = SessionPersistenceToken.generate(@p)
# correct token id, but nonsense uuid and persistence_token
get "/", {}, "HTTP_COOKIE" => "pseudonym_credentials=#{token.id}::blah::blah"
response.should redirect_to("https://www.example.com/login")
# correct token id and persistence_token, but nonsense uuid
get "/", {}, "HTTP_COOKIE" => "pseudonym_credentials=#{token.id}::#{@p.persistence_token}::blah"
response.should redirect_to("https://www.example.com/login")
end
it "should login via persistence token when no session exists" do
token = SessionPersistenceToken.generate(@p)
get "/", {}, "HTTP_COOKIE" => "pseudonym_credentials=#{token.pseudonym_credentials}"
response.should be_success
cookies['_normandy_session'].should be_present
session[:used_remember_me_token].should be_true
# accessing sensitive areas of canvas require a fresh login
get "/profile/settings"
response.should redirect_to login_url
flash[:warning].should_not be_empty
post "/login", :pseudonym_session => { :unique_id => @p.unique_id, :password => 'asdfasdf' }
response.should redirect_to settings_profile_url
session[:used_remember_me_token].should_not be_true
follow_redirect!
response.should be_success
end
it "should not allow login via the same valid token twice" do
token = SessionPersistenceToken.generate(@p)
get "/", {}, "HTTP_COOKIE" => "pseudonym_credentials=#{token.pseudonym_credentials}"
response.should be_success
SessionPersistenceToken.find_by_id(token.id).should be_nil
reset!
https!
get "/", {}, "HTTP_COOKIE" => "pseudonym_credentials=#{token.pseudonym_credentials}"
response.should 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 "/", {}, "HTTP_COOKIE" => "pseudonym_credentials=#{token.pseudonym_credentials}"
response.should be_success
s1 = cookies['_normandy_session']
s1.should be_present
cookie = cookies['pseudonym_credentials']
cookie.should be_present
token2 = SessionPersistenceToken.find_by_pseudonym_credentials(CGI.unescape(cookie))
token2.should be_present
token2.should_not == token
token2.pseudonym.should == @p
reset!
https!
# check that the new token is valid too
get "/", {}, "HTTP_COOKIE" => "pseudonym_credentials=#{cookie}"
response.should be_success
s2 = cookies['_normandy_session']
s2.should be_present
s2.should_not == s1
end
it "should generate and return a token when remember_me is checked" do
post "/login", "pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf",
"pseudonym_session[remember_me]" => "1"
assert_response 302
cookie = cookies['pseudonym_credentials']
cookie.should be_present
token = SessionPersistenceToken.find_by_pseudonym_credentials(CGI.unescape(cookie))
token.should be_present
token.pseudonym.should == @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 "/"
cookies['pseudonym_credentials'].should == cookie
end
it "should destroy the token both user agent and server side on logout" do
expect {
post "/login", "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']
c.should be_present
expect {
delete "/logout"
}.to change(SessionPersistenceToken, :count).by(-1)
cookies['pseudonym_credentials'].should_not be_present
SessionPersistenceToken.find_by_pseudonym_credentials(CGI.unescape(c)).should 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", "pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf",
"pseudonym_session[remember_me]" => "1"
c1 = s1.cookies['pseudonym_credentials']
s2.post "/login", "pseudonym_session[unique_id]" => "nobody@example.com",
"pseudonym_session[password]" => "asdfasdf",
"pseudonym_session[remember_me]" => "1"
c2 = s2.cookies['pseudonym_credentials']
c1.should_not == c2
s3 = open_session
s3.https!
s3.get "/", {}, "HTTP_COOKIE" => "pseudonym_credentials=#{c1}"
s3.response.should be_success
s3.delete "/logout"
# make sure c2 can still work
s4 = open_session
s4.https!
s4.get "/", {}, "HTTP_COOKIE" => "pseudonym_credentials=#{c2}"
s4.response.should be_success
end
it "should not login if the pseudonym is deleted" do
token = SessionPersistenceToken.generate(@p)
@p.destroy
get "/", {}, "HTTP_COOKIE" => "pseudonym_credentials=#{token.pseudonym_credentials}"
response.should 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 = 'newpass'
@p.save!
pers2 = @p.persistence_token
pers1.should_not == pers2
get "/", {}, "HTTP_COOKIE" => "pseudonym_credentials=#{creds}"
response.should 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 "/", {}, "HTTP_COOKIE" => "pseudonym_credentials=#{token.pseudonym_credentials}"
response.should be_success
cookies['_normandy_session'].should be_present
session[:used_remember_me_token].should be_true
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_via_redirect "/login",
{ "pseudonym_session[unique_id]" => "nobody@example.com", "pseudonym_session[password]" => "failboat" },
{ "REMOTE_ADDR" => ip }
end
it "should be limited for the same ip" do
bad_login("5.5.5.5")
response.body.should match(/Incorrect username/)
bad_login("5.5.5.5")
response.body.should match(/Too many failed login attempts/)
# should still fail
post_via_redirect "/login",
{ "pseudonym_session[unique_id]" => "nobody@example.com", "pseudonym_session[password]" => "asdfasdf" },
{ "REMOTE_ADDR" => "5.5.5.5" }
response.body.should match(/Too many failed login attempts/)
end
it "should have a higher limit for other ips" do
bad_login("5.5.5.5")
response.body.should match(/Incorrect username/)
bad_login("5.5.5.6") # different IP, so allowed
response.body.should match(/Incorrect username/)
bad_login("5.5.5.7") # different IP, but too many total failures
response.body.should match(/Too many failed login attempts/)
# should still fail
post_via_redirect "/login",
{ "pseudonym_session[unique_id]" => "nobody@example.com", "pseudonym_session[password]" => "asdfasdf" },
{ "REMOTE_ADDR" => "5.5.5.7" }
response.body.should match(/Too many failed login attempts/)
end
it "should not block other users with the same ip" do
bad_login("5.5.5.5")
response.body.should match(/Incorrect 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_via_redirect "/login",
{ "pseudonym_session[unique_id]" => "second@example.com", "pseudonym_session[password]" => "12341234" },
{ "REMOTE_ADDR" => "5.5.5.5" }
request.fullpath.should eql("/?login_success=1")
end
it "should apply limitations correctly for cross-account logins" do
account = Account.create!
Account.any_instance.stubs(:trusted_account_ids).returns([account.id])
@pseudonym.account = account
@pseudonym.save!
bad_login("5.5.5.5")
response.body.should match(/Incorrect username/)
bad_login("5.5.5.6") # different IP, so allowed
response.body.should match(/Incorrect username/)
bad_login("5.5.5.7") # different IP, but too many total failures
response.body.should match(/Too many failed login attempts/)
# should still fail
post_via_redirect "/login",
{ "pseudonym_session[unique_id]" => "nobody@example.com", "pseudonym_session[password]" => "asdfasdf" },
{ "REMOTE_ADDR" => "5.5.5.5" }
response.body.should 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 :active_all => true
@course.enroll_teacher(@teacher).tap do |e|
e.workflow_state = 'active'
e.save!
end
@student = user :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", :user_list => "A1234567, A345678"
response.should_not be_success
user_session(@teacher)
post "/courses/#{@course.id}/user_lists.json", :user_list => "A1234567, A345678"
assert_response :success
json_parse.should == {
"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
response.location.should match "/users/#{@student.id}/masquerade$"
session[:masquerade_return_to].should == "/"
session[:become_user_id].should be_nil
assigns['current_user'].id.should == @admin.id
assigns['real_current_user'].should be_nil
follow_redirect!
assert_response 200
path.should == "/users/#{@student.id}/masquerade"
session[:become_user_id].should be_nil
assigns['current_user'].id.should == @admin.id
assigns['real_current_user'].should be_nil
post "/users/#{@student.id}/masquerade"
assert_response 302
session[:become_user_id].should == @student.id.to_s
get "/"
assert_response 200
session[:become_user_id].should == @student.id.to_s
assigns['current_user'].id.should == @student.id
assigns['current_pseudonym'].should == @student_pseudonym
assigns['real_current_user'].id.should == @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
session[:become_user_id].should be_nil
assigns['current_user'].id.should == @admin.id
assigns['real_current_user'].should 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
session[:become_user_id].should be_nil
assigns['current_user'].id.should == @student.id
assigns['real_current_user'].should be_nil
post "/users/#{@teacher.id}/masquerade"
assert_response 401
assigns['current_user'].id.should == @student.id
session[:become_user_id].should 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
response.location.should match "/users/#{@student.id}/masquerade$"
session[:masquerade_return_to].should == "/"
session[:become_user_id].should be_nil
assigns['current_user'].id.should == @admin.id
assigns['real_current_user'].should be_nil
follow_redirect!
assert_response 200
path.should == "/users/#{@student.id}/masquerade"
session[:become_user_id].should be_nil
assigns['current_user'].id.should == @admin.id
assigns['real_current_user'].should be_nil
PageView.last.user_id.should == @admin.id
PageView.last.real_user_id.should be_nil
post "/users/#{@student.id}/masquerade"
assert_response 302
session[:become_user_id].should == @student.id.to_s
get "/"
assert_response 200
session[:become_user_id].should == @student.id.to_s
assigns['current_user'].id.should == @student.id
assigns['real_current_user'].id.should == @admin.id
PageView.last.user_id.should == @student.id
PageView.last.real_user_id.should == @admin.id
end
it "should remember the destination with an intervening auth" do
token = SessionPersistenceToken.generate(@admin.pseudonyms.first)
get "/", {}, "HTTP_COOKIE" => "pseudonym_credentials=#{token.pseudonym_credentials}"
response.should be_success
cookies['_normandy_session'].should be_present
session[:used_remember_me_token].should be_true
# accessing sensitive areas of canvas require a fresh login
get "/conversations?become_user_id=#{@student.id}"
response.should redirect_to user_masquerade_url(@student)
follow_redirect!
response.should redirect_to login_url
flash[:warning].should_not be_empty
post "/login", :pseudonym_session => { :unique_id => @admin.pseudonyms.first.unique_id, :password => 'password' }
response.should redirect_to user_masquerade_url(@student)
session[:used_remember_me_token].should_not be_true
post "/users/#{@student.id}/masquerade"
response.should redirect_to conversations_url
follow_redirect!
response.should be_success
session[:become_user_id].should == @student.id.to_s
end
end
it "should not allow logins to safefiles domains" do
HostUrl.stubs(:is_file_host?).returns(true)
HostUrl.stubs(:default_host).returns('test.host')
get "http://files-test.host/login"
response.should be_redirect
uri = URI.parse response['Location']
uri.host.should == 'test.host'
HostUrl.stubs(:is_file_host?).returns(false)
get "http://test.host/login"
response.should be_success
end
describe "admin permissions" do
before(:each) do
account_admin_user(:account => Account.site_admin, :membership_type => 'Limited Admin')
user_session(@admin)
end
def add_permission(permission)
Account.site_admin.role_overrides.create!(:permission => permission.to_s,
:enrollment_type => 'Limited Admin',
:enabled => true)
end
def remove_permission(permission, enrollment_type)
Account.default.role_overrides.create!(:permission => permission.to_s,
:enrollment_type => enrollment_type,
:enabled => false)
end
describe "site admin" do
it "role_overrides" do
get "/accounts/#{Account.site_admin.id}/settings"
response.should be_success
response.body.should_not 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"
response.should be_success
get "/accounts/#{Account.site_admin.id}/settings"
response.should be_success
response.body.should 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"
response.should be_success
response.body.should_not match /Find A User/
get "/accounts/#{Account.default.id}/statistics"
response.should be_success
response.body.should_not match /Recently Logged-In Users/
add_permission :read_roster
get "/accounts/#{Account.default.id}/users"
response.should be_success
get "/accounts/#{Account.default.id}/settings"
response.should be_success
response.body.should match /Find A User/
get "/accounts/#{Account.default.id}/statistics"
response.should be_success
response.body.should match /Recently Logged-In Users/
end
it "read_course_list" do
add_permission :view_statistics
course
get "/accounts/#{Account.default.id}"
response.should be_redirect
get "/accounts/#{Account.default.id}/settings"
response.should be_success
response.body.should_not match /Course Filtering/
response.body.should_not match /Find a Course/
get "/accounts/#{Account.default.id}/statistics"
response.should be_success
response.body.should_not match /Recently Started Courses/
response.body.should_not match /Recently Ended Courses/
add_permission :read_course_list
get "/accounts/#{Account.default.id}"
response.should be_success
response.body.should match /Courses/
response.body.should match /Course Filtering/
response.body.should match /Find a Course/
get "/accounts/#{Account.default.id}/statistics"
response.should be_success
response.body.should match /Recently Started Courses/
response.body.should match /Recently Ended Courses/
end
it "view_statistics" do
get "/accounts/#{Account.default.id}/statistics"
assert_status(401)
get "/accounts/#{Account.default.id}/settings"
response.should be_success
response.body.should_not match /Statistics/
add_permission :view_statistics
get "/accounts/#{Account.default.id}/statistics"
response.should be_success
get "/accounts/#{Account.default.id}/settings"
response.should be_success
response.body.should 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"
response.should be_success
response.body.should_not 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"
response.should be_success
get "/accounts/#{Account.default.id}/settings"
response.should be_success
response.body.should match /Faculty Journal/
get "/users/#{@student.id}/user_notes"
response.should be_success
post "/users/#{@student.id}/user_notes.json"
response.should be_success
get "/users/#{@student.id}/user_notes/#{@user_note.id}.json"
response.should be_success
delete "/users/#{@student.id}/user_notes/#{@user_note.id}.json"
response.should be_success
end
it "view_jobs" do
get "/jobs"
response.should be_redirect
add_permission :view_jobs
get "/jobs"
response.should be_success
end
end
describe 'course' do
before (:each) do
course(:active_all => 1)
Account.default.update_attribute(:settings, { :no_enrollments_can_create_courses => false })
end
it 'read_as_admin' do
get "/courses/#{@course.id}"
response.should be_redirect
get "/courses/#{@course.id}/details"
response.should be_success
html = Nokogiri::HTML(response.body)
html.css('.edit_course_link').should be_empty
html.css('#tab-users').should be_empty
html.css('#tab-navigation').should be_empty
@course.enroll_teacher(@admin).accept!
@admin.reload
get "/courses/#{@course.id}"
response.should be_success
get "/courses/#{@course.id}/details"
response.should be_success
html = Nokogiri::HTML(response.body)
html.css('.edit_course_link').should_not be_empty
html.css('#tab-navigation').should_not 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"
response.should be_success
response.body.should_not match /People/
html = Nokogiri::HTML(response.body)
html.css('#tab-users').should be_empty
add_permission :read_roster
get "/courses/#{@course.id}/users"
response.should be_success
response.body.should match /View User Groups/
response.body.should match /View Prior Enrollments/
response.body.should_not match /Manage Users/
get "/courses/#{@course.id}/users/prior"
response.should be_success
get "/courses/#{@course.id}/groups"
response.should be_success
get "/courses/#{@course.id}/details"
response.should be_success
response.body.should 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"
response.should be_success
response.body.should_not match /People/
add_permission :manage_students
get "/courses/#{@course.id}/users"
response.should be_success
response.body.should_not match /View User Groups/
response.body.should match /View Prior Enrollments/
get "/courses/#{@course.id}/users/prior"
response.should be_success
get "/courses/#{@course.id}/groups"
assert_status(401)
get "/courses/#{@course.id}/details"
response.should be_success
response.body.should 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"
response.should be_success
response.body.should 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"
response.should be_redirect
get "/courses/#{@course.id}/gradebook"
response.should be_success
end
it 'read_course_content' do
@course.assignments.create!
@course.wiki.front_page.save!
@course.quizzes.create!
@course.attachments.create!(:uploaded_data => default_uploaded_data)
get "/courses/#{@course.id}"
response.should be_redirect
get "/courses/#{@course.id}/assignments"
assert_status(401)
get "/courses/#{@course.id}/assignments/syllabus"
assert_status(401)
get "/courses/#{@course.id}/wiki"
response.should be_redirect
follow_redirect!
response.should be_redirect
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"
response.should be_success
html = Nokogiri::HTML(response.body)
html.css('.section .assignments').should be_empty
html.css('.section .syllabus').should be_empty
html.css('.section .pages').should be_empty
html.css('.section .quizzes').should be_empty
html.css('.section .discussions').should be_empty
html.css('.section .files').should be_empty
response.body.should_not match /Copy this Course/
response.body.should_not match /Import Content into this Course/
response.body.should_not match /Export this Course/
add_permission :read_course_content
add_permission :read_roster
add_permission :read_forum
get "/courses/#{@course.id}"
response.should be_success
response.body.should match /People/
@course.tab_configuration = [ { :id => Course::TAB_PEOPLE, :hidden => true } ]
@course.save!
get "/courses/#{@course.id}/assignments"
response.should be_success
response.body.should_not match /People/
get "/courses/#{@course.id}/assignments/syllabus"
response.should be_success
get "/courses/#{@course.id}/wiki"
response.should be_redirect
follow_redirect!
response.should be_success
get "/courses/#{@course.id}/quizzes"
response.should be_success
get "/courses/#{@course.id}/discussion_topics"
response.should be_success
get "/courses/#{@course.id}/files"
response.should be_success
get "/courses/#{@course.id}/copy"
assert_status(401)
get "/courses/#{@course.id}/content_exports"
response.should be_success
get "/courses/#{@course.id}/details"
response.should be_success
html = Nokogiri::HTML(response.body)
html.css('.section .assignments').should_not be_empty
html.css('.section .syllabus').should_not be_empty
html.css('.section .pages').should_not be_empty
html.css('.section .quizzes').should_not be_empty
html.css('.section .discussions').should_not be_empty
html.css('.section .files').should_not be_empty
response.body.should_not match /Copy this Course/
response.body.should_not match /Import Content into this Course/
response.body.should match /Export Course Content/
response.body.should_not match /Delete this Course/
response.body.should_not match /End this Course/
html.css('#course_account_id').should be_empty
html.css('#course_enrollment_term_id').should be_empty
delete "/courses/#{@course.id}"
assert_status(401)
delete "/courses/#{@course.id}", :event => 'delete'
assert_status(401)
add_permission :manage_courses
get "/courses/#{@course.id}/details"
response.should be_success
response.body.should match /Copy this Course/
response.body.should_not match /Import Content into this Course/
response.body.should match /Export Course Content/
response.body.should match /Delete this Course/
html = Nokogiri::HTML(response.body)
html.css('#course_account_id').should_not be_empty
html.css('#course_enrollment_term_id').should_not be_empty
get "/courses/#{@course.id}/copy"
response.should be_success
delete "/courses/#{@course.id}", :event => 'delete'
response.should be_redirect
@course.reload.should be_deleted
end
it 'manage_content' do
get "/courses/#{@course.id}/details"
response.should be_success
response.body.should_not match /Import Content into this Course/
get "/courses/#{@course.id}/content_migrations"
assert_status(401)
add_permission :manage_content
get "/courses/#{@course.id}/details"
response.should be_success
response.body.should match /Import Content into this Course/
get "/courses/#{@course.id}/content_migrations"
response.should be_success
end
it 'read_reports' do
student_in_course(:active_all => 1)
add_permission :read_roster
get "/courses/#{@course.id}/users/#{@student.id}"
response.should be_success
response.body.should_not 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}"
response.should be_success
response.body.should match "Access Report"
get "/courses/#{@course.id}/users/#{@student.id}/usage"
response.should be_success
end
it 'manage_sections' do
course_with_teacher_logged_in(:active_all => 1)
remove_permission(:manage_sections, 'TeacherEnrollment')
get "/courses/#{@course.id}/settings"
response.should be_success
response.body.should_not match 'Add Section'
post "/courses/#{@course.id}/sections"
assert_status(401)
get "/courses/#{@course.id}/sections/#{@course.default_section.id}"
response.should be_success
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, 'TeacherEnrollment')
get "/courses/#{@course.id}/settings"
response.should be_success
response.body.should_not match 'End this Course'
delete "/courses/#{@course.id}", :event => 'conclude'
assert_status(401)
end
it 'view_statistics' do
course_with_teacher_logged_in(:active_all => 1)
@student = user :active_all => true
@course.enroll_student(@student).tap do |e|
e.workflow_state = 'active'
e.save!
end
get "/courses/#{@course.id}/users/#{@student.id}"
response.should be_success
get "/users/#{@student.id}"
assert_status(401)
admin = account_admin_user :account => Account.site_admin
user_session(admin)
get "/users/#{@student.id}"
response.should be_success
end
end
end
end