RSpec.describe Metasploit::Credential::Creation do

  let(:dummy_class) {
    Class.new do
      include Metasploit::Credential::Creation
    end
  }

  let(:session) { FactoryGirl.create(:mdm_session) }

  let(:task) { FactoryGirl.create(:mdm_task, workspace: workspace)}

  let(:user) { FactoryGirl.create(:mdm_user)}

  let(:workspace) { FactoryGirl.create(:mdm_workspace) }

  subject(:test_object) { dummy_class.new }
  
  context '#create_credential' do
    let(:workspace) { FactoryGirl.create(:mdm_workspace) }
    let(:service) { FactoryGirl.create(:mdm_service, host: FactoryGirl.create(:mdm_host, workspace: workspace)) }
    let(:task) { FactoryGirl.create(:mdm_task, workspace: workspace) }
    {
      cracked_password: Metasploit::Credential::Origin::CrackedPassword,
      import: Metasploit::Credential::Origin::Import,
      manual: Metasploit::Credential::Origin::Manual,
      service: Metasploit::Credential::Origin::Service,
      session: Metasploit::Credential::Origin::Session
    }.each_pair do |origin_type, origin_class|
      context "Origin[#{origin_type}], Public[Username], Private[Password]" do
        let(:service) { FactoryGirl.create(:mdm_service) }
        let!(:origin_data) {{
          cracked_password: {
            originating_core_id: FactoryGirl.create(
              :metasploit_credential_core, workspace: workspace, origin_factory: :metasploit_credential_origin_manual).id
          },
          import: {
            filename: FactoryGirl.generate(:metasploit_credential_origin_import_filename)
          },
          manual: {user_id: user.id},
          service: {
            module_fullname: "exploit/" + FactoryGirl.generate(:metasploit_credential_origin_service_reference_name),
            address: service.host.address,
            port: service.port,
            service_name: service.name,
            protocol: service.proto
          },
          session: {
            session_id: FactoryGirl.create(:mdm_session, workspace: workspace, host: service.host),
            post_reference_name: FactoryGirl.generate(:metasploit_credential_origin_session_post_reference_name)
          }
        }}
        let(:credential_data) {{
          workspace_id: workspace.id,
          origin_type: origin_type,
          username: 'admin',
          private_data: 'password',
          private_type: :password,
          workspace_id: workspace.id
        }.merge(origin_data[origin_type])}
        it 'creates a credential core' do
          expect{ test_object.create_credential(credential_data) }.to change{ Metasploit::Credential::Core.count }.by(1)
        end
        it "creates a Origin of type #{origin_type}" do
          expect{ test_object.create_credential(credential_data) }.to change{ origin_class.count }.by(1)
        end
        it 'creates a Private with data \'password\'' do
          expect{ test_object.create_credential(credential_data) }.to change{ Metasploit::Credential::Private.where(data: 'password').count }.by(1)
        end
        it 'creates a Public with username \'username\'' do
          expect{ test_object.create_credential(credential_data) }.to change{ Metasploit::Credential::Public.where(username: 'admin').count }.by(1)
        end
      end
    end
    [
      Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
      Metasploit::Model::Realm::Key::DB2_DATABASE,
      Metasploit::Model::Realm::Key::ORACLE_SYSTEM_IDENTIFIER,
      Metasploit::Model::Realm::Key::POSTGRESQL_DATABASE,
      Metasploit::Model::Realm::Key::RSYNC_MODULE,
      Metasploit::Model::Realm::Key::WILDCARD
    ].each do |realm_type|
      context "Origin[manual], Realm[#{realm_type}], Public[Username], Private[Password]" do
        let(:credential_data) {{
          workspace_id: workspace.id,
          user_id: user.id,
          realm_key: realm_type,
          realm_value: 'Some Value',
          origin_type: :manual,
          username: 'admin',
          private_data: 'password',
          private_type: :password,
          workspace_id: workspace.id
        }}
        it 'creates a credential core' do
          expect{ test_object.create_credential(credential_data) }.to change{ Metasploit::Credential::Core.count }.by(1)
        end
        it "creates a Realm with #{realm_type} key" do
          expect{ test_object.create_credential(credential_data) }.to change{ Metasploit::Credential::Realm.where(key: realm_type).count }.by(1)
        end
        it 'creates a Private with data \'password\'' do
          expect{ test_object.create_credential(credential_data) }.to change{ Metasploit::Credential::Private.where(data: 'password').count }.by(1)
        end
        it 'creates a Public with username \'username\'' do
          expect{ test_object.create_credential(credential_data) }.to change{ Metasploit::Credential::Public.where(username: 'admin').count }.by(1)
        end
      end
    end
    {
      "Metasploit::Credential::Username" => 'admin',
      "Metasploit::Credential::BlankUsername" => ''
    }.each_pair do |public_type, public_value|
      context "Origin[manual], Public[#{public_type}], Private[Password]" do
        let(:credential_data) {{
          workspace_id: workspace.id,
          user_id: user.id,
          origin_type: :manual,
          username: public_value,
          private_data: 'password',
          private_type: :password,
          workspace_id: workspace.id
        }}
        it 'creates a credential core' do
          expect{ test_object.create_credential(credential_data) }.to change{ Metasploit::Credential::Core.count }.by(1)
        end
        it 'creates a Private with data \'password\'' do
          expect{ test_object.create_credential(credential_data) }.to change{ Metasploit::Credential::Private.where(data: 'password').count }.by(1)
        end
        it 'creates a Public with username \'username\'' do
          expect{ test_object.create_credential(credential_data) }.to change{ Metasploit::Credential::Public.where(type: public_type).count }.by(1)
        end
      end 
    end
    {
      password: "Metasploit::Credential::Password",
      blank_password: "Metasploit::Credential::BlankPassword",
      nonreplayable_hash: "Metasploit::Credential::NonreplayableHash",
      ntlm_hash: "Metasploit::Credential::NTLMHash",
      postgres_md5: "Metasploit::Credential::PostgresMD5",
      ssh_key: "Metasploit::Credential::SSHKey"
    }.each_pair do |private_type, public_class|
      context "Origin[manual], Public[Username], Private[#{private_type}]" do
        let(:ssh_key) {
          key_class = OpenSSL::PKey.const_get(:RSA)
          key_class.generate(512).to_s
        }
        let(:private_data) { {
          password: 'password',
          blank_password: '',
          nonreplayable_hash: '435ba65d2e46d35bc656086694868d1ab2c0f9fd',
          ntlm_hash: 'aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0',
          postgres_md5: 'md5ac4bbe016b808c3c0b816981f240dcae',
          ssh_key: ssh_key
        }}
        let(:credential_data) {{
          workspace_id: workspace.id,
          user_id: user.id,
          origin_type: :manual,
          username: 'admin',
          private_data: private_data[private_type],
          private_type: private_type,
          workspace_id: workspace.id
        }}
        it 'creates a credential core' do
          expect{ test_object.create_credential(credential_data) }.to change{ Metasploit::Credential::Core.count }.by(1)
        end
        it 'creates a Private with data \'password\'' do
          expect{ test_object.create_credential(credential_data) }.to change{ Metasploit::Credential::Private.where(type: public_class).count }.by(1)
        end
        it 'creates a Public with username \'username\'' do
          expect{ test_object.create_credential(credential_data) }.to change{ Metasploit::Credential::Public.where(username: 'admin').count }.by(1)
        end
      end 
    end
  end
  
  context '#create_credential_and_login' do
    let(:workspace) { FactoryGirl.create(:mdm_workspace) }
    let(:service) { FactoryGirl.create(:mdm_service, host: FactoryGirl.create(:mdm_host, workspace: workspace)) }
    let(:task) { FactoryGirl.create(:mdm_task, workspace: workspace) }
    {
      cracked_password: Metasploit::Credential::Origin::CrackedPassword,
      import: Metasploit::Credential::Origin::Import,
      manual: Metasploit::Credential::Origin::Manual,
      service: Metasploit::Credential::Origin::Service,
      session: Metasploit::Credential::Origin::Session
    }.each_pair do |origin_type, origin_class|
      context "Origin[#{origin_type}], Public[Username], Private[Password]" do
        let!(:origin_data) {{
          cracked_password: {
            originating_core_id: FactoryGirl.create(
              :metasploit_credential_core, workspace: workspace, origin_factory: :metasploit_credential_origin_manual).id
          },
          import: {
            filename: FactoryGirl.generate(:metasploit_credential_origin_import_filename)
          },
          manual: {user_id: user.id},
          service: {
            module_fullname: "exploit/" + FactoryGirl.generate(:metasploit_credential_origin_service_reference_name),
            address: service.host.address,
            port: service.port,
            service_name: service.name,
            protocol: service.proto
          },
          session: {
            session_id: FactoryGirl.create(:mdm_session, workspace: workspace, host: service.host),
            post_reference_name: FactoryGirl.generate(:metasploit_credential_origin_session_post_reference_name)
          }
        }}
        let(:login_data) {{
          workspace_id: workspace.id,
          origin_type: origin_type,
          username: 'admin',
          private_data: 'password',
          private_type: :password,
          workspace_id: workspace.id,
          address: service.host.address,
          port: service.port,
          service_name: service.name,
          protocol: service.proto,
          last_attempted_at: DateTime.current,
          status: Metasploit::Model::Login::Status::SUCCESSFUL,
        }.merge(origin_data[origin_type])}
        it 'creates a credential core' do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Core.count }.by(1)
        end
        it "creates a Origin of type #{origin_type}" do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ origin_class.count }.by(1)
        end
        it 'creates a Private with data \'password\'' do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Private.where(data: 'password').count }.by(1)
        end
        it 'creates a Public with username \'username\'' do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Public.where(username: 'admin').count }.by(1)
        end
        it 'creates a Login with status for the service' do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Login.where(service_id: service.id, status: login_data[:status]).count }.by(1)
        end
      end
    end
    [
      Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
      Metasploit::Model::Realm::Key::DB2_DATABASE,
      Metasploit::Model::Realm::Key::ORACLE_SYSTEM_IDENTIFIER,
      Metasploit::Model::Realm::Key::POSTGRESQL_DATABASE,
      Metasploit::Model::Realm::Key::RSYNC_MODULE,
      Metasploit::Model::Realm::Key::WILDCARD
    ].each do |realm_type|
      context "Origin[manual], Realm[#{realm_type}], Public[Username], Private[Password]" do
        let(:login_data) {{
          workspace_id: workspace.id,
          user_id: user.id,
          realm_key: realm_type,
          realm_value: 'Some Value',
          origin_type: :manual,
          username: 'admin',
          private_data: 'password',
          private_type: :password,
          workspace_id: workspace.id,
          address: service.host.address,
          port: service.port,
          service_name: service.name,
          protocol: service.proto,
          last_attempted_at: DateTime.current,
          status: Metasploit::Model::Login::Status::SUCCESSFUL,
        }}
        it 'creates a credential core' do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Core.count }.by(1)
        end
        it "creates a Realm with #{realm_type} key" do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Realm.where(key: realm_type).count }.by(1)
        end
        it 'creates a Private with data \'password\'' do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Private.where(data: 'password').count }.by(1)
        end
        it 'creates a Public with username \'username\'' do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Public.where(username: 'admin').count }.by(1)
        end
        it 'creates a Login with status for the service' do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Login.where(service_id: service.id, status: login_data[:status]).count }.by(1)
        end
      end
    end
    
    {
      "Metasploit::Credential::Username" => 'admin',
      "Metasploit::Credential::BlankUsername" => ''
    }.each_pair do |public_type, public_value|
      context "Origin[manual], Public[#{public_type}], Private[Password]" do
        let(:login_data) {{
          workspace_id: workspace.id,
          user_id: user.id,
          origin_type: :manual,
          username: public_value,
          private_data: 'password',
          private_type: :password,
          workspace_id: workspace.id,
          address: service.host.address,
          port: service.port,
          service_name: service.name,
          protocol: service.proto,
          last_attempted_at: DateTime.current,
          status: Metasploit::Model::Login::Status::SUCCESSFUL,
        }}
        it 'creates a credential core' do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Core.count }.by(1)
        end
        it 'creates a Private with data \'password\'' do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Private.where(data: 'password').count }.by(1)
        end
        it 'creates a Public with username \'username\'' do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Public.where(type: public_type).count }.by(1)
        end
        it 'creates a Login with status for the service' do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Login.where(service_id: service.id, status: login_data[:status]).count }.by(1)
        end
      end 
    end
    {
      password: "Metasploit::Credential::Password",
      blank_password: "Metasploit::Credential::BlankPassword",
      nonreplayable_hash: "Metasploit::Credential::NonreplayableHash",
      ntlm_hash: "Metasploit::Credential::NTLMHash",
      postgres_md5: "Metasploit::Credential::PostgresMD5",
      ssh_key: "Metasploit::Credential::SSHKey"
    }.each_pair do |private_type, public_class|
      context "Origin[manual], Public[Username], Private[#{private_type}]" do
        let(:ssh_key) {
          key_class = OpenSSL::PKey.const_get(:RSA)
          key_class.generate(512).to_s
        }
        let(:private_data) { {
          password: 'password',
          blank_password: '',
          nonreplayable_hash: '435ba65d2e46d35bc656086694868d1ab2c0f9fd',
          ntlm_hash: 'aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0',
          postgres_md5: 'md5ac4bbe016b808c3c0b816981f240dcae',
          ssh_key: ssh_key
        }}
        let(:login_data) {{
          workspace_id: workspace.id,
          user_id: user.id,
          origin_type: :manual,
          username: 'admin',
          private_data: private_data[private_type],
          private_type: private_type,
          workspace_id: workspace.id,
          address: service.host.address,
          port: service.port,
          service_name: service.name,
          protocol: service.proto,
          last_attempted_at: DateTime.current,
          status: Metasploit::Model::Login::Status::SUCCESSFUL,
        }}
        it 'creates a credential core' do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Core.count }.by(1)
        end
        it 'creates a Private with data \'password\'' do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Private.where(type: public_class).count }.by(1)
        end
        it 'creates a Public with username \'username\'' do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Public.where(username: 'admin').count }.by(1)
        end
        it 'creates a Login with status for the service' do
          expect{ test_object.create_credential_and_login(login_data) }.to change{ Metasploit::Credential::Login.where(service_id: service.id, status: login_data[:status]).count }.by(1)
        end
      end 
    end
  end

  context '#create_cracked_credential' do
    let(:public) { FactoryGirl.create(:metasploit_credential_public) }
    let(:hash) { FactoryGirl.create(:metasploit_credential_nonreplayable_hash) }
    let(:origin) { FactoryGirl.create(:metasploit_credential_origin_manual) }
    let(:password) { "omgwtfbbq" }
    let(:realm) { FactoryGirl.create(:metasploit_credential_realm) }

    let!(:old_core) do
      FactoryGirl.create(:metasploit_credential_core, public: public, private: hash, realm: realm, workspace: workspace, origin: origin)
    end

    it 'creates a Core' do
      expect {
        test_object.create_cracked_credential(
            core_id: old_core,
            username: public.username,
            password: password
        )
      }.to change{ Metasploit::Credential::Core.count }.by(1)
      expect(Metasploit::Credential::Private.last).to be_a Metasploit::Credential::Password
    end

    it 'replicates realm in new credential' do
      expect {
        test_object.create_cracked_credential(
          core_id: old_core.id,
          workspace_id: workspace.id,
          username: public.username,
          password: password
        )
      }.to change{ Metasploit::Credential::Core.count }.by(1)
      expect(Metasploit::Credential::Core.last.realm).to eq(realm)
    end

    context 'when previous core has logins' do
      let(:host) { FactoryGirl.create(:mdm_host, workspace: workspace) }
      let(:service) { FactoryGirl.create(:mdm_service, host: host) }

      before do
        FactoryGirl.create(:metasploit_credential_login,
                           service: service,
                           core: old_core,
                           status: Metasploit::Model::Login::Status::UNTRIED
                          )
      end

      it 'creates mirrored Logins' do
        expect(old_core.logins.count).to eq(1)
        expect {
          test_object.create_cracked_credential(
            core_id: old_core.id,
            workspace_id: workspace.id,
            username: public.username,
            password: password
          )
        }.to change {
          Metasploit::Credential::Login.count
        }.by(1)
      end

    end

  end

  context '#create_credential_origin_import' do
    it 'creates a Metasploit::Credential::Origin object' do
      opts = {
          filename: "test_import.xml",
      }
      expect { test_object.create_credential_origin_import(opts)}.to change{ Metasploit::Credential::Origin::Import.count }.by(1)
    end

    it 'should return nil if there is no database connection' do
      expect(test_object).to receive(:active_db?).and_return(false)
      expect(test_object.create_credential_origin_import).to be_nil
    end

    context 'when called twice with the same options' do
      it 'does not create duplicate objects' do
        opts = {
            filename: "test_import.xml",
            task_id: task.id
        }
        test_object.create_credential_origin_import(opts)
        expect { test_object.create_credential_origin_import(opts)}.to_not change{ Metasploit::Credential::Origin::Import.count }
      end
    end

    context 'when missing a required option' do
      it 'throws a KeyError' do
        opts = {}
        expect{ test_object.create_credential_origin_import(opts)}.to raise_error KeyError
      end
    end


  end

  context '#create_credential_origin_manual' do
    it 'creates a Metasploit::Credential::Origin object' do
      opts = {
          user_id: user.id
      }
      expect { test_object.create_credential_origin_manual(opts)}.to change{ Metasploit::Credential::Origin::Manual.count }.by(1)
    end

    it 'should return nil if there is no database connection' do
      expect(test_object).to receive(:active_db?).and_return(false)
      expect(test_object.create_credential_origin_manual).to be_nil
    end

    context 'when called twice with the same options' do
      it 'does not create duplicate objects' do
        opts = {
            user_id: user.id
        }
        test_object.create_credential_origin_manual(opts)
        expect { test_object.create_credential_origin_manual(opts)}.to_not change{ Metasploit::Credential::Origin::Manual.count }
      end
    end

    context 'when missing an option' do
      it 'throws a KeyError' do
        opts = {}
        expect{ test_object.create_credential_origin_manual(opts)}.to raise_error KeyError
      end
    end
  end

  context "#create_credential_service" do
    let(:opts) do
      {
        address: '192.168.172.3',
        port: 445,
        service_name: 'smb',
        protocol: 'tcp',
        workspace_id: workspace.id
      }
    end

    it 'should create an Mdm::Service in state "open"' do
      service = test_object.create_credential_service opts
      expect(service.state).to eq("open")
    end
  end

  context '#create_credential_origin_service' do
    it 'creates a Metasploit::Credential::Origin object' do
      opts = {
          address: '192.168.172.3',
          port: 445,
          service_name: 'smb',
          protocol: 'tcp',
          module_fullname: 'auxiliary/scanner/smb/smb_login',
          workspace_id: workspace.id,
          origin_type: :service
      }
      expect { test_object.create_credential_origin_service(opts)}.to change{ Metasploit::Credential::Origin::Service.count }.by(1)
    end

    it 'should return nil if there is no database connection' do
      my_module = test_object
      expect(my_module).to receive(:active_db?).and_return(false)
      expect(my_module.create_credential_origin_service).to be_nil
    end

    context 'when there is a matching host record' do
      it 'uses the existing host record' do
        opts = {
            address: '192.168.172.3',
            port: 445,
            service_name: 'smb',
            protocol: 'tcp',
            module_fullname: 'auxiliary/scanner/smb/smb_login',
            workspace_id: workspace.id,
            origin_type: :service
        }
        FactoryGirl.create(:mdm_host, address: opts[:address], workspace_id: opts[:workspace_id])
        expect { test_object.create_credential_origin_service(opts)}.to_not change{Mdm::Host.count }
      end
    end

    context 'when there is not a matching host record' do
      it 'create a new host record' do
        opts = {
            address: '192.168.172.3',
            port: 445,
            service_name: 'smb',
            protocol: 'tcp',
            module_fullname: 'auxiliary/scanner/smb/smb_login',
            workspace_id: workspace.id,
            origin_type: :service
        }
        expect { test_object.create_credential_origin_service(opts)}.to change{Mdm::Host.count }.by(1)
      end
    end

    context 'when there is a matching service record' do
      it 'uses the existing service record' do
        opts = {
            address: '192.168.172.3',
            port: 445,
            service_name: 'smb',
            protocol: 'tcp',
            module_fullname: 'auxiliary/scanner/smb/smb_login',
            workspace_id: workspace.id,
            origin_type: :service
        }
        host = FactoryGirl.create(:mdm_host, address: opts[:address], workspace_id: opts[:workspace_id])
        FactoryGirl.create(:mdm_service, host_id: host.id, port: opts[:port], proto: opts[:protocol])
        expect { test_object.create_credential_origin_service(opts)}.to_not change{Mdm::Service.count }
      end
    end

    context 'when there is no matching service record' do
      it 'creates a new service record' do
        opts = {
            address: '192.168.172.3',
            port: 445,
            service_name: 'smb',
            protocol: 'tcp',
            module_fullname: 'auxiliary/scanner/smb/smb_login',
            workspace_id: workspace.id,
            origin_type: :service
        }
        expect { test_object.create_credential_origin_service(opts)}.to change{Mdm::Service.count }.by(1)
      end
    end

    context 'when called twice with the same options' do
      it 'does not create duplicate objects' do
        opts = {
            address: '192.168.172.3',
            port: 445,
            service_name: 'smb',
            protocol: 'tcp',
            module_fullname: 'auxiliary/scanner/smb/smb_login',
            workspace_id: workspace.id,
            origin_type: :service
        }
        test_object.create_credential_origin_service(opts)
        expect { test_object.create_credential_origin_service(opts)}.to_not change{ Metasploit::Credential::Origin::Service.count }
      end
    end

    context 'when missing an option' do
      it 'throws a KeyError' do
        opts = {}
        expect{ test_object.create_credential_origin_service(opts)}.to raise_error KeyError
      end
    end
  end

  context '#create_credential_origin_session' do
    it 'creates a Metasploit::Credential::Origin object' do
      opts = {
          post_reference_name: 'windows/gather/hashdump',
          session_id: session.id
      }
      expect { test_object.create_credential_origin_session(opts)}.to change{ Metasploit::Credential::Origin::Session.count }.by(1)
    end

    it 'should return nil if there is no database connection' do
      expect(test_object).to receive(:active_db?).and_return(false)
      expect(test_object.create_credential_origin_session).to be_nil
    end

    context 'when called twice with the same options' do
      it 'does not create duplicate objects' do
        opts = {
            post_reference_name: 'windows/gather/hashdump',
            session_id: session.id
        }
        test_object.create_credential_origin_session(opts)
        expect { test_object.create_credential_origin_session(opts)}.to_not change{ Metasploit::Credential::Origin::Session.count }
      end
    end

    context 'when missing an option' do
      it 'throws a KeyError' do
        opts = {}
        expect{ test_object.create_credential_origin_session(opts) }.to raise_error KeyError
      end
    end
  end

  context '#create_credential_origin' do
    it 'should return nil if there is no database connection' do
      expect(test_object).to receive(:active_db?).and_return(false)
      expect(test_object.create_credential_origin).to be_nil
    end

    it 'calls the correct method to create Origin::Import records' do
      opts = {
          filename: "test_import.xml",
          origin_type: :import,
          task_id: task.id
      }
      my_module = test_object
      expect(my_module).to receive(:create_credential_origin_import)
      my_module.create_credential_origin(opts)
    end

    it 'calls the correct method to create Origin::Manual records' do
      opts = {
          origin_type: :manual,
          user_id: user.id
      }
      my_module = test_object
      expect(my_module).to receive(:create_credential_origin_manual)
      my_module.create_credential_origin(opts)
    end

    it 'calls the correct method to create Origin::Service records' do
      opts = {
          address: '192.168.172.3',
          port: 445,
          service_name: 'smb',
          protocol: 'tcp',
          module_fullname: 'auxiliary/scanner/smb/smb_login',
          workspace_id: workspace.id,
          origin_type: :service
      }
      expect(test_object).to receive(:create_credential_origin_service)
      test_object.create_credential_origin(opts)
    end

    it 'calls the correct method to create Origin::Session records' do
      opts = {
          origin_type: :session,
          post_reference_name: 'windows/gather/hashdump',
          session_id: session.id
      }
      my_module = test_object
      expect(my_module).to receive(:create_credential_origin_session)
      my_module.create_credential_origin(opts)
    end

    it 'raises an exception if there is no origin type' do
      opts = {
          post_reference_name: 'windows/gather/hashdump',
          session_id: session.id
      }
      expect{ test_object.create_credential_origin(opts)}.to raise_error ArgumentError, "Unknown Origin Type "
    end

    it 'raises an exception if given an invalid origin type' do
      opts = {
          origin_type: 'aaaaa',
          post_reference_name: 'windows/gather/hashdump',
          session_id: session.id
      }
      expect{ test_object.create_credential_origin(opts)}.to raise_error ArgumentError, "Unknown Origin Type aaaaa"
    end
  end

  context '#create_credential_realm' do
    it 'creates a Metasploit::Credential::Realm object' do
      opts = {
          realm_key: 'Active Directory Domain',
          realm_value: 'contosso'
      }
      expect { test_object.create_credential_realm(opts)}.to change{ Metasploit::Credential::Realm.count }.by(1)
    end

    it 'should return nil if there is no database connection' do
      expect(test_object).to receive(:active_db?).and_return(false)
      expect(test_object.create_credential_realm).to be_nil
    end

    context 'when called twice with the same options' do
      it 'does not create duplicate objects' do
        opts = {
            realm_key: 'Active Directory Domain',
            realm_value: 'contosso'
        }
        test_object.create_credential_realm(opts)
        expect { test_object.create_credential_realm(opts)}.to_not change{ Metasploit::Credential::Realm.count }
      end
    end

    context 'when missing an option' do
      it 'throws a KeyError' do
        opts = {}
        expect{ test_object.create_credential_origin_manual(opts)}.to raise_error KeyError
      end
    end
  end

  context '#create_credential_private' do
    it 'should return nil if there is no database connection' do
      expect(test_object).to receive(:active_db?).and_return(false)
      expect(test_object.create_credential_private).to be_nil
    end

    context 'when missing an option' do
      it 'throws a KeyError' do
        opts = {}
        expect{ test_object.create_credential_private(opts)}.to raise_error KeyError
      end
    end

    context 'when :private_type is password' do
      it 'creates a Metasploit::Credential::Password' do
        opts = {
            private_data: 'password1',
            private_type: :password
        }
        expect{ test_object.create_credential_private(opts) }.to change{ Metasploit::Credential::Password.count }.by(1)
      end
    end

    context 'when :private_type is sshkey' do
      it 'creates a Metasploit::Credential::SSHKey' do
        opts = {
            private_data: OpenSSL::PKey::RSA.generate(2048).to_s,
            private_type: :ssh_key
        }
        expect{ test_object.create_credential_private(opts) }.to change{ Metasploit::Credential::SSHKey.count }.by(1)
      end
    end

    context 'when :private_type is ntlmhash' do
      it 'creates a Metasploit::Credential::NTLMHash' do
        opts = {
            private_data: Metasploit::Credential::NTLMHash.data_from_password_data('password1'),
            private_type: :ntlm_hash
        }
        expect{ test_object.create_credential_private(opts) }.to change{ Metasploit::Credential::NTLMHash.count }.by(1)
      end
    end

    context 'when :private_type is nonreplayable_hash' do
      it 'creates a Metasploit::Credential::NonreplayableHash' do
        opts = {
            private_data: '10b222970537b97919db36ec757370d2',
            private_type: :nonreplayable_hash
        }
        expect{ test_object.create_credential_private(opts) }.to change{ Metasploit::Credential::NonreplayableHash.count }.by(1)
      end
    end

    context 'when passed a blank string' do
      it 'creates a Metasploit::Credential::BlankPassword' do
        opts = {
          private_data: '',
          private_type: :password
        }
        expect(test_object.create_credential_private(opts)).to be_kind_of Metasploit::Credential::BlankPassword
      end
    end
  end

  context '#create_credential_core' do
    let(:origin)    { FactoryGirl.create(:metasploit_credential_origin_service) }
    let(:public)    { FactoryGirl.create(:metasploit_credential_public)}
    let(:private)   { FactoryGirl.create(:metasploit_credential_password)}
    let(:realm)     { FactoryGirl.create(:metasploit_credential_realm)}
    let(:workspace) { origin.service.host.workspace }
    let(:task)      { FactoryGirl.create(:mdm_task, workspace: workspace) }

    it 'raises a KeyError if any required option is missing' do
      opts = {}
      expect{ test_object.create_credential_core(opts)}.to raise_error KeyError
    end

    it 'returns nil if there is no active database connection' do
      expect(test_object).to receive(:active_db?).and_return(false)
      expect(test_object.create_credential_core).to be_nil
    end

    it 'creates a Metasploit::Credential::Core' do
      opts = {
          origin: origin,
          public: public,
          private: private,
          realm: realm,
          workspace_id: workspace.id
      }
      expect{ test_object.create_credential_core(opts)}.to change{ Metasploit::Credential::Core.count }.by(1)
    end
    it 'associates the new Metasploit::Credential::Core with a task if passed' do
      opts = {
          origin: origin,
          public: public,
          private: private,
          realm: realm,
          workspace_id: workspace.id,
          task_id: task.id
      }
      core = test_object.create_credential_core(opts)
      expect(core.tasks).to include(task)
    end

  end

  context '#create_credential_login' do
    let(:workspace) { FactoryGirl.create(:mdm_workspace) }
    let(:service) { FactoryGirl.create(:mdm_service, host: FactoryGirl.create(:mdm_host, workspace: workspace)) }
    let(:task) { FactoryGirl.create(:mdm_task, workspace: workspace) }
    let(:credential_core) { FactoryGirl.create(:metasploit_credential_core_manual, workspace: workspace) }

    it 'creates a Metasploit::Credential::Login' do
      login_data = {
        address: service.host.address,
        port: service.port,
        service_name: service.name,
        protocol: service.proto,
        workspace_id: workspace.id,
        core: credential_core,
        last_attempted_at: DateTime.current,
        status: Metasploit::Model::Login::Status::SUCCESSFUL,
      }
      expect{ test_object.create_credential_login(login_data) }.to change{ Metasploit::Credential::Login.count }.by(1)
    end
    it "associates the Metasploit::Credential::Core with a task if passed" do
      login_data = {
        address: service.host.address,
        port: service.port,
        service_name: service.name,
        protocol: service.proto,
        workspace_id: workspace.id,
        task_id: task.id,
        core: credential_core,
        last_attempted_at: DateTime.current,
        status: Metasploit::Model::Login::Status::SUCCESSFUL,
      }
      login = test_object.create_credential_login(login_data)
      expect(login.tasks).to include(task)

    end

  end

  context '#invalidate_login' do

    context 'when an untried login exists' do
      let(:untried_login) { FactoryGirl.create(:metasploit_credential_login, status: Metasploit::Model::Login::Status::UNTRIED)}

      let(:opts) {{
        address: untried_login.service.host.address.to_s,
        port: untried_login.service.port,
        protocol: untried_login.service.proto,
        username: untried_login.core.public.username,
        private_data: untried_login.core.private.data,
        realm_key: untried_login.core.realm.try(:key),
        realm_value: untried_login.core.realm.try(:value),
        status: Metasploit::Model::Login::Status::INCORRECT
        }}

      it 'sets the supplied status on that login' do
        expect{ test_object.invalidate_login(opts) }.to change{untried_login.reload.status}.from(Metasploit::Model::Login::Status::UNTRIED).to(Metasploit::Model::Login::Status::INCORRECT)
      end

      it 'changes the last_attempted_at timestamp' do
        expect{ test_object.invalidate_login(opts) }.to change{untried_login.reload.last_attempted_at}
      end

      context 'when a login exists on the same service for a different credential' do
        let(:other_origin) {
          FactoryGirl.create(:metasploit_credential_origin_manual)
        }
        let(:other_core) {
          FactoryGirl.create(:metasploit_credential_core,
            workspace: untried_login.core.workspace,
            origin: other_origin
          )
        }
        let(:other_login) {
          FactoryGirl.create(:metasploit_credential_login,
            status: Metasploit::Model::Login::Status::UNTRIED,
            service: untried_login.service,
            core: other_core
          )
        }

        it 'updates the status on the correct login' do
          expect{ test_object.invalidate_login(opts) }.to_not change{other_login.reload.status}
        end
      end
    end
  end

end
