#!/usr/bin/python
# -*- coding: utf-8 -*-
"""Tests for the Security Account Manager (SAM) collector."""

import unittest

from dfwinreg import definitions as dfwinreg_definitions
from dfwinreg import fake as dfwinreg_fake
from dfwinreg import registry as dfwinreg_registry

from winregrc import errors
from winregrc import output_writer
from winregrc import sam

from tests import test_lib as shared_test_lib


_F_VALUE_DATA = bytes(bytearray([
    0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xd2, 0x12, 0xa1,
    0xfc, 0x88, 0xcb, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x46, 0x8f, 0x64, 0xcc, 0xfd, 0x88, 0xcb, 0x01, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0xf4, 0x01, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x11, 0x02, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x88, 0x52, 0x35, 0x00]))

_V_VALUE_DATA = bytes(bytearray([
    0x00, 0x00, 0x00, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00,
    0xbc, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0xd8, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x44, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x44, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x44, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x44, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x44, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x44, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x44, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x44, 0x01, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00,
    0x5c, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
    0x64, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x68, 0x01, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x7c, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x80, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x01, 0x00, 0x14, 0x80, 0x9c, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00,
    0x14, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x02, 0x00, 0x30, 0x00,
    0x02, 0x00, 0x00, 0x00, 0x02, 0xc0, 0x14, 0x00, 0x44, 0x00, 0x05, 0x01,
    0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
    0x02, 0xc0, 0x14, 0x00, 0xff, 0xff, 0x1f, 0x00, 0x01, 0x01, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x05, 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x58, 0x00,
    0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x5b, 0x03, 0x02, 0x00,
    0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x18, 0x00, 0xff, 0x07, 0x0f, 0x00, 0x01, 0x02, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x05, 0x20, 0x00, 0x00, 0x00, 0x20, 0x02, 0x00, 0x00,
    0x00, 0x00, 0x24, 0x00, 0x44, 0x00, 0x02, 0x00, 0x01, 0x05, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x05, 0x15, 0x00, 0x00, 0x00, 0x9b, 0x7d, 0xee, 0x68,
    0xf4, 0xd1, 0xe6, 0x5e, 0xe5, 0xbd, 0xa3, 0x09, 0xf4, 0x01, 0x00, 0x00,
    0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x20, 0x00, 0x00, 0x00,
    0x20, 0x02, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
    0x20, 0x00, 0x00, 0x00, 0x20, 0x02, 0x00, 0x00, 0x41, 0x00, 0x64, 0x00,
    0x6d, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x73, 0x00, 0x74, 0x00,
    0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x18, 0x00,
    0x42, 0x00, 0x75, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x74, 0x00, 0x2d, 0x00,
    0x69, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x61, 0x00, 0x63, 0x00, 0x63, 0x00,
    0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x20, 0x00, 0x66, 0x00,
    0x6f, 0x00, 0x72, 0x00, 0x20, 0x00, 0x61, 0x00, 0x64, 0x00, 0x6d, 0x00,
    0x69, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x73, 0x00, 0x74, 0x00, 0x65, 0x00,
    0x72, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, 0x74, 0x00,
    0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00,
    0x70, 0x00, 0x75, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2f, 0x00,
    0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x00,
    0x01, 0x02, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00,
    0x03, 0x00, 0x01, 0x00, 0xd8, 0x31, 0x5b, 0x1f, 0x33, 0x69, 0x27, 0x07,
    0x1f, 0xda, 0xf2, 0x03, 0xec, 0x2c, 0xf6, 0x84, 0x03, 0x00, 0x01, 0x00,
    0x03, 0x00, 0x01, 0x00]))


class TestOutputWriter(output_writer.StdoutOutputWriter):
  """Output writer for testing.

  Attributes:
    user_accounts (list[UserAccount]): user accounts.
  """

  def __init__(self):
    """Initializes an output writer object."""
    super(TestOutputWriter, self).__init__()
    self.user_accounts = []

  def WriteUserAccount(self, user_account):
    """Writes an user account to stdout.

    Args:
      user_account: the user account to write.
    """
    self.user_accounts.append(user_account)


class SecurityAccountManagerDataParserTest(shared_test_lib.BaseTestCase):
  """Tests for the Security Account Manager (SAM) data parser."""

  def testParseFValue(self):
    """Tests the ParseFValue function."""
    user_account = sam.UserAccount()

    data_parser = sam.SecurityAccountManagerDataParser()

    data_parser.ParseFValue(_F_VALUE_DATA, user_account)

    self.assertEqual(user_account.last_login_time, 129347632925692440)
    self.assertEqual(user_account.last_password_set_time, 129347637947436870)
    self.assertEqual(user_account.account_expiration_time, 0)
    self.assertEqual(user_account.last_password_failure_time, 0)
    self.assertEqual(user_account.rid, 500)
    self.assertEqual(user_account.primary_gid, 513)
    self.assertEqual(user_account.user_account_control_flags, 0x211)
    self.assertEqual(user_account.codepage, 0)
    self.assertEqual(user_account.number_of_password_failures, 0)
    self.assertEqual(user_account.number_of_logons, 6)

    with self.assertRaises(errors.ParseError):
      data_parser.ParseFValue(b'', user_account)

    # TODO: add bogus data tests.

  def testParseVValue(self):
    """Tests the ParseVValue function."""
    user_account = sam.UserAccount()

    data_parser = sam.SecurityAccountManagerDataParser()

    data_parser.ParseVValue(_V_VALUE_DATA, user_account)

    self.assertEqual(user_account.username, u'Administrator')

    # TODO: tests other values set by ParseVValue.

    with self.assertRaises(errors.ParseError):
      data_parser.ParseVValue(b'', user_account)

    # TODO: add bogus data tests.

  # TODO: add more tests.


class SecurityAccountManagerCollectorTest(shared_test_lib.BaseTestCase):
  """Tests for the Security Account Manager (SAM) collector."""

  _RID = u'000001F4'

  def _CreateTestRegistry(self):
    """Creates Registry keys and values for testing.

    Returns:
      dfwinreg.WinRegistry: Windows Registry for testing.
    """
    key_path_prefix = u'HKEY_LOCAL_MACHINE\\SAM'

    registry_file = dfwinreg_fake.FakeWinRegistryFile(
        key_path_prefix=key_path_prefix)

    registry_key = dfwinreg_fake.FakeWinRegistryKey(u'Users')
    registry_file.AddKeyByPath(u'\\SAM\\Domains\\Account', registry_key)

    subkey = dfwinreg_fake.FakeWinRegistryKey(self._RID)
    registry_key.AddSubkey(subkey)

    registry_value = dfwinreg_fake.FakeWinRegistryValue(
        u'F', data=_F_VALUE_DATA, data_type=dfwinreg_definitions.REG_BINARY)
    subkey.AddValue(registry_value)

    registry_value = dfwinreg_fake.FakeWinRegistryValue(
        u'V', data=_V_VALUE_DATA, data_type=dfwinreg_definitions.REG_BINARY)
    subkey.AddValue(registry_value)

    registry_file.Open(None)

    registry = dfwinreg_registry.WinRegistry()
    registry.MapFile(key_path_prefix, registry_file)
    return registry

  def testCollect(self):
    """Tests the Collect function."""
    registry = self._CreateTestRegistry()

    collector_object = sam.SecurityAccountManagerCollector()

    test_output_writer = TestOutputWriter()
    collector_object.Collect(registry, test_output_writer)
    test_output_writer.Close()

    self.assertEqual(len(test_output_writer.user_accounts), 1)

    user_account = test_output_writer.user_accounts[0]
    self.assertIsNotNone(user_account)
    self.assertEqual(user_account.username, u'Administrator')

  def testCollectEmpty(self):
    """Tests the Collect function on an empty Registry."""
    registry = dfwinreg_registry.WinRegistry()

    collector_object = sam.SecurityAccountManagerCollector()

    test_output_writer = TestOutputWriter()
    collector_object.Collect(registry, test_output_writer)
    test_output_writer.Close()

    self.assertEqual(len(test_output_writer.user_accounts), 0)


if __name__ == '__main__':
  unittest.main()
