204 lines
7.3 KiB
Ruby
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
|