import unittest
import configparser
import struct
import time
from datetime import datetime

from gmsad.gmsa import GMSAState
from gmsad.salt import get_salt_from_heuristic

SAMPLE_CONFIG = """
[sample]
gMSA_sAMAccountName = super_gmsa$
gMSA_domain = WINDOMAIN.LOCAL
gMSA_keytab = /tmp/super_gmsa.keytab
gMSA_enctypes = 24
principal = test_machine@WINDOMAIN.LOCAL
keytab = /tmp/test_machine.keytab
"""

SAMPLE_BLOB = b'\x01\x00\x00\x00"\x01\x00\x00\x10\x00\x00\x00\x12\x01\x1a\x01\x91\xea\x93!\xd0\xd7\xcb5X\x12\x98\xdc\xd5X\x07\xa23\x84\x8d\xd1\x1c\xb5\x9c\xcaB\x1a\xe28f\xbfm4\x99\xc2\xe0\x03l\xec\xe7\xaa_\xa1\x0ct\xe3>$G\xed\xf1c\xd9\xcd\xdb\xe5i7\r\x19G\x9d\xd0@;j6L\x86/\x1b\x93d\xf9\xf1=(/1&\xea\xbc\xe6,\x1f\xfc\xfd\xed\x1c=\x93\x11\x88\x134\t\xb8\x8b\xbd\x16wGw_L\x18\xc9\xdf\xd2\x93"\xae\xecv\xbf\x99\xf5\xc7jQ:\xae=E6\x12\x9e\xeb9\xc4#=\xc0"C@\xfb{A\x1c*t\xb0\xb3\xa7\xc6\xbb\xb4r\xfd8:\xec\xc6\x18\x90\xbd\xb0T\xb0]>\xe4Ao\x95[2\xb7\x9dB@\x81vb\xd9\xf20c@o\xa7\xfc\xb5\xc8\xd1Gi\xd6\x08u\xe9\xf8\xa9E\xcd\xd97\xcd3\x9e#\xd7[\x18\xfa\xd7\xae\x1f\x0e6\x04`\xa3\'\x8a\xc4P\x1fR\x86\x02&c\xda\xedR^e\xabs\xb0\x8d\xccU\xeb\x9f\x16\t]Z\xcf\tL\xfb\xad\r\x88s\xb4\xd3\xe6Ob\xa7u\xaa\x00\x00Oa\xc5\xb2\x07\x0e\x00\x00O\x03\xf5\xff\x06\x0e\x00\x00'

SAMPLE_BLOB_CURRENT_PASSWORD = b'\xee\xaa\x91\xe2\x86\x93\xed\x9f\x90\xe3\x97\x8b\xe1\x89\x98\xef\xbf\xbd\xe5\xa3\x95\xea\x88\x87\xe8\x90\xb3\xed\x86\x8d\xeb\x94\x9c\xec\xaa\x9c\xe1\xa9\x82\xe3\xa3\xa2\xeb\xbd\xa6\xe3\x91\xad\xec\x8a\x99\xcf\xa0\xee\xb1\xac\xea\xab\xa7\xea\x85\x9f\xe7\x90\x8c\xe3\xbb\xa3\xe4\x9c\xa4\xef\x87\xad\xef\xbf\xbd\xef\xbf\xbd\xe6\xa7\xa5\xe0\xb4\xb7\xe4\x9c\x99\xed\x82\x9d\xe3\xad\x80\xe3\x99\xaa\xe8\x99\x8c\xe1\xac\xaf\xe6\x92\x93\xef\x87\xb9\xe2\xa0\xbd\xe3\x84\xaf\xee\xa8\xa6\xee\x9a\xbc\xe1\xbc\xac\xef\xb7\xbc\xe1\xb3\xad\xe9\x8c\xbd\xe8\xa0\x91\xe3\x90\x93\xeb\xa0\x89\xeb\xb6\x8b\xe7\x9c\x96\xe7\x9d\x87\xe4\xb1\x9f\xec\xa4\x98\xed\x8b\x9f\xe2\x8a\x93\xee\xb2\xae\xeb\xbd\xb6\xef\x96\x99\xe6\xab\x87\xe3\xa9\x91\xe3\xb6\xae\xe3\x99\x85\xe9\xb8\x92\xe3\xa7\xab\xe2\x8f\x84\xec\x80\xbd\xe4\x8c\xa2\xef\xad\x80\xe4\x85\xbb\xe2\xa8\x9c\xeb\x81\xb4\xea\x9e\xb3\xeb\xaf\x86\xe7\x8a\xb4\xe3\xa3\xbd\xee\xb0\xba\xe1\xa3\x86\xeb\xb6\x90\xe5\x92\xb0\xe5\xb6\xb0\xee\x90\xbe\xe6\xbd\x81\xe5\xae\x95\xeb\x9c\xb2\xe4\x8a\x9d\xe8\x85\x80\xe6\x89\xb6\xef\x8b\x99\xe6\x8c\xb0\xe6\xbd\x80\xef\xb2\xa7\xec\xa2\xb5\xe4\x9f\x91\xed\x99\xa9\xe7\x94\x88\xef\xa3\xa9\xe4\x96\xa9\xef\xbf\xbd\xec\xb4\xb7\xe9\xb8\xb3\xed\x9c\xa3\xe1\xa1\x9b\xed\x9f\xba\xe1\xbe\xae\xe3\x98\x8e\xe6\x80\x84\xe2\x9e\xa3\xec\x92\x8a\xe1\xbd\x90\xe8\x99\x92\xe2\x98\x82\xef\xbf\xbd\xe5\x8b\xad\xe6\x95\x9e\xe7\x8e\xab\xe8\xb6\xb0\xe5\x97\x8c\xe9\xbf\xab\xe0\xa4\x96\xe5\xa9\x9d\xe0\xa7\x8f\xef\xad\x8c\xe0\xb6\xad\xe7\x8e\x88\xed\x8e\xb4\xe4\xbf\xa6\xea\x9d\xa2\xea\xa9\xb5'

SAMPLE_BLOB_2 = b'\x01\x00\x00\x00$\x02\x00\x00\x10\x00\x12\x01\x14\x02\x1c\x02\x99\xfd\xcafo\xf6\xc7t\x1d\x05\xc5\x1b\xef\xfd\x06\x03\xb8\xdbtln\xe51y\xa4\xc7qV\xfd$\x9f\x97PF\xe6E\x0b\xa3\xfe\xed\xfa\x19\x14,\xad\xdb\xa0\x86\x90\xc9\xe6\xd0\t\xf0\x8b-,\x14~\x02(E*\xbaGF\xa8\x97\xb0\x88\xe9\xab\xfd\x02\xe2\x9bs\x80\xbbf\x96\xfbM\xdb\x8a\xc3\xd2A\x03\x86\xc9\xdcY\xe6\xa97\xaa\x8e\x1d;S\xa7\xab@nc\x87\xdb\xa2\x08\x9e&X\xdb\xd4\x1ad\xb7\xac\xc5\x1d\x967\x94\xe9\x8aP\xfb\xc1f|\x18\x05\xd9\xa8\x08B\xcd\x07\x15}\xa7\x166SBl\x1d\xa7\xdd)\x96I\x05\x83\xbd}\xf2(\xb7\x0b\x03\xd0\xba_G\xf4e\xed\xca\xb3\x16R\xdd@w\xc4\x9b?2\xfa\x16\xb9\xcc\x9e\x99\xa8*\xe4\xa9\xbb\xba\xdbT\x1e\xd3%\xbf\xb8MUsv\x132H\x01l|\x99\xb1\xc3(3c\x08*\x1d\xfe\x88`\x05[\n\x1e\xff\xf9\xef\xd5\xe5\xd5\xe2{G%^@\x98\xd9\x10\x98!0\xaej\xf0\xaa\xdel}@\x16)\xa7\x04\x8f\x00\x005\x0e\xd8\xeeW\x92\x1a\x93\xe9\x07\xe6H\xba\x1e\xd8\xb0\x94 T\x9e!T;\x90\x81x4\xed\xf3\x08\x1b\x02\xfb\xb1^\x1c:\xba\x0b[\x8f\x93k?(<T\xe8\x89\xd4E:g\xaa\xc5B\x96\xb1zq\x93\x9cXg\x14\xae\xc5JZ"\xb1~\xf4\xe6>5\x13\x8e\x9af\x902\x04\x15\xd2\xdauU\xc5\x8auSG\x06B\x9d\xbc\x92/\x87\x97\xd0\x07\xd4\x05\xdfC\x88\xf8;/*\xc9\xb7\tB\xd2\xd2\x9e\xaf\x97\xcbh\x81o\x1f\xeb\xdc\x9d\x8e\xf1\xd2\x1f\xf2\xba\x1bD\xd1hb\xac\xb0+\xe8\xdc\xf9\xf4\x8fB\x1ev\x8a\xd1j\xb7*\x14j@\x18\xdco\xab\xba\xd6z\xe7`P\x97I{\x173\x9d=\xf0\xe1\x06\x01K3\xdd{>L\xa7\x9b6\x82\x90\xf0\x9a\xe9\xd5\xda\x8f\xe1\xbaD\xc7\xae\xf7\xeb3 \xeeb*\x94wR\x0c\x8dsn\xe3\x18\xfa\xf1.{p\xb5\xe0\x84\x9a\x92\x83\xe7ao%\xbd\xb0\xed!\xc9Zb{\xb8)*\xa6v\xc4\x82\x8a\x98\xa1k:\x9d\x8c0\x00\x00\xd3W`\x8b\x97\x00\x00\x00\xd3\xf9\x8f\xd8\x96\x00\x00\x00'

SAMPLE_BLOB_2_CURRENT_PASSWORD = b'\xef\xb6\x99\xe6\x9b\x8a\xef\x99\xaf\xe7\x93\x87\xd4\x9d\xe1\xaf\x85\xef\xb7\xaf\xcc\x86\xef\xbf\xbd\xe6\xb1\xb4\xee\x95\xae\xe7\xa4\xb1\xec\x9e\xa4\xe5\x99\xb1\xe2\x93\xbd\xe9\x9e\x9f\xe4\x99\x90\xe4\x97\xa6\xea\x8c\x8b\xee\xb7\xbe\xe1\xa7\xba\xe2\xb0\x94\xef\xbf\xbd\xe8\x9a\xa0\xec\xa6\x90\xed\x83\xa6\xef\x80\x89\xe2\xb6\x8b\xe1\x90\xac\xc9\xbe\xe4\x94\xa8\xeb\xa8\xaa\xe4\x99\x87\xe9\x9e\xa8\xe8\xa2\xb0\xea\xaf\xa9\xcb\xbd\xe9\xaf\xa2\xe8\x81\xb3\xe6\x9a\xbb\xef\xae\x96\xef\xbf\xbd\xec\x8e\x8a\xe4\x87\x92\xe8\x98\x83\xef\xbf\xbd\xee\x99\x99\xe3\x9e\xa9\xe8\xba\xaa\xe3\xac\x9d\xea\x9d\x93\xe4\x82\xab\xe6\x8d\xae\xef\xbf\xbd\xe0\xa2\xa2\xe2\x9a\x9e\xef\xbf\xbd\xe1\xab\x94\xeb\x9d\xa4\xec\x96\xac\xe9\x98\x9d\xe9\x90\xb7\xe8\xab\xa9\xef\xad\x90\xe6\x9b\x81\xe1\xa1\xbc\xef\xbf\xbd\xe0\xa2\xa8\xec\xb5\x82\xe1\x94\x87\xea\x9d\xbd\xe3\x98\x96\xe4\x89\x93\xe1\xb5\xac\xef\xbf\xbd\xe9\x98\xa9\xd5\x89\xeb\xb6\x83\xef\x89\xbd\xeb\x9c\xa8\xcc\x8b\xeb\xab\x90\xe4\x9d\x9f\xe6\x97\xb4\xec\xab\xad\xe1\x9a\xb3\xef\xbf\xbd\xe7\x9d\x80\xe9\xaf\x84\xe3\x88\xbf\xe1\x9b\xba\xec\xb2\xb9\xe9\xa6\x9e\xe2\xaa\xa8\xea\xa7\xa4\xeb\xaa\xbb\xe5\x93\x9b\xed\x8c\x9e\xeb\xbc\xa5\xe4\xb6\xb8\xe7\x8d\x95\xe1\x8d\xb6\xe4\xa0\xb2\xe6\xb0\x81\xe9\xa5\xbc\xec\x8e\xb1\xe3\x8c\xa8\xe0\xa1\xa3\xe1\xb4\xaa\xe8\xa3\xbe\xd5\xa0\xe0\xa9\x9b\xef\xbc\x9e\xee\xbf\xb9\xee\x97\x95\xee\x8b\x95\xe4\x9d\xbb\xe5\xb8\xa5\xe9\xa1\x80\xe1\x83\x99\xe2\x86\x98\xea\xb8\xb0\xef\x81\xaa\xef\xbf\xbd\xe7\xb5\xac\xe1\x99\x80\xea\x9c\xa9\xe8\xbc\x84'

SAMPLE_BLOB_2_PREVIOUS_PASSWORD = b'\xe0\xb8\xb5\xee\xbb\x98\xe9\x89\x97\xe9\x8c\x9a\xdf\xa9\xe4\xa3\xa6\xe1\xba\xba\xeb\x83\x98\xe2\x82\x94\xe9\xb9\x94\xe5\x90\xa1\xe9\x80\xbb\xe7\xa2\x81\xee\xb4\xb4\xe0\xa3\xb3\xc8\x9b\xeb\x87\xbb\xe1\xb1\x9e\xeb\xa8\xba\xe5\xac\x8b\xe9\x8e\x8f\xe3\xbd\xab\xe3\xb0\xa8\xee\xa1\x94\xed\x92\x89\xe3\xa9\x85\xea\xa9\xa7\xe4\x8b\x85\xeb\x86\x96\xe7\x85\xba\xe9\xb2\x93\xe6\x9d\x98\xea\xb8\x94\xe4\xab\x85\xe2\x89\x9a\xe7\xba\xb1\xee\x9b\xb4\xe3\x94\xbe\xe8\xb8\x93\xe6\x9a\x9a\xe3\x8a\x90\xe1\x94\x84\xef\xbf\xbd\xe5\x95\xb5\xe8\xab\x85\xe5\x8d\xb5\xd9\x87\xe9\xb5\x82\xe9\x8a\xbc\xe8\x9c\xaf\xed\x82\x97\xed\x90\x87\xef\xbf\xbd\xe8\xa1\x83\xe3\xaf\xb8\xe2\xa8\xaf\xeb\x9f\x89\xe4\x88\x89\xed\x8b\x92\xea\xbe\x9e\xec\xae\x97\xe8\x85\xa8\xe1\xbd\xaf\xef\xbf\xbd\xe8\xba\x9d\xed\x8b\xb1\xef\x88\x9f\xe1\xae\xba\xed\x85\x84\xe6\x89\xa8\xeb\x82\xac\xee\xa0\xab\xef\xa7\x9c\xe8\xbf\xb4\xe1\xb9\x82\xe8\xa9\xb6\xe6\xab\x91\xe2\xaa\xb7\xe6\xa8\x94\xe1\xa1\x80\xe6\xbf\x9c\xeb\xaa\xab\xe7\xab\x96\xe6\x83\xa7\xe9\x9d\x90\xe7\xad\x89\xe3\x8c\x97\xe3\xb6\x9d\xee\x87\xb0\xc4\x86\xe3\x8d\x8b\xe7\xaf\x9d\xe4\xb0\xbe\xe9\xae\xa7\xe8\x88\xb6\xef\x82\x90\xee\xa6\x9a\xef\xbf\xbd\xee\x86\x8f\xe4\x92\xba\xea\xbb\x87\xee\xaf\xb7\xe2\x80\xb3\xe6\x8b\xae\xe9\x90\xaa\xe5\x89\xb7\xe8\xb4\x8c\xe6\xb9\xb3\xe1\xa3\xa3\xef\x87\xba\xe7\xac\xae\xeb\x95\xb0\xe8\x93\xa0\xe9\x8a\x9a\xee\x9e\x83\xe6\xbd\xa1\xeb\xb4\xa5\xee\xb6\xb0\xec\xa4\xa1\xe6\x89\x9a\xeb\xa1\xbb\xe2\xa8\xa9\xe7\x9a\xa6\xe8\x8b\x84\xe9\xa2\x8a\xe6\xae\xa1\xe9\xb4\xba\xe3\x82\x8c'

class TestGMSA(unittest.TestCase):

    def setUp(self):
        config = configparser.ConfigParser()
        config.read_string(SAMPLE_CONFIG)
        keytabs = {}
        self.gmsa_state = GMSAState(config["sample"], keytabs)

    def test_managedpassword_too_short(self):
        blob = bytes([0]*31)
        with self.assertLogs(level="ERROR"):
            with self.assertRaises(ValueError):
                self.gmsa_state.parse_managedpassword_blob(blob)

    def test_managedpassword_invalid_version(self):
        blob = bytearray(SAMPLE_BLOB)
        version = 2
        blob[0], blob[1] = struct.pack('<H', version)

        with self.assertLogs(level="ERROR"):
            with self.assertRaises(ValueError):
                self.gmsa_state.parse_managedpassword_blob(blob)

    def test_managed_password_invalid_length(self):
        blob = SAMPLE_BLOB[0:len(SAMPLE_BLOB)-1]

        with self.assertLogs(level="ERROR"):
            with self.assertRaises(ValueError):
                self.gmsa_state.parse_managedpassword_blob(blob)

        blob = SAMPLE_BLOB + b'0'

        with self.assertLogs(level="ERROR"):
            with self.assertRaises(ValueError):
                self.gmsa_state.parse_managedpassword_blob(blob)

    def test_managed_password_only_current(self):
        self.gmsa_state.parse_managedpassword_blob(SAMPLE_BLOB)
        now = time.time()

        self.assertEqual(
            self.gmsa_state.query_password_date,
            datetime.fromtimestamp(int(now + 1542622.683989)).astimezone())

        self.assertEqual(
            self.gmsa_state.unchanged_password_date,
            datetime.fromtimestamp(int(now + 1542322.683989)).astimezone())

        self.assertEqual(
            self.gmsa_state.current_password,
            SAMPLE_BLOB_CURRENT_PASSWORD)

        self.assertEqual(
            self.gmsa_state.previous_password,
            bytes())

    def test_managed_password_current_previous(self):
        self.gmsa_state.parse_managedpassword_blob(SAMPLE_BLOB_2)
        now = time.time()

        self.assertEqual(
            self.gmsa_state.query_password_date,
            datetime.fromtimestamp(int(now + 65087.840866)).astimezone())

        self.assertEqual(
            self.gmsa_state.unchanged_password_date,
            datetime.fromtimestamp(int(now + 64787.840866)).astimezone())

        self.assertEqual(
            self.gmsa_state.current_password,
            SAMPLE_BLOB_2_CURRENT_PASSWORD)

        self.assertEqual(
            self.gmsa_state.previous_password,
            SAMPLE_BLOB_2_PREVIOUS_PASSWORD)

    def test_salt(self):
        self.assertEqual(
            get_salt_from_heuristic(
                self.gmsa_state.config['gMSA_sAMAccountName'],
                self.gmsa_state.config['gMSA_domain']),
            "WINDOMAIN.LOCALhostsuper_gmsa.windomain.local")
