cartodb/app/models/table/privacy_manager.rb
2020-06-15 10:58:47 +08:00

172 lines
5.4 KiB
Ruby

require_relative '../../../lib/varnish/lib/cartodb-varnish'
require_relative '../user'
require_relative 'user_table'
module CartoDB
class TablePrivacyManager
def initialize(table)
@table = table
end
def apply_privacy_change(metadata_table, old_privacy, privacy_changed = false)
if @table.table_visualization
affected_entities = [@table.table_visualization] + metadata_table.affected_visualizations
@table.map.save
# Separate on purpose as if fails here no need to revert visualizations' privacy
revertable_privacy_change(metadata_table, old_privacy) do
# Map saving actually doesn't changes privacy, but to keep on same reverting logic
@table.map.save
set_from_table_privacy(@table.privacy)
end
revertable_privacy_change(metadata_table, old_privacy, affected_entities) do
propagate_to([@table.table_visualization])
notify_privacy_affected_entities(metadata_table) if privacy_changed
end
end
rescue NoMethodError => exception
CartoDB::Logger.debug(
exception: exception,
message: "#{exception.message} #{exception.backtrace}",
user: Carto::User.find(@table.user_id),
table_id: @table.id,
table_name: @table.name,
data_import_id: @table.data_import_id
)
raise exception
end
def set_from_visualization(visualization)
set_public if visualization.public?
set_public_with_link_only if visualization.public_with_link?
set_private if visualization.private? || visualization.organization?
table.update(privacy: privacy)
self
end
def update_cdb_tablemetadata
table.update_cdb_tablemetadata
end
private
attr_reader :table
attr_accessor :privacy
def set_from_table_privacy(table_privacy)
case table_privacy
when Carto::UserTable::PRIVACY_PUBLIC
set_public
when Carto::UserTable::PRIVACY_LINK
set_public_with_link_only
else
set_private
end
end
# Propagation flow: Table -> Table PrivacyManager -> Visualization -> Visualization NamedMap
def propagate_to(visualizations, table_privacy_changed = false)
visualizations.each do |visualization|
if visualization.type == CartoDB::Visualization::Member::TYPE_CANONICAL
# Each table has a canonical visualization which must have privacy synced
visualization.privacy = Carto::UserTable::PRIVACY_VALUES_TO_TEXTS[privacy]
visualization.store_using_table(table_privacy_changed)
else
visualization.invalidate_cache
end
end
self
end
def set_public
self.privacy = Carto::UserTable::PRIVACY_PUBLIC
Carto::TableAndFriends.apply(owner.in_database, owner.database_schema, table.name) do |schema, table_name|
set_database_permissions(grant_query(schema, table_name))
end
self
end
def set_private
self.privacy = Carto::UserTable::PRIVACY_PRIVATE
Carto::TableAndFriends.apply(owner.in_database, owner.database_schema, table.name) do |schema, table_name|
set_database_permissions(revoke_query(schema, table_name))
end
self
end
def set_public_with_link_only
self.privacy = Carto::UserTable::PRIVACY_LINK
Carto::TableAndFriends.apply(owner.in_database, owner.database_schema, table.name) do |schema, table_name|
set_database_permissions(grant_query(schema, table_name))
end
self
end
def owner
@owner ||= ::User.where(id: table.user_id).first
end
def set_database_permissions(query)
owner.in_database(as: :superuser).run(query)
end
def revoke_query(schema, table_name)
%{
REVOKE SELECT ON #{fully_qualified_table_name(schema, table_name)}
FROM #{CartoDB::PUBLIC_DB_USER}
}
end
def grant_query(schema, table_name)
%{
GRANT SELECT ON #{fully_qualified_table_name(schema, table_name)}
TO #{CartoDB::PUBLIC_DB_USER};
}
end
def revertable_privacy_change(metadata_table, old_privacy = nil, entities = [])
yield
rescue => exception
CartoDB.notify_exception(exception, user_id: metadata_table.user_id, table_id: metadata_table.id)
revert_to_previous_privacy(metadata_table, old_privacy, entities)
raise exception
end
def revert_to_previous_privacy(metadata_table, old_privacy, additional_revert_targets = [])
return unless old_privacy
set_from_table_privacy(old_privacy)
errors = []
additional_revert_targets.each do |visualization|
begin
propagate_to([visualization])
rescue => exception
# Can't do much more, just let remaining ones finish
errors << exception
end
end
if !errors.empty?
CartoDB.notify_error("Errors reverting Table privacy", user_id: metadata_table.user_id,
table_id: metadata_table.id,
errors: errors)
end
end
def notify_privacy_affected_entities(metadata_table)
propagate_to(metadata_table.affected_visualizations, true)
update_cdb_tablemetadata
end
def fully_qualified_table_name(schema, table_name)
%{"#{schema}"."#{table_name}"}
end
end
end