Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
philippthun committed Nov 18, 2022
1 parent 2adc01f commit 54cdc4f
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 123 deletions.
8 changes: 5 additions & 3 deletions app/controllers/v3/roles_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ def show
decorators << IncludeRoleOrganizationDecorator if IncludeRoleOrganizationDecorator.match?(message.include)
decorators << IncludeRoleSpaceDecorator if IncludeRoleSpaceDecorator.match?(message.include)

role = readable_roles.first(guid: hashed_params[:guid])
role = readable_roles.where(guid: hashed_params[:guid]).first
resource_not_found!(:role) unless role

render status: :ok, json: Presenters::V3::RolePresenter.new(role, decorators: decorators)
end

def destroy
role = readable_roles.first(guid: hashed_params[:guid])
role = readable_roles.where(guid: hashed_params[:guid]).first
resource_not_found!(:role) unless role

if role.for_space?
Expand Down Expand Up @@ -147,7 +147,9 @@ def readable_roles
if permission_queryer.can_read_globally?
Role
else
Role.where(space_id: visible_space_ids).or(organization_id: visible_org_ids)
readable_by_space = { space_id: visible_space_ids }
readable_by_org = { organization_id: visible_org_ids }
Role.where { Sequel.|(readable_by_space, readable_by_org) }
end
end

Expand Down
8 changes: 4 additions & 4 deletions app/fetchers/base_list_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ def advanced_filtering(message, dataset, klass)
values.map do |operator, given_timestamp|
if operator == RelationalOperators::LESS_THAN_COMPARATOR
normalized_timestamp = Time.parse(given_timestamp).utc
dataset = dataset.where { Sequel.qualify(klass.table_name, filter) < normalized_timestamp }
dataset = dataset.where(Sequel.qualify(klass.table_name, filter) < normalized_timestamp)
elsif operator == RelationalOperators::LESS_THAN_OR_EQUAL_COMPARATOR
normalized_timestamp = (Time.parse(given_timestamp).utc + 0.999999).utc
dataset = dataset.where { Sequel.qualify(klass.table_name, filter) <= normalized_timestamp }
dataset = dataset.where(Sequel.qualify(klass.table_name, filter) <= normalized_timestamp)
elsif operator == RelationalOperators::GREATER_THAN_COMPARATOR
normalized_timestamp = (Time.parse(given_timestamp).utc + 0.999999).utc
dataset = dataset.where { Sequel.qualify(klass.table_name, filter) > normalized_timestamp }
dataset = dataset.where(Sequel.qualify(klass.table_name, filter) > normalized_timestamp)
elsif operator == RelationalOperators::GREATER_THAN_OR_EQUAL_COMPARATOR
normalized_timestamp = Time.parse(given_timestamp).utc
dataset = dataset.where { Sequel.qualify(klass.table_name, filter) >= normalized_timestamp }
dataset = dataset.where(Sequel.qualify(klass.table_name, filter) >= normalized_timestamp)
end
end
else
Expand Down
4 changes: 2 additions & 2 deletions app/fetchers/role_list_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
module VCAP::CloudController
class RoleListFetcher < BaseListFetcher
class << self
def fetch(message, readable_users_dataset, eager_loaded_associations: [])
filter(message, readable_users_dataset).eager(eager_loaded_associations)
def fetch(message, readable_roles, eager_loaded_associations: [])
filter(message, readable_roles).eager(eager_loaded_associations)
end

private
Expand Down
2 changes: 1 addition & 1 deletion app/models/runtime/pollable_job_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def resource_exists?
Sequel::Model(ActiveSupport::Inflector.pluralize(resource_type).to_sym)
end

!!model.find(guid: resource_guid)
!!model.where(guid: resource_guid).first
end

def self.find_by_delayed_job(delayed_job)
Expand Down
305 changes: 196 additions & 109 deletions app/models/runtime/role.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,97 +3,72 @@
module VCAP::CloudController
SPACE_OR_ORGANIZATION_NOT_SPECIFIED = -1

# Sequel allows to create models based on datasets. The following is a dataset that unions all the individual roles
# tables and labels each row with a `type` column based on which table it came from
class Role < Sequel::Model(
OrganizationUser.select(
Sequel.as(VCAP::CloudController::RoleTypes::ORGANIZATION_USER, :type),
Sequel.as(:role_guid, :guid),
:user_id,
:organization_id,
Sequel.as(SPACE_OR_ORGANIZATION_NOT_SPECIFIED, :space_id),
:created_at,
:updated_at
).union(
OrganizationManager.select(
Sequel.as(VCAP::CloudController::RoleTypes::ORGANIZATION_MANAGER, :type),
Sequel.as(:role_guid, :guid),
:user_id,
:organization_id,
Sequel.as(SPACE_OR_ORGANIZATION_NOT_SPECIFIED, :space_id),
:created_at,
:updated_at),
all: true,
from_self: false
).union(
OrganizationBillingManager.select(
Sequel.as(VCAP::CloudController::RoleTypes::ORGANIZATION_BILLING_MANAGER, :type),
Sequel.as(:role_guid, :guid),
:user_id,
:organization_id,
Sequel.as(SPACE_OR_ORGANIZATION_NOT_SPECIFIED, :space_id),
:created_at,
:updated_at),
all: true,
from_self: false
).union(
OrganizationAuditor.select(
Sequel.as(VCAP::CloudController::RoleTypes::ORGANIZATION_AUDITOR, :type),
Sequel.as(:role_guid, :guid),
:user_id,
:organization_id,
Sequel.as(SPACE_OR_ORGANIZATION_NOT_SPECIFIED, :space_id),
:created_at,
:updated_at),
all: true,
from_self: false
).union(
SpaceDeveloper.select(
Sequel.as(VCAP::CloudController::RoleTypes::SPACE_DEVELOPER, :type),
Sequel.as(:role_guid, :guid),
:user_id,
Sequel.as(SPACE_OR_ORGANIZATION_NOT_SPECIFIED, :organization_id),
:space_id,
:created_at,
:updated_at),
all: true,
from_self: false
).union(
SpaceAuditor.select(
Sequel.as(VCAP::CloudController::RoleTypes::SPACE_AUDITOR, :type),
Sequel.as(:role_guid, :guid),
:user_id,
Sequel.as(SPACE_OR_ORGANIZATION_NOT_SPECIFIED, :organization_id),
:space_id,
:created_at,
:updated_at),
all: true,
from_self: false
).union(
SpaceSupporter.select(
Sequel.as(VCAP::CloudController::RoleTypes::SPACE_SUPPORTER, :type),
Sequel.as(:role_guid, :guid),
:user_id,
Sequel.as(SPACE_OR_ORGANIZATION_NOT_SPECIFIED, :organization_id),
:space_id,
:created_at,
:updated_at),
all: true,
from_self: false
).union(
SpaceManager.select(
Sequel.as(VCAP::CloudController::RoleTypes::SPACE_MANAGER, :type),
Sequel.as(:role_guid, :guid),
:user_id,
Sequel.as(SPACE_OR_ORGANIZATION_NOT_SPECIFIED, :organization_id),
:space_id,
:created_at,
:updated_at),
all: true,
from_self: false
).from_self
)
class RoleDataset
class << self
def model_by_role(role)
case role
when RoleTypes::ORGANIZATION_USER
OrganizationUser
when RoleTypes::ORGANIZATION_AUDITOR
OrganizationAuditor
when RoleTypes::ORGANIZATION_BILLING_MANAGER
OrganizationBillingManager
when RoleTypes::ORGANIZATION_MANAGER
OrganizationManager
when RoleTypes::SPACE_AUDITOR
SpaceAuditor
when RoleTypes::SPACE_SUPPORTER
SpaceSupporter
when RoleTypes::SPACE_DEVELOPER
SpaceDeveloper
when RoleTypes::SPACE_MANAGER
SpaceManager
else
raise "Invalid role type: #{role}"
end
end

def dataset_from_model_and_filters(model, filters)
filters.inject(model) do |dataset, filter|
dataset.where(filter)
end
end

def dataset_with_select(dataset, role)
ds = dataset.select(Sequel.as(role, :type), Sequel.as(:role_guid, :guid))
ds = if RoleTypes::ORGANIZATION_ROLES.include?(role)
ds.select_append(:organization_id, Sequel.as(SPACE_OR_ORGANIZATION_NOT_SPECIFIED, :space_id))
else
ds.select_append(Sequel.as(SPACE_OR_ORGANIZATION_NOT_SPECIFIED, :organization_id), :space_id)
end
ds.select_append(:user_id, :created_at, :updated_at)
end

def datasets_for_individual_roles(filters_per_role)
RoleTypes::ALL_ROLES.map do |role|
model = model_by_role(role)
filters = filters_per_role[role] || []
dataset = dataset_from_model_and_filters(model, filters)
dataset_with_select(dataset, role)
end
end

def unioned_dataset(datasets)
datasets.inject do |dataset, ds|
dataset.union(ds, all: true, from_self: false)
end
end

def build(filters_per_role={})
datasets = datasets_for_individual_roles(filters_per_role)
unioned_dataset(datasets)
end
end
end

# Sequel allows to create models based on datasets. The following is a dataset that unions all the individual roles
# tables and labels each row with a `type` column based on which table it came from.
class Role < Sequel::Model(RoleDataset.build.from_self)
many_to_one :user, key: :user_id
many_to_one :organization, key: :organization_id
many_to_one :space, key: :space_id
Expand All @@ -113,30 +88,142 @@ def space_guid
end

def for_space?
VCAP::CloudController::RoleTypes::SPACE_ROLES.include?(type)
RoleTypes::SPACE_ROLES.include?(type)
end

def model_class
case type
when VCAP::CloudController::RoleTypes::SPACE_MANAGER
SpaceManager
when VCAP::CloudController::RoleTypes::SPACE_AUDITOR
SpaceAuditor
when VCAP::CloudController::RoleTypes::SPACE_DEVELOPER
SpaceDeveloper
when VCAP::CloudController::RoleTypes::SPACE_SUPPORTER
SpaceSupporter
when VCAP::CloudController::RoleTypes::ORGANIZATION_USER
OrganizationUser
when VCAP::CloudController::RoleTypes::ORGANIZATION_AUDITOR
OrganizationAuditor
when VCAP::CloudController::RoleTypes::ORGANIZATION_BILLING_MANAGER
OrganizationBillingManager
when VCAP::CloudController::RoleTypes::ORGANIZATION_MANAGER
OrganizationManager
RoleDataset.model_by_role(type)
end
end

# rubocop:disable Metrics/BlockLength
Role.dataset_module do
def first(*cond)
raise 'use where(cond).first instead' unless cond.empty?

super
end

def where(*cond)
return super if block_given?

filters_per_role = self.cache_get(:filters_per_role) || init_filters([])

Array.wrap(cond).each do |condition|
filters = case condition
when Hash
hash_condition_to_filters(condition)
when Sequel::SQL::BooleanExpression
boolean_expression_to_filters(condition)
else
raise "Unsupported condition type: #{condition.class}"
end

append_filters(filters_per_role, filters)
end

dataset = self.from(RoleDataset.build(filters_per_role))
dataset.cache_set(:filters_per_role, filters_per_role)
dataset
end

private

INCLUDE = { 1 => 1 }.freeze
EXCLUDE = { 1 => 0 }.freeze

def init_filters(default)
RoleTypes::ALL_ROLES.each_with_object({}) do |role, filters|
filters[role] = default.dup
end
end

def append_filters(all_filters, new_filters)
RoleTypes::ALL_ROLES.each do |role|
all_filters[role] << new_filters[role]
end
end

def remove_table_name(identifier)
case identifier
when String, Symbol
identifier.to_s.remove(/.*__/).to_sym
when Sequel::SQL::QualifiedIdentifier
identifier.column.to_sym
else
raise Error.new("Invalid role type: #{type}")
raise "Unsupported identifier type: #{identifier.class}"
end
end

def adapt_column_name(column_name)
case column_name
when :guid
:role_guid
else
column_name
end
end

def adapt_identifier(identifier)
column_name = remove_table_name(identifier)
adapt_column_name(column_name)
end

def apply_filter_to_roles(all_filters, roles, new_filter)
roles.each do |role|
all_filters[role]&.merge!(new_filter)
end
end

def adapt_boolean_expression(expression)
args = expression.args.map do |arg|
case arg
when Sequel::SQL::BooleanExpression
adapt_boolean_expression(arg)
when Sequel::SQL::QualifiedIdentifier
column_name = adapt_identifier(arg)
raise "Unsupported column: #{column_name}" unless [:created_at, :updated_at].include?(column_name)

Sequel::SQL::Identifier.new(column_name)
else
arg
end
end
Sequel::SQL::BooleanExpression.new(expression.op, *args)
end

def hash_condition_to_filters(condition)
filters = init_filters({})

condition.transform_keys! { |key| adapt_identifier(key) }

condition.each do |column_name, filter|
case column_name
when :type
roles = Array.wrap(filter)
apply_filter_to_roles(filters, roles, INCLUDE)
apply_filter_to_roles(filters, RoleTypes::ALL_ROLES - roles, EXCLUDE)
when :organization_id
apply_filter_to_roles(filters, RoleTypes::ORGANIZATION_ROLES, { organization_id: filter })
apply_filter_to_roles(filters, RoleTypes::SPACE_ROLES, EXCLUDE)
when :space_id
apply_filter_to_roles(filters, RoleTypes::ORGANIZATION_ROLES, EXCLUDE)
apply_filter_to_roles(filters, RoleTypes::SPACE_ROLES, { space_id: filter })
when :role_guid, :user_id
apply_filter_to_roles(filters, RoleTypes::ALL_ROLES, { column_name => filter })
else
raise "Unsupported column: #{column_name}"
end
end

filters
end

def boolean_expression_to_filters(expression)
RoleTypes::ALL_ROLES.each_with_object({}) do |role, filters|
filters[role] = adapt_boolean_expression(expression)
end
end
end
# rubocop:enable Metrics/BlockLength
end
Loading

0 comments on commit 54cdc4f

Please sign in to comment.