Prevent repository access to users without repository READ permission on team level [SCI-10972]

This commit is contained in:
Oleksii Kriuchykhin 2024-08-09 15:56:59 +02:00
parent 3a6888b276
commit ac32b5b04a
30 changed files with 92 additions and 95 deletions

View file

@ -18,7 +18,7 @@ module Api
end
def create
inventory_item_to_link = RepositoryRow.where(repository: Repository.accessible_by_teams(@team))
inventory_item_to_link = RepositoryRow.where(repository: Repository.viewable_by_user(current_user, @team))
.find(connection_params[:child_id])
child_connection = @inventory_item.child_connections.create!(
child: inventory_item_to_link,

View file

@ -20,7 +20,7 @@ module Api
end
def create
inventory_item_to_link = RepositoryRow.where(repository: Repository.accessible_by_teams(@team))
inventory_item_to_link = RepositoryRow.where(repository: Repository.viewable_by_user(current_user, @team))
.find(connection_params[:parent_id])
parent_connection = @inventory_item.parent_connections.create!(
parent: inventory_item_to_link,

View file

@ -27,7 +27,7 @@ class AtWhoController < ApplicationController
if params[:repository_id].present?
Repository.find_by(id: params[:repository_id])
else
Repository.active.accessible_by_teams(@team).first
Repository.active.viewable_by_user(current_user, @team).first
end
items = []
@ -54,8 +54,8 @@ class AtWhoController < ApplicationController
end
def menu
repositories = Repository.active.accessible_by_teams(@team)
render json: {
repositories = Repository.active.viewable_by_user(current_user, @team)
render json: {
html: render_to_string(partial: 'shared/smart_annotation/menu',
locals: { repositories: repositories },
formats: :html)

View file

@ -15,7 +15,7 @@ class HiddenRepositoryCellRemindersController < ApplicationController
private
def load_repository
@repository = Repository.accessible_by_teams(current_team).find_by(id: params[:repository_id])
@repository = Repository.viewable_by_user(current_user).find_by(id: params[:repository_id])
render_404 unless @repository
end

View file

@ -145,7 +145,7 @@ class MyModuleRepositoriesController < ApplicationController
end
def repositories_list_html
@assigned_repositories = @my_module.live_and_snapshot_repositories_list
@assigned_repositories = @my_module.readable_live_and_snapshot_repositories_list(current_user)
render json: {
html: render_to_string(partial: 'my_modules/repositories/repositories_list'),
assigned_rows_count: @assigned_repositories.map(&:assigned_rows_count).sum
@ -162,7 +162,7 @@ class MyModuleRepositoriesController < ApplicationController
end
def repositories_dropdown_list
@repositories = Repository.accessible_by_teams(current_team).joins("
@repositories = Repository.viewable_by_user(current_user).joins("
LEFT OUTER JOIN repository_rows ON
repository_rows.repository_id = repositories.id
LEFT OUTER JOIN my_module_repository_rows ON

View file

@ -153,7 +153,7 @@ class MyModuleShareableLinksController < ApplicationController
end
def load_repository
@repository = @my_module.assigned_repositories.find_by(id: params[:id])
@repository = @my_module.assigned_repositories.viewable_by_user(current_user).find_by(id: params[:id])
render_404 unless @repository
end

View file

@ -304,7 +304,7 @@ class MyModulesController < ApplicationController
def protocols
@protocol = @my_module.protocol
@assigned_repositories = @my_module.live_and_snapshot_repositories_list
@assigned_repositories = @my_module.readable_live_and_snapshot_repositories_list(current_user)
end
def protocol

View file

@ -312,7 +312,7 @@ class ReportsController < ApplicationController
def load_wizard_vars
@templates = Extends::REPORT_TEMPLATES
live_repositories = Repository.accessible_by_teams(current_team).sort_by { |r| r.name.downcase }
live_repositories = Repository.viewable_by_user(current_user).sort_by { |r| r.name.downcase }
snapshots_of_deleted = RepositorySnapshot.left_outer_joins(:original_repository)
.where(team: current_team)
.where.not(original_repository: live_repositories)
@ -348,7 +348,7 @@ class ReportsController < ApplicationController
def load_available_repositories
@available_repositories = []
repositories = Repository.active
.accessible_by_teams(current_team)
.viewable_by_user(current_user)
.name_like(search_params[:query])
.limit(Constants::SEARCH_LIMIT)
repositories.each do |repository|

View file

@ -298,14 +298,14 @@ class RepositoriesController < ApplicationController
end
def import_records
render_403 unless can_create_repository_rows?(Repository.accessible_by_teams(current_team)
render_403 unless can_create_repository_rows?(Repository.viewable_by_user(current_user)
.find_by(id: import_params[:id]))
# Check if there exist mapping for repository record (it's mandatory)
if import_params[:mappings].present? && import_params[:mappings].value?('-1')
status = ImportRepository::ImportRecords
.new(
temp_file: TempFile.find_by(id: import_params[:file_id]),
repository: Repository.accessible_by_teams(current_team).find_by(id: import_params[:id]),
repository: Repository.viewable_by_user(current_user).find_by(id: import_params[:id]),
mappings: import_params[:mappings],
session: session,
user: current_user,
@ -452,12 +452,12 @@ class RepositoriesController < ApplicationController
def load_repository
repository_id = params[:id] || params[:repository_id]
@repository = Repository.accessible_by_teams(current_user.teams).find_by(id: repository_id)
@repository = Repository.viewable_by_user(current_user).find_by(id: repository_id)
render_404 unless @repository
end
def load_repositories
@repositories = Repository.accessible_by_teams(current_team)
@repositories = Repository.viewable_by_user(current_user)
end
def load_repositories_for_archiving

View file

@ -107,7 +107,7 @@ class RepositoryColumnsController < ApplicationController
AvailableRepositoryColumn = Struct.new(:id, :name)
def load_repository
@repository = Repository.accessible_by_teams(current_team).find_by(id: params[:repository_id])
@repository = Repository.viewable_by_user(current_user).find_by(id: params[:repository_id])
render_404 unless @repository
end

View file

@ -56,7 +56,7 @@ class RepositoryRowConnectionsController < ApplicationController
end
def repositories
repositories = Repository.accessible_by_teams(current_team)
repositories = Repository.viewable_by_user(current_user)
.search_by_name_and_id(current_user, current_user.teams, params[:query])
.order(name: :asc)
.page(params[:page] || 1)
@ -69,7 +69,7 @@ class RepositoryRowConnectionsController < ApplicationController
end
def repository_rows
selected_repository = Repository.accessible_by_teams(current_team).find(params[:selected_repository_id])
selected_repository = Repository.viewable_by_user(current_user).find(params[:selected_repository_id])
repository_rows = selected_repository.repository_rows
.where.not(id: @repository_row.id)
@ -93,14 +93,14 @@ class RepositoryRowConnectionsController < ApplicationController
return render_422(t('.invalid_params')) unless @relation_type
@connection_repository = Repository.accessible_by_teams(current_team)
@connection_repository = Repository.viewable_by_user(current_user)
.find_by(id: connection_params[:connection_repository_id])
return render_404 unless @connection_repository
return render_403 unless can_connect_repository_rows?(@connection_repository)
end
def load_repository
@repository = Repository.accessible_by_teams(current_team).find_by(id: params[:repository_id])
@repository = Repository.viewable_by_user(current_user).find_by(id: params[:repository_id])
render_404 unless @repository
end

View file

@ -358,14 +358,14 @@ class RepositoryRowsController < ApplicationController
AvailableRepositoryRow = Struct.new(:id, :name, :has_file_attached)
def load_repository
@repository = Repository.accessible_by_teams(current_team)
@repository = Repository.viewable_by_user(current_user)
.eager_load(:repository_columns)
.find_by(id: params[:repository_id])
render_404 unless @repository
end
def load_repository_or_snapshot
@repository = Repository.accessible_by_teams(current_team).find_by(id: params[:repository_id]) ||
@repository = Repository.viewable_by_user(current_user).find_by(id: params[:repository_id]) ||
RepositorySnapshot.find_by(id: params[:repository_id])
return render_404 unless @repository
end

View file

@ -70,7 +70,7 @@ class RepositoryTableFiltersController < ApplicationController
private
def load_repository
@repository = Repository.accessible_by_teams(current_team).find_by(id: params[:repository_id])
@repository = Repository.viewable_by_user(current_user).find_by(id: params[:repository_id])
render_403 unless can_read_repository?(@repository)
end

View file

@ -47,7 +47,7 @@ module ReportsHelper
return my_module.repository_snapshots.find_by(parent_id: repository.parent_id, selected: true)
end
return nil unless my_module.assigned_repositories.exists?(id: repository.id)
return nil unless my_module.assigned_repositories.viewable_by_user(current_user).exists?(id: repository.id)
selected_snapshot = repository.repository_snapshots.find_by(my_module: my_module, selected: true)
selected_snapshot || repository
@ -106,14 +106,4 @@ module ReportsHelper
experiment_element.experiment.description
end
end
def assigned_to_report_repository_items(report, repository_name)
repository = Repository.accessible_by_teams(report.team).where(name: repository_name).take
return RepositoryRow.none if repository.blank?
my_modules = MyModule.joins(:experiment)
.where(experiment: { project: report.project })
.where(id: report.report_elements.my_module.select(:my_module_id))
repository.repository_rows.joins(:my_modules).where(my_modules: my_modules)
end
end

View file

@ -39,7 +39,7 @@ class TeamZipExportJob < ZipExportJob
inventories = "#{project_path}/Inventories"
FileUtils.mkdir_p(inventories)
repositories = project.assigned_repositories_and_snapshots
repositories = project.assigned_readable_repositories_and_snapshots(@user)
# Iterate through every inventory repo and save it to CSV
repositories.each_with_index do |repo, repo_idx|

View file

@ -74,7 +74,10 @@ class Asset < ApplicationRecord
.pluck(:id)
assets_in_inventories = Asset.joins(repository_cell: { repository_column: :repository })
.where(repositories: { team: teams })
.where(repositories: {
id: Repository.with_granted_permissions(user, RepositoryPermissions::READ).select(:id),
team_id: teams
})
.where.not(repositories: { type: 'RepositorySnapshot' })
.pluck(:id)

View file

@ -177,14 +177,12 @@ class MyModule < ApplicationRecord
end
def assigned_repositories
team = experiment.project.team
Repository.accessible_by_teams(team)
.joins(repository_rows: :my_module_repository_rows)
Repository.joins(repository_rows: :my_module_repository_rows)
.where(my_module_repository_rows: { my_module_id: id })
.group(:id)
end
def live_and_snapshot_repositories_list
def readable_live_and_snapshot_repositories_list(user, team = user.current_team)
snapshots = repository_snapshots.left_outer_joins(:original_repository)
selected_snapshots = snapshots.where(selected: true)
@ -197,6 +195,7 @@ class MyModule < ApplicationRecord
.order(:parent_id, updated_at: :desc)
live_repositories = assigned_repositories
.viewable_by_user(user, team)
.select('repositories.*, COUNT(DISTINCT repository_rows.id) AS assigned_rows_count')
.where.not(id: repository_snapshots.where(selected: true).select(:parent_id))

View file

@ -201,9 +201,9 @@ class Project < ApplicationRecord
st
end
def assigned_repositories_and_snapshots
live_repositories = Repository.assigned_to_project(self)
snapshots = RepositorySnapshot.assigned_to_project(self)
def assigned_readable_repositories_and_snapshots(user)
live_repositories = Repository.assigned_to_project(self).readable_by_user(user)
snapshots = RepositorySnapshot.assigned_to_project(self).readable_by_user(user)
(live_repositories + snapshots).sort_by { |r| r.name.downcase }
end

View file

@ -255,11 +255,8 @@ class Protocol < ApplicationRecord
def self.viewable_by_user_my_module_protocols(user, teams)
distinct.joins(:my_module)
.joins("INNER JOIN user_assignments my_module_user_assignments " \
"ON my_module_user_assignments.assignable_type = 'MyModule' " \
"AND my_module_user_assignments.assignable_id = my_modules.id")
.where(my_module_user_assignments: { user_id: user })
.where(team: teams)
.where(my_modules: MyModule.with_granted_permissions(user, MyModulePermissions::READ)
.where(user_assignments: { team: teams }))
end
def self.filter_by_teams(teams = [])

View file

@ -103,7 +103,7 @@ class Report < ApplicationRecord
def self.generate_whole_project_report(project, current_user, current_team)
content = {
'experiments' => [],
'repositories' => project.assigned_repositories_and_snapshots.pluck(:id)
'repositories' => project.assigned_readable_repositories_and_snapshots(current_user).pluck(:id)
}
project.experiments.includes(:my_modules).find_each do |experiment|
content['experiments'].push(

View file

@ -48,21 +48,23 @@ class Repository < RepositoryBase
scope :archived, -> { where(archived: true) }
scope :globally_shared, -> { where(permission_level: %i(shared_read shared_write)) }
scope :accessible_by_teams, lambda { |teams|
accessible_repositories = left_outer_joins(:team_shared_objects)
accessible_repositories =
accessible_repositories
scope :viewable_by_user, lambda { |user, teams = user.current_team|
readable_repositories = joins(user_assignments: :user_role)
.left_outer_joins(:team_shared_objects)
readable_repositories =
readable_repositories
.where(user_assignments: { user: user })
.where('? = ANY(user_roles.permissions)', RepositoryPermissions::READ)
.where(team: teams)
.or(accessible_repositories.where(team_shared_objects: { team: teams }))
.or(accessible_repositories
.or(readable_repositories.where(team_shared_objects: { team: teams }))
.or(readable_repositories
.where(permission_level: [Extends::SHARED_OBJECTS_PERMISSION_LEVELS[:shared_read],
Extends::SHARED_OBJECTS_PERMISSION_LEVELS[:shared_write]]))
accessible_repositories.distinct
readable_repositories.distinct
}
scope :assigned_to_project, lambda { |project|
accessible_by_teams(project.team)
.joins(repository_rows: { my_module_repository_rows: { my_module: { experiment: :project } } })
joins(repository_rows: { my_module_repository_rows: { my_module: { experiment: :project } } })
.where(repository_rows: { my_module_repository_rows: { my_module: { experiments: { project: project } } } })
}
@ -151,10 +153,6 @@ class Repository < RepositoryBase
team_shared_objects.where(team: team, permission_level: :shared_write).any?
end
def self.viewable_by_user(_user, teams)
accessible_by_teams(teams)
end
def self.name_like(query)
where('repositories.name ILIKE ?', "%#{query}%")
end

View file

@ -125,18 +125,17 @@ class RepositoryRow < ApplicationRecord
def self.search(user,
include_archived,
query = nil,
_current_team = nil,
current_team = nil,
options = {})
teams = options[:teams] || current_team || user.teams.select(:id)
searchable_row_fields = [RepositoryRow::PREFIXED_ID_SQL, 'repository_rows.name', 'users.full_name']
readable_rows = distinct.joins(:repository, :created_by)
.joins("INNER JOIN user_assignments repository_user_assignments " \
"ON repository_user_assignments.assignable_type = 'RepositoryBase' " \
"AND repository_user_assignments.assignable_id = repositories.id")
.where(repository_user_assignments: { user_id: user, team_id: teams })
readable_rows =
distinct
.joins(:repository, :created_by)
.where(repositories: { id: Repository.with_granted_permissions(user, RepositoryPermissions::READ).select(:id),
team_id: teams })
readable_rows = readable_rows.active unless include_archived
repository_rows = readable_rows.where_attributes_like_boolean(searchable_row_fields, query, options)

View file

@ -47,10 +47,8 @@ class Result < ApplicationRecord
new_query = left_joins(:result_comments, :result_texts, result_tables: :table)
.joins(:my_module)
.joins("INNER JOIN user_assignments my_module_user_assignments " \
"ON my_module_user_assignments.assignable_type = 'MyModule' " \
"AND my_module_user_assignments.assignable_id = my_modules.id")
.where(my_module_user_assignments: { user_id: user, team_id: teams })
.where(my_modules: MyModule.with_granted_permissions(user, MyModulePermissions::READ)
.where(user_assignments: { team: teams }))
unless include_archived
new_query = new_query.joins(my_module: { experiment: :project })

View file

@ -374,7 +374,7 @@ class User < ApplicationRecord
end
def current_team
Team.find_by_id(self.current_team_id)
@current_team ||= teams.find_by(id: current_team_id)
end
def permission_team=(team)

View file

@ -6,7 +6,7 @@ Canaid::Permissions.register_for(RepositoryBase) do
if repository.is_a?(RepositorySnapshot)
can_read_my_module?(user, repository.my_module)
else
user.teams.include?(repository.team) || repository.shared_with?(user.current_team)
repository.permission_granted?(user, RepositoryPermissions::READ)
end
end

View file

@ -5,15 +5,24 @@ class ActivitiesService
# Create condition for view permissions checking first
visible_teams = user.teams.where(id: teams)
visible_projects = Project.viewable_by_user(user, visible_teams)
visible_repositories = Repository.viewable_by_user(user, visible_teams)
visible_by_teams = Activity.where(project: nil, team_id: visible_teams.select(:id))
.where.not(subject_type: %w(RepositoryBase RepositoryRow))
.order(created_at: :desc)
visible_by_repositories = Activity.where(subject_type: %w(RepositoryBase RepositoryRow),
subject_id: visible_repositories.select(:id))
.order(created_at: :desc)
visible_by_projects = Activity.where(project_id: visible_projects.select(:id))
.order(created_at: :desc)
query = Activity.from("((#{visible_by_teams.to_sql}) UNION ALL (#{visible_by_projects.to_sql})) AS activities")
query = Activity.from(
"((#{visible_by_teams.to_sql}) UNION ALL " \
"(#{visible_by_repositories.to_sql}) UNION ALL " \
"(#{visible_by_projects.to_sql})) AS activities"
)
if filters[:subjects].present?
subjects_with_children = load_subjects_children(filters[:subjects])
subjects_with_children = load_subjects_children(filters[:subjects], user, teams)
where_condition = subjects_with_children.to_h.map { '(subject_type = ? AND subject_id IN(?))' }.join(' OR ')
where_arguments = subjects_with_children.to_h.flatten
if subjects_with_children[:my_module]
@ -45,7 +54,7 @@ class ActivitiesService
.without_count
end
def self.load_subjects_children(subjects = {})
def self.load_subjects_children(subjects = {}, user = nil, teams = nil)
Extends::ACTIVITY_SUBJECT_CHILDREN.each do |subject_name, children|
subject_name = subject_name.to_s.camelize
next unless children && subjects[subject_name]
@ -55,19 +64,23 @@ class ActivitiesService
child_model = parent_model.reflect_on_association(child).class_name.to_sym
next if subjects[child_model]
if subject_name == 'Result'
parent_model = parent_model.with_discarded
end
parent_model = parent_model.with_discarded if subject_name == 'Result'
if child == :results
subjects[child_model] = parent_model.where(id: subjects[subject_name])
.joins(:results_include_discarded)
.pluck('results.id')
else
subjects[child_model] = parent_model.where(id: subjects[subject_name])
.joins(child)
.pluck("#{child.to_s.pluralize}.id")
end
subjects[child_model] =
case child
when :results
parent_model.where(id: subjects[subject_name])
.joins(:results_include_discarded)
.pluck('results.id')
when :repositories
parent_model.viewable_by_user(user, teams)
.where(id: subjects[subject_name])
.pluck('repositories.id')
else
parent_model.where(id: subjects[subject_name])
.joins(child)
.pluck("#{child.to_s.pluralize}.id")
end
end
end

View file

@ -81,7 +81,7 @@ module ReportActions
my_module_element = save_element!({ 'my_module_id' => my_module.id }, :my_module, experiment_element)
my_module.live_and_snapshot_repositories_list.each do |repository|
my_module.readable_live_and_snapshot_repositories_list(@user, @report.team).each do |repository|
next unless @repositories.include?(repository.parent_id || repository.id)
save_element!(

View file

@ -33,7 +33,7 @@ module ReportActions
include Canaid::Helpers::PermissionsHelper
def load_repository_collaborators
@repository = Repository.active.accessible_by_teams(@team).find_by(id: @params[:repository_id])
@repository = Repository.active.viewable_by_user(@user, @team).find_by(id: @params[:repository_id])
unless can_create_repository_rows?(@user, @repository)
raise ReportActions::RepositoryPermissionError, I18n.t('projects.reports.new.no_permissions')
end

View file

@ -25,7 +25,7 @@ module SmartAnnotations
def validate_rep_item_permissions(user, team, object)
if object.repository
return Repository.accessible_by_teams(team).find_by(id: object.repository_id).present? &&
return Repository.viewable_by_user(user, team).find_by(id: object.repository_id).present? &&
can_read_repository?(user, object.repository)
end

View file

@ -57,7 +57,7 @@
</div>
</div>
<!-- Assigned items -->
<% assigned_repositories = @my_module.live_and_snapshot_repositories_list %>
<% assigned_repositories = @my_module.readable_live_and_snapshot_repositories_list(current_user) %>
<div class="task-section">
<div class="task-section-header">
<a class="task-section-caret" role="button" data-toggle="collapse" href="#assigned-items-container" aria-expanded="true" aria-controls="assigned-items-container">