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 log_error(message: "Validation error", exception: e) 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 StandardError => 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 log_error(message: 'Validation error', exception: e) 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 log_error(message: 'Error deleting organizational user from central', exception: e, target_user: @user) 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 StandardError => e log_error(message: 'Error deleting organizational user', exception: e, target_user: @user) 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 StandardError => 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