"""
/*
 * This file is part of the Pico Fido distribution (https://github.com/polhenarejos/pico-fido).
 * Copyright (c) 2022 Pol Henarejos.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
"""


import pytest
from fido2.ctap2.extensions import CredProtectExtension
from fido2.webauthn import UserVerificationRequirement
from fido2.ctap import CtapError
from utils import generate_random_user

class CredProtect:
    UserVerificationOptional = 1
    UserVerificationOptionalWithCredentialId = 2
    UserVerificationRequired = 3

@pytest.fixture(scope="class")
def MCCredProtectOptional(resetdevice):
    res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL})['res'].attestation_object
    return res

@pytest.fixture(scope="class")
def MCCredProtectOptionalList(resetdevice):
    res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST})['res'].attestation_object
    return res

@pytest.fixture(scope="class")
def MCCredProtectRequired(resetdevice):
    res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED})['res'].attestation_object
    return res

class TestCredProtect(object):
    def test_credprotect_make_credential_1(self, MCCredProtectOptional):
        assert MCCredProtectOptional.auth_data.extensions
        assert "credProtect" in MCCredProtectOptional.auth_data.extensions
        assert MCCredProtectOptional.auth_data.extensions["credProtect"] == 1

    def test_credprotect_make_credential_2(self, MCCredProtectOptionalList):
        assert MCCredProtectOptionalList.auth_data.extensions
        assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions
        assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2

    def test_credprotect_make_credential_3(self, MCCredProtectRequired):
        assert MCCredProtectRequired.auth_data.extensions
        assert "credProtect" in MCCredProtectRequired.auth_data.extensions
        assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3

    def test_credprotect_optional_excluded(self, device, MCCredProtectOptional):
        """ CredProtectOptional Cred should be visible to be excluded with no UV """
        exclude_list = [
            {
                "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
                "type": "public-key",
            }
        ]

        with pytest.raises(CtapError) as e:
            device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list)

        assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED

    def test_credprotect_optional_list_excluded(self, device, MCCredProtectOptionalList):
        """ CredProtectOptionalList Cred should be visible to be excluded with no UV """
        exclude_list = [
            {
                "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
                "type": "public-key",
            }
        ]

        with pytest.raises(CtapError) as e:
            device.MC(options={'rk': True}, extensions={'credProtect': CredProtect.UserVerificationOptionalWithCredentialId}, exclude_list=exclude_list)

        assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED

    def test_credprotect_required_not_excluded_with_no_uv(self, device, MCCredProtectRequired):
        """ CredProtectRequired Cred should NOT be visible to be excluded with no UV """
        exclude_list = [
            {
                "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
                "type": "public-key",
            }
        ]

        # works
        device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list)

    def test_credprotect_optional_works_with_no_allowList_no_uv(self, device, MCCredProtectOptional):

        # works
        res = device.doGA()['res'].get_assertions()[0]

        # If there's only one credential, this is None
        assert res.number_of_credentials == None

    def test_credprotect_optional_and_list_works_no_uv(self, device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired):
        allow_list = [
            {
                "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
                "type": "public-key",
            },
            {
                "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
                "type": "public-key",
            },
            {
                "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
                "type": "public-key",
            },
        ]
        # works
        res1 = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()[0]
        assert res1.number_of_credentials in (None, 2)

        results = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()

        # the required credProtect is not returned.
        for res in results:
            assert res.credential["id"] != MCCredProtectRequired.auth_data.credential_data.credential_id[:]

    def test_hmac_secret_and_credProtect_make_credential(self, resetdevice, MCCredProtectOptional):

        res = resetdevice.doMC(extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL, 'hmacCreateSecret': True})['res'].attestation_object

        for ext in ["credProtect", "hmac-secret"]:
            assert res.auth_data.extensions
            assert ext in res.auth_data.extensions
            assert res.auth_data.extensions[ext] == True

class TestCredProtectUv:
    def test_credprotect_all_with_uv(self, device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired, client_pin):
        allow_list = [
            {
                "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
                "type": "public-key",
            },
            {
                "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
                "type": "public-key",
            },
            {
                "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
                "type": "public-key",
            },
        ]

        pin = "12345678"

        client_pin.set_pin(pin)

        res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0]

        assert res1.number_of_credentials in (None, 3)

