<commit_before>require 'securerandom'

class ApiKeyGrantsValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return record.errors[attribute] = ['grants has to be an array'] unless value && value.is_a?(Array)
    record.errors[attribute] << 'only one apis section is allowed' unless value.count { |v| v[:type] == 'apis' } == 1
    record.errors[attribute] << 'only one database section is allowed' if value.count { |v| v[:type] == 'database' } > 1
  end
end

class Carto::ApiKey < ActiveRecord::Base

  include Carto::AuthTokenGenerator

  TYPE_REGULAR = 'regular'.freeze
  TYPE_MASTER = 'master'.freeze
  TYPE_DEFAULT_PUBLIC = 'default_public'.freeze

  VALID_TYPES = [TYPE_REGULAR, TYPE_MASTER, TYPE_DEFAULT_PUBLIC].freeze

  self.inheritance_column = :_type

  belongs_to :user

  before_create :create_token
  before_create :create_db_config

  serialize :grants, Carto::CartoJsonSymbolizerSerializer
  validates :grants, carto_json_symbolizer: true, api_key_grants: true, json_schema: true

  validates :name, presence: true

  after_create :setup_db_role
  after_save { remove_from_redis(redis_key(token_was)) if token_changed? }
  after_save :add_to_redis
  after_save :update_role_permissions

  after_destroy :drop_db_role
  after_destroy :remove_from_redis

  validates :type, inclusion: { in: VALID_TYPES }

  attr_writer :redis_client

  def api_key_grants
    @api_key_grants ||= ::Carto::ApiKeyGrants.new(db_connection: db_connection,
                                                  db_role: db_role,
                                                  grants_json: grants)
  end

  def create_token
    begin
      self.token = generate_auth_token
    end while self.class.exists?(token: token)
  end

  private

  PASSWORD_LENGTH = 40

  REDIS_KEY_PREFIX = 'api_keys:'.freeze

  def create_db_config
    begin
      self.db_role = Carto::DB::Sanitize.sanitize_identifier("#{user.username}_role_#{SecureRandom.hex}")
    end while self.class.exists?(db_role: db_role)
    self.db_password = SecureRandom.hex(PASSWORD_LENGTH / 2) unless db_password
  end

  def setup_db_role
    db_run(
      "create role \"#{db_role}\" NOSUPERUSER NOCREATEDB NOINHERIT LOGIN ENCRYPTED PASSWORD '#{db_password}'"
    )
  end

  def drop_db_role
    revoke_privileges(*affected_schemas(api_key_grants))
    db_run("drop role \"#{db_role}\"")
  end

  def update_role_permissions
    if grants_was.present?
      api_key_grants = Carto::ApiKeyGrants.new(db_connection: db_connection, db_role: db_role, grants_json: grants_was)
      revoke_privileges(*affected_schemas(api_key_grants, from_db: true))
    end
    _, write_schemas = affected_schemas(api_key_grants)

    api_key_grants.table_permissions.each do |tp|
      unless tp.permissions.empty?
        db_run(
          "grant #{tp.permissions.join(', ')} on table \"#{tp.schema}\".\"#{tp.name}\" to \"#{db_role}\""
        )
      end
    end

    write_schemas.each { |s| grant_aux_write_privileges_for_schema(s) }

    if !write_schemas.empty?
      grant_usage_for_cartodb
    end
  end

  def affected_schemas(api_key_grants, from_db: false)
    read_schemas = []
    write_schemas = []
    api_key_grants.table_permissions(from_db: from_db).each do |tp|
      read_schemas << tp.schema
      write_schemas << tp.schema unless !tp.write?
    end
    [read_schemas.uniq, write_schemas.uniq]
  end<commit_msg>Fix api_key_grants local variable creation<commit_after>require 'securerandom'

class ApiKeyGrantsValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return record.errors[attribute] = ['grants has to be an array'] unless value && value.is_a?(Array)
    record.errors[attribute] << 'only one apis section is allowed' unless value.count { |v| v[:type] == 'apis' } == 1
    record.errors[attribute] << 'only one database section is allowed' if value.count { |v| v[:type] == 'database' } > 1
  end
end

class Carto::ApiKey < ActiveRecord::Base

  include Carto::AuthTokenGenerator

  TYPE_REGULAR = 'regular'.freeze
  TYPE_MASTER = 'master'.freeze
  TYPE_DEFAULT_PUBLIC = 'default_public'.freeze

  VALID_TYPES = [TYPE_REGULAR, TYPE_MASTER, TYPE_DEFAULT_PUBLIC].freeze

  self.inheritance_column = :_type

  belongs_to :user

  before_create :create_token
  before_create :create_db_config

  serialize :grants, Carto::CartoJsonSymbolizerSerializer
  validates :grants, carto_json_symbolizer: true, api_key_grants: true, json_schema: true

  validates :name, presence: true

  after_create :setup_db_role
  after_save { remove_from_redis(redis_key(token_was)) if token_changed? }
  after_save :add_to_redis
  after_save :update_role_permissions

  after_destroy :drop_db_role
  after_destroy :remove_from_redis

  validates :type, inclusion: { in: VALID_TYPES }

  attr_writer :redis_client

  def api_key_grants
    @api_key_grants ||= ::Carto::ApiKeyGrants.new(db_connection: db_connection,
                                                  db_role: db_role,
                                                  grants_json: grants)
  end

  def create_token
    begin
      self.token = generate_auth_token
    end while self.class.exists?(token: token)
  end

  private

  PASSWORD_LENGTH = 40

  REDIS_KEY_PREFIX = 'api_keys:'.freeze

  def create_db_config
    begin
      self.db_role = Carto::DB::Sanitize.sanitize_identifier("#{user.username}_role_#{SecureRandom.hex}")
    end while self.class.exists?(db_role: db_role)
    self.db_password = SecureRandom.hex(PASSWORD_LENGTH / 2) unless db_password
  end

  def setup_db_role
    db_run(
      "create role \"#{db_role}\" NOSUPERUSER NOCREATEDB NOINHERIT LOGIN ENCRYPTED PASSWORD '#{<PASSWORD>}'"
    )
  end

  def drop_db_role
    revoke_privileges(*affected_schemas(api_key_grants))
    db_run("drop role \"#{db_role}\"")
  end

  def update_role_permissions
    if grants_was.present?
      revoke_privileges(*affected_schemas(
        Carto::ApiKeyGrants.new(db_connection: db_connection, db_role: db_role, grants_json: grants_was),
        from_db: true)
      )
    end
    _, write_schemas = affected_schemas(api_key_grants)

    api_key_grants.table_permissions.each do |tp|
      unless tp.permissions.empty?
        db_run(
          "grant #{tp.permissions.join(', ')} on table \"#{tp.schema}\".\"#{tp.name}\" to \"#{db_role}\""
        )
      end
    end

    write_schemas.each { |s| grant_aux_write_privileges_for_schema(s) }

    if !write_schemas.empty?
      grant_usage_for_cartodb
    end
  end

  def affected_schemas(api_key_grants, from_db: false)
    read_schemas = []
    write_schemas = []
    api_key_grants.table_permissions(from_db: from_db).each do |tp|
      read_schemas << tp.schema
      write_schemas << tp.schema unless !tp.write?
    end
    [read_schemas.uniq, write_schemas.uniq]
  end
{'fraction_non_alphanumeric': 0.09812261660310942, 'fraction_numerical': 0.0014667057788207685, 'mean_word_length': 3.6641586867305063, 'pattern_counts': {'":': 0, '<': 24, '<?xml version=': 0, '>': 6, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3486570', 'n_tokens_mistral': 2366, 'n_tokens_neox': 2285, 'n_words': 458}