# @deprecated Use metasploit-credential's `Metasploit::Credential::Core`.
#
# A credential captured from a {#service}.
class Mdm::Cred < ActiveRecord::Base
  #
  # CONSTANTS
  #

  # Checks if {#proof} is an SSH Key in {#ssh_key_id}.
  KEY_ID_REGEX = /([0-9a-fA-F:]{47})/

  # Maps {#ptype_human} to {#ptype}.
  PTYPES = {
      'read/write password' => 'password_rw',
      'read-only password' => 'password_ro',
      'SMB hash' => 'smb_hash',
      'SSH private key' => 'ssh_key',
      'SSH public key' => 'ssh_pubkey'
  }

  #
  #
  # Associations
  #
  #

  # The {Mdm::Service} this Cred is for.
  belongs_to :service,
             class_name: 'Mdm::Service',
             inverse_of: :creds

  # Joins {#tasks} to this Cred.
  has_many :task_creds,
           class_name: 'Mdm::TaskCred',
           dependent: :destroy,
           inverse_of: :cred

  #
  # through: :task_creds
  #

  # Tasks that touched this service
  has_many :tasks, :through => :task_creds

  #
  # Attributes
  #

  # @!attribute active
  #   Whether the credential is active.
  #
  #   @return [false] if a captured credential cannot be used to log into {#service}.
  #   @return [true] otherwise

  # @!attribute created_at
  #   When this credential was created.
  #
  #   @return [DateTime]

  # @!attribute pass
  #   Pass of credential.
  #
  #   @return [String, nil]

  # @!attribute proof
  #   Proof of credential capture.
  #
  #   @return [String]

  # @!attribute ptype
  #   Type of {#pass}.
  #
  #   @return [String]

  # @!attribute source_id
  #   Id of source of this credential.
  #
  #   @return [Integer, nil]

  # @!attribute source_type
  #   Type of source with {#source_id}.
  #
  #   @return [String, nil]

  # @!attribute updated_at
  #   The last time this credential was updated.
  #
  #   @return [DateTime]

  # @!attribute user
  #   User name of credential.
  #
  #   @return [String, nil]

  #
  # Callbacks
  #

  after_create :increment_host_counter_cache
  after_destroy :decrement_host_counter_cache

  #
  # Instance methods
  #

  # Humanized {#ptype}.
  #
  # @return [String, nil]
  def ptype_human
    humanized = PTYPES.select do |k, v|
      v == ptype
    end.keys[0]

    humanized ? humanized : ptype
  end

  # Returns SSH Key ID.
  #
  # @return [String] SSH Key Id if ssh-type key and {#proof} matches {KEY_ID_REGEX}.
  # @return [nil] otherwise
  def ssh_key_id
    return nil unless self.ptype =~ /^ssh_/
    return nil unless self.proof =~ KEY_ID_REGEX
    $1.downcase # Can't run into NilClass problems.
  end

  # Returns whether `other`'s SSH private key or public key matches.
  #
  # @return [false] if `other` is not same class as `self`.
  # @return [false] if {#ptype} does not match.
  # @return [false] if {#ptype} is neither `"ssh_key"` nor `"ssh_pubkey"`.
  # @return [false] if {#ssh_key_id} is `nil`.
  # @return [false] if {#ssh_key_id} does not match.
  # @return [true] if {#ssh_key_id} matches.
  def ssh_key_matches?(other_cred)
    return false unless other_cred.kind_of? self.class
    return false unless self.ptype == other_cred.ptype
    case self.ptype
      when "ssh_key"
        matches = self.ssh_private_keys
      when "ssh_pubkey"
        matches = self.ssh_public_keys
      else
        return false
    end
    matches.include?(self) and matches.include?(other_cred)
  end

  # Returns all keys with matching key ids, including itself.
  #
  # @return [ActiveRecord::Relation<Mdm::Cred>] ssh_key and ssh_pubkey creds with matching {#ssh_key_id}.
  def ssh_keys
    (self.ssh_private_keys | self.ssh_public_keys)
  end

  # Returns all private keys with matching {#ssh_key_id}, including itself.
  #
  # @return [ActiveRecord::Relation<Mdm::Cred>] ssh_key creds with matching {#ssh_key_id}.
  def ssh_private_keys
    return [] unless self.ssh_key_id
    matches = self.class.all(
        :conditions => ["creds.ptype = ? AND creds.proof ILIKE ?", "ssh_key", "%#{self.ssh_key_id}%"]
    )
    matches.select {|c| c.workspace == self.workspace}
  end

  # Returns all public keys with matching {#ssh_key_id}, including itself.
  #
  # @return [ActiveRecord::Relation<Mdm::Cred>] ssh_pubkey creds with matching {#ssh_key_id}.
  def ssh_public_keys
    return [] unless self.ssh_key_id
    matches = self.class.all(
        :conditions => ["creds.ptype = ? AND creds.proof ILIKE ?", "ssh_pubkey", "%#{self.ssh_key_id}%"]
    )
    matches.select {|c| c.workspace == self.workspace}
  end

  # Returns its workspace
  #
  # @return [Mdm::Workspace]
  def workspace
    self.service.host.workspace
  end

  private

  # Decrements {Mdm::Host#cred_count}.
  #
  # @return [void]
  def decrement_host_counter_cache
    Mdm::Host.decrement_counter("cred_count", self.service.host_id)
  end

  # Increments {Mdm::Host#cred_count}.
  #
  # @return [void]
  def increment_host_counter_cache
    Mdm::Host.increment_counter("cred_count", self.service.host_id)
  end

  # Switch back to public for load hooks.
  public

  Metasploit::Concern.run(self)
end