Skip to content

Commit 8903be9

Browse files
committed
Add space_guids filtering to /v3/access_rules endpoint
Implement space-based filtering for access rules endpoint to enable querying all access rules within a given space using ?space_guids=<guid> query parameter. Changes: - Add space_guids to AccessRulesListMessage with array validation - Implement space filtering in AccessRulesController#build_dataset - Add comprehensive unit tests for AccessRulesListMessage - Add request specs for single/multiple space filtering and combinations - Follow existing CAPI patterns for space_guids filtering The filter joins through the routes table to filter access rules by the space_id of their associated routes.
1 parent ea99dbb commit 8903be9

4 files changed

Lines changed: 213 additions & 1 deletion

File tree

app/controllers/v3/access_rules_controller.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,13 @@ def build_dataset(message)
128128
select_all(:route_access_rules)
129129
end
130130

131+
if message.requested?(:space_guids)
132+
dataset = dataset.
133+
join(:routes, id: :route_id).
134+
where(routes__space_id: VCAP::CloudController::Space.where(guid: message.space_guids).select(:id)).
135+
select_all(:route_access_rules)
136+
end
137+
131138
dataset = dataset.where(name: message.names) if message.requested?(:names)
132139
dataset = dataset.where(selector: message.selectors) if message.requested?(:selectors)
133140

app/messages/access_rules_list_message.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module VCAP::CloudController
44
class AccessRulesListMessage < ListMessage
55
register_allowed_keys %i[
66
route_guids
7+
space_guids
78
names
89
selectors
910
include
@@ -12,8 +13,10 @@ class AccessRulesListMessage < ListMessage
1213
validates_with NoAdditionalParamsValidator
1314
validates_with IncludeParamValidator, valid_values: ['selector_resource', 'route']
1415

16+
validates :space_guids, array: true, allow_nil: true
17+
1518
def self.from_params(params)
16-
super(params, %w[route_guids names selectors include])
19+
super(params, %w[route_guids space_guids names selectors include])
1720
end
1821
end
1922
end

spec/request/access_rules_spec.rb

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,73 @@ def expected_rule_json(rule)
301301
expect(parsed['resources'][0]['selector']).to eq('cf:any')
302302
end
303303

304+
describe 'filtering by space_guids' do
305+
let(:other_org) { VCAP::CloudController::Organization.make }
306+
let(:other_space) { VCAP::CloudController::Space.make(organization: other_org) }
307+
let(:other_mtls_domain) do
308+
VCAP::CloudController::PrivateDomain.make(
309+
owning_organization: other_org,
310+
enforce_access_rules: true,
311+
access_rules_scope: 'space'
312+
)
313+
end
314+
let(:other_route) { VCAP::CloudController::Route.make(space: other_space, domain: other_mtls_domain) }
315+
let!(:rule_in_other_space) do
316+
VCAP::CloudController::RouteAccessRule.create(
317+
guid: SecureRandom.uuid,
318+
name: 'rule-in-other-space',
319+
selector: 'cf:any',
320+
route_id: other_route.id
321+
)
322+
end
323+
324+
before do
325+
other_org.add_user(user)
326+
other_space.add_developer(user)
327+
end
328+
329+
it 'filters by single space_guid' do
330+
get "/v3/access_rules?space_guids=#{space.guid}", nil, admin_header
331+
332+
expect(last_response.status).to eq(200)
333+
parsed = Oj.load(last_response.body)
334+
guids = parsed['resources'].map { |r| r['guid'] }
335+
expect(guids).to include(rule1.guid, rule2.guid)
336+
expect(guids).not_to include(rule_in_other_space.guid)
337+
end
338+
339+
it 'filters by multiple space_guids' do
340+
get "/v3/access_rules?space_guids=#{space.guid},#{other_space.guid}", nil, admin_header
341+
342+
expect(last_response.status).to eq(200)
343+
parsed = Oj.load(last_response.body)
344+
guids = parsed['resources'].map { |r| r['guid'] }
345+
expect(guids).to include(rule1.guid, rule2.guid, rule_in_other_space.guid)
346+
end
347+
348+
it 'combines space_guids with other filters' do
349+
get "/v3/access_rules?space_guids=#{space.guid}&names=rule-one", nil, admin_header
350+
351+
expect(last_response.status).to eq(200)
352+
parsed = Oj.load(last_response.body)
353+
expect(parsed['resources'].length).to eq(1)
354+
expect(parsed['resources'][0]['guid']).to eq(rule1.guid)
355+
expect(parsed['resources'][0]['name']).to eq('rule-one')
356+
end
357+
358+
it 'returns empty when space has no access rules' do
359+
empty_space = VCAP::CloudController::Space.make(organization: org)
360+
org.add_user(user)
361+
empty_space.add_developer(user)
362+
363+
get "/v3/access_rules?space_guids=#{empty_space.guid}", nil, admin_header
364+
365+
expect(last_response.status).to eq(200)
366+
parsed = Oj.load(last_response.body)
367+
expect(parsed['resources'].length).to eq(0)
368+
end
369+
end
370+
304371
context 'with include=selector_resource' do
305372
let!(:app) { VCAP::CloudController::AppModel.make(space: space, name: 'frontend-app') }
306373
let!(:other_space) { VCAP::CloudController::Space.make(organization: org, name: 'other-space') }
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
require 'spec_helper'
2+
require 'messages/access_rules_list_message'
3+
4+
module VCAP::CloudController
5+
RSpec.describe AccessRulesListMessage do
6+
describe '.from_params' do
7+
let(:params) do
8+
{
9+
'route_guids' => 'route1,route2',
10+
'space_guids' => 'space1,space2',
11+
'names' => 'name1,name2',
12+
'selectors' => 'selector1,selector2',
13+
'page' => 1,
14+
'per_page' => 5,
15+
'order_by' => 'created_at',
16+
'include' => 'selector_resource,route'
17+
}
18+
end
19+
20+
it 'returns the correct AccessRulesListMessage' do
21+
message = AccessRulesListMessage.from_params(params)
22+
23+
expect(message).to be_a(AccessRulesListMessage)
24+
expect(message.route_guids).to eq(%w[route1 route2])
25+
expect(message.space_guids).to eq(%w[space1 space2])
26+
expect(message.names).to eq(%w[name1 name2])
27+
expect(message.selectors).to eq(%w[selector1 selector2])
28+
expect(message.page).to eq(1)
29+
expect(message.per_page).to eq(5)
30+
expect(message.order_by).to eq('created_at')
31+
expect(message.include).to eq(%w[selector_resource route])
32+
end
33+
34+
it 'converts requested keys to symbols' do
35+
message = AccessRulesListMessage.from_params(params)
36+
37+
expect(message).to be_requested(:route_guids)
38+
expect(message).to be_requested(:space_guids)
39+
expect(message).to be_requested(:names)
40+
expect(message).to be_requested(:selectors)
41+
expect(message).to be_requested(:page)
42+
expect(message).to be_requested(:per_page)
43+
expect(message).to be_requested(:order_by)
44+
expect(message).to be_requested(:include)
45+
end
46+
end
47+
48+
describe '#to_param_hash' do
49+
let(:opts) do
50+
{
51+
route_guids: %w[route1 route2],
52+
space_guids: %w[space1 space2],
53+
names: %w[name1 name2],
54+
selectors: %w[selector1 selector2],
55+
page: 1,
56+
per_page: 5,
57+
order_by: 'created_at',
58+
include: %w[selector_resource route]
59+
}
60+
end
61+
62+
it 'excludes the pagination keys' do
63+
expected_params = %i[route_guids space_guids names selectors include]
64+
expect(AccessRulesListMessage.from_params(opts).to_param_hash.keys).to match_array(expected_params)
65+
end
66+
end
67+
68+
describe 'fields' do
69+
it 'accepts a set of fields' do
70+
expect do
71+
AccessRulesListMessage.from_params({
72+
route_guids: [],
73+
space_guids: [],
74+
names: [],
75+
selectors: [],
76+
page: 1,
77+
per_page: 5,
78+
order_by: 'created_at',
79+
include: ['selector_resource', 'route']
80+
})
81+
end.not_to raise_error
82+
end
83+
84+
it 'accepts an empty set' do
85+
message = AccessRulesListMessage.from_params({})
86+
expect(message).to be_valid
87+
end
88+
89+
it 'does not accept a field not in this set' do
90+
message = AccessRulesListMessage.from_params({ foobar: 'pants' })
91+
92+
expect(message).not_to be_valid
93+
expect(message.errors[:base][0]).to include("Unknown query parameter(s): 'foobar'")
94+
end
95+
96+
describe 'include validations' do
97+
it 'accepts valid include values' do
98+
message = AccessRulesListMessage.from_params({ 'include' => 'selector_resource' })
99+
expect(message).to be_valid
100+
101+
message = AccessRulesListMessage.from_params({ 'include' => 'route' })
102+
expect(message).to be_valid
103+
104+
message = AccessRulesListMessage.from_params({ 'include' => 'selector_resource,route' })
105+
expect(message).to be_valid
106+
end
107+
108+
it 'rejects invalid include values' do
109+
message = AccessRulesListMessage.from_params({ 'include' => 'invalid' })
110+
expect(message).not_to be_valid
111+
end
112+
end
113+
114+
describe 'validations' do
115+
it 'validates space_guids is an array' do
116+
message = AccessRulesListMessage.from_params space_guids: 'not array'
117+
expect(message).not_to be_valid
118+
expect(message.errors[:space_guids].length).to eq 1
119+
end
120+
121+
it 'allows space_guids to be nil' do
122+
message = AccessRulesListMessage.from_params({})
123+
expect(message).to be_valid
124+
expect(message.space_guids).to be_nil
125+
end
126+
127+
it 'allows space_guids to be an array' do
128+
message = AccessRulesListMessage.from_params space_guids: %w[space1 space2]
129+
expect(message).to be_valid
130+
expect(message.space_guids).to eq(%w[space1 space2])
131+
end
132+
end
133+
end
134+
end
135+
end

0 commit comments

Comments
 (0)