diff --git a/.yardopts b/.yardopts index 5d51dac2..2f9018fa 100644 --- a/.yardopts +++ b/.yardopts @@ -1,4 +1,4 @@ --markup markdown +--plugin yard-metasploit-erd --protected {app,lib}/**/*.rb -db/migrate/*.rb \ No newline at end of file diff --git a/Gemfile b/Gemfile index 0bc306a3..69c35498 100755 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,11 @@ source "http://rubygems.org" # Specify your gem's dependencies in metasploit_data_models.gemspec gemspec +group :development do + # embed ERDs on index, namespace Module and Class pages + gem 'yard-metasploit-erd', '~> 0.0.2' +end + # used by dummy application group :development, :test do # supplies factories for producing model instance for specs @@ -12,7 +17,7 @@ group :development, :test do gem 'factory_girl_rails' # rails is only used for the dummy application in spec/dummy # restrict from rails 4.0 as it requires protected_attributes gem and other changes for compatibility - # @see https://www.pivotaltracker.com/story/show/52309083 + # @see MSP-2971 gem 'rails', '>= 3.2', '< 4.0.0' # Used to create fake data gem "faker" diff --git a/app/models/mdm/client.rb b/app/models/mdm/client.rb index 530d68f6..4e61d3b8 100755 --- a/app/models/mdm/client.rb +++ b/app/models/mdm/client.rb @@ -2,7 +2,9 @@ class Mdm::Client < ActiveRecord::Base # # Relations # - belongs_to :host, :class_name => 'Mdm::Host' + belongs_to :host, + class_name: 'Mdm::Host', + inverse_of: :clients ActiveSupport.run_load_hooks(:mdm_client, self) end diff --git a/app/models/mdm/cred.rb b/app/models/mdm/cred.rb index 2bcd9c03..01b74e3d 100755 --- a/app/models/mdm/cred.rb +++ b/app/models/mdm/cred.rb @@ -19,13 +19,18 @@ class Mdm::Cred < ActiveRecord::Base # The service this cred is for # # @return [Mdm::Service] - belongs_to :service, :class_name => "Mdm::Service" + belongs_to :service, + class_name: 'Mdm::Service', + inverse_of: :creds # @!attribute [rw] task_creds # Details about what Tasks touched this cred # # @return [Array] - has_many :task_creds, :dependent => :destroy, :class_name => "Mdm::TaskCred" + has_many :task_creds, + class_name: 'Mdm::TaskCred', + dependent: :destroy, + inverse_of: :cred # @!attribute [rw] tasks # Tasks that touched this service diff --git a/app/models/mdm/event.rb b/app/models/mdm/event.rb index e4952723..68354b08 100755 --- a/app/models/mdm/event.rb +++ b/app/models/mdm/event.rb @@ -3,8 +3,13 @@ class Mdm::Event < ActiveRecord::Base # Relations # - belongs_to :host, :class_name => 'Mdm::Host' - belongs_to :workspace, :class_name => 'Mdm::Workspace' + belongs_to :host, + class_name: 'Mdm::Host', + inverse_of: :events + + belongs_to :workspace, + class_name: 'Mdm::Workspace', + inverse_of: :events # # Scopes diff --git a/app/models/mdm/exploit_attempt.rb b/app/models/mdm/exploit_attempt.rb index edd42d6d..03325fde 100755 --- a/app/models/mdm/exploit_attempt.rb +++ b/app/models/mdm/exploit_attempt.rb @@ -1,8 +1,49 @@ class Mdm::ExploitAttempt < ActiveRecord::Base # - # Relations + # Associations # - belongs_to :host, :class_name => 'Mdm::Host', :counter_cache => :exploit_attempt_count + + # @!attribute host + # Host that was attempted to be exploited. + # + # @return [Mdm::Host] + belongs_to :host, + class_name: 'Mdm::Host', + counter_cache: :exploit_attempt_count, + inverse_of: :exploit_attempts + + # @!attribute loot + # Loot gathers from the successful exploit. + # + # @return [Mdm::Loot, nil] + belongs_to :loot, + class_name: 'Mdm::Loot', + inverse_of: :exploit_attempt + + # @!attribute service + # The service being exploited on {#host}. + # + # @return [Mdm::Service, nil] + belongs_to :service, + class_name: 'Mdm::Service', + inverse_of: :exploit_attempts + + # @!attribute session + # The session that was established when this attempt was successful. + # + # @return [Mdm::Session] + # @return [nil] if session was not established. + belongs_to :session, + class_name: 'Mdm::Session', + inverse_of: :exploit_attempt + + # @!attribute vuln + # The vulnerability that was attempted to be exploited. + # + # @return [Mdm::Vuln, nil] + belongs_to :vuln, + class_name: 'Mdm::Vuln', + inverse_of: :exploit_attempts # # Validations diff --git a/app/models/mdm/exploited_host.rb b/app/models/mdm/exploited_host.rb index b0432357..ae71e5da 100755 --- a/app/models/mdm/exploited_host.rb +++ b/app/models/mdm/exploited_host.rb @@ -3,8 +3,13 @@ class Mdm::ExploitedHost < ActiveRecord::Base # Relations # - belongs_to :host, :class_name => 'Mdm::Host' - belongs_to :service, :class_name => 'Mdm::Service' + belongs_to :host, + class_name: 'Mdm::Host', + inverse_of: :exploited_hosts + + belongs_to :service, + class_name: 'Mdm::Service', + inverse_of: :exploited_hosts ActiveSupport.run_load_hooks(:mdm_exploited_host, self) end diff --git a/app/models/mdm/host.rb b/app/models/mdm/host.rb index 6b9b2532..a7939b1e 100755 --- a/app/models/mdm/host.rb +++ b/app/models/mdm/host.rb @@ -57,83 +57,122 @@ class Mdm::Host < ActiveRecord::Base # Users connected to this host # # @return [Array] - has_many :clients, class_name: 'Mdm::Client', dependent: :destroy + has_many :clients, + class_name: 'Mdm::Client', + dependent: :destroy, + inverse_of: :host + + # @!attribute events + # Events that occurred on this host. + # + # @return [ActiveRecord::Relation] + has_many :events, + class_name: 'Mdm::Event', + dependent: :destroy, + inverse_of: :host # @!attribute [rw] task_hosts # Details about what Tasks touched this host # # @return [Array] - has_many :task_hosts, :dependent => :destroy, :class_name => 'Mdm::TaskHost' - - # @!attribute [rw] tasks - # Tasks that touched this service - # - # @return [Array] - has_many :tasks, :through => :task_hosts, :class_name => 'Mdm::Task' + has_many :task_hosts, + class_name: 'Mdm::TaskHost', + dependent: :destroy, + inverse_of: :host # @!attribute [rw] exploit_attempts # Attempts to run exploits against this host. # # @return [Array 'Mdm::ExploitAttempt', - :dependent => :destroy - - # @!attribute [rw] exploited_hosts - # @todo https://www.pivotaltracker.com/story/show/48993731 - # @return [Array] - has_many :exploited_hosts, :class_name => 'Mdm::ExploitedHost', :dependent => :destroy + class_name: 'Mdm::ExploitAttempt', + dependent: :destroy, + inverse_of: :host + + # @!attribute exploited_hosts + # @todo MSP-2732 + # @return [ActiveRecord::Relation] + has_many :exploited_hosts, + class_name: 'Mdm::ExploitedHost', + dependent: :destroy, + inverse_of: :host # @!attribute [rw] host_details # @return [Array] - has_many :host_details, :class_name => 'Mdm::HostDetail', :dependent => :destroy + has_many :host_details, + class_name: 'Mdm::HostDetail', + dependent: :destroy, + inverse_of: :host - # @!attribute [rw] hosts_tags + # @!attribute hosts_tags # A join model between {Mdm::Tag} and {Mdm::Host}. Use {#tags} to get the actual {Mdm::Tag Mdm::Tags} on this host. - # {#hosts_tags} are cleaned up in a before_destroy: {#cleanup_tags}. # - # @todo https://www.pivotaltracker.com/story/show/48923201 - # @return [Array] - has_many :hosts_tags, :class_name => 'Mdm::HostTag' + # @todo MSP-2723 + # @return [ActiveRecord::Relation] + has_many :hosts_tags, + class_name: 'Mdm::HostTag', + dependent: :destroy, + inverse_of: :host - # @!attribute [rw] loots + # @!attribute loots # Loot gathered from the host with {Mdm::Loot#created_at newest loot} first. # - # @todo https://www.pivotaltracker.com/story/show/48991525 - # @return [Array] - has_many :loots, :class_name => 'Mdm::Loot', :dependent => :destroy, :order => 'loots.created_at DESC' + # @todo MSP-3065 + # @return [ActiveRecord::Relation] + has_many :loots, + class_name: 'Mdm::Loot', + dependent: :destroy, + inverse_of: :host, + order: 'loots.created_at DESC' # @!attribute [rw] notes # Notes about the host entered by a user with {Mdm::Note#created_at oldest notes} first. # # @return [Array] - has_many :notes, :class_name => 'Mdm::Note', :dependent => :delete_all, :order => 'notes.created_at' + has_many :notes, + class_name: 'Mdm::Note', + inverse_of: :host, + dependent: :delete_all, + order: 'notes.created_at' # @!attribute [rw] services # The services running on {Mdm::Service#port ports} on the host with services ordered by {Mdm::Service#port port} # and {Mdm::Service#proto protocol}. # # @return [Array] - has_many :services, :class_name => 'Mdm::Service', :dependent => :destroy, :order => 'services.port, services.proto' + has_many :services, + class_name: 'Mdm::Service', + dependent: :destroy, + inverse_of: :host, + order: 'services.port, services.proto' # @!attribute [rw] sessions # Sessions that are open or previously were open on the host ordered by {Mdm::Session#opened_at when the session was # opened} # # @return [Array 'Mdm::Session', :dependent => :destroy, :order => 'sessions.opened_at' + has_many :sessions, + class_name: 'Mdm::Session', + dependent: :destroy, + inverse_of: :host, + order: 'sessions.opened_at' # @!attribute [rw] vulns # Vulnerabilities found on the host. # # @return [Array] - has_many :vulns, :class_name => 'Mdm::Vuln', :dependent => :delete_all + has_many :vulns, + class_name: 'Mdm::Vuln', + dependent: :delete_all, + inverse_of: :host # @!attribute [rw] workspace # The workspace in which this host was found. # # @return [Mdm::Workspace] - belongs_to :workspace, :class_name => 'Mdm::Workspace' + belongs_to :workspace, + class_name: 'Mdm::Workspace', + inverse_of: :hosts # # Through host_tags @@ -162,7 +201,10 @@ class Mdm::Host < ActiveRecord::Base # # @return [Array] # @see #services - has_many :service_notes, :class_name => 'Mdm::Note', :through => :services + has_many :service_notes, + class_name: 'Mdm::Note', + source: :notes, + through: :services # @!attribute [r] web_sites # {Mdm::WebSite Web sites} running on top of {#services} on this host. @@ -171,6 +213,18 @@ class Mdm::Host < ActiveRecord::Base # @see services has_many :web_sites, :class_name => 'Mdm::WebSite', :through => :services + # + # through: :task_hosts + # + + # @!attribute tasks + # Tasks that touched this service + # + # @return [ActiveRecord::Relation] + has_many :tasks, + class_name: 'Mdm::Task', + through: :task_hosts + # # Through vulns # @@ -351,12 +405,6 @@ class Mdm::Host < ActiveRecord::Base # # @return [Integer] - # - # Callbacks - # - - before_destroy :cleanup_tags - # # Nested Attributes # @note Must be declared after relations being referenced. @@ -423,18 +471,6 @@ def attribute_locked?(attr) n && n.data[:locked] end - # Destroys any {Mdm::Tag Mdm::Tags} that will have no {Mdm::Tag#hosts} left after this host is deleted. - # - # @return [void] - def cleanup_tags - # No need to keep tags with no hosts - tags.each do |tag| - tag.destroy if tag.hosts == [self] - end - # Clean up association table records - Mdm::HostTag.delete_all("host_id = #{self.id}") - end - # This is replicated by the IpAddressValidator class. Had to put it here as well to avoid # SQL errors when checking address uniqueness. # diff --git a/app/models/mdm/host_detail.rb b/app/models/mdm/host_detail.rb index f99a6b32..ce4e2336 100755 --- a/app/models/mdm/host_detail.rb +++ b/app/models/mdm/host_detail.rb @@ -3,7 +3,10 @@ class Mdm::HostDetail < ActiveRecord::Base # Relations # - belongs_to :host, :class_name => 'Mdm::Host', :counter_cache => :host_detail_count + belongs_to :host, + class_name: 'Mdm::Host', + counter_cache: :host_detail_count, + inverse_of: :host_details # # Validations diff --git a/app/models/mdm/host_tag.rb b/app/models/mdm/host_tag.rb index 9301885d..e02229ea 100755 --- a/app/models/mdm/host_tag.rb +++ b/app/models/mdm/host_tag.rb @@ -2,11 +2,49 @@ class Mdm::HostTag < ActiveRecord::Base self.table_name = "hosts_tags" # - # Relations + # Associations # - belongs_to :host, :class_name => 'Mdm::Host' - belongs_to :tag, :class_name => 'Mdm::Tag' + # @!attribute host + # Host with {#tag}. + # + # @todo MSP-2723 + # @return [Mdm::Host] + belongs_to :host, + class_name: 'Mdm::Host', + inverse_of: :hosts_tags + + # @!attribute tag + # Tag on {#host}. + # + # @todo MSP-2723 + # @return [Mdm::Tag] + belongs_to :tag, + class_name: 'Mdm::Tag', + inverse_of: :hosts_tags + + # + # Callbacks + # + + # @see http://stackoverflow.com/a/11694704 + after_destroy :destroy_orphan_tag + + # + # Instance Methods + # + + private + + # Destroys {#tag} if it is orphaned + # + # @see http://stackoverflow.com/a/11694704 + # @return [void] + def destroy_orphan_tag + tag.destroy_if_orphaned + end + + public ActiveSupport.run_load_hooks(:mdm_host_tag, self) end diff --git a/app/models/mdm/listener.rb b/app/models/mdm/listener.rb index 9960213a..5d02e4c3 100755 --- a/app/models/mdm/listener.rb +++ b/app/models/mdm/listener.rb @@ -3,8 +3,13 @@ class Mdm::Listener < ActiveRecord::Base # Relations # - belongs_to :task, :class_name => 'Mdm::Task' - belongs_to :workspace, :class_name => 'Mdm::Workspace' + belongs_to :task, + class_name: 'Mdm::Task', + inverse_of: :listeners + + belongs_to :workspace, + class_name: 'Mdm::Workspace', + inverse_of: :listeners # # Serializations diff --git a/app/models/mdm/loot.rb b/app/models/mdm/loot.rb index 33a4d89f..ccc48e79 100755 --- a/app/models/mdm/loot.rb +++ b/app/models/mdm/loot.rb @@ -16,23 +16,45 @@ class Mdm::Loot < ActiveRecord::Base # Associations # + # @!attribute exploit_attempt + # Exploit attempt where this loot was gathered. + # + # @return [Mdm::ExploitAttempt] + has_one :exploit_attempt, + class_name: 'Mdm::ExploitAttempt', + inverse_of: :loot + # @!attribute [rw] host # The host from which the loot was gathered. # # @return [Mdm::Host] - belongs_to :host, :class_name => 'Mdm::Host' + belongs_to :host, + class_name: 'Mdm::Host', + inverse_of: :loots # @!attribute [rw] service # The service running on the {#host} from which the loot was gathered. # # @return [Mdm::Service] - belongs_to :service, :class_name => 'Mdm::Service' + belongs_to :service, + class_name: 'Mdm::Service', + inverse_of: :loots + + # @!attribute vuln_attempt + # Vuln attempt that gathered this loot. + # + # @return [Mdm::VulnAttempt] + has_one :vuln_attempt, + class_name: 'Mdm::VulnAttempt', + inverse_of: :loot # @!attribute [rw] workspace # The workspace in which the loot is stored and the {#host} exists. # # @return [Mdm::Workspace] - belongs_to :workspace, :class_name => 'Mdm::Workspace' + belongs_to :workspace, + class_name: 'Mdm::Workspace', + inverse_of: :loots # # Attributes diff --git a/app/models/mdm/nexpose_console.rb b/app/models/mdm/nexpose_console.rb index 529f585a..46b160e2 100755 --- a/app/models/mdm/nexpose_console.rb +++ b/app/models/mdm/nexpose_console.rb @@ -1,4 +1,17 @@ class Mdm::NexposeConsole < ActiveRecord::Base + # + # Associations + # + + # @!attribute vuln_details + # Details for vulnerabilities supplied by this Nexpose console. + # + # @return [ActiveRecord::Relation] + has_many :vuln_details, + class_name: 'Mdm::VulnDetail', + foreign_key: :nx_console_id, + inverse_of: :nexpose_console + # # Serializations # diff --git a/app/models/mdm/note.rb b/app/models/mdm/note.rb index 0a4aa8a2..a23f85a6 100755 --- a/app/models/mdm/note.rb +++ b/app/models/mdm/note.rb @@ -9,20 +9,27 @@ class Mdm::Note < ActiveRecord::Base # # @return [Mdm::Host] if note is attached to an {Mdm::Host}. # @return [nil] if note is attached to an {Mdm::Service}. - belongs_to :host, :class_name => 'Mdm::Host', :counter_cache => :note_count + belongs_to :host, + class_name: 'Mdm::Host', + counter_cache: :note_count, + inverse_of: :notes # @!attribute [rw] service # The service to which this note is attached. # # @return [Mdm::Service] if note is attached to an {Mdm::Service}. # @return [nil] if not is attached to an {Mdm::Host}. - belongs_to :service, :class_name => 'Mdm::Service' + belongs_to :service, + class_name: 'Mdm::Service', + inverse_of: :notes # @!attribute [rw] workspace # The workspace in which the {#host} or {#service} exists. # # @return [Mdm::Workspace] - belongs_to :workspace, :class_name => 'Mdm::Workspace' + belongs_to :workspace, + class_name: 'Mdm::Workspace', + inverse_of: :notes # # Attributes diff --git a/app/models/mdm/ref.rb b/app/models/mdm/ref.rb index 289e34b5..000f10f2 100755 --- a/app/models/mdm/ref.rb +++ b/app/models/mdm/ref.rb @@ -18,9 +18,11 @@ class Mdm::Ref < ActiveRecord::Base # @!attribute [rw] vulns_refs # Join model to {Mdm::Vuln Mdm::Vulns}. Use {#vulns} to get the actual {Mdm::Vuln Mdm::Vulns}. # - # @todo https://www.pivotaltracker.com/story/show/48915453 + # @todo MSP-3066 # @return [Array] - has_many :vulns_refs, :class_name => 'Mdm::VulnRef' + has_many :vulns_refs, + :class_name => 'Mdm::VulnRef', + inverse_of: :ref # # Through :vuln_refs diff --git a/app/models/mdm/route.rb b/app/models/mdm/route.rb index 4d13ef2f..22beabb9 100755 --- a/app/models/mdm/route.rb +++ b/app/models/mdm/route.rb @@ -3,7 +3,13 @@ class Mdm::Route < ActiveRecord::Base # Relations # - belongs_to :session, :class_name => 'Mdm::Session' + # @!attribute [rw] session + # The session over which this route traverses. + # + # @return [Mdm::Session] + belongs_to :session, + class_name: 'Mdm::Session', + inverse_of: :routes ActiveSupport.run_load_hooks(:mdm_route, self) end diff --git a/app/models/mdm/service.rb b/app/models/mdm/service.rb index 0a748d7e..6bccb671 100755 --- a/app/models/mdm/service.rb +++ b/app/models/mdm/service.rb @@ -11,52 +11,95 @@ class Mdm::Service < ActiveRecord::Base # Associations # - # @!attribute [rw] task_services - # Details about what Tasks touched this service - # - # @return [Array] - has_many :task_services, :dependent => :destroy, :class_name => 'Mdm::TaskService' - - # @!attribute [rw] tasks - # Tasks that touched this service + # @!attribute creds + # Credentials gathered from this service. # - # @return [Array] - has_many :tasks, :through => :task_services, :class_name => 'Mdm::Task' + # @return [ActiveRecord::Relation] + has_many :creds, + class_name: 'Mdm::Cred', + dependent: :destroy, + inverse_of: :service - # @!attribute [rw] creds - # Credentials gathered from this service. + # @!attribute exploit_attempts + # Exploit attempts against this service. # - # @return [Array] - has_many :creds, :dependent => :destroy, :class_name => 'Mdm::Cred' + # @return [ActiveRecord::Relation] + has_many :exploit_attempts, + class_name: 'Mdm::ExploitAttempt', + dependent: :destroy, + inverse_of: :service - # @!attribute [rw] exploited_hosts - # @todo https://www.pivotaltracker.com/story/show/48993731 + # @!attribute exploited_hosts + # @todo MSP-2732 # @return [Array] - has_many :exploited_hosts, :dependent => :destroy, :class_name => 'Mdm::ExploitedHost' + has_many :exploited_hosts, + class_name: 'Mdm::ExploitedHost', + dependent: :destroy, + inverse_of: :service - # @!attribute [rw] host + # @!attribute host # The host on which this service runs. # # @return [Mdm::Host] - belongs_to :host, :class_name => 'Mdm::Host', :counter_cache => :service_count + belongs_to :host, + class_name: 'Mdm::Host', + counter_cache: :service_count, + inverse_of: :services - # @!attribute [rw] notes + # @!attribute loots + # Loot gathers from this service. + # + # @return [ActiveRecord::Relation] + has_many :loots, + class_name: 'Mdm::Loot', + dependent: :destroy, + inverse_of: :service + + # @!attribute notes # Notes about this service. # - # @return [Array] - has_many :notes, :dependent => :destroy, :class_name => 'Mdm::Note' + # @return [ActiveRecord::Relation] + has_many :notes, + class_name: 'Mdm::Note', + dependent: :destroy, + inverse_of: :service - # @!attribute [rw] vulns + # @!attribute [rw] task_services + # Details about what Tasks touched this service + # + # @return [Array] + has_many :task_services, + class_name: 'Mdm::TaskService', + dependent: :destroy, + inverse_of: :service + + # @!attribute vulns # Vulnerabilities found in this service. # - # @return [Array] - has_many :vulns, :dependent => :destroy, :class_name => 'Mdm::Vuln' + # @return [ActiveRecord::Relation] + has_many :vulns, + class_name: 'Mdm::Vuln', + dependent: :destroy, + inverse_of: :service - # @!attribute [rw] web_sites + # @!attribute web_sites # Web sites running on top of this service. # - # @return [Array] - has_many :web_sites, :dependent => :destroy, :class_name => 'Mdm::WebSite' + # @return [ActiveRecord::Relation] + has_many :web_sites, + class_name: 'Mdm::WebSite', + dependent: :destroy, + inverse_of: :service + + # + # through: :task_services + # + + # @!attribute [rw] tasks + # Tasks that touched this service + # + # @return [Array] + has_many :tasks, :through => :task_services, :class_name => 'Mdm::Task' # # Through :web_sites diff --git a/app/models/mdm/session.rb b/app/models/mdm/session.rb index 7d778603..2a204f26 100755 --- a/app/models/mdm/session.rb +++ b/app/models/mdm/session.rb @@ -9,19 +9,44 @@ class Mdm::Session < ActiveRecord::Base # Events that occurred when this session was open. # # @return [Array] - has_many :events, :class_name => 'Mdm::SessionEvent', :order => 'created_at', :dependent => :delete_all + has_many :events, + class_name: 'Mdm::SessionEvent', + dependent: :delete_all, + inverse_of: :session, + order: 'created_at' + + # @!attribute exploit_attempt + # Exploit attempt that created this session. + # + # @return [Mdm::ExploitAttempt] + has_one :exploit_attempt, + class_name: 'Mdm::ExploitAttempt', + inverse_of: :session # @!attribute [rw] host # {Mdm::Host Host} on which this session was opened. # # @return [Mdm::Host] - belongs_to :host, :class_name => 'Mdm::Host' + belongs_to :host, + class_name: 'Mdm::Host', + inverse_of: :sessions # @!attribute [rw] routes # Routes tunneled throug this session. # # @return [Array] - has_many :routes, :class_name => 'Mdm::Route', :dependent => :delete_all + has_many :routes, + class_name: 'Mdm::Route', + dependent: :delete_all, + inverse_of: :session + + # @!attribute vuln_attempt + # Vulnerability attempt that created this session. + # + # @return [Mdm::VulnAttempt] + has_one :vuln_attempt, + class_name: 'Mdm::VulnAttempt', + inverse_of: :session # # Through :host diff --git a/app/models/mdm/session_event.rb b/app/models/mdm/session_event.rb index 99cadd22..40de569c 100755 --- a/app/models/mdm/session_event.rb +++ b/app/models/mdm/session_event.rb @@ -3,7 +3,9 @@ class Mdm::SessionEvent < ActiveRecord::Base # Relations # - belongs_to :session, :class_name => 'Mdm::Session' + belongs_to :session, + class_name: 'Mdm::Session', + inverse_of: :events ActiveSupport.run_load_hooks(:mdm_session_event, self) end diff --git a/app/models/mdm/tag.rb b/app/models/mdm/tag.rb index 5f61c4da..5f556857 100755 --- a/app/models/mdm/tag.rb +++ b/app/models/mdm/tag.rb @@ -1,20 +1,29 @@ class Mdm::Tag < ActiveRecord::Base # - # Callbacks + # Relations # - before_destroy :cleanup_hosts - - # - # Relations + # @!attribute hosts_tags + # Joins {#hosts} to this tag. # + # @return [ActiveRecord::Relation] + has_many :hosts_tags, + class_name: 'Mdm::HostTag', + dependent: :destroy, + inverse_of: :tag - has_many :hosts_tags, :class_name => 'Mdm::HostTag' - belongs_to :user, :class_name => 'Mdm::User' + belongs_to :user, + class_name: 'Mdm::User', + inverse_of: :tags # # Through :hosts_tags # + + # @!attribute [r] hosts + # Host that are tagged with this tag. + # + # @return [ActiveRecord::Relation] has_many :hosts, :through => :hosts_tags, :class_name => 'Mdm::Host' @@ -33,9 +42,19 @@ class Mdm::Tag < ActiveRecord::Base }, :presence => true - def cleanup_hosts - # Clean up association table records - Mdm::HostTag.delete_all("tag_id = #{self.id}") + # + # Instance Methods + # + + # Destroy this tag if it has no {#hosts_tags} + # + # @return [void] + def destroy_if_orphaned + self.class.transaction do + if hosts_tags.empty? + destroy + end + end end def to_s diff --git a/app/models/mdm/task.rb b/app/models/mdm/task.rb index d8860821..9015329d 100755 --- a/app/models/mdm/task.rb +++ b/app/models/mdm/task.rb @@ -9,17 +9,62 @@ class Mdm::Task < ActiveRecord::Base # Relations # + # @!attribute listeners + # Listeners spawned by this task + # + # @return [ActiveRecord::Relation] + has_many :listeners, + class_name: 'Mdm::Listener', + dependent: :destroy, + inverse_of: :task + + # @!attribute [rw] task_creds + # Joins this to {#creds}. + # + # @return [ActiveRecord::Relation] + has_many :task_creds, + class_name: 'Mdm::TaskCred', + dependent: :destroy, + inverse_of: :task + + # @!attribute task_hosts + # Joins this to {#hosts}. + # + # @return [ActiveRecord::Relation] + has_many :task_hosts, + class_name: 'Mdm::TaskHost', + dependent: :destroy, + inverse_of: :task + + # @!attribute task_services + # Joins this to {#services}. + # + # @return [ActiveRecord::Relation] + has_many :task_services, + class_name: 'Mdm::TaskService', + dependent: :destroy, + inverse_of: :task + + # @!attribute task_sessions + # Joins this to {#sessions}. + # + # @return [ActiveRecord::Relation] + has_many :task_sessions, + class_name: 'Mdm::TaskSession', + dependent: :destroy, + inverse_of: :task + # @!attribute [rw] workspace # The Workspace the Task belongs to # # @return [Mdm::Workspace] - belongs_to :workspace, :class_name => "Mdm::Workspace" + belongs_to :workspace, + class_name: 'Mdm::Workspace', + inverse_of: :tasks - # @!attribute [rw] task_creds - # Details about creds this task touched # - # @return [Array] - has_many :task_creds, :dependent => :destroy, :class_name => 'Mdm::TaskCred' + # through: :task_creds + # # @!attribute [rw] creds # Creds this task touched @@ -27,11 +72,9 @@ class Mdm::Task < ActiveRecord::Base # @return [Array] has_many :creds, :through => :task_creds, :class_name => 'Mdm::Cred' - # @!attribute [rw] task_hosts - # Details about hosts this task touched # - # @return [Array] - has_many :task_hosts, :dependent => :destroy, :class_name => 'Mdm::TaskHost' + # through: :task_hosts + # # @!attribute [rw] hosts # Hosts this task touched @@ -39,11 +82,9 @@ class Mdm::Task < ActiveRecord::Base # @return [Array has_many :hosts, :through => :task_hosts, :class_name => 'Mdm::Host' - # @!attribute [rw] task_services - # Details about services this task touched # - # @return [Array] - has_many :task_services, :dependent => :destroy, :class_name => 'Mdm::TaskService' + # through: :task_services + # # @!attribute [rw] services # Services this task touched @@ -51,11 +92,9 @@ class Mdm::Task < ActiveRecord::Base # @return [Array has_many :services, :through => :task_services, :class_name => 'Mdm::Service' - # @!attribute [rw] task_sessions - # Details about sessions this task touched # - # @return [Array] - has_many :task_sessions, :dependent => :destroy, :class_name => 'Mdm::TaskSession' + # through: :task_sessions + # # @!attribute [rw] sessions # Session this task touched diff --git a/app/models/mdm/task_cred.rb b/app/models/mdm/task_cred.rb index 9517ca5e..00e5012f 100644 --- a/app/models/mdm/task_cred.rb +++ b/app/models/mdm/task_cred.rb @@ -1,7 +1,11 @@ class Mdm::TaskCred < ActiveRecord::Base - # attr_accessible :title, :body - belongs_to :cred, :class_name => 'Mdm::Cred' - belongs_to :task, :class_name => 'Mdm::Task' + belongs_to :cred, + class_name: 'Mdm::Cred', + inverse_of: :task_creds + + belongs_to :task, + class_name: 'Mdm::Task', + inverse_of: :task_creds validates :cred_id, :uniqueness => { diff --git a/app/models/mdm/task_host.rb b/app/models/mdm/task_host.rb index dc3ce165..dabd0ff4 100644 --- a/app/models/mdm/task_host.rb +++ b/app/models/mdm/task_host.rb @@ -1,7 +1,11 @@ class Mdm::TaskHost < ActiveRecord::Base - # attr_accessible :title, :body - belongs_to :host, :class_name => 'Mdm::Host' - belongs_to :task, :class_name => 'Mdm::Task' + belongs_to :host, + class_name: 'Mdm::Host', + inverse_of: :task_hosts + + belongs_to :task, + class_name: 'Mdm::Task', + inverse_of: :task_hosts validates :host_id, :uniqueness => { diff --git a/app/models/mdm/task_service.rb b/app/models/mdm/task_service.rb index fda5a071..8c46dc49 100644 --- a/app/models/mdm/task_service.rb +++ b/app/models/mdm/task_service.rb @@ -1,7 +1,11 @@ class Mdm::TaskService < ActiveRecord::Base - # attr_accessible :title, :body - belongs_to :service, :class_name => 'Mdm::Service' - belongs_to :task, :class_name => 'Mdm::Task' + belongs_to :service, + class_name: 'Mdm::Service', + inverse_of: :task_services + + belongs_to :task, + class_name: 'Mdm::Task', + inverse_of: :task_services validates :service_id, :uniqueness => { diff --git a/app/models/mdm/task_session.rb b/app/models/mdm/task_session.rb index 89152d1b..2b3a3995 100644 --- a/app/models/mdm/task_session.rb +++ b/app/models/mdm/task_session.rb @@ -1,6 +1,11 @@ class Mdm::TaskSession < ActiveRecord::Base - belongs_to :session, :class_name => 'Mdm::Session' - belongs_to :task, :class_name => 'Mdm::Task' + belongs_to :session, + class_name: 'Mdm::Session', + inverse_of: :task_sessions + + belongs_to :task, + class_name: 'Mdm::Task', + inverse_of: :task_sessions validates :session_id, :uniqueness => { diff --git a/app/models/mdm/user.rb b/app/models/mdm/user.rb index c727f850..e4b7ee82 100755 --- a/app/models/mdm/user.rb +++ b/app/models/mdm/user.rb @@ -5,8 +5,15 @@ class Mdm::User < ActiveRecord::Base # Relations # - has_many :owned_workspaces, :foreign_key => 'owner_id', :class_name => 'Mdm::Workspace' - has_many :tags, :class_name => 'Mdm::Tag' + has_many :owned_workspaces, + class_name: 'Mdm::Workspace', + foreign_key: 'owner_id', + inverse_of: :owner + + has_many :tags, + class_name: 'Mdm::Tag', + inverse_of: :user + has_and_belongs_to_many :workspaces, :join_table => 'workspace_members', :uniq => true, :class_name => 'Mdm::Workspace' # diff --git a/app/models/mdm/vuln.rb b/app/models/mdm/vuln.rb index e82b45bc..3720c4a8 100755 --- a/app/models/mdm/vuln.rb +++ b/app/models/mdm/vuln.rb @@ -4,36 +4,57 @@ class Mdm::Vuln < ActiveRecord::Base # Associations # + # @!attribute exploit_attempts + # Attempts to exploit this vulnerability. + # + # @return [ActiveRecord::Relation] + has_many :exploit_attempts, + class_name: 'Mdm::ExploitAttempt', + inverse_of: :vuln + # @!attribute [rw] host # The host with this vulnerability. # # @return [Mdm::Host] - belongs_to :host, :class_name => 'Mdm::Host', :counter_cache => :vuln_count + belongs_to :host, + class_name: 'Mdm::Host', + counter_cache: :vuln_count, + inverse_of: :vulns # @!attribute [rw] service # The service with the vulnerability. # # @return [Mdm::Service] - belongs_to :service, :class_name => 'Mdm::Service' + belongs_to :service, + class_name: 'Mdm::Service', + inverse_of: :vulns # @!attribute [rw] vuln_attempts # Attempts to exploit this vulnerability. # # @return [Array] - has_many :vuln_attempts, :class_name => 'Mdm::VulnAttempt', :dependent => :destroy + has_many :vuln_attempts, + class_name: 'Mdm::VulnAttempt', + dependent: :destroy, + inverse_of: :vuln # @!attribute [rw] vuln_details # Additional information about this vulnerability. # # @return [Array] - has_many :vuln_details, :class_name => 'Mdm::VulnDetail', :dependent => :destroy + has_many :vuln_details, + class_name: 'Mdm::VulnDetail', + dependent: :destroy, + inverse_of: :vuln # @!attribute [rw] vulns_refs # Join model that joins this vuln to its {Mdm::Ref external references}. # - # @todo https://www.pivotaltracker.com/story/show/49004623 # @return [Array] - has_many :vulns_refs, :class_name => 'Mdm::VulnRef', :dependent => :destroy + has_many :vulns_refs, + class_name: 'Mdm::VulnRef', + dependent: :destroy, + inverse_of: :vuln # # Through :vuln_refs @@ -42,7 +63,6 @@ class Mdm::Vuln < ActiveRecord::Base # @!attribute [r] refs # External references to this vulnerability. # - # @todo https://www.pivotaltracker.com/story/show/49004623 # @return [Array] has_many :refs, :class_name => 'Mdm::Ref', :through => :vulns_refs diff --git a/app/models/mdm/vuln_attempt.rb b/app/models/mdm/vuln_attempt.rb index 7d258271..ce0b426f 100755 --- a/app/models/mdm/vuln_attempt.rb +++ b/app/models/mdm/vuln_attempt.rb @@ -1,9 +1,44 @@ class Mdm::VulnAttempt < ActiveRecord::Base # - # Relations + # Associations # - belongs_to :vuln, :class_name => 'Mdm::Vuln', :counter_cache => :vuln_attempt_count + # @!attribute loot + # Loot gathered from this attempt. + # + # @return [Mdm::Loot] if {#exploited} is `true`. + # @return [nil] if {#exploited} is `false`. + belongs_to :loot, + class_name: 'Mdm::Loot', + inverse_of: :vuln_attempt + + # @!attribute session + # The session opened by this attempt. + # + # @return [Mdm::Session] if {#exploited} is `true`. + # @return [nil] if {#exploited} is `false`. + belongs_to :session, + class_name: 'Mdm::Session', + inverse_of: :vuln_attempt + + # @!attribute vuln + # The {Mdm::Vuln vulnerability} that this attempt was exploiting. + # + # @return [Mdm::Vuln] + belongs_to :vuln, + class_name: 'Mdm::Vuln', + counter_cache: :vuln_attempt_count, + inverse_of: :vuln_attempts + + # + # Attributes + # + + # @!attribute [rw] exploited + # Whether this attempt was successful. + # + # @return [true] if {#vuln} was exploited. + # @return [false] if {#vuln} was not exploited. # # Validations diff --git a/app/models/mdm/vuln_detail.rb b/app/models/mdm/vuln_detail.rb index d0386728..3cc46c5a 100755 --- a/app/models/mdm/vuln_detail.rb +++ b/app/models/mdm/vuln_detail.rb @@ -2,7 +2,15 @@ class Mdm::VulnDetail < ActiveRecord::Base # # Relations # - belongs_to :vuln, :class_name => 'Mdm::Vuln', :counter_cache => :vuln_detail_count + + belongs_to :nexpose_console, + class_name: 'Mdm::NexposeConsole', + inverse_of: :vuln_details + + belongs_to :vuln, + class_name: 'Mdm::Vuln', + counter_cache: :vuln_detail_count, + inverse_of: :vuln_details # # Validations diff --git a/app/models/mdm/vuln_ref.rb b/app/models/mdm/vuln_ref.rb index f11f9b62..217741d5 100644 --- a/app/models/mdm/vuln_ref.rb +++ b/app/models/mdm/vuln_ref.rb @@ -5,8 +5,13 @@ class Mdm::VulnRef < ActiveRecord::Base # Relations # - belongs_to :ref, :class_name => 'Mdm::Ref' - belongs_to :vuln, :class_name => 'Mdm::Vuln' + belongs_to :ref, + class_name: 'Mdm::Ref', + inverse_of: :vulns_refs + + belongs_to :vuln, + class_name: 'Mdm::Vuln', + inverse_of: :vulns_refs ActiveSupport.run_load_hooks(:mdm_vuln_ref, self) end diff --git a/app/models/mdm/web_form.rb b/app/models/mdm/web_form.rb index 5d8ac12e..41e07461 100755 --- a/app/models/mdm/web_form.rb +++ b/app/models/mdm/web_form.rb @@ -3,7 +3,9 @@ class Mdm::WebForm < ActiveRecord::Base # Relations # - belongs_to :web_site, :class_name => 'Mdm::WebSite' + belongs_to :web_site, + class_name: 'Mdm::WebSite', + inverse_of: :web_forms # # Serializations diff --git a/app/models/mdm/web_page.rb b/app/models/mdm/web_page.rb index 538e2b0e..e8169e36 100755 --- a/app/models/mdm/web_page.rb +++ b/app/models/mdm/web_page.rb @@ -3,7 +3,9 @@ class Mdm::WebPage < ActiveRecord::Base # Relations # - belongs_to :web_site, :class_name => 'Mdm::WebSite' + belongs_to :web_site, + class_name: 'Mdm::WebSite', + inverse_of: :web_pages # # Serializations diff --git a/app/models/mdm/web_site.rb b/app/models/mdm/web_site.rb index a99750ae..9967a009 100755 --- a/app/models/mdm/web_site.rb +++ b/app/models/mdm/web_site.rb @@ -3,10 +3,25 @@ class Mdm::WebSite < ActiveRecord::Base # Relations # - belongs_to :service, :class_name => 'Mdm::Service', :foreign_key => 'service_id' - has_many :web_forms, :dependent => :destroy, :class_name => 'Mdm::WebForm' - has_many :web_pages, :dependent => :destroy, :class_name => 'Mdm::WebPage' - has_many :web_vulns, :dependent => :destroy, :class_name => 'Mdm::WebVuln' + belongs_to :service, + class_name: 'Mdm::Service', + foreign_key: 'service_id', + inverse_of: :web_sites + + has_many :web_forms, + class_name: 'Mdm::WebForm', + dependent: :destroy, + inverse_of: :web_site + + has_many :web_pages, + class_name: 'Mdm::WebPage', + dependent: :destroy, + inverse_of: :web_site + + has_many :web_vulns, + class_name: 'Mdm::WebVuln', + dependent: :destroy, + inverse_of: :web_site # # Serializations diff --git a/app/models/mdm/web_vuln.rb b/app/models/mdm/web_vuln.rb index 6695f192..206698e2 100755 --- a/app/models/mdm/web_vuln.rb +++ b/app/models/mdm/web_vuln.rb @@ -36,7 +36,9 @@ class Mdm::WebVuln < ActiveRecord::Base # Associations # - belongs_to :web_site, :class_name => 'Mdm::WebSite' + belongs_to :web_site, + class_name: 'Mdm::WebSite', + inverse_of: :web_vulns # # Attributes diff --git a/lib/tasks/yard.rake b/lib/tasks/yard.rake index fc535703..675de5e1 100644 --- a/lib/tasks/yard.rake +++ b/lib/tasks/yard.rake @@ -12,6 +12,9 @@ if defined? YARD } end + # need environment so that yard templates can load ActiveRecord::Base subclasses for Entity-Relationship Diagrams + task :doc => :eager_load + desc "Shows stats for YARD Documentation including listing undocumented modules, classes, constants, and methods" task :stats => :environment do stats = YARD::CLI::Stats.new @@ -23,4 +26,8 @@ if defined? YARD desc "Generate YARD documentation" # allow calling namespace to as a task that goes to default task for namespace task :yard => ['yard:doc'] +end + +task eager_load: :environment do + Rails.application.eager_load! end \ No newline at end of file diff --git a/metasploit_data_models.gemspec b/metasploit_data_models.gemspec index 4d53e193..71fa7d27 100644 --- a/metasploit_data_models.gemspec +++ b/metasploit_data_models.gemspec @@ -34,7 +34,9 @@ Gem::Specification.new do |s| # debugging s.add_development_dependency 'pry' - s.add_runtime_dependency 'activerecord', '>= 3.2.13' + # restrict from rails 4.0 as it requires protected_attributes gem and other changes for compatibility + # @see MSP-2971 + s.add_runtime_dependency 'activerecord', '>= 3.2.13', '< 4.0.0' s.add_runtime_dependency 'activesupport' if RUBY_PLATFORM =~ /java/ diff --git a/spec/app/models/mdm/host_spec.rb b/spec/app/models/mdm/host_spec.rb index f259e70e..2da2f34d 100644 --- a/spec/app/models/mdm/host_spec.rb +++ b/spec/app/models/mdm/host_spec.rb @@ -202,73 +202,6 @@ it { should belong_to(:workspace).class_name('Mdm::Workspace') } end - context 'callbacks' do - context 'before destroy' do - context 'cleanup_tags' do - context 'with tags' do - let!(:tag) do - FactoryGirl.create(:mdm_tag) - end - - let!(:host) do - FactoryGirl.create(:mdm_host) - end - - context 'with only this host' do - before(:each) do - FactoryGirl.create( - :mdm_host_tag, - :host => host, - :tag => tag - ) - end - - it 'should destroy the tags' do - expect { - host.destroy - }.to change(Mdm::Tag, :count).by(-1) - end - - it 'should destroy the host tags' do - expect { - host.destroy - }.to change(Mdm::HostTag, :count).by(-1) - end - end - - context 'with additional hosts' do - let(:other_host) do - FactoryGirl.create(:mdm_host) - end - - before(:each) do - FactoryGirl.create(:mdm_host_tag, :host => host, :tag => tag) - FactoryGirl.create(:mdm_host_tag, :host => other_host, :tag => tag) - end - - it 'should not destroy the tag' do - expect { - host.destroy - }.to_not change(Mdm::Tag, :count) - end - - it 'should destroy the host tags' do - expect { - host.destroy - }.to change(Mdm::HostTag, :count).by(-1) - end - - it "should not destroy the other host's tags" do - host.destroy - - other_host.hosts_tags.count.should == 1 - end - end - end - end - end - end - context 'CONSTANTS' do context 'ARCHITECTURES' do subject(:architectures) do diff --git a/spec/app/models/mdm/host_tag_spec.rb b/spec/app/models/mdm/host_tag_spec.rb index dd6445c6..6f894b26 100644 --- a/spec/app/models/mdm/host_tag_spec.rb +++ b/spec/app/models/mdm/host_tag_spec.rb @@ -25,15 +25,46 @@ end context '#destroy' do - it 'should successfully destroy the object' do - host_tag = FactoryGirl.create(:mdm_host_tag) + let(:tag) do + FactoryGirl.create( + :mdm_tag + ) + end + + let!(:host_tag) do + FactoryGirl.create( + :mdm_host_tag, + :tag => tag + ) + end + + it 'should delete 1 Mdm::HostTag' do expect { host_tag.destroy - }.to_not raise_error - expect { - host_tag.reload - }.to raise_error(ActiveRecord::RecordNotFound) + }.to change(Mdm::HostTag, :count).by(-1) + end + + context 'with multiple Mdm::HostTags using same Mdm::Tag' do + let!(:other_host_tag) do + FactoryGirl.create( + :mdm_host_tag, + :tag => tag + ) + end + + it 'should not delete Mdm::Tag' do + expect { + host_tag.destroy + }.to_not change(Mdm::Tag, :count) + end end - end + context 'with only one Mdm::HostTag using Mdm::Tag' do + it 'should delete Mdm::Tag' do + expect { + host_tag.destroy + }.to change(Mdm::Tag, :count).by(-1) + end + end + end end \ No newline at end of file diff --git a/spec/app/models/mdm/tag_spec.rb b/spec/app/models/mdm/tag_spec.rb index 04d886de..8f45f37d 100644 --- a/spec/app/models/mdm/tag_spec.rb +++ b/spec/app/models/mdm/tag_spec.rb @@ -66,23 +66,6 @@ end end - context 'callbacks' do - context 'before_destroy' do - it 'should call #cleanup_hosts' do - mytag = FactoryGirl.create(:mdm_tag) - mytag.should_receive(:cleanup_hosts) - mytag.destroy - end - - it 'should destroy the host_tag joins' do - mytag = FactoryGirl.create(:mdm_tag) - FactoryGirl.create(:mdm_host_tag, :tag => mytag) - Mdm::HostTag.should_receive(:delete_all).with("tag_id = #{mytag.id}") - mytag.destroy - end - end - end - context 'instance methods' do context '#to_s' do it 'should return the name of the tag as a string' do @@ -103,15 +86,14 @@ end context '#destroy' do + let!(:tag) do + FactoryGirl.create(:mdm_tag) + end + it 'should successfully destroy the object' do - tag = FactoryGirl.create(:mdm_tag) expect { tag.destroy - }.to_not raise_error - expect { - tag.reload - }.to raise_error(ActiveRecord::RecordNotFound) + }.to change(Mdm::Tag, :count).by(-1) end end - end \ No newline at end of file