403 leaks information about whether the requested project exists. This approach for projects is now similar to how we treat other nested objects, where we retrieve from an authorized dataset, instead of retrieving the object and then (hopefully) performing authorization on it. It should also be faster as it eliminates an unnecessary query. Unfortunatley, the route specs mock Project.[] in quite a few places. To avoid a bunch of spec churn, add Clover.authorized_project, and change the mocking to mock that instead. As a consequence of this handling, deleting an unauthorized project now returns 204 instead of 403. I believe that is how deletion of other unauthorized objects is handled, so the behavior is now more consistent, but it is something to be aware of.
776 lines
34 KiB
Ruby
776 lines
34 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative "spec_helper"
|
|
|
|
RSpec.describe Clover, "project" do
|
|
let(:user) { create_account }
|
|
let(:user2) { create_account("user2@example.com") }
|
|
|
|
let(:project) { user.create_project_with_default_policy("project-1") }
|
|
|
|
let(:project_wo_permissions) { user.create_project_with_default_policy("project-2", default_policy: nil) }
|
|
|
|
describe "unauthenticated" do
|
|
it "can not list without login" do
|
|
visit "/project"
|
|
|
|
expect(page.title).to eq("Ubicloud - Login")
|
|
end
|
|
|
|
it "can not create without login" do
|
|
visit "/project/create"
|
|
|
|
expect(page.title).to eq("Ubicloud - Login")
|
|
end
|
|
end
|
|
|
|
describe "authenticated" do
|
|
before do
|
|
login(user.email)
|
|
end
|
|
|
|
describe "list" do
|
|
it "can list no projects" do
|
|
user.remove_all_projects
|
|
|
|
visit "/project"
|
|
expect(page.title).to eq("Ubicloud - Projects")
|
|
|
|
within ".empty-state" do
|
|
expect(page).to have_content "No projects"
|
|
|
|
click_link "Create Project"
|
|
end
|
|
expect(page.title).to eq("Ubicloud - Create Project")
|
|
end
|
|
|
|
it "can not list projects when does not invited" do
|
|
project
|
|
new_project = user2.create_project_with_default_policy("project-3")
|
|
|
|
visit "/project"
|
|
|
|
expect(page.title).to eq("Ubicloud - Projects")
|
|
expect(page).to have_content project.name
|
|
expect(page).to have_no_content new_project.name
|
|
end
|
|
end
|
|
|
|
describe "create" do
|
|
it "can create new project" do
|
|
name = "new-project"
|
|
visit "/project/create"
|
|
|
|
expect(project.accounts_dataset.count).to eq 1
|
|
expect(page.title).to eq("Ubicloud - Create Project")
|
|
|
|
fill_in "Name", with: name
|
|
|
|
click_button "Create"
|
|
|
|
expect(page.title).to eq("Ubicloud - #{name}")
|
|
expect(page).to have_content name
|
|
|
|
project = Project[name: name]
|
|
expect(project.accounts_dataset.count).to eq 1
|
|
expect(project.access_control_entries.count).to eq 2
|
|
expect(project.subject_tags.map(&:name).sort).to eq %w[Admin Member]
|
|
expect(user.projects).to include project
|
|
end
|
|
|
|
it "limits number of projects per account to 10" do
|
|
visit "/project/create"
|
|
|
|
(10 - user.projects_dataset.count).times do |i|
|
|
user.create_project_with_default_policy("project-#{i}")
|
|
end
|
|
|
|
expect(user.projects_dataset.count).to eq 10
|
|
expect(page.title).to eq("Ubicloud - Create Project")
|
|
|
|
fill_in "Name", with: "new-project-10"
|
|
|
|
click_button "Create"
|
|
|
|
expect(page).to have_flash_error("Project limit exceeded. You can create up to 10 projects. Contact support@ubicloud.com if you need more.")
|
|
expect(page.title).to eq("Ubicloud - Projects")
|
|
expect(user.projects_dataset.count).to eq 10
|
|
end
|
|
end
|
|
|
|
describe "dashboard" do
|
|
it "can view project dashboard always" do
|
|
visit "#{project_wo_permissions.path}/dashboard"
|
|
|
|
expect(page.title).to eq("Ubicloud - #{project_wo_permissions.name} Dashboard")
|
|
expect(page).to have_content project_wo_permissions.name
|
|
end
|
|
|
|
it "returns not found when user isn't added to project" do
|
|
new_project = Project.create_with_id(name: "new-project")
|
|
visit "#{new_project.path}/dashboard"
|
|
|
|
expect(page.title).to eq("Ubicloud - ResourceNotFound")
|
|
expect(page.status_code).to eq(404)
|
|
expect(page).to have_content "ResourceNotFound"
|
|
end
|
|
|
|
it "not show on sidebar when does not have permissions" do
|
|
visit "#{project_wo_permissions.path}/dashboard"
|
|
|
|
within "#desktop-menu" do
|
|
expect { click_link "Users" }.to raise_error Capybara::ElementNotFound
|
|
expect { click_link "Access Policy" }.to raise_error Capybara::ElementNotFound
|
|
expect { click_link "Billing" }.to raise_error Capybara::ElementNotFound
|
|
expect { click_link "Settings" }.to raise_error Capybara::ElementNotFound
|
|
end
|
|
end
|
|
|
|
it "shows content when user has permissions" do
|
|
visit "#{project.path}/dashboard"
|
|
|
|
within "#tiles" do
|
|
expect(page).to have_content "Virtual Machines"
|
|
expect(page).to have_content "Databases"
|
|
expect(page).to have_content "Load Balancers"
|
|
expect(page).to have_content "Firewalls"
|
|
if Config.github_app_name
|
|
expect(page).to have_content "GitHub Runners"
|
|
else
|
|
expect(page).to have_no_content "GitHub Runners"
|
|
end
|
|
expect(page).to have_content "Users"
|
|
end
|
|
|
|
within "#cards" do
|
|
expect(page).to have_content "Create Virtual Machine"
|
|
if Config.github_app_name
|
|
expect(page).to have_content "Use GitHub Runners"
|
|
else
|
|
expect(page).to have_no_content "GitHub Runners"
|
|
end
|
|
expect(page).to have_content "Create Managed Database"
|
|
expect(page).to have_content "Add User to Project"
|
|
expect(page).to have_content "Load Balance Your Traffic"
|
|
expect(page).to have_content "Create Access Token"
|
|
expect(page).to have_content "Documentation"
|
|
expect(page).to have_content "Get Support"
|
|
end
|
|
end
|
|
|
|
it "does not show content when user does not have permissions" do
|
|
visit "#{project_wo_permissions.path}/dashboard"
|
|
|
|
within "#tiles" do
|
|
expect(page).to have_no_content "Virtual Machines"
|
|
expect(page).to have_no_content "Databases"
|
|
expect(page).to have_no_content "Load Balancers"
|
|
expect(page).to have_no_content "Firewalls"
|
|
expect(page).to have_no_content "GitHub Runners"
|
|
expect(page).to have_no_content "Users"
|
|
end
|
|
|
|
within "#cards" do
|
|
expect(page).to have_no_content "Create Virtual Machine"
|
|
expect(page).to have_no_content "Use GitHub Runners"
|
|
expect(page).to have_no_content "Create Managed Database"
|
|
expect(page).to have_no_content "Add User to Project"
|
|
expect(page).to have_no_content "Distribute Your Traffic"
|
|
expect(page).to have_no_content "Create Access Token"
|
|
expect(page).to have_content "Documentation"
|
|
expect(page).to have_content "Get Support"
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "details" do
|
|
it "can show project details" do
|
|
project.add_quota(quota_id: ProjectQuota.default_quotas["VmVCpu"]["id"], value: 0)
|
|
visit "/project"
|
|
|
|
expect(page.title).to eq("Ubicloud - Projects")
|
|
expect(page).to have_content project.name
|
|
|
|
find("#project-#{project.ubid}").click_link project.name
|
|
|
|
expect(page.title).to eq("Ubicloud - #{project.name} Dashboard")
|
|
expect(page).to have_content project.name
|
|
|
|
find_by_id("desktop-menu").click_link "Settings"
|
|
|
|
expect(page.title).to eq("Ubicloud - #{project.name}")
|
|
end
|
|
|
|
it "raises forbidden when does not have permissions" do
|
|
project_wo_permissions
|
|
visit "/project/#{project_wo_permissions.ubid}"
|
|
|
|
expect(page.title).to eq("Ubicloud - Forbidden")
|
|
expect(page.status_code).to eq(403)
|
|
expect(page).to have_content "Forbidden"
|
|
end
|
|
|
|
it "raises not found when project not exists" do
|
|
visit "/project/08s56d4kaj94xsmrnf5v5m3mav"
|
|
|
|
expect(page.title).to eq("Ubicloud - ResourceNotFound")
|
|
expect(page.status_code).to eq(404)
|
|
expect(page).to have_content "ResourceNotFound"
|
|
end
|
|
|
|
it "can update the project name" do
|
|
new_name = "New-Project-Name"
|
|
visit project.path
|
|
|
|
fill_in "name", with: new_name
|
|
click_button "Save"
|
|
|
|
expect(page).to have_content new_name
|
|
expect(project.reload.name).to eq(new_name)
|
|
end
|
|
|
|
it "can not update the project name when does not have permissions" do
|
|
visit project_wo_permissions.path
|
|
|
|
expect { click_button "Save" }.to raise_error Capybara::ElementNotFound
|
|
end
|
|
end
|
|
|
|
describe "users" do
|
|
it "can show project users" do
|
|
visit project.path
|
|
|
|
within "#desktop-menu" do
|
|
click_link "Users"
|
|
end
|
|
|
|
expect(page.title).to eq("Ubicloud - #{project.name} - Users")
|
|
expect(page).to have_content user.email
|
|
end
|
|
|
|
it "raises forbidden when does not have Project:user permissions" do
|
|
project_wo_permissions
|
|
visit "#{project_wo_permissions.path}/user"
|
|
|
|
expect(page.title).to eq("Ubicloud - Forbidden")
|
|
expect(page.status_code).to eq(403)
|
|
expect(page).to have_content "Forbidden"
|
|
|
|
project
|
|
AccessControlEntry.dataset.destroy
|
|
visit "#{project.path}/user"
|
|
expect(page.status_code).to eq(403)
|
|
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["Project:user"])
|
|
page.refresh
|
|
expect(page.title).to eq("Ubicloud - project-1 - Users")
|
|
end
|
|
|
|
it "requires Project:user permissions to invite users, and SubjectTag:add to add to policies" do
|
|
visit "#{project.path}/user"
|
|
AccessControlEntry.dataset.destroy
|
|
fill_in "Email", with: user2.email
|
|
select "Admin", from: "policy"
|
|
click_button "Invite"
|
|
expect(page.status_code).to eq(403)
|
|
expect(Mail::TestMailer.deliveries.length).to eq 0
|
|
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["Project:user"])
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:view"])
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:add"])
|
|
visit "#{project.path}/user"
|
|
fill_in "Email", with: user2.email
|
|
select "Admin", from: "policy"
|
|
click_button "Invite"
|
|
expect(ProjectInvitation.count).to eq 0
|
|
expect(Mail::TestMailer.deliveries.length).to eq 1
|
|
end
|
|
|
|
it "can invite existing user to project with a default policy" do
|
|
visit "#{project.path}/user"
|
|
|
|
expect(page).to have_content user.email
|
|
expect(page).to have_no_content user2.email
|
|
|
|
subject_tag = project.subject_tags.first
|
|
expect(ProjectInvitation.count).to eq 0
|
|
expect(DB[:applied_subject_tag].first(tag_id: subject_tag.id, subject_id: user2.id)).to be_nil
|
|
|
|
fill_in "Email", with: user2.email
|
|
select "Admin", from: "policy"
|
|
click_button "Invite"
|
|
|
|
expect(page).to have_content user.email
|
|
expect(page).to have_content user2.email
|
|
expect(ProjectInvitation.count).to eq 0
|
|
expect(DB[:applied_subject_tag].first(tag_id: subject_tag.id, subject_id: user2.id)).not_to be_nil
|
|
expect(Mail::TestMailer.deliveries.length).to eq 1
|
|
end
|
|
|
|
it "handles case when attempting to add user to project when they already have access" do
|
|
visit "#{project.path}/user"
|
|
|
|
expect(page).to have_content user.email
|
|
expect(page).to have_no_content user2.email
|
|
|
|
subject_tag = project.subject_tags.first
|
|
expect(ProjectInvitation.count).to eq 0
|
|
expect(DB[:applied_subject_tag].first(tag_id: subject_tag.id, subject_id: user2.id)).to be_nil
|
|
|
|
fill_in "Email", with: user2.email
|
|
select "Admin", from: "policy"
|
|
click_button "Invite"
|
|
expect(page).to have_flash_notice("Invitation sent successfully to 'user2@example.com'.")
|
|
|
|
fill_in "Email", with: user2.email
|
|
select "Admin", from: "policy"
|
|
click_button "Invite"
|
|
expect(page).to have_flash_error("The requested user already has access to this project")
|
|
|
|
expect(page).to have_content user.email
|
|
expect(page).to have_content user2.email
|
|
expect(ProjectInvitation.count).to eq 0
|
|
expect(DB[:applied_subject_tag].first(tag_id: subject_tag.id, subject_id: user2.id)).not_to be_nil
|
|
expect(Mail::TestMailer.deliveries.length).to eq 1
|
|
end
|
|
|
|
it "can only add existing invited user to subject tag if SubjectTag:add permissions are allowed for it" do
|
|
allowed = SubjectTag.create_with_id(project_id: project.id, name: "Allowed")
|
|
AccessControlEntry.dataset.destroy
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["Project:user"])
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:view"])
|
|
|
|
visit "#{project.path}/user"
|
|
fill_in "Email", with: user2.email
|
|
select "Allowed", from: "policy"
|
|
click_button "Invite"
|
|
|
|
expect(page).to have_flash_error("You don't have permission to invite users with this subject tag.")
|
|
|
|
page.refresh
|
|
fill_in "Email", with: user2.email
|
|
select "No access", from: "policy"
|
|
click_button "Invite"
|
|
|
|
expect(page).to have_content user.email
|
|
expect(page).to have_content user2.email
|
|
expect(ProjectInvitation.count).to eq 0
|
|
expect(Mail::TestMailer.deliveries.length).to eq 1
|
|
expect(allowed.member_ids).to be_empty
|
|
end
|
|
|
|
it "can invite existing user to project without a default policy" do
|
|
visit "#{project.path}/user"
|
|
|
|
subject_tag = project.subject_tags.first
|
|
expect(DB[:applied_subject_tag].first(tag_id: subject_tag.id, subject_id: user2.id)).to be_nil
|
|
|
|
fill_in "Email", with: user2.email
|
|
select "No access", from: "policy"
|
|
click_button "Invite"
|
|
|
|
expect(DB[:applied_subject_tag].first(tag_id: subject_tag.id, subject_id: user2.id)).to be_nil
|
|
expect(page).to have_content user2.email
|
|
expect(Mail::TestMailer.deliveries.length).to eq 1
|
|
end
|
|
|
|
it "can only set subject tag for new invited user if SubjectTag:add permissions are allowed for it" do
|
|
allowed = SubjectTag.create_with_id(project_id: project.id, name: "Allowed")
|
|
AccessControlEntry.dataset.destroy
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["Project:user"])
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:view"])
|
|
|
|
visit "#{project.path}/user"
|
|
fill_in "Email", with: user2.email
|
|
select "Allowed", from: "policy"
|
|
click_button "Invite"
|
|
|
|
expect(page).to have_flash_error("You don't have permission to invite users with this subject tag.")
|
|
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:add"], object_id: allowed.id)
|
|
|
|
visit "#{project.path}/user"
|
|
new_email = "newUpper@example.com"
|
|
expect(page).to have_content user.email
|
|
|
|
fill_in "Email", with: new_email
|
|
select "No access", from: "policy"
|
|
click_button "Invite"
|
|
|
|
expect(page).to have_flash_notice("Invitation sent successfully to 'newUpper@example.com'.")
|
|
expect(page).to have_content user.email
|
|
expect(page).to have_content new_email
|
|
expect(page).to have_content "Invitation sent successfully to '#{new_email}'."
|
|
expect(Mail::TestMailer.deliveries.length).to eq 1
|
|
expect(ProjectInvitation.where(email: new_email, policy: nil).count).to eq 1
|
|
end
|
|
|
|
it "can invite non-existent user to project" do
|
|
visit "#{project.path}/user"
|
|
new_email = "newUpper@example.com"
|
|
expect(page).to have_content user.email
|
|
|
|
fill_in "Email", with: new_email
|
|
select "Admin", from: "policy"
|
|
click_button "Invite"
|
|
|
|
expect(page).to have_content user.email
|
|
expect(page).to have_content new_email
|
|
expect(page).to have_flash_notice(/Invitation sent successfully to '#{new_email}'.*/)
|
|
expect(page).to have_select("invitation_policies[#{new_email}]", selected: "Admin")
|
|
expect(Mail::TestMailer.deliveries.length).to eq 1
|
|
expect(ProjectInvitation.where(email: new_email).count).to eq 1
|
|
|
|
fill_in "Email", with: new_email.downcase
|
|
click_button "Invite"
|
|
|
|
expect(page).to have_flash_error("'#{new_email.downcase}' already invited to join the project.")
|
|
end
|
|
|
|
it "requires Project:user permissions to remove users from project" do
|
|
user2.add_project(project)
|
|
visit "#{project.path}/user"
|
|
AccessControlEntry.dataset.destroy
|
|
btn = find "#user-#{user2.ubid} .delete-btn"
|
|
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
|
|
expect(page.status_code).to eq 403
|
|
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["Project:user"])
|
|
visit "#{project.path}/user"
|
|
btn = find "#user-#{user2.ubid} .delete-btn"
|
|
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
|
|
expect(page.status_code).to eq 204
|
|
end
|
|
|
|
it "can remove user from project" do
|
|
user2.add_project(project)
|
|
project.subject_tags_dataset.first(name: "Admin").add_subject(user2.id)
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user2.id)
|
|
|
|
visit "#{project.path}/user"
|
|
|
|
expect(page).to have_content user.email
|
|
expect(page).to have_content user2.email
|
|
|
|
# We send delete request manually instead of just clicking to button because delete action triggered by JavaScript.
|
|
# UI tests run without a JavaScript enginer.
|
|
btn = find "#user-#{user2.ubid} .delete-btn"
|
|
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
|
|
|
|
expect(page.body).to be_empty
|
|
|
|
DB.transaction(rollback: :always) do
|
|
DB[:account_password_hashes].where(id: user2.id).delete(force: true)
|
|
user2.destroy
|
|
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}, "HTTP_ACCEPT" => "application/json"
|
|
expect(page.status_code).to eq(404)
|
|
expect(JSON.parse(page.body).dig("error", "code")).to eq(404)
|
|
end
|
|
|
|
visit "#{project.path}/user"
|
|
expect(page).to have_content user.email
|
|
expect(page).to have_flash_notice("Removed #{user2.email} from #{project.name}")
|
|
|
|
visit "#{project.path}/user"
|
|
expect(page).to have_content user.email
|
|
expect(page).to have_no_content user2.email
|
|
expect(DB[:applied_subject_tag].where(tag_id: project.subject_tags_dataset.first(name: "Admin").id, subject_id: user2.id).all).to be_empty
|
|
expect(AccessControlEntry.where(project_id: project.id, subject_id: user2.id).all).to be_empty
|
|
end
|
|
|
|
it "requires Project:user permissions to remove invited users from project" do
|
|
invited_email = "invited@example.com"
|
|
project.add_invitation(email: invited_email, inviter_id: "bd3479c6-5ee3-894c-8694-5190b76f84cf", expires_at: Time.now + 7 * 24 * 60 * 60)
|
|
visit "#{project.path}/user"
|
|
AccessControlEntry.dataset.destroy
|
|
btn = find "#invitation-#{invited_email.gsub(/\W+/, "")} .delete-btn"
|
|
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
|
|
expect(page.status_code).to eq 403
|
|
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["Project:user"])
|
|
visit "#{project.path}/user"
|
|
btn = find "#invitation-#{invited_email.gsub(/\W+/, "")} .delete-btn"
|
|
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
|
|
expect(page.status_code).to eq 204
|
|
end
|
|
|
|
it "can remove invited user from project" do
|
|
invited_email = "invited@example.com"
|
|
project.add_invitation(email: invited_email, inviter_id: "bd3479c6-5ee3-894c-8694-5190b76f84cf", expires_at: Time.now + 7 * 24 * 60 * 60)
|
|
|
|
visit "#{project.path}/user"
|
|
expect(page).to have_content invited_email
|
|
|
|
# We send delete request manually instead of just clicking to button because delete action triggered by JavaScript.
|
|
# UI tests run without a JavaScript enginer.
|
|
btn = find "#invitation-#{invited_email.gsub(/\W+/, "")} .delete-btn"
|
|
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
|
|
|
|
visit "#{project.path}/user"
|
|
expect(page).to have_flash_notice("Invitation for '#{invited_email}' is removed successfully.")
|
|
|
|
visit "#{project.path}/user"
|
|
expect(page).to have_no_content invited_email
|
|
expect { find "#invitation-#{invited_email.gsub(/\W+/, "")} .delete-btn" }.to raise_error Capybara::ElementNotFound
|
|
end
|
|
|
|
it "requires Project:user permissions to update default policy of invited user, and SubjectTag:add for access to subject tag" do
|
|
invited_email = "invited@example.com"
|
|
project.add_invitation(email: invited_email, inviter_id: "bd3479c6-5ee3-894c-8694-5190b76f84cf", expires_at: Time.now + 7 * 24 * 60 * 60)
|
|
visit "#{project.path}/user"
|
|
AccessControlEntry.dataset.destroy
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:view"])
|
|
within "form#managed-policy" do
|
|
select "Admin", from: "invitation_policies[#{invited_email}]"
|
|
click_button "Update"
|
|
end
|
|
expect(page.status_code).to eq 403
|
|
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["Project:user"])
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:add"])
|
|
visit "#{project.path}/user"
|
|
within "form#managed-policy" do
|
|
select "Admin", from: "invitation_policies[#{invited_email}]"
|
|
click_button "Update"
|
|
end
|
|
expect(page).to have_flash_notice("1 members added to Admin")
|
|
end
|
|
|
|
it "can update default policy of invited user" do
|
|
invited_email = "invited@example.com"
|
|
|
|
project.add_invitation(email: invited_email, policy: "Member", inviter_id: "bd3479c6-5ee3-894c-8694-5190b76f84cf", expires_at: Time.now + 7 * 24 * 60 * 60)
|
|
inv2 = project.add_invitation(email: "invited2@example.com", policy: "Member", inviter_id: "bd3479c6-5ee3-894c-8694-5190b76f84cf", expires_at: Time.now + 7 * 24 * 60 * 60)
|
|
|
|
visit "#{project.path}/user"
|
|
|
|
inv2.destroy
|
|
expect(page).to have_select("invitation_policies[#{invited_email}]", selected: "Member")
|
|
within "form#managed-policy" do
|
|
select "Admin", from: "invitation_policies[#{invited_email}]"
|
|
click_button "Update"
|
|
end
|
|
expect(page).to have_flash_notice("1 members added to Admin, 1 members removed from Member")
|
|
expect(page).to have_select("invitation_policies[#{invited_email}]", selected: "Admin")
|
|
end
|
|
|
|
it "can only update default policy of invited user if new policy is allowed subject tag" do
|
|
allowed = SubjectTag.create_with_id(project_id: project.id, name: "Allowed")
|
|
to_be_removed = SubjectTag.create_with_id(project_id: project.id, name: "ToBeRemoved")
|
|
AccessControlEntry.dataset.destroy
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["Project:user"])
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:view"])
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:add"], object_id: allowed.id)
|
|
ace = AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:add"], object_id: to_be_removed.id)
|
|
|
|
invited_email = "invited@example.com"
|
|
project.add_invitation(email: invited_email, policy: "Allowed", inviter_id: "bd3479c6-5ee3-894c-8694-5190b76f84cf", expires_at: Time.now + 7 * 24 * 60 * 60)
|
|
|
|
visit "#{project.path}/user"
|
|
|
|
within "form#managed-policy" do
|
|
click_button "Update"
|
|
end
|
|
|
|
within "form#managed-policy" do
|
|
select "ToBeRemoved", from: "invitation_policies[#{invited_email}]"
|
|
click_button "Update"
|
|
end
|
|
expect(page).to have_flash_notice("No change in user policies")
|
|
expect(page).to have_flash_error("You don't have permission to remove invitation from 'Allowed' tag")
|
|
expect(page).to have_select("invitation_policies[#{invited_email}]", selected: "Allowed")
|
|
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:remove"], object_id: allowed.id)
|
|
within "form#managed-policy" do
|
|
select "ToBeRemoved", from: "invitation_policies[#{invited_email}]"
|
|
ace.destroy
|
|
click_button "Update"
|
|
end
|
|
expect(page).to have_flash_notice("No change in user policies")
|
|
expect(page).to have_flash_error("You don't have permission to add invitation to 'ToBeRemoved' tag")
|
|
expect(page).to have_select("invitation_policies[#{invited_email}]", selected: "Allowed")
|
|
|
|
within "form#managed-policy" do
|
|
select "No access", from: "invitation_policies[#{invited_email}]"
|
|
click_button "Update"
|
|
end
|
|
expect(page).to have_flash_notice("1 members removed from Allowed")
|
|
expect(page).to have_select("invitation_policies[#{invited_email}]", selected: nil)
|
|
end
|
|
|
|
it "can update default policy of existing user" do
|
|
tag1 = SubjectTag.create_with_id(project_id: project.id, name: "FirstTag")
|
|
tag2 = SubjectTag.create_with_id(project_id: project.id, name: "SecondTag")
|
|
AccessControlEntry.dataset.destroy
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["Project:user"])
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:view"])
|
|
ace = AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:add"], object_id: tag1.id)
|
|
remove_ace = AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:remove"], object_id: tag1.id)
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:add"], object_id: tag2.id)
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:remove"], object_id: tag2.id)
|
|
|
|
user2.add_project(project)
|
|
tag1.add_subject(user2.id)
|
|
|
|
visit "#{project.path}/user"
|
|
|
|
admin_tag = project.subject_tags_dataset.first(name: "Admin")
|
|
within "form#managed-policy" do
|
|
select "SecondTag", from: "user_policies[#{user2.ubid}]"
|
|
admin_tag.add_subject(user2.id)
|
|
click_button "Update"
|
|
end
|
|
expect(page).to have_flash_notice("No change in user policies")
|
|
expect(page).to have_flash_error("Cannot change the policy for user, as they are in multiple subject tags")
|
|
expect(page.find_by_id("user-#{user2.ubid}")).to have_content "Admin, FirstTag"
|
|
admin_tag.remove_members(user2.id)
|
|
|
|
page.refresh
|
|
DB.transaction(rollback: :always) do
|
|
within "form#managed-policy" do
|
|
select "SecondTag", from: "user_policies[#{user2.ubid}]"
|
|
user2.remove_project(project)
|
|
click_button "Update"
|
|
end
|
|
expect(page).to have_flash_notice("No change in user policies")
|
|
expect(page).to have_flash_error("Cannot change the policy for user, as they are not associated to project")
|
|
end
|
|
|
|
page.refresh
|
|
remove_ace.destroy
|
|
within "form#managed-policy" do
|
|
select "SecondTag", from: "user_policies[#{user2.ubid}]"
|
|
click_button "Update"
|
|
end
|
|
expect(page).to have_flash_notice("No change in user policies")
|
|
expect(page).to have_flash_error("You don't have permission to remove members from 'FirstTag' tag")
|
|
noremove = page.find_by_id("user-#{user2.ubid}-noremove")
|
|
expect(noremove["title"]).to eq "You cannot change the policy for this user"
|
|
expect(noremove.text).to eq "FirstTag"
|
|
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:remove"], object_id: tag1.id)
|
|
page.refresh
|
|
within "form#managed-policy" do
|
|
select "SecondTag", from: "user_policies[#{user2.ubid}]"
|
|
click_button "Update"
|
|
end
|
|
expect(page).to have_flash_notice("1 members added to SecondTag, 1 members removed from FirstTag")
|
|
expect(page).to have_select("user_policies[#{user2.ubid}]", selected: "SecondTag")
|
|
|
|
within "form#managed-policy" do
|
|
select "FirstTag", from: "user_policies[#{user2.ubid}]"
|
|
ace.destroy
|
|
click_button "Update"
|
|
end
|
|
expect(page).to have_flash_notice("No change in user policies")
|
|
expect(page).to have_flash_error("You don't have permission to add members to 'FirstTag' tag")
|
|
expect(page).to have_select("user_policies[#{user2.ubid}]", selected: "SecondTag")
|
|
|
|
within "form#managed-policy" do
|
|
select "No access", from: "user_policies[#{user2.ubid}]"
|
|
click_button "Update"
|
|
end
|
|
expect(page).to have_flash_notice("1 members removed from SecondTag")
|
|
expect(page).to have_select("user_policies[#{user2.ubid}]", selected: nil)
|
|
|
|
within "form#managed-policy" do
|
|
select "SecondTag", from: "user_policies[#{user2.ubid}]"
|
|
click_button "Update"
|
|
end
|
|
expect(page).to have_flash_notice("1 members added to SecondTag")
|
|
expect(page).to have_select("user_policies[#{user2.ubid}]", selected: "SecondTag")
|
|
|
|
AccessControlEntry.create_with_id(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["SubjectTag:remove"], object_id: admin_tag.id)
|
|
page.refresh
|
|
expect(page).to have_select("user_policies[#{user.ubid}]", selected: "Admin")
|
|
within "form#managed-policy" do
|
|
select "No access", from: "user_policies[#{user.ubid}]"
|
|
click_button "Update"
|
|
end
|
|
expect(page).to have_flash_error("The project must have at least one admin.")
|
|
end
|
|
|
|
it "can not have more than 50 pending invitations" do
|
|
visit "#{project.path}/user"
|
|
|
|
expect(described_class).to receive(:authorized_project).with(user, project.id).and_return(project).twice
|
|
expect(project).to receive(:invitations_dataset).and_return(instance_double(Sequel::Dataset, count: 50))
|
|
expect(project).to receive(:invitations_dataset).and_call_original
|
|
|
|
fill_in "Email", with: "new@example.com"
|
|
click_button "Invite"
|
|
|
|
expect(page).to have_no_content "new@example.com"
|
|
expect(page).to have_flash_error("You can't have more than 50 pending invitations.")
|
|
end
|
|
|
|
it "raises bad request when it's the last user" do
|
|
user
|
|
visit "#{project.path}/user"
|
|
|
|
# We send delete request manually instead of just clicking to button because delete action triggered by JavaScript.
|
|
# UI tests run without a JavaScript enginer.
|
|
btn = find "#user-#{user.ubid} .delete-btn"
|
|
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
|
|
expect(page.status_code).to eq(400)
|
|
expect(page.body).to eq({error: {message: "You can't remove the last user from '#{project.name}' project. Delete project instead."}}.to_json)
|
|
|
|
visit "#{project.path}/user"
|
|
expect(page).to have_content user.email
|
|
end
|
|
|
|
it "raises not found when user not exists" do
|
|
visit "#{project.path}/user/08s56d4kaj94xsmrnf5v5m3mav"
|
|
|
|
expect(page.title).to eq("Ubicloud - ResourceNotFound")
|
|
expect(page.status_code).to eq(404)
|
|
expect(page).to have_content "ResourceNotFound"
|
|
end
|
|
end
|
|
|
|
describe "delete" do
|
|
it "can delete project" do
|
|
visit project.path
|
|
|
|
# We send delete request manually instead of just clicking to button because delete action triggered by JavaScript.
|
|
# UI tests run without a JavaScript enginer.
|
|
btn = find ".delete-btn"
|
|
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
|
|
|
|
expect(page.status_code).to eq(204)
|
|
expect(Project[project.id].visible).to be_falsey
|
|
expect(DB[:access_tag].where(project_id: project.id).count).to eq(0)
|
|
expect(AccessControlEntry.where(project_id: project.id).count).to eq(0)
|
|
expect(SubjectTag.where(project_id: project.id).count).to eq(0)
|
|
end
|
|
|
|
it "can not delete project when it has resources" do
|
|
Prog::Vm::Nexus.assemble("k y", project.id, name: "vm1")
|
|
|
|
visit project.path
|
|
|
|
# We send delete request manually instead of just clicking to button because delete action triggered by JavaScript.
|
|
# UI tests run without a JavaScript enginer.
|
|
btn = find ".delete-btn"
|
|
Capybara.current_session.driver.header "Accept", "application/json"
|
|
response = page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
|
|
expect(response).to have_api_error(409, "'#{project.name}' project has some resources. Delete all related resources first.")
|
|
|
|
visit "/project"
|
|
|
|
expect(page).to have_content project.name
|
|
end
|
|
|
|
it "can not delete project when does not have permissions" do
|
|
# Give permission to view, so we can see the detail page
|
|
AccessControlEntry.create_with_id(project_id: project_wo_permissions.id, subject_id: user.id, action_id: ActionType::NAME_MAP["Project:view"])
|
|
|
|
visit project_wo_permissions.path
|
|
expect(page.title).to eq "Ubicloud - project-2"
|
|
|
|
expect { find ".delete-btn" }.to raise_error Capybara::ElementNotFound
|
|
end
|
|
end
|
|
end
|
|
end
|