Initial commit
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
class Admin::AdminController < ApplicationController
|
||||
protected
|
||||
|
||||
def invalidate_browser_cache
|
||||
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
||||
response.headers['Pragma'] = 'no-cache'
|
||||
response.headers['Expires'] = 'Mon, 01 Jan 1990 00:00:00 GMT'
|
||||
end
|
||||
|
||||
def valid_password_confirmation
|
||||
unless current_user.valid_password_confirmation(params[:password_confirmation])
|
||||
raise Carto::PasswordConfirmationError.new
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,78 @@
|
||||
require_relative './../helpers/organization_notifications_helper'
|
||||
|
||||
class Admin::ClientApplicationsController < Admin::AdminController
|
||||
include OrganizationNotificationsHelper
|
||||
|
||||
ssl_required :oauth, :api_key, :regenerate_api_key, :regenerate_oauth
|
||||
|
||||
before_filter :invalidate_browser_cache
|
||||
before_filter :login_required
|
||||
before_filter :enforce_engine_enabled, only: :regenerate_api_key
|
||||
before_filter :load_dashboard_notifications, only: :api_key
|
||||
before_filter :load_organization_notifications, only: :api_key
|
||||
|
||||
layout 'application'
|
||||
|
||||
def oauth
|
||||
respond_to do |format|
|
||||
format.html { render 'oauth' }
|
||||
end
|
||||
end
|
||||
|
||||
def api_key
|
||||
@has_engine_enabled = current_user.engine_enabled?
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render 'api_key' }
|
||||
end
|
||||
end
|
||||
|
||||
def regenerate_api_key
|
||||
begin
|
||||
current_user.regenerate_api_key
|
||||
rescue Errno::ECONNREFUSED => e
|
||||
CartoDB::StdoutLogger.info "Could not clear varnish cache", "#{e.inspect}"
|
||||
if Rails.env.development?
|
||||
current_user.set_map_key
|
||||
error_message = "Your API key has been regenerated succesfully but the varnish cache has not been invalidated."
|
||||
else
|
||||
raise e
|
||||
end
|
||||
rescue CartoDB::CentralCommunicationFailure => e
|
||||
CartoDB::Logger.warning(exception: e, message: 'Error updating API key in mobile apps')
|
||||
error_message = "Your API key has been successfully generated, " \
|
||||
"but there was an error updating the license keys of mobile apps"
|
||||
rescue => e
|
||||
raise e
|
||||
end
|
||||
|
||||
flash = if error_message
|
||||
{ error: error_message }
|
||||
else
|
||||
{ success: "Your API key has been regenerated successfully" }
|
||||
end
|
||||
redirect_to CartoDB.url(self, 'api_key_credentials', params: { type: 'api_key' }, user: current_user), flash: flash
|
||||
end
|
||||
|
||||
def regenerate_oauth
|
||||
@client_application = current_user.client_application
|
||||
return if request.get?
|
||||
current_user.reset_client_application!
|
||||
|
||||
redirect_to CartoDB.url(self, 'oauth_credentials', params: { type: 'oauth' }, user: current_user),
|
||||
flash: { success: "Your OAuth credentials have been updated successfully" }
|
||||
end
|
||||
|
||||
private
|
||||
def enforce_engine_enabled
|
||||
unless current_user.engine_enabled?
|
||||
render_403
|
||||
end
|
||||
end
|
||||
|
||||
def load_dashboard_notifications
|
||||
carto_user = Carto::User.where(id: current_user.id).first if current_user
|
||||
|
||||
@dashboard_notifications = carto_user ? carto_user.notifications_for_category(:dashboard) : {}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,304 @@
|
||||
require_dependency 'carto/controller_helper'
|
||||
require_dependency 'dummy_password_generator'
|
||||
|
||||
class Admin::OrganizationUsersController < Admin::AdminController
|
||||
include OrganizationUsersHelper
|
||||
include DummyPasswordGenerator
|
||||
|
||||
# Organization actions
|
||||
ssl_required :new, :create, :edit, :update, :destroy
|
||||
# Data of single users
|
||||
ssl_required :profile, :account, :oauth, :api_key, :regenerate_api_key
|
||||
|
||||
before_filter :get_config
|
||||
before_filter :login_required, :check_permissions, :load_organization
|
||||
before_filter :get_user, only: [:edit, :update, :destroy, :regenerate_api_key]
|
||||
before_filter :ensure_edit_permissions, only: [:edit, :update, :destroy, :regenerate_api_key]
|
||||
|
||||
layout 'application'
|
||||
|
||||
def new
|
||||
@user = ::User.new
|
||||
@user.quota_in_bytes = [@organization.unassigned_quota, @organization.default_quota_in_bytes].min
|
||||
|
||||
@user.soft_geocoding_limit = current_user.soft_geocoding_limit
|
||||
@user.soft_here_isolines_limit = current_user.soft_here_isolines_limit
|
||||
@user.soft_obs_snapshot_limit = current_user.soft_obs_snapshot_limit
|
||||
@user.soft_obs_general_limit = current_user.soft_obs_general_limit
|
||||
@user.soft_twitter_datasource_limit = current_user.soft_twitter_datasource_limit
|
||||
@user.soft_mapzen_routing_limit = current_user.soft_mapzen_routing_limit
|
||||
|
||||
@user.viewer = @organization.remaining_seats <= 0 && @organization.remaining_viewer_seats > 0
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render 'new' }
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
set_flash_flags
|
||||
respond_to do |format|
|
||||
format.html { render 'edit' }
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@user = ::User.new
|
||||
|
||||
# Validation is done on params to allow checking the change of the value.
|
||||
# The error is deferred to display values in the form in the error scenario.
|
||||
validation_failure = !soft_limits_validation(@user, params[:user], @organization.owner)
|
||||
|
||||
# set organization first, so some validations related to org users are applied (i.e. strong passwords)
|
||||
@user.org_admin = params[:user][:org_admin] unless params[:user][:org_admin].nil?
|
||||
@user.organization = @organization
|
||||
|
||||
if !@organization.auth_username_password_enabled &&
|
||||
!params[:user][:password].present? &&
|
||||
!params[:user][:password_confirmation].present?
|
||||
dummy_password = generate_dummy_password
|
||||
params[:user][:password] = dummy_password
|
||||
params[:user][:password_confirmation] = dummy_password
|
||||
end
|
||||
|
||||
@user.set_fields(
|
||||
params[:user],
|
||||
[
|
||||
:username, :email, :password, :quota_in_bytes, :password_confirmation,
|
||||
:twitter_datasource_enabled, :soft_geocoding_limit, :soft_here_isolines_limit,
|
||||
:soft_obs_snapshot_limit, :soft_obs_general_limit, :soft_mapzen_routing_limit
|
||||
]
|
||||
)
|
||||
@user.viewer = params[:user][:viewer] == 'true'
|
||||
current_user.copy_account_features(@user)
|
||||
|
||||
# Validate password first, so nicer errors are displayed
|
||||
model_validation_ok = @user.valid_password?(:password,
|
||||
params[:user][:password],
|
||||
params[:user][:password_confirmation]) &&
|
||||
@user.valid_creation?(current_user)
|
||||
|
||||
valid_password_confirmation
|
||||
unless model_validation_ok
|
||||
raise Sequel::ValidationFailed.new("Validation failed: #{@user.errors.full_messages.join(', ')}")
|
||||
end
|
||||
raise Carto::UnprocesableEntityError.new("Soft limits validation error") if validation_failure
|
||||
|
||||
@user.save(raise_on_failure: true)
|
||||
@user.create_in_central
|
||||
common_data_url = CartoDB::Visualization::CommonDataService.build_url(self)
|
||||
::Resque.enqueue(::Resque::UserDBJobs::CommonData::LoadCommonData, @user.id, common_data_url)
|
||||
@user.notify_new_organization_user
|
||||
@user.organization.notify_if_seat_limit_reached unless @user.viewer?
|
||||
CartoGearsApi::Events::EventManager.instance.notify(
|
||||
CartoGearsApi::Events::UserCreationEvent.new(
|
||||
CartoGearsApi::Events::UserCreationEvent::CREATED_VIA_ORG_ADMIN, @user
|
||||
)
|
||||
)
|
||||
redirect_to CartoDB.url(self, 'organization', user: current_user),
|
||||
flash: { success: "New user created successfully" }
|
||||
rescue Carto::UnprocesableEntityError => e
|
||||
CartoDB::Logger.error(exception: e, message: "Validation error")
|
||||
set_flash_flags
|
||||
flash.now[:error] = e.user_message
|
||||
render 'new', status: 422
|
||||
rescue CartoDB::CentralCommunicationFailure => e
|
||||
CartoDB.report_exception(e)
|
||||
begin
|
||||
@user.destroy
|
||||
rescue => ee
|
||||
CartoDB.report_exception(ee)
|
||||
end
|
||||
set_flash_flags
|
||||
flash.now[:error] = e.user_message
|
||||
@user = default_user
|
||||
render 'new'
|
||||
rescue Carto::PasswordConfirmationError => e
|
||||
flash.now[:error] = e.message
|
||||
render action: 'new', status: e.status
|
||||
rescue Sequel::ValidationFailed => e
|
||||
flash.now[:error] = e.message
|
||||
render 'new'
|
||||
end
|
||||
|
||||
def update
|
||||
valid_password_confirmation
|
||||
session[:show_dashboard_details_flash] = params[:show_dashboard_details_flash].present?
|
||||
session[:show_account_settings_flash] = params[:show_account_settings_flash].present?
|
||||
|
||||
# Validation is done on params to allow checking the change of the value.
|
||||
# The error is deferred to display values in the form in the error scenario.
|
||||
validation_failure = !soft_limits_validation(@user, params[:user])
|
||||
|
||||
attributes = params[:user]
|
||||
@user.set_fields(attributes, [:email]) if attributes[:email].present? && !@user.google_sign_in
|
||||
@user.set_fields(attributes, [:quota_in_bytes]) if attributes[:quota_in_bytes].present?
|
||||
|
||||
@user.set_fields(attributes, [:disqus_shortname]) if attributes[:disqus_shortname].present?
|
||||
@user.set_fields(attributes, [:available_for_hire]) if attributes[:available_for_hire].present?
|
||||
@user.set_fields(attributes, [:name]) if attributes[:name].present?
|
||||
@user.set_fields(attributes, [:website]) if attributes[:website].present?
|
||||
@user.set_fields(attributes, [:description]) if attributes[:description].present?
|
||||
@user.set_fields(attributes, [:twitter_username]) if attributes[:twitter_username].present?
|
||||
@user.set_fields(attributes, [:location]) if attributes[:location].present?
|
||||
@user.set_fields(attributes, [:org_admin]) if attributes[:org_admin].present?
|
||||
|
||||
@user.viewer = attributes[:viewer] == 'true'
|
||||
|
||||
@user.password = attributes[:password] if attributes[:password].present?
|
||||
@user.password_confirmation = attributes[:password_confirmation] if attributes[:password_confirmation].present?
|
||||
@user.soft_geocoding_limit = attributes[:soft_geocoding_limit] if attributes[:soft_geocoding_limit].present?
|
||||
@user.soft_here_isolines_limit = attributes[:soft_here_isolines_limit] if attributes[:soft_here_isolines_limit].present?
|
||||
@user.soft_obs_snapshot_limit = attributes[:soft_obs_snapshot_limit] if attributes[:soft_obs_snapshot_limit].present?
|
||||
@user.soft_obs_general_limit = attributes[:soft_obs_general_limit] if attributes[:soft_obs_general_limit].present?
|
||||
@user.twitter_datasource_enabled = attributes[:twitter_datasource_enabled] if attributes[:twitter_datasource_enabled].present?
|
||||
@user.soft_twitter_datasource_limit = attributes[:soft_twitter_datasource_limit] if attributes[:soft_twitter_datasource_limit].present?
|
||||
@user.soft_mapzen_routing_limit = attributes[:soft_mapzen_routing_limit] if attributes[:soft_mapzen_routing_limit].present?
|
||||
|
||||
model_validation_ok = @user.valid_update?(current_user)
|
||||
if attributes[:password].present? || attributes[:password_confirmation].present?
|
||||
model_validation_ok &&= @user.valid_password?(:password, attributes[:password], attributes[:password_confirmation])
|
||||
end
|
||||
|
||||
unless model_validation_ok
|
||||
raise Sequel::ValidationFailed.new("Validation failed: #{@user.errors.full_messages.join(', ')}")
|
||||
end
|
||||
|
||||
raise Carto::UnprocesableEntityError.new("Soft limits validation error") if validation_failure
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
if attributes[:mfa].present?
|
||||
service = Carto::UserMultifactorAuthUpdateService.new(user_id: @user.id)
|
||||
service.update(enabled: attributes[:mfa] == '1')
|
||||
end
|
||||
|
||||
# update_in_central is duplicated because we don't wan ta local save if Central fails,
|
||||
# but before/after save at user can change some attributes that we also want to persist.
|
||||
# Since those callbacks aren't idempotent there's no much better solution without a big refactor.
|
||||
@user.update_in_central
|
||||
|
||||
@user.save(raise_on_failure: true)
|
||||
|
||||
@user.update_in_central
|
||||
end
|
||||
|
||||
redirect_to CartoDB.url(self, 'edit_organization_user', params: { id: @user.username }, user: current_user),
|
||||
flash: { success: "Your changes have been saved correctly." }
|
||||
rescue Carto::UnprocesableEntityError => e
|
||||
CartoDB::Logger.error(exception: e, message: "Validation error")
|
||||
set_flash_flags
|
||||
flash.now[:error] = e.user_message
|
||||
render 'edit', status: 422
|
||||
rescue CartoDB::CentralCommunicationFailure => e
|
||||
set_flash_flags
|
||||
flash.now[:error] = "There was a problem while updating this user. Please, try again and contact us if the problem persists. #{e.user_message}"
|
||||
render 'edit'
|
||||
rescue Carto::PasswordConfirmationError => e
|
||||
flash.now[:error] = e.message
|
||||
render action: 'edit', status: e.status
|
||||
rescue Sequel::ValidationFailed, ActiveRecord::RecordInvalid => e
|
||||
flash.now[:error] = e.message
|
||||
render 'edit', status: 422
|
||||
end
|
||||
|
||||
def destroy
|
||||
valid_password_confirmation
|
||||
raise "Can't delete user. Has shared entities" if @user.has_shared_entities?
|
||||
|
||||
@user.destroy
|
||||
@user.delete_in_central
|
||||
flash[:success] = "User was successfully deleted."
|
||||
redirect_to CartoDB.url(self, 'organization', user: current_user)
|
||||
rescue CartoDB::CentralCommunicationFailure => e
|
||||
if e.user_message =~ /No organization user found with username/
|
||||
flash[:success] = "User was successfully deleted."
|
||||
redirect_to CartoDB.url(self, 'organization', user: current_user)
|
||||
else
|
||||
CartoDB::Logger.error(exception: e, message: 'Error deleting organizational user from central', target_user: @user.username)
|
||||
flash[:success] = "#{e.user_message}. User was deleted from the organization server."
|
||||
redirect_to organization_path(user_domain: params[:user_domain])
|
||||
end
|
||||
rescue Carto::PasswordConfirmationError => e
|
||||
flash[:error] = e.message
|
||||
redirect_to organization_path(user_domain: params[:user_domain])
|
||||
rescue => e
|
||||
CartoDB::Logger.error(exception: e, message: 'Error deleting organizational user', target_user: @user.username)
|
||||
flash[:error] = "User was not deleted. #{e.message}"
|
||||
redirect_to organization_path(user_domain: params[:user_domain])
|
||||
end
|
||||
|
||||
def regenerate_api_key
|
||||
valid_password_confirmation
|
||||
@user.regenerate_all_api_keys
|
||||
flash[:success] = "User API key regenerated successfully"
|
||||
redirect_to CartoDB.url(self, 'edit_organization_user', params: { id: @user.username }, user: current_user),
|
||||
flash: { success: "Your changes have been saved correctly." }
|
||||
rescue Carto::PasswordConfirmationError => e
|
||||
flash[:error] = e.message
|
||||
render action: 'edit', status: e.status
|
||||
rescue => e
|
||||
CartoDB.notify_exception(e, { user_id: @user.id, current_user: current_user.id })
|
||||
flash[:error] = "There was an error regenerating the API key. Please, try again and contact us if the problem persists"
|
||||
render 'edit'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_user
|
||||
::User.new(username: @user.username, email: @user.email, quota_in_bytes: @user.quota_in_bytes, twitter_datasource_enabled: @user.twitter_datasource_enabled)
|
||||
end
|
||||
|
||||
def extras_enabled?
|
||||
extra_geocodings_enabled? || extra_here_isolines_enabled? || extra_obs_snapshot_enabled? || extra_obs_general_enabled? || extra_tweets_enabled?
|
||||
end
|
||||
|
||||
def extra_geocodings_enabled?
|
||||
!Cartodb.get_config(:geocoder, 'app_id').blank?
|
||||
end
|
||||
|
||||
def extra_here_isolines_enabled?
|
||||
true
|
||||
end
|
||||
|
||||
def extra_obs_snapshot_enabled?
|
||||
true
|
||||
end
|
||||
|
||||
def extra_obs_general_enabled?
|
||||
true
|
||||
end
|
||||
|
||||
def extra_tweets_enabled?
|
||||
!Cartodb.get_config(:datasource_search, 'twitter_search', 'standard', 'username').blank?
|
||||
end
|
||||
|
||||
def set_flash_flags(show_dashboard_details_flash = nil, show_account_settings_flash = nil)
|
||||
@show_dashboard_details_flash = session[:show_dashboard_details_flash] || show_dashboard_details_flash
|
||||
@show_account_settings_flash = session[:show_account_settings_flash] || show_account_settings_flash
|
||||
session[:show_dashboard_details_flash] = nil
|
||||
session[:show_account_settings_flash] = nil
|
||||
end
|
||||
|
||||
def get_config
|
||||
@extras_enabled = extras_enabled?
|
||||
@extra_geocodings_enabled = extra_geocodings_enabled?
|
||||
@extra_tweets_enabled = extra_tweets_enabled?
|
||||
end
|
||||
|
||||
def check_permissions
|
||||
raise RecordNotFound unless current_user.organization_admin?
|
||||
end
|
||||
|
||||
def get_user
|
||||
@user = @organization.users_dataset.where(username: params[:id]).first
|
||||
raise RecordNotFound unless @user
|
||||
end
|
||||
|
||||
def load_organization
|
||||
@organization = current_user.organization
|
||||
end
|
||||
|
||||
def ensure_edit_permissions
|
||||
render_403 unless @user.editable_by?(current_user)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,227 @@
|
||||
require_relative './../helpers/avatar_helper'
|
||||
require_relative './../helpers/organization_notifications_helper'
|
||||
|
||||
class Admin::OrganizationsController < Admin::AdminController
|
||||
include AvatarHelper
|
||||
include OrganizationNotificationsHelper
|
||||
|
||||
ssl_required :show, :settings, :settings_update, :regenerate_all_api_keys, :groups, :auth, :auth_update,
|
||||
:notifications, :new_notification, :destroy_notification, :destroy
|
||||
before_filter :login_required, :load_organization_and_members, :load_ldap_configuration
|
||||
before_filter :owners_only, only: [:settings, :settings_update, :regenerate_all_api_keys, :auth, :auth_update,
|
||||
:destroy]
|
||||
before_filter :enforce_engine_enabled, only: :regenerate_all_api_keys
|
||||
before_filter :load_carto_organization, only: [:notifications, :new_notification]
|
||||
before_filter :load_notification, only: [:destroy_notification]
|
||||
before_filter :load_organization_notifications, only: [:settings, :auth, :show, :groups, :notifications,
|
||||
:new_notification]
|
||||
helper_method :show_billing
|
||||
|
||||
layout 'application'
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html { render 'show' }
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
deletion_password_confirmation = params[:deletion_password_confirmation]
|
||||
if current_user.needs_password_confirmation? && !current_user.validate_old_password(deletion_password_confirmation)
|
||||
flash.now[:error] = "Password doesn't match"
|
||||
render 'show', status: 400
|
||||
else
|
||||
@organization.destroy_cascade(delete_in_central: true)
|
||||
redirect_to logout_url
|
||||
end
|
||||
rescue => e
|
||||
CartoDB::Logger.error(message: "Error deleting organization", exception: e, organization: @organization)
|
||||
flash.now[:error] = "Error deleting organization: #{e.message}"
|
||||
render 'show', status: 500
|
||||
end
|
||||
|
||||
def settings
|
||||
@avatar_valid_extensions = AVATAR_VALID_EXTENSIONS
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render 'settings' }
|
||||
end
|
||||
end
|
||||
|
||||
def groups
|
||||
respond_to do |format|
|
||||
format.html { render 'groups' }
|
||||
end
|
||||
end
|
||||
|
||||
def notifications(status = 200)
|
||||
@notification ||= Carto::Notification.new(recipients: Carto::Notification::RECIPIENT_ALL)
|
||||
@notifications = @carto_organization.notifications.limit(12).map { |n| Carto::Api::NotificationPresenter.new(n) }
|
||||
respond_to do |format|
|
||||
format.html { render 'notifications', status: status }
|
||||
end
|
||||
end
|
||||
|
||||
def new_notification
|
||||
valid_password_confirmation
|
||||
carto_organization = Carto::Organization.find(@organization.id)
|
||||
attributes = {
|
||||
body: params[:carto_notification]['body'],
|
||||
icon: Carto::Notification::ICON_ALERT,
|
||||
recipients: params[:carto_notification]['recipients']
|
||||
}
|
||||
@notification = carto_organization.notifications.build(attributes)
|
||||
if @notification.save
|
||||
redirect_to CartoDB.url(self, 'organization_notifications_admin', user: current_user),
|
||||
flash: { success: 'Notification sent!' }
|
||||
else
|
||||
flash.now[:error] = @notification.errors.full_messages.join(', ')
|
||||
notifications
|
||||
end
|
||||
rescue Carto::PasswordConfirmationError => e
|
||||
flash.now[:error] = e.message
|
||||
notifications(e.status)
|
||||
end
|
||||
|
||||
def destroy_notification
|
||||
@notification.destroy
|
||||
|
||||
redirect_to CartoDB.url(self, 'organization_notifications_admin', user: current_user),
|
||||
flash: { success: 'Notification was successfully deleted!' }
|
||||
end
|
||||
|
||||
def settings_update
|
||||
valid_password_confirmation
|
||||
attributes = params[:organization]
|
||||
|
||||
if attributes.include?(:avatar_url) && valid_avatar_file?(attributes[:avatar_url])
|
||||
@organization.avatar_url = attributes[:avatar_url]
|
||||
end
|
||||
|
||||
@organization.website = attributes[:website]
|
||||
@organization.admin_email = attributes[:admin_email]
|
||||
@organization.description = attributes[:description]
|
||||
@organization.display_name = attributes[:display_name]
|
||||
@organization.color = attributes[:color]
|
||||
|
||||
if attributes.include?(:default_quota_in_bytes)
|
||||
default_quota_in_bytes = attributes[:default_quota_in_bytes]
|
||||
@organization.default_quota_in_bytes = default_quota_in_bytes.blank? ? nil : default_quota_in_bytes.to_i * 1024 * 1024
|
||||
end
|
||||
@organization.discus_shortname = attributes[:discus_shortname]
|
||||
@organization.twitter_username = attributes[:twitter_username]
|
||||
@organization.location = attributes[:location]
|
||||
|
||||
@organization.update_in_central
|
||||
@organization.save(raise_on_failure: true)
|
||||
|
||||
redirect_to CartoDB.url(self, 'organization_settings', user: current_user),
|
||||
flash: { success: "Your changes have been saved correctly." }
|
||||
rescue CartoDB::CentralCommunicationFailure => e
|
||||
@organization.reload
|
||||
flash.now[:error] = "There was a problem while updating your organization. Please, try again and contact us if the problem persists. #{e.user_message}"
|
||||
render action: 'settings'
|
||||
rescue Carto::PasswordConfirmationError => e
|
||||
flash.now[:error] = e.message
|
||||
render action: 'settings', status: e.status
|
||||
rescue Sequel::ValidationFailed => e
|
||||
flash.now[:error] = "There's been a validation error, check your values"
|
||||
render action: 'settings'
|
||||
end
|
||||
|
||||
def regenerate_all_api_keys
|
||||
valid_password_confirmation
|
||||
@organization.users.each(&:regenerate_all_api_keys)
|
||||
|
||||
redirect_to CartoDB.url(self, 'organization_settings', user: current_user),
|
||||
flash: { success: "Users API keys regenerated successfully" }
|
||||
rescue Carto::PasswordConfirmationError => e
|
||||
flash.now[:error] = e.message
|
||||
render action: 'settings', status: e.status
|
||||
rescue => e
|
||||
CartoDB.notify_exception(e, { organization: @organization.id, current_user: current_user.id })
|
||||
flash[:error] = "There was an error regenerating the API keys. Please, try again and contact us if the problem persists"
|
||||
render action: 'settings'
|
||||
end
|
||||
|
||||
def auth
|
||||
respond_to do |format|
|
||||
format.html { render 'auth' }
|
||||
end
|
||||
end
|
||||
|
||||
def auth_update
|
||||
valid_password_confirmation
|
||||
attributes = params[:organization]
|
||||
@organization.whitelisted_email_domains = attributes[:whitelisted_email_domains].split(",")
|
||||
@organization.auth_username_password_enabled = attributes[:auth_username_password_enabled]
|
||||
@organization.auth_google_enabled = attributes[:auth_google_enabled]
|
||||
@organization.auth_github_enabled = attributes[:auth_github_enabled]
|
||||
@organization.strong_passwords_enabled = attributes[:strong_passwords_enabled]
|
||||
@organization.password_expiration_in_d = attributes[:password_expiration_in_d]
|
||||
@organization.update_in_central
|
||||
@organization.save(raise_on_failure: true)
|
||||
|
||||
redirect_to CartoDB.url(self, 'organization_auth', user: current_user),
|
||||
flash: { success: "Your changes have been saved correctly." }
|
||||
rescue CartoDB::CentralCommunicationFailure => e
|
||||
@organization.reload
|
||||
flash.now[:error] = "There was a problem while updating your organization. Please, try again and contact us if the problem persists. #{e.user_message}"
|
||||
render action: 'auth'
|
||||
rescue Carto::PasswordConfirmationError => e
|
||||
flash.now[:error] = e.message
|
||||
render action: 'auth', status: e.status
|
||||
rescue Sequel::ValidationFailed => e
|
||||
flash.now[:error] = "There's been a validation error, check your values"
|
||||
render action: 'auth'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_organization_and_members
|
||||
raise RecordNotFound unless current_user.organization_admin?
|
||||
@organization = current_user.organization
|
||||
|
||||
display_signup_warnings if @organization.signup_page_enabled
|
||||
|
||||
# INFO: Special scenario of handcrafted URL to go to organization-based signup page
|
||||
@organization_signup_url =
|
||||
"#{CartoDB.protocol}://#{@organization.name}.#{CartoDB.account_host}#{CartoDB.path(self, 'signup_organization_user')}"
|
||||
end
|
||||
|
||||
def owners_only
|
||||
raise RecordNotFound unless current_user.organization_owner?
|
||||
end
|
||||
|
||||
def display_signup_warnings
|
||||
warning = []
|
||||
warning << "Your organization has run out of quota" unless @organization.valid_disk_quota?
|
||||
warning << "Your organization has run out of seats" unless @organization.valid_builder_seats?
|
||||
unless warning.empty?
|
||||
flash.now[:warning] = "#{warning.join('. ')}."
|
||||
flash.now[:warning_detail] = "Users won't be able to sign up to your organization. <a href='mailto:contact@carto.com'>Contact us</a> to increase your quota."
|
||||
end
|
||||
end
|
||||
|
||||
def show_billing
|
||||
!Cartodb.config[:cartodb_com_hosted].present? && (!current_user.organization.present? || current_user.organization_owner?)
|
||||
end
|
||||
|
||||
def load_ldap_configuration
|
||||
@ldap_configuration = Carto::Ldap::Configuration.where(organization_id: @organization.id).first
|
||||
end
|
||||
|
||||
def enforce_engine_enabled
|
||||
unless @organization.engine_enabled
|
||||
render_403
|
||||
end
|
||||
end
|
||||
|
||||
def load_carto_organization
|
||||
@carto_organization = Carto::Organization.find(@organization.id)
|
||||
end
|
||||
|
||||
def load_notification
|
||||
@notification = Carto::Notification.find(params[:id])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,505 @@
|
||||
require 'active_support/inflector'
|
||||
require 'carto/api/vizjson3_presenter'
|
||||
|
||||
require_relative '../../models/table'
|
||||
require_relative '../../models/visualization/member'
|
||||
require_relative '../../models/visualization/collection'
|
||||
|
||||
class Admin::PagesController < Admin::AdminController
|
||||
include Carto::HtmlSafe
|
||||
|
||||
include CartoDB
|
||||
include VisualizationsControllerHelper
|
||||
|
||||
DATASETS_PER_PAGE = 9
|
||||
MAPS_PER_PAGE = 9
|
||||
USER_TAGS_LIMIT = 100
|
||||
PAGE_NUMBER_PLACEHOLDER = 'PAGENUMBERPLACEHOLDER'
|
||||
|
||||
# TODO logic as done client-side, how and where to encapsulate this better?
|
||||
GEOMETRY_MAPPING = {
|
||||
'st_multipolygon' => 'polygon',
|
||||
'st_polygon' => 'polygon',
|
||||
'st_multilinestring' => 'line',
|
||||
'st_linestring' => 'line',
|
||||
'st_multipoint' => 'point',
|
||||
'st_point' => 'point'
|
||||
}
|
||||
|
||||
|
||||
ssl_required :common_data, :public, :datasets, :maps, :user_feed
|
||||
ssl_allowed :index, :sitemap, :datasets_for_user, :datasets_for_organization, :maps_for_user, :maps_for_organization,
|
||||
:render_not_found
|
||||
|
||||
before_filter :login_required, :except => [:public, :datasets, :maps, :sitemap, :index, :user_feed]
|
||||
before_filter :load_viewed_entity
|
||||
before_filter :set_new_dashboard_flag
|
||||
before_filter :ensure_organization_correct
|
||||
skip_before_filter :browser_is_html5_compliant?, only: [:public, :datasets, :maps, :user_feed]
|
||||
skip_before_filter :ensure_user_organization_valid, only: [:public]
|
||||
|
||||
helper_method :named_map_vizjson3
|
||||
|
||||
# Just an entrypoint to dispatch to different places according to
|
||||
def index
|
||||
if current_user
|
||||
# I am logged in, visiting my subdomain -> my dashboard
|
||||
redirect_to CartoDB.url(self, 'dashboard', user: current_user)
|
||||
elsif CartoDB.extract_subdomain(request).present?
|
||||
# I am visiting another user subdomain -> other user public pages
|
||||
redirect_to CartoDB.url(self, 'public_user_feed_home')
|
||||
elsif current_viewer
|
||||
# I am logged in but did not specify a subdomain -> my dashboard
|
||||
redirect_to CartoDB.url(self, 'dashboard', user: current_viewer)
|
||||
else
|
||||
# I am not logged in and did not specify a subdomain -> login
|
||||
# Avoid using CartoDB.url helper, since we cannot get any user information from domain, path or session
|
||||
redirect_to login_url
|
||||
end
|
||||
end
|
||||
|
||||
def common_data
|
||||
redirect_to CartoDB.url(self, 'datasets_library')
|
||||
end
|
||||
|
||||
def sitemap
|
||||
if @viewed_user.nil?
|
||||
username = CartoDB.extract_subdomain(request)
|
||||
org = get_organization_if_exists(username)
|
||||
render_404 and return if org.nil?
|
||||
visualizations = public_builder(organization_id: org.id).build
|
||||
else
|
||||
# Redirect to org url if has only user
|
||||
if eligible_for_redirect?(@viewed_user)
|
||||
redirect_to CartoDB.base_url(@viewed_user.organization.name) << CartoDB.path(self, 'public_sitemap') and return
|
||||
end
|
||||
|
||||
visualizations = public_builder(user_id: @viewed_user.id).with_prefetch_user(true).build
|
||||
end
|
||||
|
||||
@urls = visualizations.map { |vis|
|
||||
case vis.type
|
||||
when Carto::Visualization::TYPE_DERIVED
|
||||
{
|
||||
loc: CartoDB.url(self, 'public_visualizations_public_map', params: { id: vis.id }, user: vis.user),
|
||||
lastfreq: vis.updated_at.strftime("%Y-%m-%dT%H:%M:%S%:z")
|
||||
}
|
||||
when Carto::Visualization::TYPE_CANONICAL
|
||||
{
|
||||
loc: CartoDB.url(self, 'public_table', params: { id: vis.name }, user: vis.user),
|
||||
lastfreq: vis.updated_at.strftime("%Y-%m-%dT%H:%M:%S%:z")
|
||||
}
|
||||
end
|
||||
}.compact
|
||||
render :formats => [:xml]
|
||||
end
|
||||
|
||||
def datasets
|
||||
datasets = CartoDB::ControllerFlows::Public::Datasets.new(self)
|
||||
content = CartoDB::ControllerFlows::Public::Content.new(self, request, datasets)
|
||||
content.render()
|
||||
end
|
||||
|
||||
def maps
|
||||
maps = CartoDB::ControllerFlows::Public::Maps.new(self)
|
||||
content = CartoDB::ControllerFlows::Public::Content.new(self, request, maps)
|
||||
content.render()
|
||||
end
|
||||
|
||||
def public
|
||||
if current_user
|
||||
index
|
||||
else
|
||||
user_feed
|
||||
end
|
||||
end
|
||||
|
||||
def user_feed
|
||||
# The template of this endpoint get the user_feed data calling
|
||||
# to another endpoint in the front-end part
|
||||
if @viewed_user.nil?
|
||||
username = CartoDB.extract_subdomain(request).strip.downcase
|
||||
org = get_organization_if_exists(username)
|
||||
unless org.nil?
|
||||
redirect_to CartoDB.url(self, 'public_maps_home') and return
|
||||
end
|
||||
render_404
|
||||
else
|
||||
|
||||
set_layout_vars_for_user(@viewed_user, 'feed')
|
||||
|
||||
dataset_builder = user_datasets_public_builder(@viewed_user)
|
||||
maps_builder = user_maps_public_builder(@viewed_user)
|
||||
|
||||
@name = @viewed_user.name_or_username
|
||||
@avatar_url = @viewed_user.avatar
|
||||
@tables_num = dataset_builder.build.count
|
||||
@maps_count = maps_builder.build.count
|
||||
@website = website_url(@viewed_user.website)
|
||||
@website_clean = @website ? @website.gsub(/https?:\/\//, "") : ""
|
||||
|
||||
if eligible_for_redirect?(@viewed_user)
|
||||
# redirect username.host.ext => org-name.host.ext/u/username
|
||||
redirect_to CartoDB.base_url(@viewed_user.organization.name, @viewed_user.username) <<
|
||||
CartoDB.path(self, 'public_user_feed_home') and return
|
||||
end
|
||||
|
||||
description = @name.dup
|
||||
|
||||
# TODO: move to helper
|
||||
if @maps_count == 0 && @tables_num == 0
|
||||
description << " uses CARTO to transform location intelligence into dynamic renderings that enable discovery of trends and patterns"
|
||||
else
|
||||
description << " has"
|
||||
|
||||
unless @maps_count == 0
|
||||
description << " created #{@maps_count} #{'map'.pluralize(@maps_count)}"
|
||||
end
|
||||
|
||||
unless @maps_count == 0 || @tables_num == 0
|
||||
description << " and"
|
||||
end
|
||||
|
||||
unless @tables_num == 0
|
||||
description << " published #{@tables_num} public #{'dataset'.pluralize(@tables_num)}"
|
||||
end
|
||||
|
||||
description << " · View #{@name} CARTO profile for the latest activity and contribute to Open Data by creating an account in CARTO"
|
||||
end
|
||||
|
||||
@page_description = description
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render 'user_feed', layout: 'public_user_feed' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def datasets_for_user(user)
|
||||
set_layout_vars_for_user(user, 'datasets')
|
||||
render_datasets(user_datasets_public_builder(user), user)
|
||||
end
|
||||
|
||||
def datasets_for_organization(org)
|
||||
set_layout_vars_for_organization(org, 'datasets')
|
||||
render_datasets(org_datasets_public_builder(org))
|
||||
end
|
||||
|
||||
def maps_for_user(user)
|
||||
set_layout_vars_for_user(user, 'maps')
|
||||
render_maps(user_maps_public_builder(user), user)
|
||||
end
|
||||
|
||||
def maps_for_organization(org)
|
||||
set_layout_vars_for_organization(org, 'maps')
|
||||
render_maps(org_maps_public_builder(org))
|
||||
end
|
||||
|
||||
def render_not_found
|
||||
render_404
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def eligible_for_redirect?(user)
|
||||
return false if CartoDB.subdomainless_urls?
|
||||
user.has_organization? && CartoDB.subdomain_from_request(request) != user.organization.name
|
||||
end
|
||||
|
||||
def render_datasets(vis_query_builder, user = nil)
|
||||
home = CartoDB.url(self, 'public_datasets_home', params: { page: PAGE_NUMBER_PLACEHOLDER }, user: user)
|
||||
set_pagination_vars(total_count: vis_query_builder.build.count,
|
||||
per_page: DATASETS_PER_PAGE,
|
||||
first_page_url: CartoDB.url(self, 'public_datasets_home', user: user),
|
||||
numbered_page_url: home)
|
||||
|
||||
@datasets = []
|
||||
|
||||
vis_list = vis_query_builder.build_paged(current_page, DATASETS_PER_PAGE).map do |v|
|
||||
Carto::Admin::VisualizationPublicMapAdapter.new(v, current_user, self)
|
||||
end
|
||||
|
||||
vis_list.each do |vis|
|
||||
@datasets << process_dataset_render(vis)
|
||||
end
|
||||
|
||||
@datasets.compact!
|
||||
|
||||
description = @name.dup
|
||||
|
||||
# TODO: move to helper
|
||||
if @datasets.size == 0
|
||||
description << " uses CARTO to transform location intelligence into dynamic renderings that enable discovery of trends and patterns"
|
||||
else
|
||||
description << " has published #{@datasets.size} public #{'dataset'.pluralize(@datasets.size)}"
|
||||
end
|
||||
|
||||
description << " · View #{@name} CARTO profile for the latest activity and contribute to Open Data by creating an account in CARTO"
|
||||
|
||||
@page_description = description
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render 'public_datasets', layout: 'public_dashboard' }
|
||||
end
|
||||
end
|
||||
|
||||
def render_maps(vis_query_builder, user=nil)
|
||||
set_pagination_vars(
|
||||
total_count: vis_query_builder.build.count,
|
||||
per_page: MAPS_PER_PAGE,
|
||||
first_page_url: CartoDB.url(self, 'public_maps_home', user: user),
|
||||
numbered_page_url: CartoDB.url(self, 'public_maps_home', params: { page: PAGE_NUMBER_PLACEHOLDER }, user: user)
|
||||
)
|
||||
|
||||
vis_list = vis_query_builder.build_paged(current_page, MAPS_PER_PAGE).map do |v|
|
||||
Carto::Admin::VisualizationPublicMapAdapter.new(v, current_user, self)
|
||||
end
|
||||
|
||||
@visualizations = []
|
||||
vis_list.each do |vis|
|
||||
@visualizations << process_map_render(vis)
|
||||
end
|
||||
|
||||
@visualizations.compact!
|
||||
|
||||
description = @name.dup
|
||||
|
||||
# TODO: move to helper
|
||||
if @visualizations.size == 0 && @tables_num == 0
|
||||
description << " uses CARTO to transform location intelligence into dynamic renderings that enable discovery of trends and patterns"
|
||||
else
|
||||
description << " has"
|
||||
|
||||
unless @visualizations.size == 0
|
||||
description << " created #{@visualizations.size} #{'map'.pluralize(@visualizations.size)}"
|
||||
end
|
||||
|
||||
unless @visualizations.size == 0 || @tables_num == 0
|
||||
description << " and"
|
||||
end
|
||||
|
||||
unless @tables_num == 0
|
||||
description << " published #{@tables_num} public #{'dataset'.pluralize(@tables_num)}"
|
||||
end
|
||||
|
||||
description << " · View #{@name} CARTO profile for the latest activity and contribute to Open Data by creating an account in CARTO"
|
||||
end
|
||||
|
||||
@page_description = description
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render 'public_maps', layout: 'public_dashboard' }
|
||||
end
|
||||
end
|
||||
|
||||
def set_new_dashboard_flag
|
||||
ff_user = @viewed_user || @viewed_org.try(:owner)
|
||||
|
||||
unless ff_user.nil?
|
||||
@has_new_dashboard = ff_user.builder_enabled?
|
||||
end
|
||||
end
|
||||
|
||||
def set_layout_vars_for_user(user, content_type)
|
||||
builder = user_maps_public_builder(user, visualization_version)
|
||||
most_viewed = builder.with_order(:mapviews, :desc).build_paged(1, 1).first
|
||||
|
||||
set_layout_vars({
|
||||
most_viewed_vis_map: most_viewed ? Carto::Admin::VisualizationPublicMapAdapter.new(most_viewed, current_user, self) : nil,
|
||||
content_type: content_type,
|
||||
default_fallback_basemap: user.default_basemap,
|
||||
user: user,
|
||||
base_url: user.public_url(nil, request.protocol == "https://" ? "https" : "http")
|
||||
})
|
||||
set_shared_layout_vars(user, {
|
||||
name: user.name_or_username,
|
||||
avatar_url: user.avatar,
|
||||
}, {
|
||||
available_for_hire: user.available_for_hire,
|
||||
email: user.email,
|
||||
user: user
|
||||
})
|
||||
end
|
||||
|
||||
def set_layout_vars_for_organization(org, content_type)
|
||||
most_viewed_vis_map = org.public_vis_by_type(Carto::Visualization::TYPE_DERIVED,
|
||||
1,
|
||||
1,
|
||||
nil,
|
||||
'mapviews',
|
||||
visualization_version).first
|
||||
set_layout_vars(most_viewed_vis_map: most_viewed_vis_map,
|
||||
content_type: content_type,
|
||||
default_fallback_basemap: org.owner ? org.owner.default_basemap : nil,
|
||||
base_url: '')
|
||||
set_shared_layout_vars(org,
|
||||
name: org.display_name.blank? ? org.name : org.display_name,
|
||||
avatar_url: org.avatar_url)
|
||||
end
|
||||
|
||||
def set_layout_vars(required)
|
||||
@most_viewed_vis_map = required.fetch(:most_viewed_vis_map)
|
||||
@content_type = required.fetch(:content_type)
|
||||
@maps_url = CartoDB.url(view_context, 'public_maps_home', user: required.fetch(:user, nil))
|
||||
@datasets_url = CartoDB.url(view_context, 'public_datasets_home', user: required.fetch(:user, nil))
|
||||
@default_fallback_basemap = required.fetch(:default_fallback_basemap, {})
|
||||
@base_url = required.fetch(:base_url, {})
|
||||
end
|
||||
|
||||
def set_pagination_vars(required)
|
||||
# Force all number pagination vars to be integers avoiding problems with
|
||||
# undesired strings
|
||||
@total_count = required.fetch(:total_count, 0).to_i
|
||||
@per_page = required.fetch(:per_page, 9).to_i
|
||||
@current_page = current_page.to_i
|
||||
@first_page_url = required.fetch(:first_page_url)
|
||||
@numbered_page_url = required.fetch(:numbered_page_url)
|
||||
@page_number_placeholder = PAGE_NUMBER_PLACEHOLDER
|
||||
end
|
||||
|
||||
# Shared as in shared for both new and old layout
|
||||
def set_shared_layout_vars(model, required, optional = {})
|
||||
@twitter_username = model.twitter_username
|
||||
@location = model.location
|
||||
@description = model.description
|
||||
@website = website_url(model.website)
|
||||
@website_clean = @website ? @website.gsub(/https?:\/\//, "") : ""
|
||||
@name = required.fetch(:name)
|
||||
@avatar_url = required.fetch(:avatar_url)
|
||||
@email = optional.fetch(:email, nil)
|
||||
@available_for_hire = optional.fetch(:available_for_hire, false)
|
||||
@user = optional.fetch(:user, nil)
|
||||
@is_org = model.is_a? Organization
|
||||
@tables_num = (@is_org ? org_datasets_public_builder(model) : user_datasets_public_builder(model)).build.count
|
||||
@maps_count = (@is_org ? org_maps_public_builder(model) : user_maps_public_builder(model)).build.count
|
||||
|
||||
@needs_gmaps_lib = @most_viewed_vis_map.try(:map).try(:provider) == 'googlemaps'
|
||||
@needs_gmaps_lib ||= @default_fallback_basemap['className'] == 'googlemaps'
|
||||
|
||||
gmaps_user = @most_viewed_vis_map.try(:user) || @viewed_user
|
||||
@gmaps_query_string = gmaps_user ? gmaps_user.google_maps_query_string : @viewed_org.google_maps_key
|
||||
end
|
||||
|
||||
def user_datasets_public_builder(user)
|
||||
public_builder(user_id: user.id, vis_type: Carto::Visualization::TYPE_CANONICAL)
|
||||
end
|
||||
|
||||
def user_maps_public_builder(user, version = nil)
|
||||
public_builder(user_id: user.id, vis_type: Carto::Visualization::TYPE_DERIVED, version: version)
|
||||
end
|
||||
|
||||
def org_datasets_public_builder(org)
|
||||
public_builder(vis_type: Carto::Visualization::TYPE_CANONICAL, organization_id: org.id)
|
||||
end
|
||||
|
||||
def org_maps_public_builder(org)
|
||||
public_builder(vis_type: Carto::Visualization::TYPE_DERIVED, organization_id: org.id)
|
||||
end
|
||||
|
||||
def public_builder(user_id: nil, vis_type: nil, organization_id: nil, version: nil)
|
||||
tags = tag_or_nil.nil? ? nil : [tag_or_nil]
|
||||
|
||||
builder = Carto::VisualizationQueryBuilder.new
|
||||
.with_privacy(Carto::Visualization::PRIVACY_PUBLIC)
|
||||
.with_published
|
||||
.without_raster
|
||||
.with_order(:updated_at, :desc)
|
||||
.with_user_id(user_id)
|
||||
.with_type(vis_type)
|
||||
.with_tags(tags)
|
||||
.with_organization_id(organization_id)
|
||||
.with_version(version)
|
||||
|
||||
builder
|
||||
end
|
||||
|
||||
def visualization_version
|
||||
@has_new_dashboard ? Carto::Visualization::VERSION_BUILDER : nil
|
||||
end
|
||||
|
||||
def named_map_vizjson3(visualization)
|
||||
generate_named_map_vizjson3(Carto::Visualization.find(visualization.id))
|
||||
end
|
||||
|
||||
def get_organization_if_exists(name)
|
||||
Organization.where(name: name).first
|
||||
end
|
||||
|
||||
def current_page
|
||||
params[:page].to_i > 0 ? params[:page] : 1
|
||||
end
|
||||
|
||||
def tag_or_nil
|
||||
params[:tag]
|
||||
end
|
||||
|
||||
def ensure_organization_correct
|
||||
return if CartoDB.subdomainless_urls?
|
||||
|
||||
user_or_org_domain = CartoDB.subdomain_from_request(request)
|
||||
user_domain = CartoDB.extract_subdomain(request)
|
||||
user = ::User.where(username: user_domain).first
|
||||
|
||||
unless user.nil?
|
||||
if user.username != user_or_org_domain and not user.belongs_to_organization?(get_organization_if_exists(user_or_org_domain))
|
||||
render_404
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def process_dataset_render(dataset)
|
||||
geometry_type = dataset.kind
|
||||
if geometry_type != 'raster'
|
||||
table_geometry_types = dataset.table.geometry_types
|
||||
geometry_type = GEOMETRY_MAPPING.fetch(table_geometry_types.first.try(&:downcase), '')
|
||||
end
|
||||
|
||||
vis_item(dataset).merge(
|
||||
rows_count: dataset.table.rows_counted,
|
||||
size_in_bytes: dataset.table.table_size,
|
||||
geometry_type: geometry_type,
|
||||
source: markdown_html_safe(dataset.source)
|
||||
)
|
||||
rescue StandardError => e
|
||||
# A dataset might be invalid. For example, having the table deleted and not yet cleaned.
|
||||
# We don't want public page to be broken, but error must be traced.
|
||||
CartoDB.notify_exception(e, vis: dataset)
|
||||
nil
|
||||
end
|
||||
|
||||
def process_map_render(map)
|
||||
vis_item(map)
|
||||
end
|
||||
|
||||
def vis_item(vis)
|
||||
return {
|
||||
id: vis.id,
|
||||
title: vis.name,
|
||||
description: markdown_html_safe(vis.description),
|
||||
tags: vis.tags,
|
||||
updated_at: vis.updated_at,
|
||||
owner: vis.user,
|
||||
map_zoom: vis.map.zoom
|
||||
}
|
||||
end
|
||||
|
||||
def load_viewed_entity
|
||||
username = CartoDB.extract_subdomain(request)
|
||||
@viewed_user = ::User.where(username: username).first
|
||||
|
||||
if @viewed_user.nil?
|
||||
username = username.strip.downcase
|
||||
@viewed_org = get_organization_if_exists(username)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def website_url(url)
|
||||
if url.blank?
|
||||
""
|
||||
else
|
||||
!url.blank? && url[/^https?:\/\//].nil? ? "http://#{url}" : url
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,63 @@
|
||||
require_relative '../../models/map/presenter'
|
||||
|
||||
class Admin::TablesController < Admin::AdminController
|
||||
ssl_required :index, :show, :public
|
||||
|
||||
skip_before_filter :browser_is_html5_compliant?, :only => [:embed_map]
|
||||
before_filter :login_required, :only => [:index]
|
||||
|
||||
after_filter :update_user_last_activity, only: [:index, :show]
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
# We only require login for index, so we must manage the security at this level.
|
||||
# we present different actions depending on if there is a user logged in or not.
|
||||
# if the user is not logged in, we redirect them to the public page
|
||||
def show
|
||||
if current_user.present?
|
||||
@table = ::Table.get_by_id(params[:id], current_user)
|
||||
respond_to do |format|
|
||||
format.html
|
||||
download_formats @table, format
|
||||
end
|
||||
else
|
||||
redirect_to CartoDB.path(self, 'public_table', { id: params[:id], :format => params[:format] })
|
||||
end
|
||||
end
|
||||
|
||||
def public
|
||||
@table = nil
|
||||
@subdomain = CartoDB.extract_subdomain(request)
|
||||
@table = ::Table.get_by_id(params[:id], ::User.find(:username => @subdomain))
|
||||
|
||||
# Has quite strange checks to see if a user can access a public table
|
||||
if @table.blank? || @table.private? || ((current_user && current_user.id != @table.user_id) && @table.private?)
|
||||
render_403
|
||||
else
|
||||
@vizjson = CartoDB::Map::Presenter.new(
|
||||
@table.map,
|
||||
{ full: true },
|
||||
Cartodb.config
|
||||
)
|
||||
respond_to do |format|
|
||||
format.html { render 'public', layout: 'application_table_public' }
|
||||
download_formats @table, format
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def download_formats table, format
|
||||
format.sql { send_data table.to_sql, send_data_conf(table, 'zip', 'zip') }
|
||||
format.kml { send_data table.to_kml, send_data_conf(table, 'zip', 'kmz') }
|
||||
format.csv { send_data table.to_csv, send_data_conf(table, 'zip', 'zip') }
|
||||
format.shp { send_data table.to_shp, send_data_conf(table, 'octet-stream', 'zip') }
|
||||
end
|
||||
|
||||
def send_data_conf table, type, ext
|
||||
{ :type => "application/#{type}; charset=binary; header=present",
|
||||
:disposition => "attachment; filename=#{table.name}.#{ext}" }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
class Admin::UsersController < Admin::AdminController
|
||||
include LoginHelper
|
||||
|
||||
ssl_required :account, :profile, :lockout, :maintenance
|
||||
|
||||
before_filter :invalidate_browser_cache
|
||||
before_filter :login_required
|
||||
before_filter :setup_user
|
||||
|
||||
layout 'application'
|
||||
|
||||
def profile
|
||||
render(file: "public/static/profile/index.html", layout: false)
|
||||
end
|
||||
|
||||
def account
|
||||
render(file: "public/static/account/index.html", layout: false)
|
||||
end
|
||||
|
||||
def lockout
|
||||
if current_user.locked?
|
||||
@expiration_days = @user.remaining_days_deletion
|
||||
@payments_url = @user.plan_url(request.protocol)
|
||||
render locals: { breadcrumb: false }
|
||||
else
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
def maintenance
|
||||
if current_user.maintenance_mode?
|
||||
render locals: { breadcrumb: false }
|
||||
else
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setup_user
|
||||
@user = current_user
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,680 @@
|
||||
require_relative '../../models/map/presenter'
|
||||
require_relative '../carto/admin/user_table_public_map_adapter'
|
||||
require_relative '../carto/admin/visualization_public_map_adapter'
|
||||
require_relative '../carto/api/visualization_presenter'
|
||||
require_relative '../carto/api/received_notification_presenter'
|
||||
require_relative '../../helpers/embed_redis_cache'
|
||||
|
||||
require_dependency 'carto/tracking/events'
|
||||
require_dependency 'resque/user_jobs'
|
||||
require_dependency 'static_maps_url_helper'
|
||||
require_dependency 'carto/helpers/frame_options_helper'
|
||||
require_dependency 'carto/visualization'
|
||||
|
||||
class Admin::VisualizationsController < Admin::AdminController
|
||||
include CartoDB, VisualizationsControllerHelper
|
||||
include Carto::FrameOptionsHelper
|
||||
|
||||
MAX_MORE_VISUALIZATIONS = 3
|
||||
DEFAULT_PLACEHOLDER_CHARS = 4
|
||||
|
||||
ssl_allowed :embed_map, :public_map, :show_protected_embed_map, :public_table,
|
||||
:show_organization_public_map, :show_organization_embed_map,
|
||||
:embed_protected, :public_map_protected, :embed_forbidden, :track_embed
|
||||
ssl_required :index, :show, :protected_public_map, :show_protected_public_map
|
||||
|
||||
before_filter :x_frame_options_allow, only: [:embed_forbidden, :embed_map, :embed_protected,
|
||||
:show_organization_embed_map, :show_protected_embed_map,
|
||||
:track_embed]
|
||||
before_filter :login_required, only: [:index]
|
||||
before_filter :table_and_schema_from_params, only: [:show, :public_table, :public_map, :show_protected_public_map,
|
||||
:show_protected_embed_map, :embed_map]
|
||||
before_filter :get_viewed_user, only: [:public_map, :public_table, :show_protected_public_map, :show_organization_public_map, :public_map_protected, :embed_map, :embed_protected]
|
||||
|
||||
before_filter :resolve_visualization_and_table,
|
||||
:ensure_visualization_viewable,
|
||||
only: [:show, :public_table, :public_map,
|
||||
:show_organization_public_map, :show_organization_embed_map,
|
||||
:show_protected_public_map, :show_protected_embed_map]
|
||||
|
||||
before_filter :resolve_visualization_and_table_if_not_cached, only: [:embed_map]
|
||||
before_filter :redirect_to_builder_embed_if_v3, only: [:embed_map, :show_organization_public_map,
|
||||
:show_organization_embed_map, :show_protected_public_map,
|
||||
:show_protected_embed_map,
|
||||
:public_map, :show_protected_public_map]
|
||||
|
||||
after_filter :update_user_last_activity, only: [:show]
|
||||
|
||||
skip_before_filter :browser_is_html5_compliant?, only: [:public_map, :embed_map, :track_embed,
|
||||
:show_protected_embed_map, :show_protected_public_map]
|
||||
skip_before_filter :verify_authenticity_token, only: [:show_protected_public_map, :show_protected_embed_map]
|
||||
|
||||
def index
|
||||
render(file: "public/static/dashboard/index.html", layout: false)
|
||||
end
|
||||
|
||||
def show
|
||||
table_action = request.original_fullpath =~ %r{/tables/}
|
||||
unless current_user.present?
|
||||
if table_action
|
||||
return(redirect_to CartoDB.url(self, 'public_table_map', params: { id: request.params[:id] }))
|
||||
else
|
||||
return(redirect_to CartoDB.url(self, 'public_visualizations_public_map', params: { id: request.params[:id] }))
|
||||
end
|
||||
end
|
||||
|
||||
@google_maps_query_string = @visualization.user.google_maps_query_string
|
||||
@basemaps = @visualization.user.basemaps
|
||||
|
||||
if table_action
|
||||
if current_user.builder_enabled? && @visualization.has_read_permission?(current_user)
|
||||
return redirect_to CartoDB.url(self, 'builder_dataset', params: { id: request.params[:id] }, user: current_user)
|
||||
elsif !@visualization.has_write_permission?(current_user)
|
||||
return redirect_to CartoDB.url(self, 'public_table_map', params: { id: request.params[:id], redirected: true })
|
||||
end
|
||||
elsif current_user.builder_enabled? && !@visualization.open_in_editor?
|
||||
return redirect_to CartoDB.url(self, 'builder_visualization', params: { id: request.params[:id] },
|
||||
user: current_user)
|
||||
elsif current_user.has_feature_flag?('static_editor') && !current_user.builder_enabled?
|
||||
return render(file: 'public/static/show/index.html', layout: false)
|
||||
elsif !@visualization.has_write_permission?(current_user)
|
||||
return redirect_to CartoDB.url(self, 'public_visualizations_public_map',
|
||||
params: { id: request.params[:id], redirected: true })
|
||||
end
|
||||
|
||||
if @visualization.is_privacy_private? && @visualization.has_read_permission?(current_user)
|
||||
@auth_tokens = current_user.get_auth_tokens
|
||||
end
|
||||
|
||||
respond_to { |format| format.html }
|
||||
end
|
||||
|
||||
def public_table
|
||||
return(render_pretty_404) if @visualization.private?
|
||||
|
||||
get_viewed_user
|
||||
ff_user = @viewed_user || @org.try(:owner)
|
||||
|
||||
if @visualization.derived?
|
||||
if current_user.nil? || current_user.username != request.params[:user_domain]
|
||||
destination_user = ::User.where(username: request.params[:user_domain]).first
|
||||
else
|
||||
destination_user = nil
|
||||
end
|
||||
return(redirect_to CartoDB.url(self, 'public_visualizations_public_map', params: { id: request.params[:id] },
|
||||
user: destination_user))
|
||||
end
|
||||
|
||||
if current_user.nil? && !request.params[:redirected].present?
|
||||
redirect_url = get_corrected_url_if_proceeds(for_table=true)
|
||||
unless redirect_url.nil?
|
||||
redirect_to redirect_url and return
|
||||
end
|
||||
end
|
||||
|
||||
if @visualization.organization?
|
||||
unless current_user && @visualization.has_read_permission?(current_user)
|
||||
return(embed_forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
return(redirect_to protocol: 'https://') if @visualization.is_privacy_private? \
|
||||
&& !(request.ssl? || request.local? || Rails.env.development?)
|
||||
|
||||
# Legacy redirect, now all public pages also with org. name
|
||||
if eligible_for_redirect?(@visualization.user)
|
||||
redirect_to CartoDB.url(self,
|
||||
'public_table',
|
||||
params: { id: params[:id].to_s, redirected: true },
|
||||
user: @visualization.user)
|
||||
return
|
||||
end
|
||||
|
||||
@vizjson = @visualization.to_vizjson({https_request: request.protocol == 'https://'})
|
||||
@auth_tokens = nil
|
||||
@use_https = false
|
||||
@api_key = nil
|
||||
@can_copy = false
|
||||
|
||||
if current_user && @visualization.has_read_permission?(current_user)
|
||||
if @visualization.is_privacy_private?
|
||||
@auth_tokens = current_user.get_auth_tokens
|
||||
@use_https = true
|
||||
@api_key = current_user.api_key
|
||||
end
|
||||
@can_copy = true # this table can be copied to user dashboard
|
||||
end
|
||||
|
||||
owner = @visualization.user
|
||||
# set user to current user only if the user is in the same organization
|
||||
# this allows to enable "copy this table to your tables" button
|
||||
if current_user && current_user.organization.present? && owner.organization.present? &&
|
||||
current_user.organization_id == owner.organization_id
|
||||
@user = current_user
|
||||
response.headers['Cache-Control'] = "no-cache,private"
|
||||
else
|
||||
@user = @visualization.user
|
||||
end
|
||||
|
||||
@name = @visualization.user.name_or_username
|
||||
@user_url = CartoDB.url(self, 'public_user_feed_home', user: @visualization.user)
|
||||
|
||||
@is_data_library = data_library_user?
|
||||
|
||||
if @is_data_library
|
||||
@name = "Data Library"
|
||||
@user_url = Cartodb.get_config(:data_library, 'path') ? "#{request.protocol}#{CartoDB.account_host}#{Cartodb.config[:data_library]['path']}" : @user_url
|
||||
end
|
||||
|
||||
@avatar_url = @visualization.user.avatar
|
||||
@twitter_username = @visualization.user.twitter_username.present? ? @visualization.user.twitter_username : nil
|
||||
@location = @visualization.user.location.present? ? @visualization.user.location : nil
|
||||
|
||||
@user_domain = user_domain_variable(request)
|
||||
|
||||
@visualization_id = @visualization.id
|
||||
|
||||
@disqus_shortname = @visualization.user.disqus_shortname.presence || 'cartodb'
|
||||
@public_tables_count = @visualization.user.public_table_count
|
||||
|
||||
@total_visualizations = @table.dependent_visualizations.select do |vis|
|
||||
vis.privacy == Carto::Visualization::PRIVACY_PUBLIC && vis.published?
|
||||
end
|
||||
|
||||
@total_nonpublic_total_vis_count = @table.dependent_visualizations.reject { |vis|
|
||||
vis.privacy == Carto::Visualization::PRIVACY_PUBLIC
|
||||
}.count
|
||||
|
||||
# Public export API SQL url
|
||||
@export_sql_api_url = "#{ sql_api_url("SELECT * FROM #{ @table.owner.sql_safe_database_schema }.#{ @table.name }", @user) }&format=shp"
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render 'public_dataset', layout: 'application_table_public' }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def public_map
|
||||
if current_user.nil? && !request.params[:redirected].present?
|
||||
redirect_url = get_corrected_url_if_proceeds(for_table=false)
|
||||
unless redirect_url.nil?
|
||||
redirect_to redirect_url and return
|
||||
end
|
||||
end
|
||||
|
||||
return(embed_forbidden) unless @visualization.is_accesible_by_user?(current_user)
|
||||
|
||||
if current_user && @visualization.is_privacy_private? &&
|
||||
@visualization.has_read_permission?(current_user)
|
||||
return(show_organization_public_map)
|
||||
end
|
||||
|
||||
# Legacy redirect, now all public pages also with org. name
|
||||
if eligible_for_redirect?(@visualization.user)
|
||||
# INFO: here we only want the presenter to rewrite the url of @visualization.user namespacing it like 'schema.id',
|
||||
# so current_user also equals @visualization.user
|
||||
visualization_presenter = Carto::Api::VisualizationPresenter.new(@visualization, @visualization.user, self)
|
||||
redirect_to visualization_presenter.privacy_aware_map_url({ redirected: true },
|
||||
'public_visualizations_public_map') and return
|
||||
end
|
||||
|
||||
return(public_map_protected) if @visualization.password_protected?
|
||||
|
||||
if @visualization.can_be_cached?
|
||||
response.headers['X-Cache-Channel'] = "#{@visualization.varnish_key}:vizjson"
|
||||
end
|
||||
|
||||
if @more_visualizations && @more_visualizations.length > 0
|
||||
additional_keys = []
|
||||
@more_visualizations.each do |vis_adapter|
|
||||
additional_keys << vis_adapter.visualization.surrogate_key
|
||||
end
|
||||
additional_keys = " #{additional_keys.join(' ')}"
|
||||
else
|
||||
additional_keys = ''
|
||||
end
|
||||
|
||||
if @visualization.can_be_cached?
|
||||
response.headers['Surrogate-Key'] =
|
||||
"#{CartoDB::SURROGATE_NAMESPACE_PUBLIC_PAGES} #{@visualization.surrogate_key}#{additional_keys}"
|
||||
|
||||
response.headers['Cache-Control'] = "no-cache,max-age=86400,must-revalidate, public"
|
||||
end
|
||||
|
||||
@name = @visualization.user.name_or_username
|
||||
@avatar_url = @visualization.user.avatar
|
||||
@twitter_username = @visualization.user.twitter_username.present? ? @visualization.user.twitter_username : nil
|
||||
@location = @visualization.user.location.present? ? @visualization.user.location : nil
|
||||
@google_maps_query_string = @visualization.user.google_maps_query_string
|
||||
|
||||
@mapviews = @visualization.total_mapviews
|
||||
|
||||
@disqus_shortname = @visualization.user.disqus_shortname.presence || 'cartodb'
|
||||
@visualization_count = @visualization.user.public_visualization_count
|
||||
@related_tables = @visualization.related_tables
|
||||
@related_canonical_visualizations = @visualization.related_canonical_visualizations
|
||||
@related_tables_owners = Hash.new
|
||||
@related_tables.each { |table|
|
||||
unless @related_tables_owners.include?(table.user_id)
|
||||
table_owner = ::User.where(id: table.user_id).first
|
||||
if table_owner.nil?
|
||||
# strange scenario, as user has been deleted but his table still exists
|
||||
@related_tables_owners[table.user_id] = nil
|
||||
else
|
||||
@related_tables_owners[table.user_id] = table_owner
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
@user_domain = user_domain_variable(request)
|
||||
|
||||
@public_tables_count = @visualization.user.public_table_count
|
||||
@nonpublic_tables_count = @related_tables.select{|t| !t.public? }.count
|
||||
|
||||
# We need to know if visualization logo is visible or not
|
||||
@hide_logo = is_logo_hidden(@visualization, params)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render layout: 'application_public_visualization_layout' }
|
||||
format.js { render 'public_map', content_type: 'application/javascript' }
|
||||
end
|
||||
rescue => e
|
||||
CartoDB.notify_exception(e, {user:current_user})
|
||||
embed_forbidden
|
||||
end
|
||||
|
||||
def show_organization_public_map
|
||||
return(embed_forbidden) unless org_user_has_map_permissions?(current_user, @visualization)
|
||||
|
||||
response.headers['Cache-Control'] = "no-cache,private"
|
||||
|
||||
@protected_map_tokens = current_user.get_auth_tokens
|
||||
|
||||
@name = @visualization.user.name_or_username
|
||||
@avatar_url = @visualization.user.avatar
|
||||
|
||||
@disqus_shortname = @visualization.user.disqus_shortname.presence || 'cartodb'
|
||||
@visualization_count = @visualization.user.public_visualization_count
|
||||
@related_tables = @visualization.related_tables
|
||||
@related_canonical_visualizations = @visualization.related_canonical_visualizations
|
||||
@public_tables_count = @visualization.user.public_table_count
|
||||
@nonpublic_tables_count = @related_tables.select{|p| !p.public? }.count
|
||||
|
||||
# We need to know if visualization logo is visible or not
|
||||
@hide_logo = is_logo_hidden(@visualization, params)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render 'public_map', layout: 'application_public_visualization_layout' }
|
||||
end
|
||||
end
|
||||
|
||||
def show_organization_embed_map
|
||||
return(embed_forbidden) unless org_user_has_map_permissions?(current_user, @visualization)
|
||||
|
||||
response.headers['X-Cache-Channel'] = "#{@visualization.varnish_key}:vizjson"
|
||||
response.headers['Surrogate-Key'] = "#{CartoDB::SURROGATE_NAMESPACE_PUBLIC_PAGES} #{@visualization.surrogate_key}"
|
||||
response.headers['Cache-Control'] = "no-cache,max-age=86400,must-revalidate, public"
|
||||
|
||||
@protected_map_tokens = current_user.get_auth_tokens
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render 'embed_map', layout: 'application_public_visualization_layout' }
|
||||
end
|
||||
end
|
||||
|
||||
def show_protected_public_map
|
||||
submitted_password = params.fetch(:password, nil)
|
||||
return(render_pretty_404) unless @visualization.password_protected? and @visualization.has_password?
|
||||
|
||||
unless @visualization.password_valid?(submitted_password)
|
||||
flash[:placeholder] = '*' * (submitted_password ? submitted_password.size : DEFAULT_PLACEHOLDER_CHARS)
|
||||
flash[:error] = "Invalid password"
|
||||
return(public_map_protected)
|
||||
end
|
||||
|
||||
response.headers['X-Cache-Channel'] = "#{@visualization.varnish_key}:vizjson"
|
||||
response.headers['Surrogate-Key'] = "#{CartoDB::SURROGATE_NAMESPACE_PUBLIC_PAGES} #{@visualization.surrogate_key}"
|
||||
response.headers['Cache-Control'] = "no-cache,max-age=86400,must-revalidate, public"
|
||||
|
||||
@protected_map_tokens = @visualization.get_auth_tokens
|
||||
|
||||
@name = @visualization.user.name_or_username
|
||||
@avatar_url = @visualization.user.avatar
|
||||
|
||||
@user_domain = user_domain_variable(request)
|
||||
|
||||
@disqus_shortname = @visualization.user.disqus_shortname.presence || 'cartodb'
|
||||
@visualization_count = @visualization.user.public_visualization_count
|
||||
@related_tables = @visualization.related_tables
|
||||
@related_canonical_visualizations = @visualization.related_canonical_visualizations
|
||||
@public_tables_count = @visualization.user.public_table_count
|
||||
@nonpublic_tables_count = @related_tables.select{|p| !p.public? }.count
|
||||
|
||||
# We need to know if visualization logo is visible or not
|
||||
@hide_logo = is_logo_hidden(@visualization, params)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render 'public_map', layout: 'application_public_visualization_layout' }
|
||||
end
|
||||
rescue => e
|
||||
CartoDB::Logger.error(exception: e)
|
||||
public_map_protected
|
||||
end
|
||||
|
||||
def show_protected_embed_map
|
||||
submitted_password = params.fetch(:password, nil)
|
||||
return(render_pretty_404) unless @visualization.password_protected? and @visualization.has_password?
|
||||
|
||||
unless @visualization.password_valid?(submitted_password)
|
||||
flash[:placeholder] = '*' * (submitted_password ? submitted_password.size : DEFAULT_PLACEHOLDER_CHARS)
|
||||
flash[:error] = "Invalid password"
|
||||
return(embed_protected)
|
||||
end
|
||||
|
||||
get_viewed_user
|
||||
|
||||
response.headers['Cache-Control'] = "no-cache, private"
|
||||
|
||||
@protected_map_tokens = @visualization.get_auth_tokens
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render 'embed_map', layout: 'application_public_visualization_layout' }
|
||||
end
|
||||
rescue => e
|
||||
CartoDB::Logger.error(exception: e)
|
||||
embed_protected
|
||||
end
|
||||
|
||||
def embed_map
|
||||
if @viewed_user && @viewed_user.has_feature_flag?('static_embed_map')
|
||||
return render(file: "public/static/embed_map/index.html", layout: false)
|
||||
end
|
||||
|
||||
if request.format == 'text/javascript'
|
||||
error_message = "/* Javascript embeds are deprecated, please use the html iframe instead */"
|
||||
return render inline: error_message, status: 400
|
||||
end
|
||||
|
||||
if @cached_embed
|
||||
response.headers.merge! @cached_embed[:headers].stringify_keys
|
||||
respond_to do |format|
|
||||
# Use html_safe to mark the string as trusted since it comes from a successful response.
|
||||
# We cannot use `render body: @cached_embed[:body]` in Rails 3
|
||||
format.html { render inline: "<%= @cached_embed[:body].html_safe %>" }
|
||||
end
|
||||
else
|
||||
resp = embed_map_actual
|
||||
if response.ok? && (@visualization.public? || @visualization.public_with_link?)
|
||||
#cache response
|
||||
is_https = (request.protocol == 'https://')
|
||||
embed_redis_cache.set(@visualization.id, is_https, response.headers, response.body)
|
||||
end
|
||||
resp
|
||||
end
|
||||
end
|
||||
|
||||
# Renders input password view
|
||||
def embed_protected
|
||||
render 'embed_map_password', :layout => 'application_password_layout'
|
||||
end
|
||||
|
||||
def public_map_protected
|
||||
render 'public_map_password', :layout => 'application_password_layout'
|
||||
end
|
||||
|
||||
def embed_forbidden
|
||||
render 'embed_map_error', layout: false, status: :forbidden
|
||||
end
|
||||
|
||||
def track_embed
|
||||
response.headers['X-Cache-Channel'] = "embeds_google_analytics"
|
||||
response.headers['Cache-Control'] = "no-cache,max-age=86400,must-revalidate, public"
|
||||
render 'track', layout: false
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def disallowed_type?(visualization)
|
||||
return true if visualization.nil?
|
||||
visualization.type_slide?
|
||||
end
|
||||
|
||||
# Check if visualization logo should be hidden or not
|
||||
def is_logo_hidden(vis, parameters)
|
||||
has_logo = vis.overlays.any? {|o| o.type == "logo" }
|
||||
(!has_logo && vis.user.remove_logo? && (!parameters['cartodb_logo'] || parameters['cartodb_logo'] != "true")) || (has_logo && vis.user.remove_logo? && (parameters["cartodb_logo"] == 'false'))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def more_visualizations(user, excluded_visualization)
|
||||
vqb = Carto::VisualizationQueryBuilder.user_public_visualizations(user).with_order(:updated_at, :desc)
|
||||
vqb.with_excluded_ids([excluded_visualization.id]) if excluded_visualization
|
||||
visualizations = vqb.build_paged(1, MAX_MORE_VISUALIZATIONS)
|
||||
visualizations.map { |v|
|
||||
Carto::Admin::VisualizationPublicMapAdapter.new(v, current_user, self)
|
||||
}
|
||||
end
|
||||
|
||||
def eligible_for_redirect?(user)
|
||||
return false if CartoDB.subdomainless_urls?
|
||||
user.has_organization? && !request.params[:redirected].present? &&
|
||||
CartoDB.subdomain_from_request(request) != user.organization.name
|
||||
end
|
||||
|
||||
def org_user_has_map_permissions?(user, visualization)
|
||||
user && visualization && visualization.has_read_permission?(user)
|
||||
end
|
||||
|
||||
def resolve_visualization_and_table
|
||||
filters = { exclude_raster: true }
|
||||
@visualization, @table =
|
||||
get_visualization_and_table(@table_id, username_from_schema || CartoDB.extract_subdomain(request), filters)
|
||||
if @visualization && @visualization.user
|
||||
@more_visualizations = more_visualizations(@visualization.user, @visualization)
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_visualization_viewable
|
||||
render_pretty_404 if disallowed_type?(@visualization)
|
||||
end
|
||||
|
||||
def resolve_visualization_and_table_if_not_cached
|
||||
is_https = (request.protocol == 'https://')
|
||||
# TODO review the naming confusion about viz and tables, I suspect templates also need review
|
||||
@cached_embed = embed_redis_cache.get(@table_id, is_https)
|
||||
if !@cached_embed
|
||||
resolve_visualization_and_table
|
||||
render('embed_map_error', layout: false, status: :not_found) if disallowed_type?(@visualization)
|
||||
end
|
||||
end
|
||||
|
||||
# If user A shares to user B a table link (being both from same org), attept to rewrite the url to the correct format
|
||||
# Messing with sessions is bad so just redirect to newly formed url and let new request handle permissions/access
|
||||
def get_corrected_url_if_proceeds(for_table=true)
|
||||
url = nil
|
||||
|
||||
return url if CartoDB.subdomainless_urls?
|
||||
|
||||
org_name = CartoDB.subdomain_from_request(request)
|
||||
if CartoDB.extract_subdomain(request) != org_name
|
||||
# Might be an org url, try getting the org
|
||||
organization = Organization.where(name: org_name).first
|
||||
unless organization.nil?
|
||||
authenticated_users = request.session.to_hash.select { |k, _v|
|
||||
k.start_with?("warden.user") && !k.end_with?(".session")
|
||||
}.values
|
||||
authenticated_users.each { |username|
|
||||
user = ::User.where(username: username).first
|
||||
if url.nil? && !user.nil? && !user.organization.nil?
|
||||
if user.organization.id == organization.id
|
||||
if for_table
|
||||
url = CartoDB.url(self, 'public_tables_show',
|
||||
params: { id: "#{params[:user_domain]}.#{params[:id]}", redirected: true },
|
||||
user: user)
|
||||
else
|
||||
url = CartoDB.url(self, 'public_visualizations_show',
|
||||
params: { id: "#{params[:user_domain]}.#{params[:id]}", redirected: true },
|
||||
user: user)
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
url
|
||||
end
|
||||
|
||||
def username_from_schema
|
||||
(@schema && @schema != 'public') ? @schema : nil
|
||||
end
|
||||
|
||||
def table_and_schema_from_params
|
||||
if params.fetch('id', nil) =~ /\./
|
||||
@table_id, @schema = params.fetch('id').split('.').reverse
|
||||
else
|
||||
@table_id, @schema = [params.fetch('id', nil), nil]
|
||||
end
|
||||
end
|
||||
|
||||
def full_table_id
|
||||
id = @table_id
|
||||
if @schema
|
||||
id = @schema + "." + id
|
||||
end
|
||||
id
|
||||
end
|
||||
|
||||
def public_url
|
||||
if request.path_info =~ %r{/tables/}
|
||||
CartoDB.path(self, 'public_table', { id: full_table_id })
|
||||
else
|
||||
CartoDB.path(self, 'public_visualization', { id: full_table_id })
|
||||
end
|
||||
end
|
||||
|
||||
def public_map_url
|
||||
if request.path_info =~ %r{/tables/}
|
||||
CartoDB.path(self, 'public_table_map', { id: full_table_id })
|
||||
else
|
||||
CartoDB.path(self, 'public_visualizations_public_map', { id: full_table_id })
|
||||
end
|
||||
end
|
||||
|
||||
def embed_map_url_for(id)
|
||||
if request.path_info =~ %r{/tables/}
|
||||
CartoDB.path(self, 'public_tables_embed_map', { id: id })
|
||||
else
|
||||
CartoDB.path(self, 'public_visualizations_embed_map', { id: id })
|
||||
end
|
||||
end
|
||||
|
||||
def download_formats(table, format)
|
||||
format.sql { send_data table.to_sql, data_for(table, 'zip', 'zip') }
|
||||
format.kml { send_data table.to_kml, data_for(table, 'zip', 'kmz') }
|
||||
format.csv { send_data table.to_csv, data_for(table, 'zip', 'zip') }
|
||||
format.shp { send_data table.to_shp, data_for(table, 'octet-stream', 'zip') }
|
||||
end
|
||||
|
||||
def data_for(table, type, extension)
|
||||
{
|
||||
type: "application/#{type}; charset=binary; header=present",
|
||||
disposition: "attachment; filename=#{table.name}.#{extension}"
|
||||
}
|
||||
end
|
||||
|
||||
def render_pretty_404
|
||||
render(file: "public/404.html", layout: false, status: 404)
|
||||
end
|
||||
|
||||
def user_domain_variable(request)
|
||||
if params[:user_domain].present?
|
||||
CartoDB.subdomain_from_request(request) != params[:user_domain] ? params[:user_domain] : nil
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_visualization_and_table(table_id, schema, filter)
|
||||
user = Carto::User.where(username: schema).first
|
||||
# INFO: organization public visualizations
|
||||
if user
|
||||
visualization = get_priority_visualization(table_id, user_id: user.id)
|
||||
else
|
||||
organization = Carto::Organization.where(name: schema).first
|
||||
visualization = get_priority_visualization(table_id, organization_id: organization.id) if organization
|
||||
end
|
||||
|
||||
return get_visualization_and_table_from_table_id(table_id) if visualization.nil?
|
||||
render_pretty_404 if visualization.kind == Carto::Visualization::KIND_RASTER
|
||||
return Carto::Admin::VisualizationPublicMapAdapter.new(visualization, current_user, self), visualization.table_service
|
||||
end
|
||||
|
||||
def get_visualization_and_table_from_table_id(table_id)
|
||||
return nil, nil if !is_uuid?(table_id)
|
||||
user_table = Carto::UserTable.where({ id: table_id }).first
|
||||
return nil, nil if user_table.nil?
|
||||
visualization = user_table.visualization
|
||||
return Carto::Admin::VisualizationPublicMapAdapter.new(visualization, current_user, self), visualization.table_service
|
||||
end
|
||||
|
||||
# TODO: remove this method and use app/helpers/carto/uuidhelper.rb. Not used yet because this changed was pushed before
|
||||
def is_uuid?(text)
|
||||
!(Regexp.new(%r{\A#{UUIDTools::UUID_REGEXP}\Z}) =~ text).nil?
|
||||
end
|
||||
|
||||
def sql_api_url(query, user)
|
||||
"#{ ApplicationHelper.sql_api_template("public").gsub! '{user}', user.username }#{ Cartodb.config[:sql_api]['public']['endpoint'] }?q=#{ URI::encode query }"
|
||||
end
|
||||
|
||||
def embed_map_actual
|
||||
return(embed_forbidden) if @visualization.private?
|
||||
return(embed_protected) if @visualization.password_protected?
|
||||
return(show_organization_embed_map) if org_user_has_map_permissions?(current_user, @visualization)
|
||||
|
||||
response.headers['X-Cache-Channel'] = "#{@visualization.varnish_key}:vizjson"
|
||||
response.headers['Surrogate-Key'] = "#{CartoDB::SURROGATE_NAMESPACE_PUBLIC_PAGES} #{@visualization.surrogate_key}"
|
||||
response.headers['Cache-Control'] = "no-cache,max-age=86400,must-revalidate, public"
|
||||
|
||||
# We need to know if visualization logo is visible or not
|
||||
@hide_logo = is_logo_hidden(@visualization, params)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render layout: 'application_public_visualization_layout' }
|
||||
end
|
||||
rescue => e
|
||||
CartoDB::Logger.error(exception: e)
|
||||
embed_forbidden
|
||||
end
|
||||
|
||||
def embed_redis_cache
|
||||
@embed_redis_cache ||= EmbedRedisCache.new($tables_metadata)
|
||||
end
|
||||
|
||||
def get_viewed_user
|
||||
username = CartoDB.extract_subdomain(request)
|
||||
@viewed_user = ::User.where(username: username).first
|
||||
|
||||
if @viewed_user.nil?
|
||||
username = username.strip.downcase
|
||||
@org = get_organization_if_exists(username)
|
||||
end
|
||||
end
|
||||
|
||||
def get_organization_if_exists(name)
|
||||
Organization.where(name: name).first
|
||||
end
|
||||
|
||||
def data_library_user?
|
||||
@viewed_user && Cartodb.get_config(:data_library, 'username') == @viewed_user.username
|
||||
end
|
||||
|
||||
def redirect_to_builder_embed_if_v3
|
||||
# @visualization is not loaded if the embed is cached
|
||||
# Changing version invalidates the embed cache
|
||||
if @visualization && @visualization.version == 3
|
||||
redirect_to CartoDB.url(self, 'builder_visualization_public_embed',
|
||||
params: { visualization_id: @visualization.id })
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user