CartoDB/cartodb20

View on GitHub
app/models/carto/dbdirect_ip.rb

Summary

Maintainability
A
35 mins
Test Coverage
require 'ip_checker'
require 'carto/dbdirect/metadata_manager'

module Carto
  class DbdirectIp < ActiveRecord::Base
    belongs_to :user, inverse_of: :dbdirect_ip, foreign_key: :user_id

    # Note about the `ips` attribute:
    # The corresponding column is of type json, so there's no need for an explicit serializer
    # But there's a subtle difference here from having a text field and defining `serialize :ips JSON`
    # If a string is assigned to `ips` it is interpreted and parsed and JSON, and the result is nil
    # if not valid JSON. With the serializer version, the string would be preserved as string.

    validate :validate_ips

    after_save do
      persist_ips(user.username, ips)
    end

    after_destroy do
      persist_ips(user.username, ips)
    end

    def self.metadata_manager
      metadata_manager_class.new(config, $users_metadata)
    end

    def self.metadata_manager_class
      Carto::Dbdirect::MetadataManager
    end

    def self.config
      Cartodb.get_config(:dbdirect, 'metadata_persist') || {}
    end

    def self.metadata_persist_enabled?
      !!config['enabled']
    end

    private

    MAX_IP_MASK_HOST_BITS = 8

    def validate_ips
      # Check type
      unless ips.kind_of?(Array) && ips.all? { |ip| ip.kind_of?(String) }
        errors.add(:ips, "IPs must be an array of strings ")
        return false
      end
      ok = true
      if ips.present?
        # Validate each IP
        ips.each do |ip|
          error = validate_ip(ip)
          if error.present?
            ok = false
            errors.add(:ips, error)
          end
        end
      end
      ok
    end

    def validate_ip(ip)
      IpChecker.validate(
        ip,
        max_host_bits: MAX_IP_MASK_HOST_BITS,
        exclude_0: false,
        exclude_private: true,
        exclude_local: true,
        exclude_loopback: true
      )
    end

    def normalize_ip(ip)
      IpChecker.normalize(ip)
    end

    def persist_ips(username, ips)
      return unless self.class.metadata_persist_enabled?

      ips = ips.map { |ip| normalize_ip(ip) }
      self.class.metadata_manager.save(username, ips)
    end
  end
end