Implement storage location sharing logic and permissions [SCI-10865]

This commit is contained in:
Martin Artnik 2024-09-03 15:50:10 +02:00
parent 40355ae6c8
commit 5ba07aec28
18 changed files with 105 additions and 67 deletions

View file

@ -93,7 +93,7 @@ class StorageLocationRepositoryRowsController < ApplicationController
end
def load_storage_location
@storage_location = StorageLocation.where(team: current_team).find(
@storage_location = StorageLocation.viewable_by_user(current_user).find(
storage_location_repository_row_params[:storage_location_id]
)
render_404 unless @storage_location
@ -110,12 +110,10 @@ class StorageLocationRepositoryRowsController < ApplicationController
end
def check_read_permissions
render_403 unless can_read_storage_location_containers?(current_team)
render_403 unless can_read_storage_location?(@storage_location)
end
def check_manage_permissions
unless can_manage_storage_location_containers?(current_team) && can_read_repository?(@repository_row.repository)
render_403
end
render_403 unless can_manage_storage_location?(@storage_location)
end
end

View file

@ -12,7 +12,7 @@ class StorageLocationsController < ApplicationController
respond_to do |format|
format.html
format.json do
storage_locations = Lists::StorageLocationsService.new(current_team, params).call
storage_locations = Lists::StorageLocationsService.new(current_user, current_team, params).call
render json: storage_locations, each_serializer: Lists::StorageLocationSerializer,
user: current_user, meta: pagination_dict(storage_locations)
end
@ -35,9 +35,11 @@ class StorageLocationsController < ApplicationController
def create
@storage_location = StorageLocation.new(
storage_location_params.merge({ team: current_team, created_by: current_user })
storage_location_params.merge({ created_by: current_user })
)
@storage_location.team = @storage_location.root_storage_location.team
@storage_location.image.attach(params[:signed_blob_id]) if params[:signed_blob_id]
if @storage_location.save
@ -122,7 +124,7 @@ class StorageLocationsController < ApplicationController
end
def load_storage_location
@storage_location = current_team.storage_locations.find_by(id: storage_location_params[:id])
@storage_location = StorageLocation.viewable_by_user(current_user).find_by(id: storage_location_params[:id])
render_404 unless @storage_location
end

View file

@ -38,9 +38,9 @@ class TeamSharedObjectsController < ApplicationController
def load_vars
case params[:object_type]
when 'Repository'
@model = current_team.repositories.find_by(id: params[:object_id])
@model = Repository.viewable_by_user(current_user).find_by(id: params[:object_id])
when 'StorageLocation'
@model = current_team.storage_locations.find_by(id: params[:object_id])
@model = StorageLocation.viewable_by_user(current_user).find_by(id: params[:object_id])
end
render_404 unless @model

View file

@ -12,7 +12,8 @@
<div class="sci-divider my-4" v-if="index > 0"></div>
<div class="flex items-center gap-2 mb-3">
{{ i18n.t('repositories.locations.container') }}:
<a :href="containerUrl(location.id)">{{ location.name }}</a>
<a v-if="location.readable" :href="containerUrl(location.id)">{{ location.name }}</a>
<span v-else>{{ location.name }}</span>
<span v-if="location.metadata.display_type !== 'grid'">
({{ location.positions.length }})
</span>

View file

@ -66,6 +66,10 @@ export default {
RemindersRender
},
props: {
canManage: {
type: String,
required: true
},
dataSource: {
type: String,
required: true
@ -143,13 +147,15 @@ export default {
toolbarActions() {
const left = [];
left.push({
name: 'assign',
icon: 'sn-icon sn-icon-new-task',
label: this.i18n.t('storage_locations.show.toolbar.assign'),
type: 'emit',
buttonStyle: 'btn btn-primary'
});
if (this.canManage) {
left.push({
name: 'assign',
icon: 'sn-icon sn-icon-new-task',
label: this.i18n.t('storage_locations.show.toolbar.assign'),
type: 'emit',
buttonStyle: 'btn btn-primary'
});
}
return {
left,

View file

@ -120,11 +120,6 @@ export default {
headerName: this.i18n.t('storage_locations.index.table.items'),
sortable: true
},
{
field: 'free_spaces',
headerName: this.i18n.t('storage_locations.index.table.free_spaces'),
sortable: true
},
{
field: 'shared',
headerName: this.i18n.t('storage_locations.index.table.shared'),

View file

@ -32,8 +32,18 @@ module Shareable
.where(team: teams)
.or(readable.where(team_shared_objects: { team: teams }))
.or(readable
.where(permission_level: [Extends::SHARED_OBJECTS_PERMISSION_LEVELS[:shared_read], Extends::SHARED_OBJECTS_PERMISSION_LEVELS[:shared_write]])
.where.not(team: teams))
.where(
if column_names.include?('permission_level')
{
permission_level: [
Extends::SHARED_OBJECTS_PERMISSION_LEVELS[:shared_read],
Extends::SHARED_OBJECTS_PERMISSION_LEVELS[:shared_write]
]
}
else
{}
end
).where.not(team: teams))
.distinct
}
end

View file

@ -93,10 +93,6 @@ class Repository < RepositoryBase
['repository_rows.name', RepositoryRow::PREFIXED_ID_SQL, 'users.full_name']
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

@ -174,17 +174,6 @@ class RepositoryRow < ApplicationRecord
self[:archived]
end
def grouped_storage_locations
storage_location_repository_rows.joins(:storage_location).group(:storage_location_id).select(
"storage_location_id as id,
(ARRAY_AGG(storage_locations.metadata))[1] as metadata,
MAX(storage_locations.name) as name,
jsonb_agg(jsonb_build_object(
'id', storage_location_repository_rows.id,
'metadata', storage_location_repository_rows.metadata)
) as positions").as_json
end
def has_reminders?(user)
stock_reminders = RepositoryCell.stock_reminder_repository_cells_scope(
repository_cells.joins(:repository_column), user)

View file

@ -17,16 +17,42 @@ class StorageLocation < ApplicationRecord
has_many :storage_location_repository_rows, inverse_of: :storage_location, dependent: :destroy
has_many :storage_locations, foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy
has_many :repository_rows, through: :storage_location_repository_row
has_many :repository_rows, through: :storage_location_repository_rows
validates :name, length: { maximum: Constants::NAME_MAX_LENGTH }
validate :parent_validation, if: -> { parent.present? }
scope :readable_by_user, (lambda do |user, team = user.current_team|
next StorageLocation.none unless team.permission_granted?(user, TeamPermissions::STORAGE_LOCATIONS_READ)
where(team: team)
end)
after_discard do
StorageLocation.where(parent_id: id).find_each(&:discard)
storage_location_repository_rows.each(&:discard)
end
def shared_with?(team)
return false if self.team == team
(root? ? self : root_storage_location).private_shared_with?(team)
end
def root?
parent_id.nil?
end
def root_storage_location
return self if root?
storage_location = self
storage_location = storage_location.parent while storage_location.parent_id
storage_location
end
def duplicate!
ActiveRecord::Base.transaction do
new_storage_location = dup

View file

@ -2,9 +2,13 @@
Canaid::Permissions.register_for(StorageLocation) do
can :read_storage_location do |user, storage_location|
storage_location.team.permission_granted?(
root_storage_location = storage_location.root_storage_location
next true if root_storage_location.shared_with?(user.current_team)
user.current_team == root_storage_location.team && root_storage_location.team.permission_granted?(
user,
if @storage_location.container
if root_storage_location.container?
TeamPermissions::STORAGE_LOCATION_CONTAINERS_READ
else
TeamPermissions::STORAGE_LOCATIONS_READ
@ -13,9 +17,13 @@ Canaid::Permissions.register_for(StorageLocation) do
end
can :manage_storage_location do |user, storage_location|
storage_location.team.permission_granted?(
root_storage_location = storage_location.root_storage_location
next true if root_storage_location.shared_with_write?(user.current_team)
user.current_team == root_storage_location.team && root_storage_location.team.permission_granted?(
user,
if @storage_location.container
if root_storage_location.container?
TeamPermissions::STORAGE_LOCATION_CONTAINERS_MANAGE
else
TeamPermissions::STORAGE_LOCATIONS_MANAGE
@ -24,6 +32,8 @@ Canaid::Permissions.register_for(StorageLocation) do
end
can :share_storage_location do |user, storage_location|
storage_location.team.permission_granted?(user, TeamPermissions::STORAGE_LOCATIONS_MANAGE)
user.current_team == storage_location.team &&
storage_location.root? &&
can_manage_storage_location?(user, storage_location)
end
end

View file

@ -9,7 +9,7 @@ module Lists
:have_reminders, :reminders_url
def row_id
object.repository_row.id unless hidden
object.repository_row.code
end
def row_name

View file

@ -2,7 +2,8 @@
module Lists
class StorageLocationsService < BaseService
def initialize(team, params)
def initialize(user, team, params)
@user = user
@team = team
@parent_id = params[:parent_id]
@filters = params[:filters] || {}
@ -13,8 +14,8 @@ module Lists
@records =
StorageLocation.joins('LEFT JOIN storage_locations AS sub_locations ' \
'ON storage_locations.id = sub_locations.parent_id')
.viewable_by_user(@user, @team)
.select('storage_locations.*, COUNT(sub_locations.id) AS sub_location_count')
.accessible_by_teams(@team)
.group(:id)
end

View file

@ -27,6 +27,8 @@ module Toolbars
private
def unassign_action
return unless can_manage_storage_location?(@storage_location)
{
name: 'unassign',
label: I18n.t('storage_locations.show.toolbar.unassign'),
@ -37,7 +39,7 @@ module Toolbars
end
def move_action
return unless @single
return unless @single && can_manage_storage_location?(@storage_location)
{
name: 'move',

View file

@ -29,9 +29,7 @@ module Toolbars
private
def edit_action
return unless @single
return unless can_manage_storage_locations?(current_user.current_team)
return unless @single && can_manage_storage_location?(@storage_locations.first)
{
name: 'edit',
@ -43,13 +41,11 @@ module Toolbars
end
def move_action
return unless @single
return unless can_manage_storage_locations?(current_user.current_team)
return unless @single && can_manage_storage_location?(@storage_locations.first)
{
name: 'move',
label: I18n.t("storage_locations.index.toolbar.move"),
label: I18n.t('storage_locations.index.toolbar.move'),
icon: 'sn-icon sn-icon-move',
path: move_storage_location_path(@storage_locations.first),
type: :emit
@ -57,9 +53,7 @@ module Toolbars
end
def duplicate_action
return unless @single
return unless can_manage_storage_locations?(current_user.current_team)
return unless @single && can_manage_storage_location?(@storage_locations.first)
{
name: 'duplicate',
@ -71,9 +65,7 @@ module Toolbars
end
def delete_action
return unless @single
return unless can_manage_storage_locations?(current_user.current_team)
return unless @single && can_manage_storage_location?(@storage_locations.first)
storage_location = @storage_locations.first

View file

@ -38,7 +38,19 @@ json.actions do
end
json.storage_locations do
json.locations @repository_row.grouped_storage_locations
json.locations(
@repository_row.storage_locations.distinct.map do |storage_location|
readable = can_read_storage_location?(storage_location)
{
id: storage_location.id,
readable: readable,
name: readable ? storage_location.name : storage_location.code,
metadata: storage_location.metadata,
positions: readable ? storage_location.storage_location_repository_rows.where(repository_row: @repository_row) : []
}
end
)
json.enabled StorageLocation.storage_locations_enabled?
end

View file

@ -13,6 +13,7 @@
<storage-locations-container
actions-url="<%= actions_toolbar_storage_location_storage_location_repository_rows_path(@storage_location) %>"
data-source="<%= storage_location_storage_location_repository_rows_path(@storage_location) %>"
:can-manage="<%= can_manage_storage_location?(@storage_location) %>"
:with-grid="<%= @storage_location.with_grid? %>"
:grid-size="<%= @storage_location.grid_size.to_json %>"
:container-id="<%= @storage_location.id %>"

View file

@ -1082,7 +1082,4 @@ Rails.application.routes.draw do
end
end
end
post "/rest-v1/license/grant", to: "application#grant"
post "/marvin4js-license", to: "application#grant", format: :cxl
end