require_dependency 'carto/helpers/batch_queries_statement_timeout' require_dependency 'carto/helpers/billing' require_dependency 'carto/helpers/google_maps' require_dependency 'carto/helpers/has_connector_configuration' require_dependency 'carto/helpers/limits' require_dependency 'carto/helpers/multifactor_authentication' require_dependency 'carto/helpers/oauth_services' require_dependency 'carto/helpers/password' require_dependency 'carto/helpers/password_rate_limit' require_dependency 'carto/helpers/urls' require_dependency 'carto/helpers/varnish_cache_handler' require_dependency 'carto/gcloud_user_settings' require_dependency 'carto/helpers/sessions_invalidations' module Carto::UserCommons include Carto::BatchQueriesStatementTimeout include Carto::Billing include Carto::GoogleMaps include Carto::HasConnectorConfiguration include Carto::Limits include Carto::MultifactorAuthentication include Carto::OauthServices include Carto::Password include Carto::PasswordRateLimit include Carto::Urls include Carto::VarnishCacheHandler include Carto::SessionsInvalidations STATE_ACTIVE = 'active'.freeze STATE_LOCKED = 'locked'.freeze LOGGING_ATTRIBUTES = %i[username admin enabled map_enabled quota_in_bytes table_quota account_type private_tables_enabled period_end_date map_view_quota max_layers database_timeout user_timeout upgraded_at map_view_block_price geocoding_quota dashboard_viewed_at sync_tables_enabled database_host geocoding_block_price notification organization_id created_at updated_atid soft_geocoding_limit twitter_datasource_enabled twitter_datasource_block_price twitter_datasource_block_size twitter_datasource_quota soft_twitter_datasource_limit private_maps_enabled google_sign_in max_import_file_size max_import_table_row_count max_concurrent_import_count last_common_data_update_date here_isolines_quota here_isolines_block_price soft_here_isolines_limit obs_snapshot_quota obs_snapshot_block_price soft_obs_snapshot_limit mobile_xamarin mobile_custom_watermark mobile_offline_maps mobile_gis_extension mobile_max_open_users mobile_max_private_users obs_general_quota obs_general_block_price soft_obs_general_limit viewer salesforce_datasource_enabled builder_enabled geocoder_provider isolines_provider routing_provider engine_enabled mapzen_routing_quota mapzen_routing_block_price soft_mapzen_routing_limit no_map_logo org_admin user_render_timeout database_render_timeout frontend_version asset_host state rate_limit_id public_map_quota regular_api_key_quota maintenance_mode private_map_quota public_dataset_quota] # Make sure the following date is after Jan 29, 2015, # which is the date where a message to accept the Terms and # conditions and the Privacy policy was included in the Signup page. # See https://github.com/CartoDB/cartodb-central/commit/3627da19f071c8fdd1604ddc03fb21ab8a6dff9f FULLSTORY_ENABLED_MIN_DATE = Date.new(2017, 1, 1) # +--------+---------+------+ # valid_privacy logic | Public | Private | Link | # +-------------------------+--------+---------+------+ # | private_tables_enabled | T | T | T | # | !private_tables_enabled | T | F | F | # +-------------------------+--------+---------+------+ # def valid_privacy?(privacy) private_tables_enabled || privacy == Carto::UserTable::PRIVACY_PUBLIC end def twitter_configured? # DatasourcesFactory.config_for takes configuration from organization if user is an organization user CartoDB::Datasources::DatasourcesFactory.customized_config?(Search::Twitter::DATASOURCE_NAME, self) end def oauth_signin? google_sign_in || github_user_id.present? end def created_with_http_authentication? Carto::UserCreation.http_authentication.find_by_user_id(id).present? end def database_public_username database_schema == CartoDB::DEFAULT_DB_SCHEMA ? CartoDB::PUBLIC_DB_USER : "cartodb_publicuser_#{id}" end # Gets the list of OAuth accounts the user has (currently only used for synchronization) # @return CartoDB::OAuths def oauths @oauths ||= CartoDB::OAuths.new(self) end def unverified? (active? || locked?) && email_verification_token.present? && email_verification_sent_at.present? && email_verification_sent_at < 1.hour.ago && !oauth_signin? end def remove_logo? has_organization? ? organization.no_map_logo : no_map_logo end def viewable_by?(viewer) id == viewer.id || organization.try(:admin?, viewer) end def editable_by?(user) id == user.id || user.belongs_to_organization?(organization) && (user.organization_owner? || !organization_admin?) end # create the core user_metadata key that is used in redis def key "rails:users:#{username}" end def timeout_key "limits:timeout:#{username}" end def get_auth_tokens tokens = [get_auth_token] if has_organization? tokens << organization.get_auth_token tokens += groups.map(&:get_auth_token) end tokens end def can_change_email? (!google_sign_in || last_password_change_date.present?) && !Carto::Ldap::Manager.new.configuration_present? end def cant_be_deleted_reason if organization_owner? "You can't delete your account because you are admin of an organization" elsif Carto::UserCreation.http_authentication.where(user_id: id).first.present? "You can't delete your account because you are using HTTP Header Authentication" end end def remaining_quota(db_size = db_size_in_bytes) return nil unless db_size quota_in_bytes - db_size end def remaining_table_quota if table_quota.present? remaining = table_quota - table_count remaining.negative? ? 0 : remaining end end def organization_user? organization.present? end alias_method :has_organization?, :organization_user? def organization_owner? organization_user? && organization.owner_id == id end def organization_admin? organization_user? && (organization_owner? || org_admin) end def belongs_to_organization?(org) organization_user? && organization_id == org&.id end def sql_safe_database_schema database_schema.include?('-') ? "\"#{database_schema}\"" : database_schema end def name_or_username name.present? || last_name.present? ? [name, last_name].select(&:present?).join(' ') : username end def mobile_sdk_enabled? private_apps_enabled? || open_apps_enabled? end def private_apps_enabled? mobile_max_private_users.positive? end def open_apps_enabled? mobile_max_open_users.positive? end def builder? !viewer? end def viewer? viewer end def builder_enabled? if has_organization? && builder_enabled.nil? organization.builder_enabled else !!builder_enabled end end def engine_enabled? if has_organization? && engine_enabled.nil? organization.engine_enabled else !!engine_enabled end end def new_visualizations_version builder_enabled? ? 3 : 2 end def relevant_frontend_version frontend_version || CartoDB::Application.frontend_version end def active? state == STATE_ACTIVE end def locked? state == STATE_LOCKED end def maintenance_mode? maintenance_mode == true end def fullstory_enabled? Carto::AccountType::FULLSTORY_SUPPORTED_PLANS.include?(account_type) && created_at > FULLSTORY_ENABLED_MIN_DATE end def get_database_roles api_key_roles = api_keys.reject { |k| k.db_role =~ /^publicuser/ }.map(&:db_role) oauth_app_owner_roles = api_keys.reject { |k| k.effective_ownership_role_name == nil }.map(&:effective_ownership_role_name) (api_key_roles + oauth_app_owner_roles).uniq end def make_token Carto::Common::EncryptionService.make_token end def role_display viewer ? 'viewer' : 'builder' end # TODO: replace .to_hash with .to_h, and monkeypatch Sequel model to respond to :attributes def logging_attrs if self.respond_to?(:attributes) # AR attrs = attributes.symbolize_keys else # Sequel attrs = to_hash end attrs.slice(*LOGGING_ATTRIBUTES) end def update_do_subscription(attributes) return if attributes.nil? license_srv = Carto::DoLicensingService.new(self.username) if attributes[:action] == 'rm' license_srv.remove_from_redis(attributes[:do_dataset][:dataset_id]) elsif attributes[:action] == 'add' license_srv.add_to_redis(attributes[:do_dataset]) else message = 'Error updating a DO subscription: unknown action' log_error(message: message) raise message end end def do_subscription(dataset_id) subscriptions = Carto::DoLicensingService.new(username).subscriptions subscriptions.find { |subscription| subscription['id'] == dataset_id }&.with_indifferent_access end def update_gcloud_settings(attributes) return if attributes.nil? settings = Carto::GCloudUserSettings.new(self) settings.update attributes end def gcloud_settings @gcloud_settings ||= Carto::GCloudUserSettings.new(self).read&.with_indifferent_access end def do_enabled? gcloud_settings[:service_account].present? end def has_access_to_coverband? return true unless Rails.env.production? organization&.name == 'team' end def feature_flags feature_flags_ids = self_feature_flags.pluck(:id) + Carto::FeatureFlag.not_restricted.pluck(:id) feature_flags_ids += organization.inheritable_feature_flags.pluck(:id) if organization Carto::FeatureFlag.where(id: feature_flags_ids) end def feature_flags_names feature_flags.pluck(:name) end def has_feature_flag?(feature_flag_name) feature_flags.exists?(name: feature_flag_name) end def activate_feature_flag!(feature_flag) return if Carto::FeatureFlagsUser.exists?(feature_flag: feature_flag, user_id: id) Carto::FeatureFlagsUser.create!(feature_flag: feature_flag, user_id: id) end def update_feature_flags(feature_flag_ids = nil) return unless feature_flag_ids self_feature_flags_user.where.not(feature_flag_id: feature_flag_ids).destroy_all new_feature_flags_ids = feature_flag_ids - self_feature_flags_user.pluck(:feature_flag_id) new_feature_flags_ids.each do |feature_flag_id| self_feature_flags_user.find_or_create_by(feature_flag_id: feature_flag_id) end end def remaining_twitter_quota if active_record_organization.present? remaining = active_record_organization.remaining_twitter_quota else remaining = twitter_datasource_quota - get_twitter_imports_count end (remaining > 0 ? remaining : 0) end def effective_twitter_total_quota active_record_organization.present? ? active_record_organization.twitter_datasource_quota : twitter_datasource_quota end def effective_twitter_block_price active_record_organization.present? ? active_record_organization.twitter_datasource_block_price : twitter_datasource_block_price end def effective_twitter_datasource_block_size active_record_organization.present? ? active_record_organization.twitter_datasource_block_size : twitter_datasource_block_size end def effective_get_twitter_imports_count active_record_organization.present? ? active_record_organization.get_twitter_imports_count : get_twitter_imports_count end # Should return the number of tweets imported by this user for the specified period of time, as an integer def get_twitter_imports_count(options = {}) date_to = (options[:to] ? options[:to].to_date : Date.today) date_from = (options[:from] ? options[:from].to_date : last_billing_cycle) Carto::SearchTweet.twitter_imports_count(search_tweets, date_from, date_to) end alias get_twitter_datasource_calls get_twitter_imports_count def active_record_organization if organization.present? if organization.kind_of?(ActiveRecord::Base) organization else Carto::Organization.find(organization.id) end end end end