Files
ubicloud/spec/routes/api/project_spec.rb
Jeremy Evans c3e89fa568 Return 404 instead of 403 for unauthorized project access
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.
2025-05-16 02:06:02 +09:00

180 lines
5.5 KiB
Ruby
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# frozen_string_literal: true
require_relative "spec_helper"
RSpec.describe Clover, "project" do
let(:user) { create_account }
let(:project) { project_with_default_policy(user) }
describe "unauthenticated" do
it "cannot perform authenticated operations" do
[
[:get, "/project"],
[:post, "/project", {name: "p-1"}],
[:delete, "/project/#{project.ubid}"]
].each do |method, path, body|
send(method, path, body)
expect(last_response).to have_api_error(401, "must include personal access token in Authorization header")
end
end
it "does not recognize invalid personal access tokens" do
account = Account[email: user.email]
pat = ApiKey.create_with_id(owner_table: "accounts", owner_id: account.id, used_for: "api", project_id: project.id)
header "Authorization", "Bearer pat-#{pat.ubid[0...-1]}-#{pat.key}"
get "/project"
expect(last_response.status).to eq(401)
header "Authorization", "Bearer pat-#{pat.ubid}-#{pat.key[0...-1]}"
get "/project"
expect(last_response.status).to eq(401)
header "Authorization", "Bearer pat-#{account.ubid}-#{pat.key}"
get "/project"
expect(last_response.status).to eq(401)
pat.update(is_valid: false)
header "Authorization", "Bearer pat-#{pat.ubid}-#{pat.key}"
get "/project"
expect(last_response.status).to eq(401)
end
end
describe "authenticated" do
before do
login_api
end
describe "list" do
it "success" do
project
get "/project"
expect(last_response.status).to eq(200)
parsed_body = JSON.parse(last_response.body)
expect(parsed_body["count"]).to eq(2)
end
it "invalid order column" do
project
get "/project?order_column=name"
expect(last_response).to have_api_error(400, "Validation failed for following fields: order_column")
end
it "invalid id" do
project
get "/project?start_after=invalid_id"
expect(last_response).to have_api_error(400, "Validation failed for following fields: start_after")
end
end
describe "create" do
it "success" do
project
post "/project", {
name: "test-project"
}.to_json
expect(last_response.status).to eq(200)
expect(JSON.parse(last_response.body)["name"]).to eq("test-project")
end
it "creates up to 10 projects per account" do
project
(10 - user.projects_dataset.count).times do |i|
post "/project", {
name: "test-project-#{i}"
}.to_json
expect(last_response.status).to eq(200)
expect(JSON.parse(last_response.body)["name"]).to eq("test-project-#{i}")
end
expect(user.projects_dataset.count).to eq(10)
post "/project", {
name: "test-project"
}.to_json
expect(last_response).to have_api_error(400, "Project limit exceeded. You can create up to 10 projects. Contact support@ubicloud.com if you need more.")
expect(user.projects_dataset.count).to eq(10)
end
end
describe "delete" do
it "success" do
delete "/project/#{project.ubid}"
expect(last_response.status).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(SubjectTag.where(project_id: project.id).count).to eq(0)
expect(AccessControlEntry.where(project_id: project.id).count).to eq(0)
end
it "success with non-existing project" do
project_with_default_policy(user)
delete "/project/pj000000000000000000000000"
expect(last_response.status).to eq(204)
end
it "can not delete project when it has resources" do
Prog::Vm::Nexus.assemble("k y", project.id, name: "vm1")
delete "/project/#{project.ubid}"
expect(last_response).to have_api_error(409, "'#{project.name}' project has some resources. Delete all related resources first.")
end
it "not authorized" do
project_with_default_policy(user)
p = create_account("test@test.com").create_project_with_default_policy("project-1")
delete "/project/#{p.ubid}"
expect(last_response.status).to eq(204)
end
end
describe "show" do
it "success" do
get "/project/#{project.ubid}"
expect(last_response.status).to eq(200)
expect(JSON.parse(last_response.body)["name"]).to eq(project.name)
end
it "failure with unauthorized personal access token" do
project
AccessControlEntry.dataset.destroy
AccessControlEntry.create_with_id(project_id: project.id, subject_id: @pat.id, action_id: ActionType::NAME_MAP["Project:edit"])
get "/project/#{project.ubid}"
expect(last_response).to have_api_error(403, "Sorry, you don't have permission to continue with this request.")
end
it "not found" do
project
get "/project/pj000000000000000000000000"
expect(last_response).to have_api_error(404, "Sorry, we couldnt find the resource youre looking for.")
end
it "not authorized" do
project
u = create_account("test@test.com")
p = u.create_project_with_default_policy("project-1")
get "/project/#{p.ubid}"
expect(last_response).to have_api_error(404, "Sorry, we couldnt find the resource youre looking for.")
end
end
end
end