Module: Metasploit::Credential::EntityRelationshipDiagram

Defined in:
lib/metasploit/credential/entity_relationship_diagram.rb

Constant Summary collapse

ATTRIBUTES =

Enable all attributes

[
    :content,
    :foreign_keys,
    :primary_keys,
    :timestamps
]
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.

false
INHERITANCE =

Show inheritance for Single-Table Inheritance

true
NOTATION =

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

:crowsfoot
DEFAULT_OPTIONS =

Default options for Diagram.

{
    attributes: ATTRIBUTES,
    indirect: INDIRECT,
    inheritance: INHERITANCE,
    notation: NOTATION
}

Class Method Summary collapse

Class Method Details

.cluster(*classes) ⇒ Set<Class<ActiveRecord::Base>>

Cluster of classes that are reachable through belongs_to from classes.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# 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_classHash{Class<ActiveRecord::Base> => Set<Class<ActiveRecord::Base>>}

All clusters of classes that are reachable through belongs_to from each ActiveRecord::Base descendant



43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/metasploit/credential/entity_relationship_diagram.rb', line 43

def self.cluster_by_class
  cluster_by_class = {}

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

  ActiveRecord::Base.descendants.each do |klass|
    klass_cluster = cluster(klass)
    cluster_by_class[klass] = klass_cluster
  end

  cluster_by_class
end

.create(options = {}) ⇒ String

Creates Graphviz diagram.

Options Hash (options):

  • :domain (RailsERD::Domain) — default: {domain}

    The domain to diagram.

  • :filename (String)

    name of file (without extension) to which to write diagram.

  • :title (String)

    Title of the diagram to include on the diagram.



98
99
100
101
102
103
104
105
106
107
108
109
110
# 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

.domainRailsERD::Domain

Domain containing all models in this gem.



116
117
118
119
120
121
# File 'lib/metasploit/credential/entity_relationship_diagram.rb', line 116

def self.domain
  require_models

  require 'rails_erd/domain'
  RailsERD::Domain.generate
end

.maximal_clustersArray<Set<Class<ActiveRecord::Base>>>

Set of largest clusters from cluster_by_class.



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# 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) ⇒ Array<ActiveRecord::Base>

Calculates the target classes for a polymorphic belongs_to.



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/metasploit/credential/entity_relationship_diagram.rb', line 156

def self.polymorphic_classes(belongs_to_reflection)
  name = belongs_to_reflection.name

  ActiveRecord::Base.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