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

204 lines
7.3 KiB
Ruby

require 'active_record'
require_dependency 'cartodb/errors'
require_dependency 'carto/helpers/auth_token_generator'
require_relative 'paged_model'
module Carto
# Groups are created by the editor because of extension requests, so
# standard Rails operations (creation, destruction, etc) doesn't trigger
# extension management functions. In order to keep extension and database
# in sync, there're several methods that do trigger it:
# - create_group_with_extension
# - rename_group_with_extension
# - destroy_group_with_extension
# - add_users_with_extension
# - remove_users_with_extension
class Group < ActiveRecord::Base
include PagedModel
include AuthTokenGenerator
belongs_to :organization, class_name: Carto::Organization
has_many :users_group, dependent: :destroy, class_name: Carto::UsersGroup
has_many :users, through: :users_group
private_class_method :new
validates :name, :database_role, :organization, presence: true
# In order to avoid locks between CDB_Organization_Remove_Organization_Access_Permission and CDB_Group_DropGroup
# the "shared with" deletion must be performed outside the transaction, on deletion.
after_commit :destroy_shared_with
# Constructor for groups already existing in the database
def self.new_instance(database_name, name, database_role, display_name = name)
organization = Organization.find_by_database_name(database_name)
raise "Organization not found for database #{database_name}" unless organization
raise CartoDB::ModelAlreadyExistsError if Group.find_by_organization_id_and_name_and_database_role(organization.id, name, database_role)
new(name: name, database_role: database_role, display_name: display_name, organization: organization)
end
# Creation of brand-new group with the extension
def self.create_group_with_extension(organization, display_name)
name = valid_group_name(display_name)
organization.owner.in_database do |conn|
create_group_extension_query(conn, name)
end
# Extension triggers a request to the editor databases endpoint which actually creates the group
group = Carto::Group.find_by_organization_id_and_name(organization.id, name)
raise "Group was not created by the extension. Is it installed and configured?" if group.nil?
group.display_name = display_name
group.save
group
end
# Constructor for groups metadata, ignores database roles. Should be generally avoided.
def self.new_instance_without_validation(name:, database_role:, display_name: name, auth_token: nil)
new(
name: name,
display_name: display_name,
database_role: database_role,
auth_token: auth_token
)
end
def rename_group_with_extension(new_display_name)
raise CartoDB::ModelAlreadyExistsError if Group.find_by_organization_id_and_display_name(organization.id, new_display_name)
new_name = Carto::Group.valid_group_name(new_display_name)
organization.owner.in_database do |conn|
Carto::Group.rename_group_extension_query(conn, name, new_name)
end
reload
self.display_name = new_display_name
save
end
# INFO: public because it's called by Organization.
def destroy_group_with_extension
# INFO: currently only a superuser can destroy a group. See CartoDB/cartodb-postgresql#114
organization.owner.in_database(as: :superuser) do |conn|
Carto::Group.destroy_group_extension_query(conn, name)
end
end
def add_users_with_extension(users)
organization.owner.in_database do |conn|
Carto::Group.add_users_group_extension_query(conn, name, users.collect(&:username))
end
reload
end
def remove_users_with_extension(users)
organization.owner.in_database do |conn|
Carto::Group.remove_users_group_extension_query(conn, name, users.collect(&:username))
end
reload
end
def grant_db_permission(table, access)
table.owner.in_database do |conn|
case access
when CartoDB::Permission::ACCESS_NONE
Carto::Group.revoke_all(conn, name, table.database_schema, table.name)
when CartoDB::Permission::ACCESS_READONLY
Carto::Group.grant_read(conn, name, table.database_schema, table.name)
when CartoDB::Permission::ACCESS_READWRITE
Carto::Group.grant_write(conn, name, table.database_schema, table.name)
else
raise "Unknown access: #{access}"
end
end
end
def rename(new_name, new_database_role)
self.name = new_name
self.database_role = new_database_role
end
def add_user(username)
user = Carto::User.find_by_username(username)
raise "User #{username} not found" unless user
raise CartoDB::ModelAlreadyExistsError unless users_group.where(user_id: user.id, group_id: id).first.nil?
user_group = Carto::UsersGroup.new(user: user, group: self)
users_group << user_group
user_group
end
def remove_user(username)
user = Carto::User.find_by_username(username)
raise "User #{username} not found" unless user
if users.include?(user)
users.destroy(user)
user
end
end
def database_name
organization.database_name
end
private
def destroy_shared_with
if transaction_include_any_action?([:destroy])
Carto::SharedEntity.where(recipient_id: id).each do |se|
viz = Carto::Visualization.find(se.entity_id)
permission = viz.permission
permission.remove_group_permission(self)
permission.save
end
end
end
# TODO: PG Format("%I", strvar); ?
def self.valid_group_name(display_name)
name = display_name.squish
name = "g_#{name}" unless name[/^[a-zA-Z_]{1}/]
name.gsub(/[^a-zA-Z0-9_ ]/, '_').gsub(/_{2,}/, '_')
end
def self.create_group_extension_query(conn, name)
conn.execute(%{ select cartodb.CDB_Group_CreateGroup('#{name}') })
end
def self.rename_group_extension_query(conn, name, new_name)
conn.execute(%{ select cartodb.CDB_Group_RenameGroup('#{name}', '#{new_name}') })
end
def self.destroy_group_extension_query(conn, name)
conn.execute(%{ select cartodb.CDB_Group_DropGroup('#{name}') })
end
def self.add_users_group_extension_query(conn, name, usernames)
conn.execute(%{ select cartodb.CDB_Group_AddUsers('#{name}', ARRAY['#{usernames.join("','")}']) })
end
def self.remove_users_group_extension_query(conn, name, usernames)
conn.execute(%{ select cartodb.CDB_Group_RemoveUsers('#{name}', ARRAY['#{usernames.join("','")}']) })
end
def self.revoke_all(conn, group_name, table_database_schema, table_name)
conn.execute(%{ select cartodb._CDB_Group_Table_RevokeAll('#{group_name}', '#{table_database_schema}', '#{table_name}', false) })
end
def self.grant_read(conn, group_name, table_database_schema, table_name)
conn.execute(%{ select cartodb._CDB_Group_Table_GrantRead('#{group_name}', '#{table_database_schema}', '#{table_name}', false) })
end
def self.grant_write(conn, group_name, table_database_schema, table_name)
conn.execute(%{ select cartodb._CDB_Group_Table_GrantReadWrite('#{group_name}', '#{table_database_schema}', '#{table_name}', false) })
end
end
end