module Metasploit::Credential::EntityRelationshipDiagram

@todo Extract (along with MetasploitDataModel::EntityRelationshipDiagram), common ERD code and move to metasploit-documentation or metasploit-entity_relationship_diagram

Constants

ATTRIBUTES

Enable all attributes

DEFAULT_OPTIONS

Default options for Diagram.

INDIRECT

Only show direct relationships since the ERD is for use with SQL and there is no need to show has_many :through for those purposes.

INHERITANCE

Show inheritance for Single-Table Inheritance

NOTATION

Use crowsfoot notation since its what we use for manually drawn diagrams.

Public Class Methods

cluster(*classes) click to toggle source

Cluster of classes that are reachable through belongs_to from ‘classes`.

@param classes [Array<Class<ApplicationRecord>>] classes that must be in cluster. All other classes in the

returned cluster will be classes to which `classes` belong directly or indirectly.

@return [Set<Class<ApplicationRecord>>]

# File lib/metasploit/credential/entity_relationship_diagram.rb, line 61
def self.cluster(*classes)
  class_queue = classes.dup
  visited_class_set = Set.new

  until class_queue.empty?
    klass = class_queue.pop
    # add immediately to visited set in case there are recursive associations
    visited_class_set.add klass

    # only iterate belongs_to as they need to be included so that foreign keys aren't let dangling in the ERD.
    reflections = klass.reflect_on_all_associations(:belongs_to)

    reflections.each do |reflection|
      if reflection.options[:polymorphic]
        target_klasses = polymorphic_classes(reflection)
      else
        target_klasses = [reflection.klass]
      end

      target_klasses.each do |target_klass|
        unless visited_class_set.include? target_klass
          class_queue << target_klass
        end
      end
    end
  end

  visited_class_set
end
cluster_by_class() click to toggle source

All {cluster clusters} of classes that are reachable through belongs_to from each ApplicationRecord descendant

@return [Hash{Class<ApplicationRecord> => Set<Class<ApplicationRecord>>}] Maps entry point to cluster to its

cluster.
# File lib/metasploit/credential/entity_relationship_diagram.rb, line 43
def self.cluster_by_class
  cluster_by_class = {}

  Metasploit::Credential::Engine.instance.eager_load!

  ApplicationRecord.descendants.each do |klass|
    klass_cluster = cluster(klass)
    cluster_by_class[klass] = klass_cluster
  end

  cluster_by_class
end
create(options={}) click to toggle source

Creates Graphviz diagram.

@param options [Hash{Symbol => Object}] @option options [RailsERD::Domain] :domain ({domain}) The domain to diagram. @option options [String] :filename name of file (without extension) to which to write diagram. @option options [String] :title Title of the diagram to include on the diagram. @return [String] path where diagram was written.

# File lib/metasploit/credential/entity_relationship_diagram.rb, line 98
def self.create(options={})
  domain = options[:domain]
  domain ||= self.domain

  diagram_options = options.except(:domain)
  merged_diagram_options = DEFAULT_OPTIONS.merge(diagram_options)

  require 'rails_erd/domain'
  diagram = RailsERD::Diagram::Graphviz.new(domain, merged_diagram_options)
  path = diagram.create

  path
end
domain() click to toggle source

Domain containing all models in this gem.

@return [RailsERD::Domain]

# File lib/metasploit/credential/entity_relationship_diagram.rb, line 116
def self.domain
  require_models

  require 'rails_erd/domain'
  RailsERD::Domain.generate
end
maximal_clusters() click to toggle source

Set of largest clusters from {cluster_by_class}.

@return [Array<Set<Class<ApplicationRecord>>>]

# File lib/metasploit/credential/entity_relationship_diagram.rb, line 126
def self.maximal_clusters
  clusters = cluster_by_class.values
  unique_clusters = clusters.uniq

  maximal_clusters = unique_clusters.dup
  cluster_queue = unique_clusters.dup

  until cluster_queue.empty?
    cluster = cluster_queue.pop

    proper_subset = false

    maximal_clusters.each do |maximal_cluster|
      if cluster.proper_subset? maximal_cluster
        proper_subset = true
        break
      end
    end

    if proper_subset
      maximal_clusters.delete(cluster)
    end
  end

  maximal_clusters
end
polymorphic_classes(belongs_to_reflection) click to toggle source

Calculates the target classes for a polymorphic ‘belongs_to`.

@return [Array<ApplicationRecord>]

# File lib/metasploit/credential/entity_relationship_diagram.rb, line 156
def self.polymorphic_classes(belongs_to_reflection)
  name = belongs_to_reflection.name

  ApplicationRecord.descendants.each_with_object([]) { |descendant, target_classes|
    has_many_reflections = descendant.reflect_on_all_associations(:has_many)

    has_many_reflections.each do |has_many_reflection|
      as = has_many_reflection.options[:as]

      if as == name
        target_classes << descendant
      end
    end
  }
end