Skip to content

Commit abdcfda

Browse files
committed
Consolidate access rules migrations, fix RuboCop offenses, and clean up tests
- Collapse 4 migrations into 1 consolidated migration (20260407100001) that creates route_access_rules, route_access_rule_labels, and route_access_rule_annotations tables - Remove name field from access rules per RFC updates - Fix all RuboCop offenses: Style/CollectionQuerying (.count > 0 -> .any?), Migration/AddConstraintName (primary_key :id, name: :id), Metrics/BlockLength, Metrics/CyclomaticComplexity, and others - Add stale resource detection in presenter (null data for deleted resources) - Extract controller methods to reduce complexity - Use relative class names within VCAP::CloudController module - Fix test shadowing of rack-test app method (let(:app) -> let(:frontend_app)) - Fix Sequel validation assertion style (.include(:presence) not strings)
1 parent 3791874 commit abdcfda

17 files changed

Lines changed: 189 additions & 274 deletions

app/access/access_rule_access.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ def read_for_update_with_token?(_)
4747
admin_user? || has_write_scope?
4848
end
4949

50-
def can_remove_related_object_with_token?(*args)
51-
read_for_update_with_token?(*args)
50+
def can_remove_related_object_with_token?(*)
51+
read_for_update_with_token?(*)
5252
end
5353

54-
def read_related_object_for_update_with_token?(*args)
55-
read_for_update_with_token?(*args)
54+
def read_related_object_for_update_with_token?(*)
55+
read_for_update_with_token?(*)
5656
end
5757

5858
def update_with_token?(_)

app/controllers/v3/access_rules_controller.rb

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,9 @@ def create
3939
message = AccessRuleCreateMessage.new(hashed_params[:body])
4040
unprocessable!(message.errors.full_messages) unless message.valid?
4141

42-
route = VCAP::CloudController::Route.find(guid: message.route_guid)
43-
resource_not_found!(:route) unless route && permission_queryer.can_read_from_space?(route.space.id, route.space.organization_id)
44-
unauthorized! unless permission_queryer.can_write_to_active_space?(route.space.id)
45-
suspended! unless permission_queryer.is_space_active?(route.space.id)
46-
47-
if route.domain.internal?
48-
unprocessable!('Cannot create access rules for routes on internal domains. Internal routes use container-to-container networking and bypass GoRouter.')
49-
end
50-
unprocessable!("Cannot create access rules for route '#{route.guid}': the route's domain does not have enforce_access_rules enabled.") unless route.domain.enforce_access_rules
51-
52-
# Enforce cf:any exclusivity: if route already has a cf:any rule, reject new rules;
53-
# if new rule is cf:any, reject if route already has any rules.
54-
existing_selectors = route.access_rules.map(&:selector)
55-
unprocessable!("Cannot add 'cf:any' selector when other access rules already exist for this route.") if message.selector == 'cf:any' && existing_selectors.any?
56-
unprocessable!("Cannot add selector '#{message.selector}': route already has a 'cf:any' rule.") if existing_selectors.include?('cf:any') && message.selector != 'cf:any'
57-
58-
# Uniqueness: selector must be unique per route
59-
unprocessable!("An access rule with selector '#{message.selector}' already exists for this route.") if existing_selectors.include?(message.selector)
42+
route = find_and_authorize_route(message.route_guid)
43+
validate_route_domain(route)
44+
validate_selector_exclusivity(route, message.selector)
6045

6146
access_rule = VCAP::CloudController::RouteAccessRule.new(
6247
guid: SecureRandom.uuid,
@@ -102,6 +87,33 @@ def destroy
10287

10388
private
10489

90+
def find_and_authorize_route(route_guid)
91+
route = VCAP::CloudController::Route.find(guid: route_guid)
92+
resource_not_found!(:route) unless route && permission_queryer.can_read_from_space?(route.space.id, route.space.organization_id)
93+
unauthorized! unless permission_queryer.can_write_to_active_space?(route.space.id)
94+
suspended! unless permission_queryer.is_space_active?(route.space.id)
95+
route
96+
end
97+
98+
def validate_route_domain(route)
99+
if route.domain.internal?
100+
unprocessable!('Cannot create access rules for routes on internal domains. Internal routes use container-to-container networking and bypass GoRouter.')
101+
end
102+
unprocessable!("Cannot create access rules for route '#{route.guid}': the route's domain does not have enforce_access_rules enabled.") unless route.domain.enforce_access_rules
103+
end
104+
105+
def validate_selector_exclusivity(route, selector)
106+
existing_selectors = route.access_rules.map(&:selector)
107+
108+
# Enforce cf:any exclusivity: if route already has a cf:any rule, reject new rules;
109+
# if new rule is cf:any, reject if route already has any rules.
110+
unprocessable!("Cannot add 'cf:any' selector when other access rules already exist for this route.") if selector == 'cf:any' && existing_selectors.any?
111+
unprocessable!("Cannot add selector '#{selector}': route already has a 'cf:any' rule.") if existing_selectors.include?('cf:any') && selector != 'cf:any'
112+
113+
# Uniqueness: selector must be unique per route
114+
unprocessable!("An access rule with selector '#{selector}' already exists for this route.") if existing_selectors.include?(selector)
115+
end
116+
105117
def build_dataset(message)
106118
dataset = VCAP::CloudController::RouteAccessRule.dataset
107119

app/decorators/include_access_rule_route_decorator.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ def self.decorate(hash, access_rules)
1414
route_ids = access_rules.map(&:route_id).uniq
1515

1616
# Fetch routes with their associations
17-
routes = VCAP::CloudController::Route.where(id: route_ids).
17+
routes = Route.where(id: route_ids).
1818
order(:created_at, :guid).
19-
eager(VCAP::CloudController::Presenters::V3::RoutePresenter.associated_resources).all
19+
eager(Presenters::V3::RoutePresenter.associated_resources).all
2020

2121
# Present routes
22-
hash[:included][:routes] = routes.map { |route| VCAP::CloudController::Presenters::V3::RoutePresenter.new(route).to_hash }
22+
hash[:included][:routes] = routes.map { |route| Presenters::V3::RoutePresenter.new(route).to_hash }
2323

2424
hash
2525
end

app/decorators/include_access_rule_selector_resource_decorator.rb

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def self.match?(include_params)
99
return false unless include_params
1010

1111
# Match if any of: selector_resource, app, space, organization
12-
(include_params & %w[selector_resource app space organization]).any?
12+
include_params.intersect?(%w[selector_resource app space organization])
1313
end
1414

1515
def self.decorate(hash, access_rules)
@@ -48,28 +48,28 @@ def self.decorate(hash, access_rules)
4848
private_class_method def self.fetch_and_present_apps(guids)
4949
return [] if guids.empty?
5050

51-
apps = VCAP::CloudController::AppModel.where(guid: guids).
51+
apps = AppModel.where(guid: guids).
5252
order(:created_at, :guid).
53-
eager(VCAP::CloudController::Presenters::V3::AppPresenter.associated_resources).all
54-
apps.map { |app| VCAP::CloudController::Presenters::V3::AppPresenter.new(app).to_hash }
53+
eager(Presenters::V3::AppPresenter.associated_resources).all
54+
apps.map { |app| Presenters::V3::AppPresenter.new(app).to_hash }
5555
end
5656

5757
private_class_method def self.fetch_and_present_spaces(guids)
5858
return [] if guids.empty?
5959

60-
spaces = VCAP::CloudController::Space.where(guid: guids).
60+
spaces = Space.where(guid: guids).
6161
order(:created_at, :guid).
62-
eager(VCAP::CloudController::Presenters::V3::SpacePresenter.associated_resources).all
63-
spaces.map { |space| VCAP::CloudController::Presenters::V3::SpacePresenter.new(space).to_hash }
62+
eager(Presenters::V3::SpacePresenter.associated_resources).all
63+
spaces.map { |space| Presenters::V3::SpacePresenter.new(space).to_hash }
6464
end
6565

6666
private_class_method def self.fetch_and_present_organizations(guids)
6767
return [] if guids.empty?
6868

69-
orgs = VCAP::CloudController::Organization.where(guid: guids).
69+
orgs = Organization.where(guid: guids).
7070
order(:created_at, :guid).
71-
eager(VCAP::CloudController::Presenters::V3::OrganizationPresenter.associated_resources).all
72-
orgs.map { |org| VCAP::CloudController::Presenters::V3::OrganizationPresenter.new(org).to_hash }
71+
eager(Presenters::V3::OrganizationPresenter.associated_resources).all
72+
orgs.map { |org| Presenters::V3::OrganizationPresenter.new(org).to_hash }
7373
end
7474
end
7575
end

app/messages/access_rules_list_message.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class AccessRulesListMessage < ListMessage
1818
validates :selector_resource_guids, array: true, allow_nil: true
1919

2020
def self.from_params(params)
21-
super(params, %w[guids route_guids space_guids selectors selector_resource_guids include])
21+
super(params, %w[route_guids space_guids selectors selector_resource_guids include])
2222
end
2323
end
2424
end

app/messages/domain_create_message.rb

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,17 +106,14 @@ def router_group_validation
106106
end
107107

108108
def access_rules_scope_validation
109-
if requested?(:access_rules_scope)
110-
unless access_rules_scope.nil? || %w[any org space].include?(access_rules_scope)
111-
errors.add(:access_rules_scope, "must be one of 'any', 'org', 'space'")
112-
end
109+
if requested?(:access_rules_scope) && !(access_rules_scope.nil? || %w[any org space].include?(access_rules_scope))
110+
errors.add(:access_rules_scope, "must be one of 'any', 'org', 'space'")
113111
end
114112

115-
if requested?(:enforce_access_rules) && enforce_access_rules == true
116-
if !requested?(:access_rules_scope) || access_rules_scope.nil?
117-
errors.add(:access_rules_scope, 'is required when enforce_access_rules is true')
118-
end
119-
end
113+
return unless requested?(:enforce_access_rules) && enforce_access_rules == true
114+
return unless !requested?(:access_rules_scope) || access_rules_scope.nil?
115+
116+
errors.add(:access_rules_scope, 'is required when enforce_access_rules is true')
120117
end
121118

122119
class Relationships < BaseMessage

app/presenters/v3/access_rule_presenter.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,24 +38,28 @@ def build_relationships
3838
}
3939

4040
# Extract resource GUID from selector and populate read-only relationships
41+
# Only include the guid in data if the resource actually exists
4142
selector_match = access_rule.selector.match(/\Acf:(app|space|org):([0-9a-f-]+)\z/)
4243
if selector_match
4344
resource_type = selector_match[1]
4445
resource_guid = selector_match[2]
4546

4647
case resource_type
4748
when 'app'
48-
relationships[:app] = { data: { guid: resource_guid } }
49+
app_exists = VCAP::CloudController::AppModel.where(guid: resource_guid).any?
50+
relationships[:app] = { data: app_exists ? { guid: resource_guid } : nil }
4951
relationships[:space] = { data: nil }
5052
relationships[:organization] = { data: nil }
5153
when 'space'
54+
space_exists = VCAP::CloudController::Space.where(guid: resource_guid).any?
5255
relationships[:app] = { data: nil }
53-
relationships[:space] = { data: { guid: resource_guid } }
56+
relationships[:space] = { data: space_exists ? { guid: resource_guid } : nil }
5457
relationships[:organization] = { data: nil }
5558
when 'org'
59+
org_exists = VCAP::CloudController::Organization.where(guid: resource_guid).any?
5660
relationships[:app] = { data: nil }
5761
relationships[:space] = { data: nil }
58-
relationships[:organization] = { data: { guid: resource_guid } }
62+
relationships[:organization] = { data: org_exists ? { guid: resource_guid } : nil }
5963
end
6064
else
6165
# cf:any or malformed - all relationships are null

app/presenters/v3/route_presenter.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ def to_hash
5656
@decorators.reduce(hash) { |memo, d| d.decorate(memo, [route]) }
5757
end
5858

59-
private
60-
6159
INTERNAL_ROUTE_OPTIONS = %w[access_scope access_rules].freeze
6260

61+
private
62+
6363
def route
6464
@resource
6565
end

db/migrations/20260407100001_create_route_access_rules.rb

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,57 @@
22
up do
33
unless table_exists?(:route_access_rules)
44
create_table :route_access_rules do
5+
primary_key :id, name: :id
56
String :guid, size: 255, null: false
6-
primary_key :id
7-
String :name, size: 255, null: false
87
String :selector, size: 255, null: false
98
Integer :route_id, null: false
109
DateTime :created_at, null: false
1110
DateTime :updated_at, null: false
1211

1312
index :guid, unique: true, name: :route_access_rules_guid_index
14-
index %i[route_id name], unique: true, name: :route_access_rules_route_id_name_index
1513
index %i[route_id selector], unique: true, name: :route_access_rules_route_id_selector_index
1614
foreign_key [:route_id], :routes, on_delete: :cascade, name: :fk_route_access_rules_route_id
1715
end
1816
end
17+
18+
unless table_exists?(:route_access_rule_labels)
19+
create_table :route_access_rule_labels do
20+
primary_key :id, name: :id
21+
String :guid, null: false, size: 255
22+
String :resource_guid, null: false, size: 255
23+
String :key_prefix, size: 253
24+
String :key_name, null: false, size: 63
25+
String :value, null: false, size: 63
26+
DateTime :created_at, null: false
27+
DateTime :updated_at
28+
29+
index :guid, unique: true, name: :route_access_rule_labels_guid_index
30+
index :resource_guid, name: :route_access_rule_labels_resource_guid_index
31+
index %i[resource_guid key_prefix key_name], unique: true, name: :route_access_rule_labels_compound_index
32+
foreign_key [:resource_guid], :route_access_rules, key: :guid, on_delete: :cascade, name: :fk_route_access_rule_labels_resource_guid
33+
end
34+
end
35+
36+
unless table_exists?(:route_access_rule_annotations)
37+
create_table :route_access_rule_annotations do
38+
primary_key :id, name: :id
39+
String :guid, null: false, size: 255
40+
String :resource_guid, null: false, size: 255
41+
String :key_prefix, size: 253
42+
String :key, null: false, size: 1000
43+
String :value, size: 5000
44+
DateTime :created_at, null: false
45+
DateTime :updated_at
46+
47+
index :guid, unique: true, name: :route_access_rule_annotations_guid_index
48+
index :resource_guid, name: :route_access_rule_annotations_resource_guid_index
49+
index %i[resource_guid key], unique: true, name: :route_access_rule_annotations_key_index
50+
foreign_key [:resource_guid], :route_access_rules, key: :guid, on_delete: :cascade, name: :fk_route_access_rule_annotations_resource_guid
51+
end
52+
end
1953
end
2054

2155
down do
22-
drop_table(:route_access_rules) if table_exists?(:route_access_rules)
56+
%i[route_access_rule_annotations route_access_rule_labels route_access_rules].each { |t| drop_table(t) if table_exists?(t) }
2357
end
2458
end

db/migrations/20260415000001_remove_name_from_route_access_rules.rb

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)