cartodb/app/controllers/admin/organization_users_controller.rb
2020-06-15 10:58:47 +08:00

305 lines
13 KiB
Ruby

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