diff --git a/app/controllers/v3/application_controller.rb b/app/controllers/v3/application_controller.rb index 0b32357d58b..9c304f9a6e6 100644 --- a/app/controllers/v3/application_controller.rb +++ b/app/controllers/v3/application_controller.rb @@ -92,7 +92,9 @@ def permission_queryer perm_client, SecurityContext, configuration.get(:perm, :enabled), - configuration.get(:perm, :query_enabled)) + configuration.get(:perm, :query_enabled), + configuration.get(:perm, :query_raise_on_mismatch) + ) end private diff --git a/lib/cloud_controller/perm/client.rb b/lib/cloud_controller/perm/client.rb index cef4a3367fe..ae323048a7e 100644 --- a/lib/cloud_controller/perm/client.rb +++ b/lib/cloud_controller/perm/client.rb @@ -38,7 +38,9 @@ def rehydrate end def create_org_role(role:, org_id:) - create_role(org_role(role, org_id)) + create_role(org_role(role, org_id), [ + org_role_to_permission(role, org_id) + ]) end def delete_org_role(role:, org_id:) @@ -54,7 +56,9 @@ def unassign_org_role(role:, org_id:, user_id:, issuer:) end def create_space_role(role:, space_id:) - create_role(space_role(role, space_id)) + create_role(space_role(role, space_id), [ + space_role_to_permission(role, space_id) + ]) end def delete_space_role(role:, space_id:) @@ -129,10 +133,24 @@ def space_role(role, space_id) "space-#{role}-#{space_id}" end - def create_role(role) + def org_role_to_permission(role, org_id) + CloudFoundry::Perm::V1::Models::Permission.new( + name: "org.#{role}", + resource_pattern: org_id.to_s + ) + end + + def space_role_to_permission(role, space_id) + CloudFoundry::Perm::V1::Models::Permission.new( + name: "space.#{role}", + resource_pattern: space_id.to_s + ) + end + + def create_role(role, permissions=[]) if enabled begin - client.create_role(role_name: role) + client.create_role(role_name: role, permissions: permissions) rescue CloudFoundry::Perm::V1::Errors::AlreadyExists logger.debug('create-role.role-already-exists', role: role) rescue CloudFoundry::Perm::V1::Errors::BadStatus => e diff --git a/lib/cloud_controller/perm/permissions.rb b/lib/cloud_controller/perm/permissions.rb index 7a3bc6975bd..4c050aaf6ae 100644 --- a/lib/cloud_controller/perm/permissions.rb +++ b/lib/cloud_controller/perm/permissions.rb @@ -28,7 +28,7 @@ def can_read_from_org?(org_id) permissions = [ { permission_name: 'org.manager', resource_id: org_id }, { permission_name: 'org.auditor', resource_id: org_id }, - { permission_name: 'org.member', resource_id: org_id }, + { permission_name: 'org.user', resource_id: org_id }, { permission_name: 'org.billing_manager', resource_id: org_id }, ] can_read_globally? || has_any_permission?(permissions) diff --git a/lib/cloud_controller/permissions/queryer.rb b/lib/cloud_controller/permissions/queryer.rb index 4a318d86ea0..8b1d5e4c1bb 100644 --- a/lib/cloud_controller/permissions/queryer.rb +++ b/lib/cloud_controller/permissions/queryer.rb @@ -1,7 +1,9 @@ class VCAP::CloudController::Permissions::Queryer attr_reader :perm_permissions, :db_permissions - def self.build(perm_client, security_context, perm_enabled, query_enabled) + def self.build(perm_client, security_context, perm_enabled, query_enabled, query_raise_on_mismatch=false) + VCAP::CloudController::Science::Experiment.raise_on_mismatches = query_raise_on_mismatch + db_permissions = VCAP::CloudController::Permissions.new( security_context.current_user diff --git a/spec/integration/perm_spec.rb b/spec/integration/perm_spec.rb index 6115b85637b..3be207bfabc 100644 --- a/spec/integration/perm_spec.rb +++ b/spec/integration/perm_spec.rb @@ -22,14 +22,20 @@ let(:uaa_origin) { 'test-origin' } let(:client) { CloudFoundry::Perm::V1::Client.new(hostname: perm_hostname, port: perm_port, trusted_cas: ca_certs) } - let(:issuer) { 'https://auth.example.com/oauth/token' } - let(:admin_headers) { + + let(:issuer) { UAAIssuer::ISSUER } + + def http_headers(token) { - 'authorization' => "bearer #{admin_token}", + 'authorization' => "bearer #{token}", 'accept' => 'application/json', 'content-type' => 'application/json' } - } + end + + def admin_headers + http_headers(admin_token) + end if ENV['CF_RUN_PERM_SPECS'] == 'true' before(:all) do @@ -44,7 +50,8 @@ hostname: perm_hostname, port: perm_port, ca_cert_path: perm_server.tls_ca_path, - timeout_in_milliseconds: 1000 + timeout_in_milliseconds: 1000, + query_raise_on_mismatch: true, # Gives us 500s in Querying tests when perm and DB return different answers } ca_certs = [perm_server.tls_ca.clone] @@ -80,154 +87,120 @@ set_current_user_as_admin(iss: issuer) end - describe 'POST /v3/organizations' do - ORG_ROLES.each do |role| - it "creates the org-#{role}- role" do - body = { name: SecureRandom.uuid }.to_json - response = make_post_request('/v3/organizations', body, admin_headers) + describe 'Administrative tasks' do + describe 'POST /v3/organizations' do + ORG_ROLES.each do |role| + it "creates the org-#{role}- role" do + body = { name: SecureRandom.uuid }.to_json + response = make_post_request('/v3/organizations', body, admin_headers) - expect(response.code).to eq('201') + expect(response.code).to eq('201') - json_body = JSON.parse(response.body) - org_id = json_body['guid'] - role_name = "org-#{role}-#{org_id}" + json_body = response.json_body + org_id = json_body['guid'] + role_name = "org-#{role}-#{org_id}" - role = client.get_role(role_name) - expect(role.name).to eq(role_name) - end + role = client.get_role(role_name) + expect(role.name).to eq(role_name) + end - it 'does not allow the user to create an org that already exists' do - body = { name: SecureRandom.uuid }.to_json - response = make_post_request('/v3/organizations', body, admin_headers) + it 'does not allow the user to create an org that already exists' do + body = { name: SecureRandom.uuid }.to_json + response = make_post_request('/v3/organizations', body, admin_headers) - expect(response.code).to eq('201') + expect(response.code).to eq('201') - response = make_post_request('/v3/organizations', body, admin_headers) + response = make_post_request('/v3/organizations', body, admin_headers) - expect(response.code).to eq('422') + expect(response.code).to eq('422') + end end end - end - describe 'POST /v2/organizations' do - ORG_ROLES.each do |role| - it "creates the org-#{role}- role" do - post '/v2/organizations', { name: SecureRandom.uuid }.to_json + describe 'POST /v2/organizations' do + ORG_ROLES.each do |role| + it "creates the org-#{role}- role" do + post '/v2/organizations', { name: SecureRandom.uuid }.to_json - expect(last_response.status).to eq(201) + expect(last_response.status).to eq(201) - json_body = JSON.parse(last_response.body) - org_id = json_body['metadata']['guid'] - role_name = "org-#{role}-#{org_id}" + json_body = JSON.parse(last_response.body) + org_id = json_body['metadata']['guid'] + role_name = "org-#{role}-#{org_id}" - role = client.get_role(role_name) - expect(role.name).to eq(role_name) - end + role = client.get_role(role_name) + expect(role.name).to eq(role_name) + end - it 'does not allow the user to create an org that already exists' do - body = { name: SecureRandom.uuid }.to_json - post '/v2/organizations', body + it 'does not allow the user to create an org that already exists' do + body = { name: SecureRandom.uuid }.to_json + post '/v2/organizations', body - expect(last_response.status).to eq(201) + expect(last_response.status).to eq(201) - post '/v2/organizations', body + post '/v2/organizations', body - expect(last_response.status).to eq(400) + expect(last_response.status).to eq(400) - json_body = JSON.parse(last_response.body) - expect(json_body['error_code']).to eq('CF-OrganizationNameTaken') + json_body = JSON.parse(last_response.body) + expect(json_body['error_code']).to eq('CF-OrganizationNameTaken') + end end end - end - - describe 'DELETE /v2/organizations/:guid' do - let(:worker) { Delayed::Worker.new } - - ORG_ROLES.each do |role| - describe 'when the org does not have spaces' do - describe 'synchronous deletion' do - it "deletes the org-#{role}- role" do - post '/v2/organizations', { name: SecureRandom.uuid }.to_json - - expect(last_response.status).to eq(201) - json_body = JSON.parse(last_response.body) - org_id = json_body['metadata']['guid'] - role_name = "org-#{role}-#{org_id}" - - delete "/v2/organizations/#{org_id}" - - expect(last_response.status).to eq(204) - - expect { - client.get_role(role_name) - }.to raise_error CloudFoundry::Perm::V1::Errors::NotFound - end - end - - describe 'async deletion' do - it "deletes the org-#{role}- role" do - post '/v2/organizations', { name: SecureRandom.uuid }.to_json + describe 'DELETE /v2/organizations/:guid' do + let(:worker) { Delayed::Worker.new } - expect(last_response.status).to eq(201) + ORG_ROLES.each do |role| + describe 'when the org does not have spaces' do + describe 'synchronous deletion' do + it "deletes the org-#{role}- role" do + post '/v2/organizations', { name: SecureRandom.uuid }.to_json - json_body = JSON.parse(last_response.body) - org_id = json_body['metadata']['guid'] - role_name = "org-#{role}-#{org_id}" + expect(last_response.status).to eq(201) - delete "/v2/organizations/#{org_id}?async=true" + json_body = JSON.parse(last_response.body) + org_id = json_body['metadata']['guid'] + role_name = "org-#{role}-#{org_id}" - expect(last_response.status).to eq(202) + delete "/v2/organizations/#{org_id}" - succeeded_jobs, failed_jobs = worker.work_off - expect(succeeded_jobs).to be > 0 - expect(failed_jobs).to equal(0) + expect(last_response.status).to eq(204) - expect { - client.get_role(role_name) - }.to raise_error(CloudFoundry::Perm::V1::Errors::NotFound), "Expected that role #{role_name} was not found" + expect { + client.get_role(role_name) + }.to raise_error CloudFoundry::Perm::V1::Errors::NotFound + end end - end - end - - describe 'when the org has spaces' do - describe 'without "recursive" param' do - it 'alerts the user without deleting any roles' do - post '/v2/organizations', { name: SecureRandom.uuid }.to_json - - expect(last_response.status).to eq(201) - json_body = JSON.parse(last_response.body) - org_id = json_body['metadata']['guid'] - org_role_name = "org-#{role}-#{org_id}" + describe 'async deletion' do + it "deletes the org-#{role}- role" do + post '/v2/organizations', { name: SecureRandom.uuid }.to_json - post '/v2/spaces', { - name: SecureRandom.uuid, - organization_guid: org_id - }.to_json + expect(last_response.status).to eq(201) - expect(last_response.status).to eq(201) + json_body = JSON.parse(last_response.body) + org_id = json_body['metadata']['guid'] + role_name = "org-#{role}-#{org_id}" - json_body = JSON.parse(last_response.body) - space_id = json_body['metadata']['guid'] - space_role_name = "space-developer-#{space_id}" + delete "/v2/organizations/#{org_id}?async=true" - delete "/v2/organizations/#{org_id}?recursive=false" + expect(last_response.status).to eq(202) - expect(last_response.status).to eq(400) + succeeded_jobs, failed_jobs = worker.work_off + expect(succeeded_jobs).to be > 0 + expect(failed_jobs).to equal(0) - expect { - client.get_role(org_role_name) - }.not_to raise_error - expect { - client.get_role(space_role_name) - }.not_to raise_error + expect { + client.get_role(role_name) + }.to raise_error(CloudFoundry::Perm::V1::Errors::NotFound), "Expected that role #{role_name} was not found" + end end end - describe 'with "recursive" param' do - describe 'synchronous deletion' do - it 'deletes the roles recursively' do + describe 'when the org has spaces' do + describe 'without "recursive" param' do + it 'alerts the user without deleting any roles' do post '/v2/organizations', { name: SecureRandom.uuid }.to_json expect(last_response.status).to eq(201) @@ -237,8 +210,8 @@ org_role_name = "org-#{role}-#{org_id}" post '/v2/spaces', { - name: SecureRandom.uuid, - organization_guid: org_id + name: SecureRandom.uuid, + organization_guid: org_id }.to_json expect(last_response.status).to eq(201) @@ -247,70 +220,106 @@ space_id = json_body['metadata']['guid'] space_role_name = "space-developer-#{space_id}" - delete "/v2/organizations/#{org_id}?recursive=true" + delete "/v2/organizations/#{org_id}?recursive=false" - expect(last_response.status).to eq(204) + expect(last_response.status).to eq(400) expect { client.get_role(org_role_name) - }.to raise_error CloudFoundry::Perm::V1::Errors::NotFound + }.not_to raise_error expect { client.get_role(space_role_name) - }.to raise_error CloudFoundry::Perm::V1::Errors::NotFound + }.not_to raise_error end end - describe 'async deletion' do - it 'deletes the roles recursively' do - post '/v2/organizations', { name: SecureRandom.uuid }.to_json + describe 'with "recursive" param' do + describe 'synchronous deletion' do + it 'deletes the roles recursively' do + post '/v2/organizations', { name: SecureRandom.uuid }.to_json - expect(last_response.status).to eq(201) + expect(last_response.status).to eq(201) - json_body = JSON.parse(last_response.body) - org_id = json_body['metadata']['guid'] - org_role_name = "org-#{role}-#{org_id}" + json_body = JSON.parse(last_response.body) + org_id = json_body['metadata']['guid'] + org_role_name = "org-#{role}-#{org_id}" - post '/v2/spaces', { + post '/v2/spaces', { name: SecureRandom.uuid, organization_guid: org_id - }.to_json + }.to_json - expect(last_response.status).to eq(201) + expect(last_response.status).to eq(201) - json_body = JSON.parse(last_response.body) - space_id = json_body['metadata']['guid'] - space_role_name = "space-developer-#{space_id}" + json_body = JSON.parse(last_response.body) + space_id = json_body['metadata']['guid'] + space_role_name = "space-developer-#{space_id}" - delete "/v2/organizations/#{org_id}?recursive=true&async=true" + delete "/v2/organizations/#{org_id}?recursive=true" - expect(last_response.status).to eq(202) + expect(last_response.status).to eq(204) - succeeded_jobs, failed_jobs = worker.work_off - expect(succeeded_jobs).to be > 0 - expect(failed_jobs).to equal(0) + expect { + client.get_role(org_role_name) + }.to raise_error CloudFoundry::Perm::V1::Errors::NotFound + expect { + client.get_role(space_role_name) + }.to raise_error CloudFoundry::Perm::V1::Errors::NotFound + end + end - expect { - client.get_role(org_role_name) - }.to raise_error(CloudFoundry::Perm::V1::Errors::NotFound), "Expected that role #{org_role_name} was not found" - expect { - client.get_role(space_role_name) - }.to raise_error(CloudFoundry::Perm::V1::Errors::NotFound), "Expected that role #{space_role_name} was not found" + describe 'async deletion' do + it 'deletes the roles recursively' do + post '/v2/organizations', { name: SecureRandom.uuid }.to_json + + expect(last_response.status).to eq(201) + + json_body = JSON.parse(last_response.body) + org_id = json_body['metadata']['guid'] + org_role_name = "org-#{role}-#{org_id}" + + post '/v2/spaces', { + name: SecureRandom.uuid, + organization_guid: org_id + }.to_json + + expect(last_response.status).to eq(201) + + json_body = JSON.parse(last_response.body) + space_id = json_body['metadata']['guid'] + space_role_name = "space-developer-#{space_id}" + + delete "/v2/organizations/#{org_id}?recursive=true&async=true" + + expect(last_response.status).to eq(202) + + succeeded_jobs, failed_jobs = worker.work_off + expect(succeeded_jobs).to be > 0 + expect(failed_jobs).to equal(0) + + expect { + client.get_role(org_role_name) + }.to raise_error(CloudFoundry::Perm::V1::Errors::NotFound), "Expected that role #{org_role_name} was not found" + expect { + client.get_role(space_role_name) + }.to raise_error(CloudFoundry::Perm::V1::Errors::NotFound), "Expected that role #{space_role_name} was not found" + end end end - end - it 'alerts the user if the org does not exist' do - post '/v2/organizations', { name: SecureRandom.uuid }.to_json - expect(last_response.status).to eq(201) + it 'alerts the user if the org does not exist' do + post '/v2/organizations', { name: SecureRandom.uuid }.to_json + expect(last_response.status).to eq(201) - json_body = JSON.parse(last_response.body) - org_id = json_body['metadata']['guid'] + json_body = JSON.parse(last_response.body) + org_id = json_body['metadata']['guid'] - delete "/v2/organizations/#{org_id}" - expect(last_response.status).to eq(204) + delete "/v2/organizations/#{org_id}" + expect(last_response.status).to eq(204) - delete "/v2/organizations/#{org_id}" - expect(last_response.status).to eq(404) + delete "/v2/organizations/#{org_id}" + expect(last_response.status).to eq(404) + end end end end @@ -464,20 +473,20 @@ SPACE_ROLES.each do |role| it "creates the space-#{role}- role" do body = { - name: SecureRandom.uuid, - relationships: { - organization: { - data: { - guid: org_guid - } - } + name: SecureRandom.uuid, + relationships: { + organization: { + data: { + guid: org_guid + } } + } }.to_json response = make_post_request('/v3/spaces', body, admin_headers) expect(response.code).to eq('201') - json_body = JSON.parse(response.body) + json_body = response.json_body space_id = json_body['guid'] role_name = "space-#{role}-#{space_id}" @@ -487,14 +496,14 @@ it 'does not allow user to create space that already exists' do body = { - name: SecureRandom.uuid, - relationships: { - organization: { - data: { - guid: org_guid - } - } + name: SecureRandom.uuid, + relationships: { + organization: { + data: { + guid: org_guid + } } + } }.to_json response = make_post_request('/v3/spaces', body, admin_headers) @@ -514,8 +523,8 @@ SPACE_ROLES.each do |role| it "creates the space-#{role}- role" do post '/v2/spaces', { - name: SecureRandom.uuid, - organization_guid: org.guid + name: SecureRandom.uuid, + organization_guid: org.guid }.to_json expect(last_response.status).to eq(201) @@ -532,15 +541,15 @@ space_name = SecureRandom.uuid post '/v2/spaces', { - name: space_name, - organization_guid: org.guid + name: space_name, + organization_guid: org.guid }.to_json expect(last_response.status).to eq(201) post '/v2/spaces', { - name: space_name, - organization_guid: org.guid + name: space_name, + organization_guid: org.guid }.to_json expect(last_response.status).to eq(400) @@ -560,8 +569,8 @@ describe 'synchronous deletion' do it "deletes the space-#{role}- role" do post '/v2/spaces', { - name: SecureRandom.uuid, - organization_guid: org.guid + name: SecureRandom.uuid, + organization_guid: org.guid }.to_json expect(last_response.status).to eq(201) @@ -583,8 +592,8 @@ describe 'async deletion' do it "deletes the space-#{role}- role" do post '/v2/spaces', { - name: SecureRandom.uuid, - organization_guid: org.guid + name: SecureRandom.uuid, + organization_guid: org.guid }.to_json expect(last_response.status).to eq(201) @@ -609,8 +618,8 @@ it 'alerts the user if the space does not exist' do post '/v2/spaces', { - name: SecureRandom.uuid, - organization_guid: org.guid + name: SecureRandom.uuid, + organization_guid: org.guid }.to_json expect(last_response.status).to eq(201) @@ -737,7 +746,7 @@ end end - describe 'Querying' do + describe 'Querying v3 endpoints' do before do TestConfig.config[:perm] = perm_config @@ -748,16 +757,296 @@ allow_any_instance_of(VCAP::CloudController::UaaTokenDecoder).to receive(:uaa_issuer).and_return(issuer) end - describe 'can_read_from_space?' do - org_guid = nil - space_guid = nil - let!(:user) {} - before do - set_current_user_as_admin(iss: issuer) + RSpec.shared_examples 'org reader' do + it 'can read from org (can_read_from_org?)' do + opts = { + user_id: user.guid, + scope: ['cloud_controller.read', 'cloud_controller.write'], + } + response = make_get_request("/v3/organizations/#{org_guid}", http_headers(auth_token(opts))) + expect(response.code).to eq('200') - org_guid = create_org - space_guid = create_space(org_guid) + expect(response.json_body['guid']).to eq(org_guid) end + + it 'can read an isolation segment entitled to that org (can_read_from_isolation_segment?)' do + body = { + name: SecureRandom.uuid + }.to_json + + response = make_post_request('/v3/isolation_segments', body, admin_headers) + expect(response.code).to eq('201') + isolation_segment_guid = response.json_body['guid'] + + body = { + data: + [ + { guid: org_guid } + ] + }.to_json + + response = make_post_request("/v3/isolation_segments/#{isolation_segment_guid}/relationships/organizations", body, admin_headers) + expect(response.code).to eq('200') + + opts = { + user_id: user.guid, + scope: ['cloud_controller.read', 'cloud_controller.write'], + } + response = make_get_request("/v3/isolation_segments/#{isolation_segment_guid}", http_headers(auth_token(opts))) + expect(response.code).to eq('200') + + expect(response.json_body['guid']).to eq(isolation_segment_guid) + end + end + + RSpec.shared_examples 'org writer' do + it 'can create a space in the org (can_write_to_org?)' do + opts = { + user_id: user.guid, + scope: ['cloud_controller.read', 'cloud_controller.write'], + } + + space_name = SecureRandom.uuid + body = { + name: space_name, + relationships: { + organization: { + data: { + guid: org_guid + } + } + } + }.to_json + + response = make_post_request('/v3/spaces', body, http_headers(auth_token(opts))) + expect(response.code).to eq('201') + + expect(response.json_body['name']).to eq(space_name) + end + end + + RSpec.shared_examples 'space reader' do + it 'can read from space (can_read_from_space?)' do + opts = { + user_id: user.guid, + scope: ['cloud_controller.read', 'cloud_controller.write'], + } + response = make_get_request("/v3/spaces/#{space_guid}", http_headers(auth_token(opts))) + expect(response.code).to eq('200') + + expect(response.json_body['guid']).to eq(space_guid) + end + + it 'can read an isolation segment entitled to that space (can_read_from_isolation_segment?)' do + body = { + name: SecureRandom.uuid + }.to_json + + response = make_post_request('/v3/isolation_segments', body, admin_headers) + expect(response.code).to eq('201') + isolation_segment_guid = response.json_body['guid'] + + body = { + data: + [ + { guid: org_guid } + ] + }.to_json + + response = make_post_request("/v3/isolation_segments/#{isolation_segment_guid}/relationships/organizations", body, admin_headers) + expect(response.code).to eq('200') + + body = { + data: { + guid: isolation_segment_guid + } + }.to_json + + response = make_patch_request("/v3/spaces/#{space_guid}/relationships/isolation_segment", body, admin_headers) + puts response.body + expect(response.code).to eq('200') + + opts = { + user_id: user.guid, + scope: ['cloud_controller.read', 'cloud_controller.write'], + } + response = make_get_request("/v3/isolation_segments/#{isolation_segment_guid}", http_headers(auth_token(opts))) + expect(response.code).to eq('200') + + expect(response.json_body['guid']).to eq(isolation_segment_guid) + end + end + + RSpec.shared_examples 'space writer' do + it 'can create an app in the space (can_write_to_space?)' do + opts = { + user_id: user.guid, + scope: ['cloud_controller.read', 'cloud_controller.write'], + } + + app_name = SecureRandom.uuid + body = { + name: app_name, + relationships: { + space: { + data: { + guid: space_guid + } + } + } + }.to_json + + response = make_post_request('/v3/apps', body, http_headers(auth_token(opts))) + expect(response.code).to eq('201') + + expect(response.json_body['name']).to eq(app_name) + end + end + + describe 'org manager' do + setup = ->() { + let(:user) { VCAP::CloudController::User.make } + let(:org_guid) { create_org } + let(:space_guid) { create_space(org_guid) } + + before do + set_current_user_as_admin(iss: issuer) + + make_org_user(user, org_guid) + + response = make_put_request("/v2/organizations/#{org_guid}/managers/#{user.guid}", '', admin_headers) + expect(response.code).to eq('201') + + set_current_user(user, iss: issuer) + end + } + + it_behaves_like 'org reader', &setup + it_behaves_like 'org writer', &setup + it_behaves_like 'space reader', &setup + end + + describe 'org auditor' do + setup = ->() { + let(:user) { VCAP::CloudController::User.make } + let(:org_guid) { create_org } + let(:space_guid) { create_space(org_guid) } + + before do + set_current_user_as_admin(iss: issuer) + + make_org_user(user, org_guid) + + response = make_put_request("/v2/organizations/#{org_guid}/auditors/#{user.guid}", '', admin_headers) + expect(response.code).to eq('201') + + set_current_user(user, iss: issuer) + end + } + + it_behaves_like 'org reader', &setup + end + + describe 'org billing manager' do + setup = ->() { + let(:user) { VCAP::CloudController::User.make } + let(:org_guid) { create_org } + let(:space_guid) { create_space(org_guid) } + + before do + set_current_user_as_admin(iss: issuer) + + make_org_user(user, org_guid) + + response = make_put_request("/v2/organizations/#{org_guid}/billing_managers/#{user.guid}", '', admin_headers) + expect(response.code).to eq('201') + + set_current_user(user, iss: issuer) + end + } + + it_behaves_like 'org reader', &setup + end + + describe 'org user' do + setup = ->() { + let(:user) { VCAP::CloudController::User.make } + let(:org_guid) { create_org } + let(:space_guid) { create_space(org_guid) } + + before do + set_current_user_as_admin(iss: issuer) + + make_org_user(user, org_guid) + set_current_user(user, iss: issuer) + end + } + + it_behaves_like 'org reader', &setup + end + + describe 'space developer' do + setup = ->() { + let(:user) { VCAP::CloudController::User.make } + let(:org_guid) { create_org } + let(:space_guid) { create_space(org_guid) } + + before do + set_current_user_as_admin(iss: issuer) + + make_org_user(user, org_guid) + + response = make_put_request("/v2/spaces/#{space_guid}/developers/#{user.guid}", '', admin_headers) + expect(response.code).to eq('201') + + set_current_user(user, iss: issuer) + end + } + + it_behaves_like 'space reader', &setup + it_behaves_like 'space writer', &setup + end + + describe 'space manager' do + setup = ->() { + let(:user) { VCAP::CloudController::User.make } + let(:org_guid) { create_org } + let(:space_guid) { create_space(org_guid) } + + before do + set_current_user_as_admin(iss: issuer) + + make_org_user(user, org_guid) + + response = make_put_request("/v2/spaces/#{space_guid}/managers/#{user.guid}", '', admin_headers) + expect(response.code).to eq('201') + + set_current_user(user, iss: issuer) + end + } + + it_behaves_like 'space reader', &setup + end + + describe 'space auditor' do + setup = ->() { + let(:user) { VCAP::CloudController::User.make } + let(:org_guid) { create_org } + let(:space_guid) { create_space(org_guid) } + + before do + set_current_user_as_admin(iss: issuer) + + make_org_user(user, org_guid) + + response = make_put_request("/v2/spaces/#{space_guid}/auditors/#{user.guid}", '', admin_headers) + expect(response.code).to eq('201') + + set_current_user(user, iss: issuer) + end + } + + it_behaves_like 'space reader', &setup end def create_org @@ -768,7 +1057,7 @@ def create_org response = make_post_request('/v3/organizations', body, admin_headers) expect(response.code).to eq('201') - JSON.parse(response.body)['guid'] + response.json_body['guid'] end def create_space(org_guid) @@ -786,7 +1075,12 @@ def create_space(org_guid) response = make_post_request('/v3/spaces', body, admin_headers) expect(response.code).to eq('201') - JSON.parse(response.body)['guid'] + response.json_body['guid'] + end + + def make_org_user(user, org_guid) + response = make_put_request("/v2/organizations/#{org_guid}/users/#{user.guid}", {}.to_json, admin_headers) + expect(response.code).to eq('201') end end end diff --git a/spec/support/fake_uaa_server.rb b/spec/support/fake_uaa_server.rb index 004b8497456..3721f4d4972 100644 --- a/spec/support/fake_uaa_server.rb +++ b/spec/support/fake_uaa_server.rb @@ -32,11 +32,13 @@ def stop end class UAAIssuer < WEBrick::HTTPServlet::AbstractServlet + ISSUER = 'uaa_issuer'.freeze + # rubocop:disable all def do_GET(_, response) # rubocop:enable all response.status = 200 response['Content-Type'] = 'application/json' - response.body = { issuer: 'uaa_issuer' }.to_json + response.body = { issuer: ISSUER }.to_json end end diff --git a/spec/support/integration/http.rb b/spec/support/integration/http.rb index 84328405dd5..1638b15ba5a 100644 --- a/spec/support/integration/http.rb +++ b/spec/support/integration/http.rb @@ -2,18 +2,25 @@ require 'uri' module IntegrationHttp - def admin_token + def auth_token(opts) token = { + 'user_id' => opts[:user_id], 'aud' => 'cloud_controller', 'exp' => Time.now.utc.to_i + 10_000, - 'client_id' => Sham.guid, - 'scope' => ['cloud_controller.admin'], - 'iss' => 'uaa_issuer', + 'scope' => opts[:scope], + 'iss' => UAAIssuer::ISSUER, 'jti' => 'valid-jti', } CF::UAA::TokenCoder.encode(token, skey: 'tokensecret', algorithm: 'HS256') end + def admin_token + auth_token( + user_id: Sham.guid, + scope: ['cloud_controller.admin', 'cloud_controller.read', 'cloud_controller.write'], + ) + end + module JsonBody def json_body @json_body ||= JSON.parse(body) @@ -42,13 +49,20 @@ def make_post_request(path, data, headers={}, port=8181) response end - def make_put_request(path, data, headers={}) + def make_put_request(path, data='{}', headers={}) http = Net::HTTP.new('localhost', '8181') response = http.put(path, data, headers) response.extend(JsonBody) response end + def make_patch_request(path, data='{}', headers={}) + http = Net::HTTP.new('localhost', '8181') + response = http.patch(path, data, headers) + response.extend(JsonBody) + response + end + def make_delete_request(path, headers={}) http = Net::HTTP.new('localhost', '8181') response = http.delete(path, headers) diff --git a/spec/unit/lib/perm/client_spec.rb b/spec/unit/lib/perm/client_spec.rb index 981f13b61f8..2a3d64fd176 100644 --- a/spec/unit/lib/perm/client_spec.rb +++ b/spec/unit/lib/perm/client_spec.rb @@ -31,10 +31,18 @@ module VCAP::CloudController::Perm end describe '#create_org_role' do - it 'creates the correct role' do + it 'creates the correct role and creates associated permission' do subject.create_org_role(role: 'developer', org_id: org_id) - expect(client).to have_received(:create_role).with(role_name: "org-developer-#{org_id}") + expect(client).to have_received(:create_role).with( + role_name: "org-developer-#{org_id}", + permissions: [ + CloudFoundry::Perm::V1::Models::Permission.new( + name: 'org.developer', + resource_pattern: org_id.to_s + ) + ] + ) end it 'does not fail if the role already exists' do @@ -289,7 +297,15 @@ module VCAP::CloudController::Perm it 'creates the correct role' do subject.create_space_role(role: 'developer', space_id: space_id) - expect(client).to have_received(:create_role).with(role_name: "space-developer-#{space_id}") + expect(client).to have_received(:create_role).with( + role_name: "space-developer-#{space_id}", + permissions: [ + CloudFoundry::Perm::V1::Models::Permission.new( + name: 'space.developer', + resource_pattern: space_id.to_s + ) + ] + ) end it 'does not fail if the role already exists' do diff --git a/spec/unit/lib/perm/permissions_spec.rb b/spec/unit/lib/perm/permissions_spec.rb index 129913e5552..50ce79b3a8a 100644 --- a/spec/unit/lib/perm/permissions_spec.rb +++ b/spec/unit/lib/perm/permissions_spec.rb @@ -117,7 +117,7 @@ module VCAP::CloudController::Perm expected_permissions = [ { permission_name: 'org.manager', resource_id: org_id }, { permission_name: 'org.auditor', resource_id: org_id }, - { permission_name: 'org.member', resource_id: org_id }, + { permission_name: 'org.user', resource_id: org_id }, { permission_name: 'org.billing_manager', resource_id: org_id }, ] @@ -368,7 +368,7 @@ module VCAP::CloudController::Perm expected_permissions = [ { permission_name: 'org.manager', resource_id: 'some-org-id' }, { permission_name: 'org.auditor', resource_id: 'some-org-id' }, - { permission_name: 'org.member', resource_id: 'some-org-id' }, + { permission_name: 'org.user', resource_id: 'some-org-id' }, { permission_name: 'org.billing_manager', resource_id: 'some-org-id' }, ] diff --git a/spec/unit/lib/permissions/queryer_spec.rb b/spec/unit/lib/permissions/queryer_spec.rb index 3ec27703a1e..d23fc989454 100644 --- a/spec/unit/lib/permissions/queryer_spec.rb +++ b/spec/unit/lib/permissions/queryer_spec.rb @@ -43,8 +43,9 @@ module VCAP::CloudController allow(VCAP::CloudController::Permissions).to receive(:new).and_return(db_permissions) allow(VCAP::CloudController::Perm::Permissions).to receive(:new).and_return(perm_permissions) + allow(VCAP::CloudController::Science::Experiment).to receive(:raise_on_mismatches=) - queryer = Permissions::Queryer.build(perm_client, security_context, true, true) + queryer = Permissions::Queryer.build(perm_client, security_context, true, true, true) expect(VCAP::CloudController::Permissions).to have_received(:new).with(current_user) expect(VCAP::CloudController::Perm::Permissions).to have_received(:new).with( @@ -53,6 +54,7 @@ module VCAP::CloudController user_id: current_user_guid, issuer: issuer ) + expect(VCAP::CloudController::Science::Experiment).to have_received(:raise_on_mismatches=).with(true) expect(queryer.db_permissions).to eq(db_permissions) expect(queryer.perm_permissions).to eq(perm_permissions)