spec/unit/controllers/runtime/organizations_controller_spec.rb
require 'spec_helper'
## NOTICE: Prefer request specs over controller specs as per ADR #0003 ##
module VCAP::CloudController
RSpec.describe VCAP::CloudController::OrganizationsController do
let(:org) { Organization.make }
let(:assigner) { VCAP::CloudController::IsolationSegmentAssign.new }
let(:user_email) { Sham.email }
let(:uaa_client) { instance_double(UaaClient) }
before do
allow(UaaClient).to receive(:new).and_return(uaa_client)
TestConfig.override(kubernetes: {})
end
describe 'Query Parameters' do
it { expect(VCAP::CloudController::OrganizationsController).to be_queryable_by(:name) }
it { expect(VCAP::CloudController::OrganizationsController).to be_queryable_by(:space_guid) }
it { expect(VCAP::CloudController::OrganizationsController).to be_queryable_by(:user_guid) }
it { expect(VCAP::CloudController::OrganizationsController).to be_queryable_by(:manager_guid) }
it { expect(VCAP::CloudController::OrganizationsController).to be_queryable_by(:billing_manager_guid) }
it { expect(VCAP::CloudController::OrganizationsController).to be_queryable_by(:auditor_guid) }
it { expect(VCAP::CloudController::OrganizationsController).to be_queryable_by(:status) }
end
describe 'Attributes' do
it do
expect(VCAP::CloudController::OrganizationsController).to have_creatable_attributes(
{
name: { type: 'string', required: true },
billing_enabled: { type: 'bool', default: false },
status: { type: 'string', default: 'active' },
quota_definition_guid: { type: 'string' },
user_guids: { type: '[string]' },
manager_guids: { type: '[string]' },
billing_manager_guids: { type: '[string]' },
auditor_guids: { type: '[string]' },
app_event_guids: { type: '[string]' }
}
)
end
it do
expect(VCAP::CloudController::OrganizationsController).to have_updatable_attributes(
{
name: { type: 'string' },
billing_enabled: { type: 'bool' },
status: { type: 'string' },
quota_definition_guid: { type: 'string' },
user_guids: { type: '[string]' },
manager_guids: { type: '[string]' },
billing_manager_guids: { type: '[string]' },
auditor_guids: { type: '[string]' },
app_event_guids: { type: '[string]' },
space_guids: { type: '[string]' },
space_quota_definition_guids: { type: '[string]' },
default_isolation_segment_guid: { type: 'string' }
}
)
end
it 'can order by name and id when listing' do
expect(VCAP::CloudController::OrganizationsController.sortable_parameters).to match_array(%i[id name])
end
end
describe 'Permissions' do
include_context 'permissions'
before do
@obj_a = @org_a
@obj_b = @org_b
end
describe 'Org Level Permissions' do
before { set_current_user(member_a) }
describe 'OrgManager' do
let(:member_a) { @org_a_manager }
let(:member_b) { @org_b_manager }
include_examples 'permission enumeration', 'OrgManager',
name: 'organization',
path: '/v2/organizations',
enumerate: 1
it 'cannot update quota definition' do
quota = QuotaDefinition.make
expect(@org_a.quota_definition.guid).not_to eq(quota.guid)
put "/v2/organizations/#{@org_a.guid}", Oj.dump(quota_definition_guid: quota.guid)
@org_a.reload
expect(last_response.status).to eq(403)
expect(@org_a.quota_definition.guid).not_to eq(quota.guid)
end
it 'cannot update billing_enabled' do
billing_enabled_before = @org_a.billing_enabled
put "/v2/organizations/#{@org_a.guid}", Oj.dump(billing_enabled: !billing_enabled_before)
@org_a.reload
expect(last_response.status).to eq(403)
expect(@org_a.billing_enabled).to eq(billing_enabled_before)
end
end
describe 'OrgUser' do
let(:member_a) { @org_a_member }
let(:member_b) { @org_b_member }
include_examples 'permission enumeration', 'OrgUser',
name: 'organization',
path: '/v2/organizations',
enumerate: 1
end
describe 'BillingManager' do
let(:member_a) { @org_a_billing_manager }
let(:member_b) { @org_b_billing_manager }
include_examples 'permission enumeration', 'BillingManager',
name: 'organization',
path: '/v2/organizations',
enumerate: 1
end
describe 'Auditor' do
let(:member_a) { @org_a_auditor }
let(:member_b) { @org_b_auditor }
include_examples 'permission enumeration', 'Auditor',
name: 'organization',
path: '/v2/organizations',
enumerate: 1
end
end
end
describe 'Associations' do
it do
expect(VCAP::CloudController::OrganizationsController).to have_nested_routes(
{
spaces: %i[get put delete],
domains: %i[get delete],
private_domains: %i[get put delete],
users: %i[get put delete],
managers: %i[get put delete],
billing_managers: %i[get put delete],
auditors: %i[get put delete],
app_events: %i[get put delete],
space_quota_definitions: %i[get put delete]
}
)
end
end
describe 'setting the default isolation segment' do
let(:isolation_segment) { IsolationSegmentModel.make }
let(:isolation_segment2) { IsolationSegmentModel.make }
context 'when the user is neither admin nor org manager' do
let(:space) { Space.make(organization: org) }
let!(:user) { set_current_user(make_developer_for_space(space)) }
before do
assigner.assign(isolation_segment, [org])
end
it 'returns a 403' do
put "/v2/organizations/#{org.guid}", Oj.dump({
default_isolation_segment_guid: isolation_segment.guid
})
expect(last_response.status).to eq(403)
expect(decoded_response['error_code']).to match(/CF-NotAuthorized/)
end
end
context 'when the user is an org manager for the org' do
let(:user) { make_manager_for_org(org) }
before do
set_current_user(user)
end
context 'when the isolation segment does not exist' do
it 'returns a 404' do
put "/v2/organizations/#{org.guid}", Oj.dump({
default_isolation_segment_guid: 'bogus-guid'
})
expect(last_response.status).to eq(404)
end
end
context 'when the isolation segment is not in the allowed list' do
it 'returns a 400' do
put "/v2/organizations/#{org.guid}", Oj.dump({
default_isolation_segment_guid: isolation_segment.guid
})
expect(last_response.status).to eq(400)
end
end
context 'when the isolation segment is in the allowed list' do
before do
assigner.assign(isolation_segment, [org])
assigner.assign(isolation_segment2, [org])
org.update(default_isolation_segment_model: isolation_segment)
org.reload
expect(org.default_isolation_segment_model).to eq(isolation_segment)
end
it 'sets the isolation segment as the org default' do
put "/v2/organizations/#{org.guid}", Oj.dump({
default_isolation_segment_guid: isolation_segment2.guid
})
expect(last_response.status).to eq(201)
org.reload
expect(org.default_isolation_segment_model).to eq(isolation_segment2)
end
context 'when the segment is already the default isolation segment' do
it 'leaves the default unchanged' do
put "/v2/organizations/#{org.guid}", Oj.dump({
default_isolation_segment_guid: isolation_segment.guid
})
expect(last_response.status).to eq(201)
org.reload
expect(org.default_isolation_segment_model).to eq(isolation_segment)
end
end
end
end
context 'when the user is an admin' do
let(:user) { set_current_user(User.make) }
before do
set_current_user_as_admin
assigner.assign(isolation_segment, [org])
assigner.assign(isolation_segment2, [org])
end
it 'sets the isolation segment as the org default' do
put "/v2/organizations/#{org.guid}", Oj.dump({
default_isolation_segment_guid: isolation_segment2.guid
})
expect(last_response.status).to eq(201)
org.reload
expect(org.default_isolation_segment_model).to eq(isolation_segment2)
end
end
end
describe 'removing the default isolation segment' do
let(:isolation_segment) { IsolationSegmentModel.make }
let(:isolation_segment2) { IsolationSegmentModel.make }
context 'when the user is neither admin nor org manager' do
let(:space) { Space.make(organization: org) }
let!(:user) { set_current_user(make_developer_for_space(space)) }
before do
assigner.assign(isolation_segment, [org])
end
it 'returns a 403' do
delete "/v2/organizations/#{org.guid}/default_isolation_segment"
expect(last_response.status).to eq(403)
expect(decoded_response['error_code']).to match(/CF-NotAuthorized/)
end
end
context 'when the user is an org manager for the org' do
let(:user) { make_manager_for_org(org) }
before do
set_current_user(user)
end
context 'when the isolation segment does not exist' do
it 'returns a 404' do
put "/v2/organizations/#{org.guid}", Oj.dump({
default_isolation_segment_guid: 'bogus-guid'
})
expect(last_response.status).to eq(404)
end
end
context 'when the isolation segment is not in the allowed list' do
it 'returns a 400' do
put "/v2/organizations/#{org.guid}", Oj.dump({
default_isolation_segment_guid: isolation_segment.guid
})
expect(last_response.status).to eq(400)
end
end
context 'when the isolation segment is in the allowed list' do
before do
assigner.assign(isolation_segment, [org])
assigner.assign(isolation_segment2, [org])
org.update(default_isolation_segment_model: isolation_segment)
org.reload
expect(org.default_isolation_segment_model).to eq(isolation_segment)
end
it 'sets the isolation segment as the org default' do
put "/v2/organizations/#{org.guid}", Oj.dump({
default_isolation_segment_guid: isolation_segment2.guid
})
expect(last_response.status).to eq(201)
org.reload
expect(org.default_isolation_segment_model).to eq(isolation_segment2)
end
context 'when the segment is already the default isolation segment' do
it 'leaves the default unchanged' do
put "/v2/organizations/#{org.guid}", Oj.dump({
default_isolation_segment_guid: isolation_segment.guid
})
expect(last_response.status).to eq(201)
org.reload
expect(org.default_isolation_segment_model).to eq(isolation_segment)
end
end
end
end
context 'when the user is an admin' do
let(:user) { set_current_user(User.make) }
before do
set_current_user_as_admin
assigner.assign(isolation_segment, [org])
assigner.assign(isolation_segment2, [org])
end
it 'sets the isolation segment as the org default' do
put "/v2/organizations/#{org.guid}", Oj.dump({
default_isolation_segment_guid: isolation_segment2.guid
})
expect(last_response.status).to eq(201)
org.reload
expect(org.default_isolation_segment_model).to eq(isolation_segment2)
end
end
end
describe 'POST /v2/organizations' do
context 'when user_org_creation feature_flag is disabled' do
before do
FeatureFlag.make(name: 'user_org_creation', enabled: false)
end
context 'as a non admin' do
it 'returns NotAuthorized' do
set_current_user(User.make)
post '/v2/organizations', Oj.dump({ name: 'my-org-name' })
expect(last_response.status).to eq(403)
expect(decoded_response['error_code']).to match(/CF-NotAuthorized/)
end
end
context 'as an admin' do
before do
set_current_user_as_admin(email: user_email)
end
it 'does not add creator as an org manager' do
post '/v2/organizations', Oj.dump({ name: 'my-org-name' })
expect(last_response.status).to eq(201)
org = Organization.find(name: 'my-org-name')
expect(org.managers.count).to eq(0)
end
it 'does not set the default isolation segment on creation' do
isolation_segment = IsolationSegmentModel.make
post '/v2/organizations', Oj.dump({
name: 'my-org-name',
isolation_segment_guid: isolation_segment.guid
})
expect(last_response.status).to eq(201)
org = Organization.find(name: 'my-org-name')
expect(org.default_isolation_segment_model).to be_nil
end
it 'creates an audit event of type audit.organization.create' do
event = Event.find(type: 'audit.organization.create')
expect(event).to be_nil
post '/v2/organizations', Oj.dump({ name: 'my-org-name' })
org = Organization.find(name: 'my-org-name')
event = Event.find(type: 'audit.organization.create', actee: org.guid)
expect(event).not_to be_nil
expect(event.actee).to eq(org.guid)
expect(event.actor_name).to eq(SecurityContext.current_user_email)
end
end
end
context 'when user_org_creation feature_flag is enabled' do
before do
FeatureFlag.make(name: 'user_org_creation', enabled: true)
end
context 'as a non admin' do
let(:user) { User.make }
before do
set_current_user(user, email: user_email)
end
it 'adds creator as an org manager' do
post '/v2/organizations', Oj.dump({ name: 'my-org-name' })
expect(last_response.status).to eq(201)
org = Organization.find(name: 'my-org-name')
expect(org.managers).to eq([user])
expect(org.users).to eq([user])
end
it 'creates an audit event of type audit.organization.create' do
event = Event.find(type: 'audit.organization.create')
expect(event).to be_nil
post '/v2/organizations', Oj.dump({ name: 'my-org-name' })
org = Organization.find(name: 'my-org-name')
event = Event.find(type: 'audit.organization.create', actee: org.guid)
expect(event).not_to be_nil
expect(event.actee).to eq(org.guid)
expect(event.actor_name).to eq(SecurityContext.current_user_email)
end
end
end
context 'setting roles at org creation time' do
let(:other_user) { User.make }
let(:name) { 'myorg' }
before do
set_current_user_as_admin
end
context 'assigning an org manager' do
it 'records an event of type audit.user.organization_manager_add' do
event = Event.find(type: 'audit.user.organization_manager_add', actee: other_user.guid)
expect(event).to be_nil
request_body = { name: name, manager_guids: [other_user.guid] }.to_json
post '/v2/organizations', request_body
expect(last_response).to have_status_code(201)
event = Event.find(type: 'audit.user.organization_manager_add', actee: other_user.guid)
expect(event).not_to be_nil
end
end
context 'assigning an auditor' do
it 'records an event of type audit.user.organization_auditor_add' do
event = Event.find(type: 'audit.user.organization_auditor_add', actee: other_user.guid)
expect(event).to be_nil
request_body = { name: name, auditor_guids: [other_user.guid] }.to_json
post '/v2/organizations', request_body
expect(last_response).to have_status_code(201)
event = Event.find(type: 'audit.user.organization_auditor_add', actee: other_user.guid)
expect(event).not_to be_nil
end
end
context 'assigning a billing manager' do
it 'records an event of type audit.user.organization_billing_manager_add' do
event = Event.find(type: 'audit.user.organization_billing_manager_add', actee: other_user.guid)
expect(event).to be_nil
request_body = { name: name, billing_manager_guids: [other_user.guid] }.to_json
post '/v2/organizations', request_body
expect(last_response).to have_status_code(201)
event = Event.find(type: 'audit.user.organization_billing_manager_add', actee: other_user.guid)
expect(event).not_to be_nil
end
end
context 'assigning a user' do
it 'records an event of type audit.user.organization_user_add' do
event = Event.find(type: 'audit.user.organization_user_add', actee: other_user.guid)
expect(event).to be_nil
request_body = { name: name, user_guids: [other_user.guid] }.to_json
post '/v2/organizations', request_body
expect(last_response).to have_status_code(201)
event = Event.find(type: 'audit.user.organization_user_add', actee: other_user.guid)
expect(event).not_to be_nil
end
end
end
end
describe 'PUT /v2/organizations/:guid' do
let(:org) { Organization.make }
before do
set_current_user_as_admin(email: user_email)
end
it 'creates an audit event of type audit.organization.update' do
event = Event.find(type: 'audit.organization.update')
expect(event).to be_nil
put "/v2/organizations/#{org.guid}", Oj.dump({ name: 'another-name' })
expect(last_response.status).to eq(201)
event = Event.find(type: 'audit.organization.update', actee: org.guid)
expect(event).not_to be_nil
expect(event.actee).to eq(org.guid)
expect(event.actor_name).to eq(SecurityContext.current_user_email)
end
end
describe 'PUT /v2/organizations/:guid' do
context 'setting roles at org update time' do
let(:other_user) { User.make }
let(:name) { 'myorg' }
let(:uri) { "/v2/organizations/#{org.guid}" }
before do
set_current_user_as_admin
end
context 'assigning an org manager' do
it 'records an event of type audit.user.organization_manager_add' do
event = Event.find(type: 'audit.user.organization_manager_add', actee: other_user.guid)
expect(event).to be_nil
request_body = { name: name, manager_guids: [other_user.guid] }.to_json
put uri, request_body
expect(last_response).to have_status_code(201)
expect(org.managers).to include(other_user)
event = Event.find(type: 'audit.user.organization_manager_add', actee: other_user.guid)
expect(event).not_to be_nil
end
context 'when there is already another org manager' do
let(:mgr) { User.make }
before do
org.add_manager(mgr)
end
it 'does not record an event for existing org managers' do
request_body = { name: name, manager_guids: [other_user.guid, mgr.guid] }.to_json
put uri, request_body
expect(last_response).to have_status_code(201)
event = Event.find(type: 'audit.user.organization_manager_add', actee: mgr.guid)
expect(event).to be_nil
end
end
context 'assigning an manager to a user that does not exist' do
it 'returns an invalid relations error' do
request_body = { name: name, manager_guids: ['bogus-guid'] }.to_json
put uri, request_body
expect(last_response).to have_status_code(400)
expect(decoded_response['code']).to eq(1002)
end
end
end
context 'deassigning an org manager' do
let(:another_user) { User.make }
before do
org.add_manager(other_user)
org.add_manager(another_user)
end
it 'records an event of type audit.user.organization_manager_remove' do
event = Event.find(type: 'audit.user.organization_manager_remove', actee: other_user.guid)
expect(event).to be_nil
request_body = { name: name, manager_guids: [another_user.guid] }.to_json
put uri, request_body
expect(last_response).to have_status_code(201)
org.reload
expect(org.managers).not_to include(other_user)
event = Event.find(type: 'audit.user.organization_manager_remove', actee: other_user.guid)
expect(event).not_to be_nil
end
end
context 'assigning an auditor' do
it 'records an event of type audit.user.organization_auditor_add' do
event = Event.find(type: 'audit.user.organization_auditor_add', actee: other_user.guid)
expect(event).to be_nil
request_body = { name: name, auditor_guids: [other_user.guid] }.to_json
put uri, request_body
expect(last_response).to have_status_code(201)
expect(org.auditors).to include(other_user)
event = Event.find(type: 'audit.user.organization_auditor_add', actee: other_user.guid)
expect(event).not_to be_nil
end
context 'assigning an auditor to a user that does not exist' do
it 'returns an invalid relations error' do
request_body = { name: name, auditor_guids: ['bogus-guid'] }.to_json
put uri, request_body
expect(last_response).to have_status_code(400)
expect(decoded_response['code']).to eq(1002)
end
end
end
context 'deassigning an auditor' do
before do
org.add_auditor(other_user)
end
it 'records an event of type audit.user.organization_auditor_remove' do
event = Event.find(type: 'audit.user.organization_auditor_remove', actee: other_user.guid)
expect(event).to be_nil
request_body = { name: name, auditor_guids: [] }.to_json
put uri, request_body
expect(last_response).to have_status_code(201)
org.reload
expect(org.auditors).not_to include(other_user)
event = Event.find(type: 'audit.user.organization_auditor_remove', actee: other_user.guid)
expect(event).not_to be_nil
end
end
context 'assigning a billing manager' do
it 'records an event of type audit.user.organization_billing_manager_add' do
event = Event.find(type: 'audit.user.organization_billing_manager_add', actee: other_user.guid)
expect(event).to be_nil
request_body = { name: name, billing_manager_guids: [other_user.guid] }.to_json
put uri, request_body
expect(last_response).to have_status_code(201)
expect(org.billing_managers).to include(other_user)
event = Event.find(type: 'audit.user.organization_billing_manager_add', actee: other_user.guid)
expect(event).not_to be_nil
end
context 'assigning an billing_manager to a user that does not exist' do
it 'returns an invalid relations error' do
request_body = { name: name, billing_manager_guids: ['bogus-guid'] }.to_json
put uri, request_body
expect(last_response).to have_status_code(400)
expect(decoded_response['code']).to eq(1002)
end
end
end
context 'deassigning a billing manager' do
before do
org.add_billing_manager(other_user)
end
it 'records an event of type audit.user.organization_billing_manager_remove' do
event = Event.find(type: 'audit.user.organization_billing_manager_remove', actee: other_user.guid)
expect(event).to be_nil
request_body = { name: name, billing_manager_guids: [] }.to_json
put uri, request_body
expect(last_response).to have_status_code(201)
org.reload
expect(org.billing_managers).not_to include(other_user)
event = Event.find(type: 'audit.user.organization_billing_manager_remove', actee: other_user.guid)
expect(event).not_to be_nil
end
end
context 'assigning a user' do
it 'records an event of type audit.user.organization_user_add' do
event = Event.find(type: 'audit.user.organization_user_add', actee: other_user.guid)
expect(event).to be_nil
request_body = { name: name, user_guids: [other_user.guid] }.to_json
put uri, request_body
expect(last_response).to have_status_code(201)
expect(org.users).to include(other_user)
event = Event.find(type: 'audit.user.organization_user_add', actee: other_user.guid)
expect(event).not_to be_nil
end
context 'assigning an user to a user that does not exist' do
it 'returns an invalid relations error' do
request_body = { name: name, user_guids: ['bogus-guid'] }.to_json
put uri, request_body
expect(last_response).to have_status_code(400)
expect(decoded_response['code']).to eq(1002)
end
end
end
context 'removing a user' do
before do
org.add_user(other_user)
end
it 'records an event of type audit.user.organization_user_remove' do
event = Event.find(type: 'audit.user.organization_user_remove', actee: other_user.guid)
expect(event).to be_nil
request_body = { name: name, user_guids: [] }.to_json
put uri, request_body
expect(last_response).to have_status_code(201)
org.reload
expect(org.users).not_to include(other_user)
event = Event.find(type: 'audit.user.organization_user_remove', actee: other_user.guid)
expect(event).not_to be_nil
end
end
end
end
describe 'GET /v2/organizations/:guid/user_roles' do
context 'when the user is admin' do
let(:mgr) { User.make(guid: 'mgr-lemon') }
let(:user) { User.make(guid: 'user-lime') }
let(:org) { Organization.make(manager_guids: [mgr.guid], user_guids: [mgr.guid, user.guid]) }
before do
allow(uaa_client).to receive(:usernames_for_ids).and_return({})
end
it 'returns a 200 and the expected users and roles' do
set_current_user_as_admin
get "/v2/organizations/#{org.guid}/user_roles"
expect(last_response.status).to eq(200), last_response.body
expect(parsed_response['resources'].size).to eq(2)
parts = parsed_response['resources'].map { |res| [res['metadata']['guid'], res['entity']['organization_roles'].sort] }
expect(parts).to contain_exactly([mgr.guid, %w[org_manager org_user]], [user.guid, %w[org_user]])
end
it 'supports querying by user' do
set_current_user_as_admin
get "/v2/organizations/#{org.guid}/user_roles?q=user_guid:#{user.guid}"
expect(last_response.status).to eq(200), last_response.body
expect(parsed_response['resources'].size).to eq(1)
parts = parsed_response['resources'].map { |res| [res['metadata']['guid'], res['entity']['organization_roles'].sort] }
expect(parts).to contain_exactly([user.guid, %w[org_user]])
end
it 'supports querying for the manager' do
set_current_user_as_admin
get "/v2/organizations/#{org.guid}/user_roles?q=user_guid:#{mgr.guid}"
expect(last_response.status).to eq(200), last_response.body
expect(parsed_response['resources'].size).to eq(1)
parts = parsed_response['resources'].map { |res| [res['metadata']['guid'], res['entity']['organization_roles'].sort] }
expect(parts).to contain_exactly([mgr.guid, %w[org_manager org_user]])
end
end
context 'for a suspended organization as manager' do
let(:mgr) { User.make(guid: 'mgr-lemon') }
let(:org) { Organization.make(manager_guids: [mgr.guid], user_guids: [mgr.guid]) }
before do
allow(uaa_client).to receive(:usernames_for_ids).and_return({})
org.update(status: 'suspended')
end
it 'returns a 200 and the expected users and roles' do
set_current_user(mgr)
get "/v2/organizations/#{org.guid}/user_roles"
expect(last_response.status).to eq(200), last_response.body
expect(parsed_response['resources'].size).to eq(1)
parts = parsed_response['resources'].map { |res| [res['metadata']['guid'], res['entity']['organization_roles'].sort] }
expect(parts).to contain_exactly([mgr.guid, %w[org_manager org_user]])
end
end
context 'for an organization that does not exist' do
it 'returns a 404' do
set_current_user_as_admin
get '/v2/organizations/foobar/user_roles'
expect(last_response.status).to eq(404)
end
end
context 'when the user does not have permissions to read' do
it 'returns a 403' do
set_current_user(User.make)
get "/v2/organizations/#{org.guid}/user_roles"
expect(last_response.status).to eq(403)
end
end
end
describe 'GET', '/v2/organizations/:guid/services' do
let(:other_org) { Organization.make }
let(:space_one) { Space.make(organization: org) }
let(:user) { make_developer_for_space(space_one) }
before do
user.add_organization(other_org)
space_one.add_developer(user)
set_current_user(user)
end
def decoded_guids
decoded_response['resources'].map { |r| r['metadata']['guid'] }
end
context 'with an offering that has private plans' do
before do
@service = Service.make(active: true)
@service_plan = ServicePlan.make(service: @service, public: false)
ServicePlanVisibility.make(service_plan: @service.service_plans.first, organization: org)
end
it "removes the offering when the org does not have access to any of the service's plans" do
get "/v2/organizations/#{other_org.guid}/services"
expect(last_response).to be_ok
expect(decoded_guids).not_to include(@service.guid)
end
it "returns the offering when the org has access to one of the service's plans" do
get "/v2/organizations/#{org.guid}/services"
expect(last_response).to be_ok
expect(decoded_guids).to include(@service.guid)
end
it 'includes plans that are visible to the org' do
get "/v2/organizations/#{org.guid}/services?inline-relations-depth=1"
expect(last_response).to be_ok
service = decoded_response.fetch('resources').fetch(0)
service_plans = service.fetch('entity').fetch('service_plans')
expect(service_plans.length).to eq(1)
expect(service_plans.first.fetch('metadata').fetch('guid')).to eq(@service_plan.guid)
expect(service_plans.first.fetch('metadata').fetch('url')).to eq("/v2/service_plans/#{@service_plan.guid}")
end
it 'excludes plans that are not visible to the org' do
public_service_plan = ServicePlan.make(service: @service, public: true)
get "/v2/organizations/#{other_org.guid}/services?inline-relations-depth=1"
expect(last_response).to be_ok
service = decoded_response.fetch('resources').fetch(0)
service_plans = service.fetch('entity').fetch('service_plans')
expect(service_plans.length).to eq(1)
expect(service_plans.first.fetch('metadata').fetch('guid')).to eq(public_service_plan.guid)
end
end
describe 'get /v2/organizations/:guid/services?q=active:<t|f>' do
before do
@active = Array.new(3) { Service.make(active: true).tap { |svc| ServicePlan.make(service: svc) } }
@inactive = Array.new(2) { Service.make(active: false).tap { |svc| ServicePlan.make(service: svc) } }
end
it 'can remove inactive services' do
get "/v2/organizations/#{org.guid}/services?q=active:t"
expect(last_response).to be_ok
expect(decoded_guids).to match_array(@active.map(&:guid))
end
it 'can only get inactive services' do
get "/v2/organizations/#{org.guid}/services?q=active:f"
expect(last_response).to be_ok
expect(decoded_guids).to match_array(@inactive.map(&:guid))
end
end
describe 'get /v2/organizations/:guid/services?q=service_broker_guid:<broker.guid>' do
context 'with an offering that has public plans' do
let(:broker) { ServiceBroker.make }
let(:service) { Service.make(service_broker: broker, active: true) }
let(:service_plan) { ServicePlan.make(service: service, public: false) }
before do
service.service_plans << service_plan
ServicePlanVisibility.make(service_plan: service.service_plans.first, organization: org)
end
it "returns the org's service" do
get "/v2/organizations/#{org.guid}/services?q=service_broker_guid:#{broker.guid}"
expect(last_response.status).to eq(200), last_response.body
expect(decoded_guids).to include(service.guid)
end
end
end
end
describe 'GET /v2/organizations/:guid/memory_usage' do
before do
space = Space.make(organization: org)
ProcessModelFactory.make(space: space, memory: 200, instances: 2, state: 'STARTED', type: 'worker')
end
context 'for an organization that does not exist' do
it 'returns a 404' do
set_current_user_as_admin
get '/v2/organizations/foobar/memory_usage'
expect(last_response.status).to eq(404)
end
end
context 'when the user does not have permissions to read' do
it 'returns a 403' do
set_current_user(User.make)
get "/v2/organizations/#{org.guid}/memory_usage"
expect(last_response.status).to eq(403)
end
end
it 'calls the organization memory usage calculator' do
set_current_user_as_admin
get "/v2/organizations/#{org.guid}/memory_usage"
expect(last_response.status).to eq(200)
expect(Oj.load(last_response.body)).to eq({ 'memory_usage_in_mb' => 400 })
end
end
describe 'GET /v2/organizations/:guid/instance_usage' do
context 'for an organization that does not exist' do
it 'returns a 404' do
set_current_user_as_admin
get '/v2/organizations/foobar/instance_usage'
expect(last_response.status).to eq(404)
end
end
context 'when the user does not have permissions to read' do
it 'returns a 403' do
set_current_user(User.make)
get "/v2/organizations/#{org.guid}/instance_usage"
expect(last_response.status).to eq(403)
end
end
it 'calls the organization instance usage calculator' do
set_current_user_as_admin
allow(OrganizationInstanceUsageCalculator).to receive(:get_instance_usage).and_return(2)
get "/v2/organizations/#{org.guid}/instance_usage"
expect(last_response.status).to eq(200)
expect(OrganizationInstanceUsageCalculator).to have_received(:get_instance_usage).with(org)
expect(Oj.load(last_response.body)).to eq({ 'instance_usage' => 2 })
end
end
describe 'GET /v2/organizations/:guid/domains' do
let(:organization) { Organization.make }
let(:manager) { make_manager_for_org(organization) }
before do
PrivateDomain.make(owning_organization: organization)
set_current_user(manager)
end
it 'returns the private domains associated with the organization and all shared domains' do
get "/v2/organizations/#{organization.guid}/domains"
expect(last_response.status).to eq(200)
resources = decoded_response.fetch('resources')
guids = resources.map { |x| x['metadata']['guid'] }
expect(guids).to match_array(organization.domains.map(&:guid))
end
context 'space roles' do
let(:organization) { Organization.make }
let(:space) { Space.make(organization:) }
context 'space developers without org role' do
let(:space_developer) { make_developer_for_space(space) }
before { set_current_user(space_developer) }
it 'returns private domains' do
private_domain = PrivateDomain.make(owning_organization: organization)
get "/v2/organizations/#{organization.guid}/domains"
expect(last_response.status).to eq(200)
guids = decoded_response.fetch('resources').map { |x| x['metadata']['guid'] }
expect(guids).to include(private_domain.guid)
end
end
end
end
describe 'Deprecated endpoints' do
describe 'GET /v2/organizations/:guid/domains' do
it 'is deprecated' do
set_current_user_as_admin
get "/v2/organizations/#{org.guid}/domains"
expect(last_response).to be_a_deprecated_response
end
end
end
describe 'Removing a user from the organization' do
let(:mgr) { User.make }
let(:user) { User.make }
let(:org_users) { [user.guid] }
let(:org) { Organization.make(manager_guids: org_managers, user_guids: org_users) }
let(:org_managers) { [mgr.guid] }
let(:org_space_empty) { Space.make(organization: org) }
let(:org_space_full) { Space.make(organization: org, manager_guids: [user.guid], developer_guids: [user.guid], auditor_guids: [user.guid]) }
before { set_current_user_as_admin }
context 'DELETE /v2/organizations/org_guid/users/user_guid' do
context 'without the recursive flag' do
context 'a single organization' do
context 'as an admin' do
it 'removes the user from the organization if that user does not belong to any space' do
org.add_space(org_space_empty)
expect(org.users).to include(user)
delete "/v2/organizations/#{org.guid}/users/#{user.guid}"
expect(last_response.status).to be(204)
org.refresh
expect(org.user_guids).not_to include(user)
end
it 'does not remove the user from the organization if that user belongs to a space associated with the organization' do
org.add_space(org_space_full)
delete "/v2/organizations/#{org.guid}/users/#{user.guid}"
expect(last_response.status).to be(400)
org.refresh
expect(org.users).to include(user)
end
end
context 'as an organization user' do
before do
set_current_user(user)
end
context 'when org has at least one more user' do
let(:org_users) { [user.guid, mgr.guid] }
it 'allows the user to remove themselves from the organization' do
org.add_space(org_space_empty)
expect(org.users).to include(user)
delete "/v2/organizations/#{org.guid}/users/#{user.guid}"
expect(last_response.status).to be(204)
org.refresh
expect(org.user_guids).not_to include(user.guid)
end
end
context 'when the user is the last user in the org' do
let(:org_users) { [user.guid] }
it 'does NOT allow the user to remove themselves' do
org.add_space(org_space_empty)
expect(org.users).to include(user)
delete "/v2/organizations/#{org.guid}/users/#{user.guid}"
expect(last_response.status).to be(403)
org.refresh
expect(org.user_guids).to include(user.guid)
end
end
end
end
end
context 'with recursive flag' do
context 'a single organization' do
it 'removes the user from each space that is associated with the organization' do
org.add_space(org_space_full)
%w[developers auditors managers].each { |type| expect(org_space_full.send(type)).to include(user) }
delete "/v2/organizations/#{org.guid}/users/#{user.guid}?recursive=true"
expect(last_response.status).to be(204)
org_space_full.refresh
%w[developers auditors managers].each { |type| expect(org_space_full.send(type)).not_to include(user) }
end
it 'removes the user from the organization' do
org.add_space(org_space_full)
expect(org.users).to include(user)
delete "/v2/organizations/#{org.guid}/users/#{user.guid}?recursive=true"
expect(last_response.status).to be(204)
org.refresh
expect(org.users).not_to include(user)
end
it 'removes all org roles from the user in the organization' do
org.add_space(org_space_full)
org.add_manager(user)
expect(org.users).to include(user)
expect(org.managers).to include(user)
delete "/v2/organizations/#{org.guid}/users/#{user.guid}?recursive=true"
expect(last_response.status).to be(204)
org.refresh
expect(org.users).not_to include(user)
expect(org.managers).not_to include(user)
end
end
context 'multiple organizations' do
let(:org_2) { Organization.make(user_guids: [user.guid]) }
let(:org2_space) { Space.make(organization: org_2, developer_guids: [user.guid]) }
it 'removes all user roles from one organization, but no the other' do
org.add_space(org_space_full)
org_2.add_space(org2_space)
[org, org_2].each { |organization| expect(organization.users).to include(user) }
org.add_manager(user)
org.add_billing_manager(user)
expect(org.managers).to include(user)
expect(org.billing_managers).to include(user)
delete "/v2/organizations/#{org.guid}/users/#{user.guid}?recursive=true"
expect(last_response.status).to be(204)
[org, org_2].each(&:refresh)
expect(org.users).not_to include(user)
expect(org.managers).not_to include(user)
expect(org.billing_managers).not_to include(user)
expect(org_2.users).to include(user)
end
it 'removes a user from each space associated with the organization being removed, but not the other' do
org.add_space(org_space_full)
org_2.add_space(org2_space)
%w[developers auditors managers].each { |type| expect(org_space_full.send(type)).to include(user) }
expect(org2_space.developers).to include(user)
delete "/v2/organizations/#{org.guid}/users/#{user.guid}?recursive=true"
expect(last_response.status).to be(204)
[org_space_full, org2_space].each(&:refresh)
%w[developers auditors managers].each { |type| expect(org_space_full.send(type)).not_to include(user) }
expect(org2_space.developers).to include(user)
end
end
end
describe 'removing the last user' do
let(:org) { Organization.make }
let(:user) { User.make }
before do
org.add_user(user)
end
context 'as an admin' do
it 'is allowed' do
set_current_user_as_admin
delete "/v2/organizations/#{org.guid}/users/#{user.guid}"
expect(last_response.status).to eq(204)
org.reload
expect(org.users).not_to include(user)
end
end
context 'as a user' do
it 'is not allowed' do
set_current_user(user)
delete "/v2/organizations/#{org.guid}/users/#{user.guid}"
expect(last_response.status).to eq(403)
expect(decoded_response['code']).to eq(30_006)
org.reload
expect(org.users).to include(user)
end
end
end
end
context 'PUT /v2/organizations/org_guid' do
it 'removes the user if that user does not belong to any space associated with the organization' do
org.add_space(org_space_empty)
expect(org.users).to include(user)
put "/v2/organizations/#{org.guid}", Oj.dump('user_guids' => [])
org.refresh
expect(org.users).not_to include(user)
end
it 'does not remove the user if the user belongs to a space within the org' do
org.add_space(org_space_full)
put "/v2/organizations/#{org.guid}", Oj.dump('user_guids' => [])
expect(last_response.status).to be(400)
org.refresh
expect(org.users).to include(user)
end
end
context 'PUT /v2/organizations/org_guid/private_domains/domain_guid' do
context 'when PrivateDomain is shared' do
let(:org1) { Organization.make }
let(:org2) { Organization.make }
let(:private_domain) { PrivateDomain.make(owning_organization: org1) }
let(:user) { User.make }
let(:manager) { User.make }
let(:target_manager) { User.make }
before do
org1.add_manager(manager)
org2.add_manager(manager)
org1.add_auditor(target_manager)
org2.add_manager(target_manager)
end
it 'allows a user who is a manager of both the target org and the owning org to share a private domain' do
set_current_user(manager)
put "/v2/organizations/#{org2.guid}/private_domains/#{private_domain.guid}"
expect(last_response.status).to eq(201)
expect(org2.private_domains).to include(private_domain)
end
it 'does not allow the user to share domains to an org that the user is not a org manager of' do
set_current_user(user)
put "/v2/organizations/#{org2.guid}/private_domains/#{private_domain.guid}"
expect(last_response.status).to eq(403)
end
it 'does not allow the user to share domains that user is not a manager in the owning organization of' do
set_current_user(target_manager)
put "/v2/organizations/#{org2.guid}/private_domains/#{private_domain.guid}"
expect(last_response.status).to eq(403)
end
end
end
end
describe 'GET /v2/organizations/:guid/users' do
let(:mgr) { User.make }
let(:user) { User.make }
let(:org) { Organization.make(manager_guids: [mgr.guid], user_guids: [mgr.guid, user.guid]) }
before do
allow(uaa_client).to receive(:usernames_for_ids).and_return({})
end
it 'allows org managers' do
set_current_user(mgr)
get "/v2/organizations/#{org.guid}/users"
expect(last_response.status).to eq(200)
end
it 'allows org users' do
set_current_user(user)
get "/v2/organizations/#{org.guid}/users"
expect(last_response.status).to eq(200)
end
end
describe 'when the default quota does not exist' do
before do
QuotaDefinition.default.organizations.each(&:destroy)
QuotaDefinition.default.destroy
set_current_user(User.make)
end
it 'returns an OrganizationInvalid message' do
post '/v2/organizations', Oj.dump({ name: 'gotcha' })
expect(last_response.status).to be(400)
expect(decoded_response['code']).to eq(30_001)
expect(decoded_response['description']).to include('Quota Definition could not be found')
end
end
describe 'deleting an organization' do
let(:org) { Organization.make }
before do
set_current_user_as_admin(email: user_email)
end
it 'deletes the org' do
delete "/v2/organizations/#{org.guid}"
expect(last_response).to have_status_code 204
expect { org.refresh }.to raise_error Sequel::Error, 'Record not found'
end
it 'creates an event of type audit.organization.delete-request' do
event = Event.find(type: 'audit.organization.delete-request', actee: org.guid)
expect(event).to be_nil
delete "/v2/organizations/#{org.guid}"
event = Event.find(type: 'audit.organization.delete-request', actee: org.guid)
expect(event).not_to be_nil
expect(event.actee).to eq(org.guid)
expect(event.actor_name).to eq(SecurityContext.current_user_email)
end
context 'with recursive=false' do
it 'raises an error when the org has anything but labels it' do
Space.make(organization: org)
delete "/v2/organizations/#{org.guid}"
expect(last_response).to have_status_code 400
expect(decoded_response['error_code']).to eq 'CF-AssociationNotEmpty'
end
it 'deletes the associated labels' do
label = OrganizationLabelModel.make(key_name: 'some_key', value: 'some_value', resource_guid: org.guid)
delete "/v2/organizations/#{org.guid}"
expect(last_response).to have_status_code(204)
expect(Organization.find(guid: org.guid)).to be_nil
expect(OrganizationLabelModel.find(guid: label.guid))
end
end
context 'with recursive=true' do
it 'deletes the org and all of its spaces' do
space_1 = Space.make(organization: org)
space_2 = Space.make(organization: org)
delete "/v2/organizations/#{org.guid}?recursive=true"
expect(last_response).to have_status_code 204
expect { org.refresh }.to raise_error Sequel::Error, 'Record not found'
expect { space_1.refresh }.to raise_error Sequel::Error, 'Record not found'
expect { space_2.refresh }.to raise_error Sequel::Error, 'Record not found'
end
context 'when one of the spaces has a v3 app in it' do
let!(:space) { Space.make(organization: org) }
let!(:app_model) { AppModel.make(space_guid: space.guid) }
let(:user) { User.make }
before { set_current_user(user, admin: true) }
it 'deletes the v3 app' do
delete "/v2/organizations/#{org.guid}?recursive=true"
expect(last_response).to have_status_code 204
expect { app_model.refresh }.to raise_error Sequel::Error, 'Record not found'
end
it 'records an audit event that the app was deleted' do
delete "/v2/organizations/#{org.guid}?recursive=true"
expect(last_response).to have_status_code 204
event = Event.find(type: 'audit.app.delete-request', actee: app_model.guid)
expect(event).not_to be_nil
expect(event.actor).to eq user.guid
end
end
context 'when there are users are managers and such' do
before do
org.add_user(User.make)
org.add_manager(User.make)
org.add_billing_manager(User.make)
end
OrganizationUserJoin = Sequel::Model(:organizations_users)
OrganizationManagerJoin = Sequel::Model(:organizations_managers)
OrganizationBillingManagerJoin = Sequel::Model(:organizations_billing_managers)
it 'removes the join records' do
expect(OrganizationUserJoin.count).to eq(1)
expect(OrganizationManagerJoin.count).to eq(1)
expect(OrganizationBillingManagerJoin.count).to eq(1)
delete "/v2/organizations/#{org.guid}?recursive=true"
expect(last_response).to have_status_code 204
expect(OrganizationUserJoin.count).to eq(0)
expect(OrganizationManagerJoin.count).to eq(0)
expect(OrganizationBillingManagerJoin.count).to eq(0)
end
end
context 'when one of the spaces has a service instance in it' do
before do
stub_deprovision(service_instance, accepts_incomplete: true)
set_current_user_as_admin
end
let!(:space) { Space.make(organization: org) }
let!(:service_instance) { ManagedServiceInstance.make(space:) }
it 'deletes the service instance' do
delete "/v2/organizations/#{org.guid}?recursive=true"
expect(last_response).to have_status_code 204
expect { service_instance.refresh }.to raise_error Sequel::Error, 'Record not found'
end
context 'and one of the instances fails to delete' do
let!(:service_instance_2) { ManagedServiceInstance.make(space:) }
let!(:service_instance_3) { ManagedServiceInstance.make(space:) }
let!(:service_binding) { ServiceBinding.make(service_instance:) }
before do
stub_deprovision(service_instance_2, status: 500, accepts_incomplete: true)
stub_deprovision(service_instance_3, status: 200, accepts_incomplete: true)
stub_unbind(service_binding, accepts_incomplete: true)
end
it 'does not delete the org or the space' do
delete "/v2/organizations/#{org.guid}?recursive=true"
expect(last_response).to have_status_code 502
expect { org.refresh }.not_to raise_error
expect { space.refresh }.not_to raise_error
end
it 'does not rollback deletion of other instances or bindings' do
delete "/v2/organizations/#{org.guid}?recursive=true"
expect { service_instance.refresh }.to raise_error Sequel::Error, 'Record not found'
expect { service_instance_3.refresh }.to raise_error Sequel::Error, 'Record not found'
expect { service_binding.refresh }.to raise_error Sequel::Error, 'Record not found'
end
end
end
context 'and async=true' do
let(:space) { Space.make(organization: org) }
let(:service_instance) { ManagedServiceInstance.make(space:) }
before do
stub_deprovision(service_instance, accepts_incomplete: true)
set_current_user(User.make, admin: true)
end
it 'successfully deletes the space in a background job' do
space_guid = space.guid
app_guid = AppModel.make(space_guid:).guid
service_instance_guid = service_instance.guid
route_guid = Route.make(space_guid:).guid
delete "/v2/organizations/#{org.guid}?recursive=true&async=true"
expect(last_response).to have_status_code(202)
expect(Organization.find(guid: org.guid)).not_to be_nil
expect(Space.find(guid: space_guid)).not_to be_nil
expect(AppModel.find(guid: app_guid)).not_to be_nil
expect(ServiceInstance.find(guid: service_instance_guid)).not_to be_nil
expect(Route.find(guid: route_guid)).not_to be_nil
org_delete_jobs = Delayed::Job.where(Sequel.lit("handler like '%OrganizationDelete%'"))
expect(org_delete_jobs.count).to eq 1
job = org_delete_jobs.first
Delayed::Worker.new.work_off
# a successfully completed job is removed from the table
expect(Delayed::Job.find(id: job.id)).to be_nil
expect(Organization.find(guid: org.guid)).to be_nil
expect(Space.find(guid: space_guid)).to be_nil
expect(AppModel.find(guid: app_guid)).to be_nil
expect(ServiceInstance.find(guid: service_instance_guid)).to be_nil
expect(Route.find(guid: route_guid)).to be_nil
end
context 'and the job times out' do
before do
TestConfig.override(jobs: { global: { timeout_in_seconds: 0.1 } })
stub_deprovision(service_instance, accepts_incomplete: true) do
sleep 0.11
{ status: 200, body: {}.to_json }
end
end
it 'fails the job with a OrganizationDeleteTimeout error' do
delete "/v2/organizations/#{org.guid}?recursive=true&async=true"
expect(last_response).to have_status_code(202)
job_guid = decoded_response['metadata']['guid']
execute_all_jobs(expected_successes: 0, expected_failures: 1)
get "/v2/jobs/#{job_guid}"
expect(decoded_response['entity']['status']).to eq 'failed'
expect(decoded_response['entity']['error_details']['error_code']).to eq 'CF-OrganizationDeleteTimeout'
end
end
context 'and a resource fails to delete' do
before do
stub_deprovision(service_instance, accepts_incomplete: true) do
{ status: 500, body: {}.to_json }
end
end
it 'fails the job with a OrganizationDeleteTimeout error' do
service_instance_error_string = "#{service_instance.name}: The service broker returned an invalid response."
delete "/v2/organizations/#{org.guid}?recursive=true&async=true"
expect(last_response).to have_status_code(202)
job_guid = decoded_response['metadata']['guid']
execute_all_jobs(expected_successes: 0, expected_failures: 1)
get "/v2/jobs/#{job_guid}"
expect(decoded_response['entity']['status']).to eq 'failed'
expect(decoded_response['entity']['error_details']['error_code']).to eq 'CF-OrganizationDeletionFailed'
expect(decoded_response['entity']['error_details']['description']).to include "Deletion of organization #{org.name}"
expect(decoded_response['entity']['error_details']['description']).to include "Deletion of space #{space.name}"
expect(decoded_response['entity']['error_details']['description']).to include service_instance_error_string
expect(decoded_response['entity']['error_details']['description']).to include 'The service broker returned an invalid response'
end
end
end
end
context 'when the user is not an admin' do
it 'raises an error' do
set_current_user(User.make)
delete "/v2/organizations/#{org.guid}"
expect(last_response).to have_status_code 403
end
end
end
describe 'DELETE /v2/organizations/:guid/private_domains/:domain_guid' do
before { set_current_user_as_admin }
context 'when PrivateDomain is owned by the organization' do
let(:organization) { Organization.make }
let(:private_domain) { PrivateDomain.make(owning_organization: organization) }
it 'fails' do
delete "/v2/organizations/#{organization.guid}/private_domains/#{private_domain.guid}"
expect(last_response.status).to eq(400), last_response.body
end
end
context 'when PrivateDomain is shared' do
let(:space) { Space.make }
let(:private_domain) { PrivateDomain.make }
it 'removes associated routes' do
private_domain = PrivateDomain.make
space.organization.add_private_domain(private_domain)
Route.make(space: space, domain: private_domain)
delete "/v2/organizations/#{space.organization.guid}/private_domains/#{private_domain.guid}"
expect(last_response.status).to eq(204)
expect(private_domain.routes.count).to eq(0)
end
end
end
describe 'DELETE /v2/organizations/:guid/managers/:user_guid' do
let(:org_manager) { User.make }
before do
org.add_manager org_manager
org.save
end
describe 'removing the last org manager' do
context 'as an admin' do
it 'is allowed' do
set_current_user_as_admin
delete "/v2/organizations/#{org.guid}/managers/#{org_manager.guid}"
expect(last_response.status).to eq(204)
end
end
context 'as the manager' do
it 'is not allowed' do
set_current_user(org_manager)
delete "/v2/organizations/#{org.guid}/managers/#{org_manager.guid}"
expect(last_response.status).to be(403)
expect(decoded_response['code']).to eq(30_004)
end
end
end
describe 'when there are other managers' do
describe 'removing oneself' do
before do
org.add_manager User.make
end
it 'is allowed' do
set_current_user(org_manager)
delete "/v2/organizations/#{org.guid}/managers/#{org_manager.guid}"
expect(last_response.status).to eq(204)
end
end
end
end
describe 'DELETE /v2/organizations/:guid/billing_managers/:user_guid' do
let(:billing_manager) { User.make }
before do
org.add_billing_manager billing_manager
org.save
end
describe 'removing the last billing manager' do
context 'as an admin' do
it 'is allowed' do
set_current_user_as_admin
delete "/v2/organizations/#{org.guid}/billing_managers/#{billing_manager.guid}"
expect(last_response.status).to eq(204)
end
end
context 'as the manager' do
it 'is not allowed' do
set_current_user(billing_manager)
delete "/v2/organizations/#{org.guid}/billing_managers/#{billing_manager.guid}"
expect(last_response.status).to be(403)
expect(decoded_response['code']).to eq(30_005)
end
end
end
describe 'when there are other billing managers' do
describe 'removing oneself' do
before do
org.add_billing_manager User.make
end
it 'is allowed' do
set_current_user(billing_manager)
delete "/v2/organizations/#{org.guid}/billing_managers/#{billing_manager.guid}"
expect(last_response.status).to eq(204)
end
end
end
end
describe 'DELETE /v2/organizations/:guid/auditors/:user_guid' do
let(:auditor) { User.make }
before do
org.add_auditor auditor
org.save
end
describe 'removing the last auditor' do
context 'as an admin' do
it 'is allowed' do
set_current_user_as_admin
delete "/v2/organizations/#{org.guid}/auditors/#{auditor.guid}"
expect(last_response.status).to eq(204)
end
end
context 'as the auditor' do
it 'is allowed' do
set_current_user(auditor)
delete "/v2/organizations/#{org.guid}/auditors/#{auditor.guid}"
expect(last_response.status).to be(204)
end
end
context 'as the org manager' do
let(:org_manager) { User.make }
before do
org.add_manager org_manager
set_current_user org_manager
end
it 'is allowed' do
delete "/v2/organizations/#{org.guid}/auditors/#{auditor.guid}"
expect(last_response.status).to be(204)
end
end
context 'as a user' do
let(:user) { User.make }
before do
org.add_user user
set_current_user user
end
it 'is not allowed' do
delete "/v2/organizations/#{org.guid}/auditors/#{auditor.guid}"
expect(last_response.status).to be(403)
end
end
end
end
describe 'adding user roles by username' do
%i[user manager billing_manager auditor].each do |role|
plural_role = role.to_s.pluralize
describe "PUT /v2/organizations/:guid/#{plural_role}" do
let(:user) { User.make(username: 'larry_the_user') }
let(:event_type) { "audit.user.organization_#{role}_add" }
before do
allow(uaa_client).to receive(:origins_for_username).with(user.username).and_return(['uaa'])
allow(uaa_client).to receive(:id_for_username).with(user.username).and_return(user.guid)
set_current_user_as_admin(email: user_email)
end
context 'origin' do
let(:user) { User.make(username: 'larry_the_user') }
let(:user2) { User.make(username: 'larry_the_user') }
let(:origin1) { 'larry_origin' }
let(:origin2) { 'another_larry_origin' }
context 'when an origin is specified' do
context 'when the specified origin is not in the user\'s origins' do
let(:user) { User.make(username: 'fake@example.com') }
let(:fake_origin) { 'fake_origin' }
before do
allow(uaa_client).to receive(:origins_for_username).with(user.username).and_return(['bogus-origin'])
end
it 'returns a 404 when the user does not exist in UAA' do
put "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: 'fake@example.com', origin: origin1 })
expect(last_response.status).to eq(404), last_response.body
expect(decoded_response['code']).to eq(20_007)
expect(decoded_response['description']).to eq("The user could not be found, username: 'fake@example.com', origin: '#{origin1}'")
end
end
context 'when the specified origin is in the user\'s origins' do
before do
allow(uaa_client).to receive(:origins_for_username).with(user.username).and_return([origin1])
end
it "makes the user an org #{role}" do
expect(uaa_client).to receive(:id_for_username).with(user.username, origin: origin1).
and_return(user.guid)
put "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username, origin: origin1 })
expect(last_response.status).to eq(201)
expect(org.send(plural_role)).to include(user)
expect(decoded_response['metadata']['guid']).to eq(org.guid)
end
end
end
context 'when an origin is not specified' do
before do
allow(uaa_client).to receive(:id_for_username).with(user.username, { origin: nil }).
and_return(user.guid)
end
it 'returns a 400 if the user is found in multiple origins' do
expect(uaa_client).to receive(:origins_for_username).with(user.username).
and_return([origin1, origin2])
put "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: 'larry_the_user' })
expect(last_response.status).to eq(400), " Expected 400, got #{last_response.status}: body: #{last_response.body}"
expect(decoded_response['code']).to eq(20_006)
expect(decoded_response['description']).
to eq("The user exists in multiple origins. Specify an origin for the requested user from: '#{origin1}', '#{origin2}'")
end
it "makes the user an org #{role}" do
put "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
expect(last_response.status).to eq(201), last_response.body
expect(org.send(plural_role)).to include(user)
expect(decoded_response['metadata']['guid']).to eq(org.guid)
end
it "makes the user an org #{role}, and creates a user record when one does not exist" do
put "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
expect(last_response.status).to eq(201), last_response.body
expect(org.send("#{plural_role}_dataset").where(guid: user.guid)).not_to be_empty
end
it 'verifies the user has update access to the org' do
expect_any_instance_of(OrganizationsController).to receive(:find_guid_and_validate_access).with(:update, org.guid).and_call_original
put "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
expect(last_response.status).to eq(201), last_response.body
end
it 'returns a 404 when the user does not exist in UAA' do
allow(uaa_client).to receive(:id_for_username).with(user.username, origin: nil).and_return(nil)
put "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
expect(last_response.status).to eq(404), last_response.body
expect(decoded_response['code']).to eq(20_003)
end
it 'returns an error when UAA is not available' do
expect(uaa_client).to receive(:id_for_username).and_raise(UaaUnavailable)
put "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
expect(last_response.status).to eq(503)
expect(decoded_response['code']).to eq(20_004)
end
it 'returns an error when UAA is unavailable' do
expect(uaa_client).to receive(:id_for_username).and_raise(UaaUnavailable)
put "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
expect(last_response.status).to eq(503)
expect(decoded_response['code']).to eq(20_004)
end
it 'creates an audit.user.organization_role_add when a role is associated to a space' do
before_event = Event.find(type: event_type, actee: user.guid)
expect(before_event).to be_nil
put "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
expect(last_response.status).to eq(201), last_response.body
event = Event.find(type: event_type, actee: user.guid)
expect(event).not_to be_nil
expect(event.organization_guid).to eq(org.guid)
expect(event.actor_name).to eq(SecurityContext.current_user_email)
end
context 'when the feature flag "set_roles_by_username" is disabled' do
before do
FeatureFlag.new(name: 'set_roles_by_username', enabled: false).save
end
it 'raises a feature flag error for non-admins' do
set_current_user(user)
put "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
expect(last_response.status).to eq(403)
expect(decoded_response['code']).to eq(330_002)
end
it 'succeeds for admins' do
put "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
expect(last_response.status).to eq(201)
expect(org.send(plural_role)).to include(user)
expect(decoded_response['metadata']['guid']).to eq(org.guid)
end
end
end
end
end
end
end
describe 'removing user roles by username with the POST action' do
%i[user manager billing_manager auditor].each do |role|
plural_role = role.to_s.pluralize
describe "POST /v2/organizations/:guid/#{role}" do
let(:user) { User.make(username: 'larry_the_user') }
let(:user2) { User.make(username: 'larry_the_user') }
let(:origin1) { 'larry_origin' }
let(:origin2) { 'another_larry_origin' }
let(:event_type) { "audit.user.organization_#{role}_add" }
before do
allow(uaa_client).to receive(:origins_for_username).with(user.username).and_return(['uaa'])
set_current_user_as_admin(email: user_email)
end
context 'when an origin is specified' do
context 'when the specified origin is not in the user\'s origins' do
before do
allow(uaa_client).to receive(:origins_for_username).with(user.username).and_return(['bogus-origin'])
end
it 'returns a 404 when the user does not exist in UAA for the specified origin' do
post "/v2/organizations/#{org.guid}/#{plural_role}/remove", Oj.dump({ username: user.username, origin: origin1 })
expect(last_response.status).to eq(404)
expect(decoded_response['code']).to eq(20_007)
expect(decoded_response['description']).to eq("The user could not be found, username: '#{user.username}', origin: '#{origin1}'")
end
end
context 'when the specified origin is in the user\'s origins' do
before do
expect(uaa_client).to receive(:id_for_username).with(user.username, origin: origin1).and_return(user.guid)
allow(uaa_client).to receive(:origins_for_username).with(user.username).and_return([origin1, origin2])
org.send("add_#{role}", user)
end
it "unsets the user as an org #{role}" do
expect(org.send(plural_role)).to include(user)
post "/v2/organizations/#{org.guid}/#{plural_role}/remove",
Oj.dump(username: user.username, origin: origin1)
expect(last_response.status).to eq(204)
expect(org.reload.send(plural_role)).not_to include(user)
expect(last_response.body).to be_empty
end
end
end
context 'when an origin is not specified' do
context 'when the username exists in only one UAA origin' do
before do
expect(uaa_client).to receive(:id_for_username).with(user.username, origin: nil).and_return(user.guid)
allow(uaa_client).to receive(:origins_for_username).with(user.username).and_return([origin1])
org.send("add_#{role}", user)
end
it "unsets the user as an org #{role}" do
expect(org.send(plural_role)).to include(user)
post "/v2/organizations/#{org.guid}/#{plural_role}/remove",
Oj.dump(username: user.username)
expect(last_response.status).to eq(204)
expect(org.reload.send(plural_role)).not_to include(user)
expect(last_response.body).to be_empty
end
end
context 'when the username exists in multiple UAA origins' do
it 'returns a 400 error' do
expect(uaa_client).to receive(:origins_for_username).and_return(%w[origin1 origin2])
post "/v2/organizations/#{org.guid}/#{plural_role}/remove",
Oj.dump({ username: user.username })
expect(last_response.status).to eq(400)
expect(decoded_response['code']).to eq(20_006)
end
end
end
end
end
end
describe 'removing user roles by username with the DELETE action' do
%i[user manager billing_manager auditor].each do |role|
plural_role = role.to_s.pluralize
describe "DELETE /v2/organizations/:guid/#{plural_role}" do
let(:user) { User.make(username: 'larry_the_user') }
let(:event_type) { "audit.user.organization_#{role}_remove" }
before do
allow(uaa_client).to receive(:id_for_username).with(user.username, origin: nil).and_return(user.guid)
allow(uaa_client).to receive(:origins_for_username).and_return(['uaa'])
org.send("add_#{role}", user)
set_current_user_as_admin(email: user_email)
end
it "unsets the user as an org #{role}" do
expect(org.send(plural_role)).to include(user)
delete "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
expect(last_response.status).to eq(204)
expect(org.reload.send(plural_role)).not_to include(user)
expect(last_response.body).to be_empty
end
it 'verifies the user has update access to the org' do
expect_any_instance_of(OrganizationsController).to receive(:find_guid_and_validate_access).with(:update, org.guid).and_call_original
delete "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
end
it 'returns a 404 when the user does not exist in CC' do
expect(uaa_client).to receive(:id_for_username).with('fake@example.com', origin: nil).and_return('not-a-guid')
delete "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: 'fake@example.com' })
expect(last_response.status).to eq(404)
expect(decoded_response['code']).to eq(20_003)
end
it 'returns an error when UAA is not available' do
expect(uaa_client).to receive(:id_for_username).and_raise(UaaUnavailable)
delete "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
expect(last_response.status).to eq(503)
expect(decoded_response['code']).to eq(20_004)
end
it 'returns an error when UAA is unavailable' do
expect(uaa_client).to receive(:id_for_username).and_raise(UaaUnavailable)
delete "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
expect(last_response.status).to eq(503)
expect(decoded_response['code']).to eq(20_004)
end
it 'creates an event of type audit.user.organization_role_remove when a user-role association is removed from the organization' do
before_event = Event.find(type: event_type, actee: user.guid)
expect(before_event).to be_nil
delete "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
event = Event.find(type: event_type, actee: user.guid)
expect(event).not_to be_nil
expect(event.organization_guid).to eq(org.guid)
expect(event.actor_name).to eq(SecurityContext.current_user_email)
end
context 'when the username exists in multiple UAA origins' do
it 'returns a 400 error' do
expect(uaa_client).to receive(:origins_for_username).and_return(%w[origin1 origin2])
delete "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
expect(last_response.status).to eq(400)
expect(decoded_response['code']).to eq(20_006)
end
end
context 'when the feature flag "set_roles_by_username" is disabled' do
before do
FeatureFlag.new(name: 'unset_roles_by_username', enabled: false).save
end
it 'raises a feature flag error for non-admins' do
set_current_user(user)
delete "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
expect(last_response.status).to eq(403)
expect(decoded_response['code']).to eq(330_002)
end
it 'succeeds for admins' do
expect(org.send(plural_role)).to include(user)
delete "/v2/organizations/#{org.guid}/#{plural_role}", Oj.dump({ username: user.username })
expect(last_response.status).to eq(204)
expect(org.reload.send(plural_role)).not_to include(user)
expect(last_response.body).to be_empty
end
end
context 'when recursive delete is requested' do
let(:space) { Space.make(organization: org) }
before do
org.add_user(user)
org.add_manager(user)
space.add_developer(user)
end
if role == :user
it 'deletes other roles in org and child spaces' do
expect(org.users).to include(user)
expect(org.managers).to include(user)
expect(space.developers).to include(user)
delete "/v2/organizations/#{org.guid}/#{plural_role}?recursive=true", Oj.dump({ username: user.username })
org.reload
space.reload
expect(org.users).not_to include(user)
expect(org.managers).not_to include(user)
expect(space.developers).not_to include(user)
end
else
it 'ignores the recursive requests' do
expect(space.developers).to include(user)
delete "/v2/organizations/#{org.guid}/#{plural_role}?recursive=true", Oj.dump({ username: user.username })
org.reload
space.reload
expect(space.developers).to include(user)
end
end
end
end
end
end
describe 'adding user roles by user guid' do
%i[user manager billing_manager auditor].each do |role|
plural_role = role.to_s.pluralize
describe "PUT /v2/organizations/:guid/#{plural_role}/:user_id" do
let(:user) { User.make(username: 'larry_the_user') }
let(:other_user) { User.make(username: 'joe_the_user') }
let(:event_type) { "audit.user.organization_#{role}_add" }
before do
set_current_user(user)
allow(uaa_client).to receive(:usernames_for_ids).and_return({ other_user.guid => other_user.username })
end
context 'as an admin' do
before do
set_current_user_as_admin
end
it "makes the user a #{role} and generates an appropriate event" do
before_event = Event.find(type: event_type, actee: other_user.guid)
expect(before_event).to be_nil
put "/v2/organizations/#{org.guid}/#{plural_role}/#{other_user.guid}"
expect(last_response.status).to eq(201)
expect(org.send(plural_role)).to include(other_user)
expect(decoded_response['metadata']['guid']).to eq(org.guid)
event = Event.find(type: event_type, actee: other_user.guid)
expect(event).not_to be_nil
expect(event.organization_guid).to eq(org.guid)
expect(event.actee_name).to eq('joe_the_user')
end
end
context 'as a regular user' do
before do
org.add_user(user)
end
it 'returns a 403 and does not generate any event' do
put "/v2/organizations/#{org.guid}/#{plural_role}/#{other_user.guid}"
expect(last_response.status).to eq(403)
event = Event.find(type: event_type, actee: other_user.guid)
expect(event).to be_nil
end
end
end
end
end
describe 'removing user roles by user_id' do
%i[user manager billing_manager auditor].each do |role|
plural_role = role.to_s.pluralize
describe "DELETE /v2/organizations/:guid/#{plural_role}/:user_id" do
let(:user) { User.make(username: 'larry_the_user') }
let(:other_user) { User.make(username: 'joe_the_user') }
let(:event_type) { "audit.user.organization_#{role}_remove" }
before do
set_current_user(user)
org.send("add_#{role}", other_user)
end
context 'as an admin' do
before do
set_current_user_as_admin
end
it "removes #{role} from the user and generates an appropriate event" do
before_event = Event.find(type: event_type, actee: other_user.guid)
expect(before_event).to be_nil
delete "/v2/organizations/#{org.guid}/#{plural_role}/#{other_user.guid}"
expect(last_response.status).to eq(204)
expect(org.send(plural_role)).not_to include(user)
event = Event.find(type: event_type, actee: other_user.guid)
expect(event).not_to be_nil
expect(event.organization_guid).to eq(org.guid)
end
end
context 'as a regular user' do
before do
org.add_user(user)
end
it 'returns a 403 and does not generate any event' do
delete "/v2/organizations/#{org.guid}/#{plural_role}/#{other_user.guid}"
expect(last_response.status).to eq(403)
event = Event.find(type: event_type, actee: other_user.guid)
expect(event).to be_nil
end
end
end
end
end
end
end