mirror of
https://github.com/certbot/certbot.git
synced 2026-08-02 19:31:51 +02:00
100% test coverage, account, auth_handler
This commit is contained in:
@@ -79,7 +79,7 @@ class AuthHandler(object):
|
|||||||
self._respond(cont_resp, dv_resp, best_effort)
|
self._respond(cont_resp, dv_resp, best_effort)
|
||||||
|
|
||||||
# Just make sure all decisions are complete.
|
# Just make sure all decisions are complete.
|
||||||
self._verify_authzr_complete()
|
self.verify_authzr_complete()
|
||||||
# Only return valid authorizations
|
# Only return valid authorizations
|
||||||
return [authzr for authzr in self.authzr.values()
|
return [authzr for authzr in self.authzr.values()
|
||||||
if authzr.body.status == messages2.STATUS_VALID]
|
if authzr.body.status == messages2.STATUS_VALID]
|
||||||
@@ -112,8 +112,7 @@ class AuthHandler(object):
|
|||||||
logging.critical("Failure in setting up challenges.")
|
logging.critical("Failure in setting up challenges.")
|
||||||
logging.info("Attempting to clean up outstanding challenges...")
|
logging.info("Attempting to clean up outstanding challenges...")
|
||||||
self._cleanup_challenges()
|
self._cleanup_challenges()
|
||||||
raise errors.AuthorizationError(
|
raise
|
||||||
"Unable to perform challenges")
|
|
||||||
|
|
||||||
assert len(cont_resp) == len(self.cont_c)
|
assert len(cont_resp) == len(self.cont_c)
|
||||||
assert len(dv_resp) == len(self.dv_c)
|
assert len(dv_resp) == len(self.dv_c)
|
||||||
@@ -159,12 +158,14 @@ class AuthHandler(object):
|
|||||||
|
|
||||||
return active_achalls
|
return active_achalls
|
||||||
|
|
||||||
def _poll_challenges(self, chall_update, best_effort, min_sleep=3):
|
def _poll_challenges(
|
||||||
|
self, chall_update, best_effort, min_sleep=3, max_rounds=15):
|
||||||
"""Wait for all challenge results to be determined."""
|
"""Wait for all challenge results to be determined."""
|
||||||
dom_to_check = set(chall_update.keys())
|
dom_to_check = set(chall_update.keys())
|
||||||
comp_domains = set()
|
comp_domains = set()
|
||||||
|
rounds = 0
|
||||||
|
|
||||||
while dom_to_check:
|
while dom_to_check and rounds < max_rounds:
|
||||||
# TODO: Use retry-after...
|
# TODO: Use retry-after...
|
||||||
time.sleep(min_sleep)
|
time.sleep(min_sleep)
|
||||||
for domain in dom_to_check:
|
for domain in dom_to_check:
|
||||||
@@ -181,13 +182,13 @@ class AuthHandler(object):
|
|||||||
# Right now... just assume a loss and carry on...
|
# Right now... just assume a loss and carry on...
|
||||||
if best_effort:
|
if best_effort:
|
||||||
comp_domains.add(domain)
|
comp_domains.add(domain)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise errors.AuthorizationError(
|
raise errors.AuthorizationError(
|
||||||
"Failed Authorization procedure for %s" % domain)
|
"Failed Authorization procedure for %s" % domain)
|
||||||
|
|
||||||
dom_to_check -= comp_domains
|
dom_to_check -= comp_domains
|
||||||
comp_domains.clear()
|
comp_domains.clear()
|
||||||
|
rounds += 1
|
||||||
|
|
||||||
def _handle_check(self, domain, achalls):
|
def _handle_check(self, domain, achalls):
|
||||||
"""Returns tuple of ('completed', 'failed')."""
|
"""Returns tuple of ('completed', 'failed')."""
|
||||||
@@ -226,7 +227,7 @@ class AuthHandler(object):
|
|||||||
"""
|
"""
|
||||||
for authzr_challb in authzr.body.challenges:
|
for authzr_challb in authzr.body.challenges:
|
||||||
if type(authzr_challb.chall) is type(achall.challb.chall):
|
if type(authzr_challb.chall) is type(achall.challb.chall):
|
||||||
return achall.challb.status
|
return authzr_challb.status
|
||||||
raise errors.AuthorizationError(
|
raise errors.AuthorizationError(
|
||||||
"Target challenge not found in authorization resource")
|
"Target challenge not found in authorization resource")
|
||||||
|
|
||||||
@@ -268,7 +269,13 @@ class AuthHandler(object):
|
|||||||
for achall in cont_c:
|
for achall in cont_c:
|
||||||
self.cont_c.remove(achall)
|
self.cont_c.remove(achall)
|
||||||
|
|
||||||
def _verify_authzr_complete(self):
|
def verify_authzr_complete(self):
|
||||||
|
"""Verifies that all authorizations have been decided.
|
||||||
|
|
||||||
|
:returns: Whether all authzr are complete
|
||||||
|
:rtype: bool
|
||||||
|
|
||||||
|
"""
|
||||||
for authzr in self.authzr.values():
|
for authzr in self.authzr.values():
|
||||||
if (authzr.body.status != messages2.STATUS_VALID and
|
if (authzr.body.status != messages2.STATUS_VALID and
|
||||||
authzr.body.status != messages2.STATUS_INVALID):
|
authzr.body.status != messages2.STATUS_INVALID):
|
||||||
@@ -298,34 +305,7 @@ class AuthHandler(object):
|
|||||||
challb = self.authzr[domain].body.challenges[index]
|
challb = self.authzr[domain].body.challenges[index]
|
||||||
chall = challb.chall
|
chall = challb.chall
|
||||||
|
|
||||||
if isinstance(chall, challenges.DVSNI):
|
achall = challb_to_achall(challb, self.account.key, domain)
|
||||||
logging.info(" DVSNI challenge for %s.", domain)
|
|
||||||
achall = achallenges.DVSNI(
|
|
||||||
challb=challb, domain=domain, key=self.account.key)
|
|
||||||
elif isinstance(chall, challenges.SimpleHTTPS):
|
|
||||||
logging.info(" SimpleHTTPS challenge for %s.", domain)
|
|
||||||
achall = achallenges.SimpleHTTPS(
|
|
||||||
challb=challb, domain=domain, key=self.account.key)
|
|
||||||
elif isinstance(chall, challenges.DNS):
|
|
||||||
logging.info(" DNS challenge for %s.", domain)
|
|
||||||
achall = achallenges.DNS(challb=challb, domain=domain)
|
|
||||||
|
|
||||||
elif isinstance(chall, challenges.RecoveryToken):
|
|
||||||
logging.info(" Recovery Token Challenge for %s.", domain)
|
|
||||||
achall = achallenges.RecoveryToken(challb=challb, domain=domain)
|
|
||||||
elif isinstance(chall, challenges.RecoveryContact):
|
|
||||||
logging.info(" Recovery Contact Challenge for %s.", domain)
|
|
||||||
achall = achallenges.RecoveryContact(
|
|
||||||
challb=challb, domain=domain)
|
|
||||||
elif isinstance(chall, challenges.ProofOfPossession):
|
|
||||||
logging.info(" Proof-of-Possession Challenge for %s", domain)
|
|
||||||
achall = achallenges.ProofOfPossession(
|
|
||||||
challb=challb, domain=domain)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise errors.LetsEncryptClientError(
|
|
||||||
"Received unsupported challenge of type: %s",
|
|
||||||
chall.typ)
|
|
||||||
|
|
||||||
if isinstance(chall, challenges.ContinuityChallenge):
|
if isinstance(chall, challenges.ContinuityChallenge):
|
||||||
cont_chall.append(achall)
|
cont_chall.append(achall)
|
||||||
@@ -335,6 +315,53 @@ class AuthHandler(object):
|
|||||||
return cont_chall, dv_chall
|
return cont_chall, dv_chall
|
||||||
|
|
||||||
|
|
||||||
|
def challb_to_achall(challb, key, domain):
|
||||||
|
"""Converts a ChallengeBody object to an AnnotatedChallenge.
|
||||||
|
|
||||||
|
:param challb: ChallengeBody
|
||||||
|
:type challb: :class:`letsencrypt.acme.messages2.ChallengeBody`
|
||||||
|
|
||||||
|
:param key: Key
|
||||||
|
:type key: :class:`letsencrypt.client.le_util.Key`
|
||||||
|
|
||||||
|
:param str domain: Domain of the challb
|
||||||
|
|
||||||
|
:returns: Appropriate AnnotatedChallenge
|
||||||
|
:rtype: :class:`letsencrypt.client.achallenges.AnnotatedChallenge`
|
||||||
|
|
||||||
|
"""
|
||||||
|
chall = challb.chall
|
||||||
|
|
||||||
|
if isinstance(chall, challenges.DVSNI):
|
||||||
|
logging.info(" DVSNI challenge for %s.", domain)
|
||||||
|
return achallenges.DVSNI(
|
||||||
|
challb=challb, domain=domain, key=key)
|
||||||
|
elif isinstance(chall, challenges.SimpleHTTPS):
|
||||||
|
logging.info(" SimpleHTTPS challenge for %s.", domain)
|
||||||
|
return achallenges.SimpleHTTPS(
|
||||||
|
challb=challb, domain=domain, key=key)
|
||||||
|
elif isinstance(chall, challenges.DNS):
|
||||||
|
logging.info(" DNS challenge for %s.", domain)
|
||||||
|
return achallenges.DNS(challb=challb, domain=domain)
|
||||||
|
|
||||||
|
elif isinstance(chall, challenges.RecoveryToken):
|
||||||
|
logging.info(" Recovery Token Challenge for %s.", domain)
|
||||||
|
return achallenges.RecoveryToken(challb=challb, domain=domain)
|
||||||
|
elif isinstance(chall, challenges.RecoveryContact):
|
||||||
|
logging.info(" Recovery Contact Challenge for %s.", domain)
|
||||||
|
return achallenges.RecoveryContact(
|
||||||
|
challb=challb, domain=domain)
|
||||||
|
elif isinstance(chall, challenges.ProofOfPossession):
|
||||||
|
logging.info(" Proof-of-Possession Challenge for %s", domain)
|
||||||
|
return achallenges.ProofOfPossession(
|
||||||
|
challb=challb, domain=domain)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise errors.LetsEncryptClientError(
|
||||||
|
"Received unsupported challenge of type: %s",
|
||||||
|
chall.typ)
|
||||||
|
|
||||||
|
|
||||||
def gen_challenge_path(challbs, preferences, combinations):
|
def gen_challenge_path(challbs, preferences, combinations):
|
||||||
"""Generate a plan to get authority over the identity.
|
"""Generate a plan to get authority over the identity.
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ class Network(object):
|
|||||||
response.links['terms-of-service']['url']
|
response.links['terms-of-service']['url']
|
||||||
if 'terms-of-service' in response.links else terms_of_service)
|
if 'terms-of-service' in response.links else terms_of_service)
|
||||||
|
|
||||||
|
# TODO: Consider removing this check based on spec clarifications #93
|
||||||
if new_authzr_uri is None:
|
if new_authzr_uri is None:
|
||||||
try:
|
try:
|
||||||
new_authzr_uri = response.links['next']['url']
|
new_authzr_uri = response.links['next']['url']
|
||||||
@@ -321,7 +322,7 @@ class Network(object):
|
|||||||
# TODO: Right now Boulder responds with the authorization resource
|
# TODO: Right now Boulder responds with the authorization resource
|
||||||
# instead of a challenge resource... this can be uncommented
|
# instead of a challenge resource... this can be uncommented
|
||||||
# once the error is fixed.
|
# once the error is fixed.
|
||||||
return challb
|
return None
|
||||||
# raise errors.NetworkError('"up" Link header missing')
|
# raise errors.NetworkError('"up" Link header missing')
|
||||||
challr = messages2.ChallengeResource(
|
challr = messages2.ChallengeResource(
|
||||||
authzr_uri=authzr_uri,
|
authzr_uri=authzr_uri,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""Tests for letsencrypt.client.account."""
|
"""Tests for letsencrypt.client.account."""
|
||||||
|
import logging
|
||||||
import mock
|
import mock
|
||||||
import os
|
import os
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
@@ -13,6 +14,7 @@ from letsencrypt.acme import messages2
|
|||||||
|
|
||||||
from letsencrypt.client import account
|
from letsencrypt.client import account
|
||||||
from letsencrypt.client import configuration
|
from letsencrypt.client import configuration
|
||||||
|
from letsencrypt.client import errors
|
||||||
from letsencrypt.client import le_util
|
from letsencrypt.client import le_util
|
||||||
|
|
||||||
from letsencrypt.client.display import util as display_util
|
from letsencrypt.client.display import util as display_util
|
||||||
@@ -22,6 +24,8 @@ class AccountTest(unittest.TestCase):
|
|||||||
"""Tests letsencrypt.client.account.Account."""
|
"""Tests letsencrypt.client.account.Account."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
logging.disable(logging.CRITICAL)
|
||||||
|
|
||||||
self.accounts_dir = tempfile.mkdtemp("accounts")
|
self.accounts_dir = tempfile.mkdtemp("accounts")
|
||||||
self.account_keys_dir = os.path.join(self.accounts_dir, "keys")
|
self.account_keys_dir = os.path.join(self.accounts_dir, "keys")
|
||||||
os.makedirs(self.account_keys_dir, 0o700)
|
os.makedirs(self.account_keys_dir, 0o700)
|
||||||
@@ -51,6 +55,7 @@ class AccountTest(unittest.TestCase):
|
|||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
shutil.rmtree(self.accounts_dir)
|
shutil.rmtree(self.accounts_dir)
|
||||||
|
logging.disable(logging.NOTSET)
|
||||||
|
|
||||||
@mock.patch("letsencrypt.client.account.zope.component.getUtility")
|
@mock.patch("letsencrypt.client.account.zope.component.getUtility")
|
||||||
@mock.patch("letsencrypt.client.account.crypto_util.init_save_key")
|
@mock.patch("letsencrypt.client.account.crypto_util.init_save_key")
|
||||||
@@ -67,6 +72,15 @@ class AccountTest(unittest.TestCase):
|
|||||||
self.assertEqual(acc.key, self.key)
|
self.assertEqual(acc.key, self.key)
|
||||||
self.assertEqual(acc.config, self.config)
|
self.assertEqual(acc.config, self.config)
|
||||||
|
|
||||||
|
@mock.patch("letsencrypt.client.account.zope.component.getUtility")
|
||||||
|
def test_prompts_cancel(self, mock_util):
|
||||||
|
# displayer = display_util.FileDisplay(sys.stdout)
|
||||||
|
# zope.component.provideUtility(displayer)
|
||||||
|
|
||||||
|
mock_util().input.return_value = (display_util.CANCEL, "")
|
||||||
|
|
||||||
|
self.assertTrue(account.Account.from_prompts(self.config) is None)
|
||||||
|
|
||||||
def test_save_from_existing_account(self):
|
def test_save_from_existing_account(self):
|
||||||
self.test_account.save()
|
self.test_account.save()
|
||||||
acc = account.Account.from_existing_account(self.config, self.email)
|
acc = account.Account.from_existing_account(self.config, self.email)
|
||||||
@@ -84,12 +98,25 @@ class AccountTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_partial_properties(self):
|
def test_partial_properties(self):
|
||||||
partial = account.Account(self.config, self.key)
|
partial = account.Account(self.config, self.key)
|
||||||
|
regr_no_authzr_uri = messages2.RegistrationResource(
|
||||||
|
uri="uri",
|
||||||
|
new_authzr_uri=None,
|
||||||
|
terms_of_service="terms_of_service",
|
||||||
|
body=messages2.Registration(
|
||||||
|
recovery_token="recovery_token", agreement="agreement")
|
||||||
|
)
|
||||||
|
partial2 = account.Account(
|
||||||
|
self.config, self.key, regr=regr_no_authzr_uri)
|
||||||
|
|
||||||
self.assertTrue(partial.uri is None)
|
self.assertTrue(partial.uri is None)
|
||||||
self.assertTrue(partial.new_authzr_uri is None)
|
self.assertTrue(partial.new_authzr_uri is None)
|
||||||
self.assertTrue(partial.terms_of_service is None)
|
self.assertTrue(partial.terms_of_service is None)
|
||||||
self.assertTrue(partial.recovery_token is None)
|
self.assertTrue(partial.recovery_token is None)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
partial2.new_authzr_uri,
|
||||||
|
"https://letsencrypt-demo.org/acme/new-authz")
|
||||||
|
|
||||||
def test_partial_account_default(self):
|
def test_partial_account_default(self):
|
||||||
partial = account.Account(self.config, self.key)
|
partial = account.Account(self.config, self.key)
|
||||||
partial.save()
|
partial.save()
|
||||||
@@ -115,9 +142,23 @@ class AccountTest(unittest.TestCase):
|
|||||||
accs = account.Account.get_accounts(self.config)
|
accs = account.Account.get_accounts(self.config)
|
||||||
self.assertEqual(len(accs), 2)
|
self.assertEqual(len(accs), 2)
|
||||||
|
|
||||||
|
def test_get_accounts_no_accounts(self):
|
||||||
|
self.assertEqual(account.Account.get_accounts(
|
||||||
|
mock.Mock(accounts_dir="non-existant")), [])
|
||||||
|
|
||||||
|
def test_failed_existing_account(self):
|
||||||
|
self.assertRaises(
|
||||||
|
errors.LetsEncryptClientError,
|
||||||
|
account.Account.from_existing_account,
|
||||||
|
self.config, "non-existant@email.org")
|
||||||
|
|
||||||
class SafeEmailTest(unittest.TestCase):
|
class SafeEmailTest(unittest.TestCase):
|
||||||
"""Test safe_email."""
|
"""Test safe_email."""
|
||||||
|
def setUp(self):
|
||||||
|
logging.disable(logging.CRITICAL)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
logging.disable(logging.NOTSET)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _call(cls, addr):
|
def _call(cls, addr):
|
||||||
@@ -131,16 +172,16 @@ class SafeEmailTest(unittest.TestCase):
|
|||||||
"abc_def.jdk@hotmail.museum"
|
"abc_def.jdk@hotmail.museum"
|
||||||
]
|
]
|
||||||
for addr in addrs:
|
for addr in addrs:
|
||||||
self.assertTrue(addr, "%s failed." % addr)
|
self.assertTrue(self._call(addr), "%s failed." % addr)
|
||||||
|
|
||||||
def test_invalid_emails(self):
|
def test_invalid_emails(self):
|
||||||
addrs = [
|
addrs = [
|
||||||
"letsencrypt@letsencrypt..org",
|
"letsencrypt@letsencrypt..org",
|
||||||
".tbd.ade@gmail.com",
|
".tbd.ade@gmail.com",
|
||||||
"~/abc_def.jdk@hotmail.museum"
|
"~/abc_def.jdk@hotmail.museum",
|
||||||
]
|
]
|
||||||
for addr in addrs:
|
for addr in addrs:
|
||||||
self.assertTrue(addr, "%s failed." % addr)
|
self.assertFalse(self._call(addr), "%s failed." % addr)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -72,15 +72,11 @@ def gen_combos(challbs):
|
|||||||
|
|
||||||
|
|
||||||
def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name
|
def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name
|
||||||
"""Return ChallengeBody from Challenge.
|
"""Return ChallengeBody from Challenge."""
|
||||||
|
|
||||||
:param str status: "valid", "invalid", "pending"...
|
|
||||||
|
|
||||||
"""
|
|
||||||
kwargs = {
|
kwargs = {
|
||||||
"chall": chall,
|
"chall": chall,
|
||||||
"uri": chall.typ+"_uri",
|
"uri": chall.typ+"_uri",
|
||||||
"status": messages2.Status(status),
|
"status": status,
|
||||||
}
|
}
|
||||||
|
|
||||||
if status == "valid":
|
if status == "valid":
|
||||||
@@ -90,12 +86,12 @@ def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name
|
|||||||
|
|
||||||
|
|
||||||
# Pending ChallengeBody objects
|
# Pending ChallengeBody objects
|
||||||
DVSNI_P = chall_to_challb(DVSNI, "pending")
|
DVSNI_P = chall_to_challb(DVSNI, messages2.STATUS_PENDING)
|
||||||
SIMPLE_HTTPS_P = chall_to_challb(SIMPLE_HTTPS, "pending")
|
SIMPLE_HTTPS_P = chall_to_challb(SIMPLE_HTTPS, messages2.STATUS_PENDING)
|
||||||
DNS_P = chall_to_challb(DNS, "pending")
|
DNS_P = chall_to_challb(DNS, messages2.STATUS_PENDING)
|
||||||
RECOVERY_CONTACT_P = chall_to_challb(RECOVERY_CONTACT, "pending")
|
RECOVERY_CONTACT_P = chall_to_challb(RECOVERY_CONTACT, messages2.STATUS_PENDING)
|
||||||
RECOVERY_TOKEN_P = chall_to_challb(RECOVERY_TOKEN, "pending")
|
RECOVERY_TOKEN_P = chall_to_challb(RECOVERY_TOKEN, messages2.STATUS_PENDING)
|
||||||
POP_P = chall_to_challb(POP, "pending")
|
POP_P = chall_to_challb(POP, messages2.STATUS_PENDING)
|
||||||
|
|
||||||
CHALLENGES_P = [SIMPLE_HTTPS_P, DVSNI_P, DNS_P,
|
CHALLENGES_P = [SIMPLE_HTTPS_P, DVSNI_P, DNS_P,
|
||||||
RECOVERY_CONTACT_P, RECOVERY_TOKEN_P, POP_P]
|
RECOVERY_CONTACT_P, RECOVERY_TOKEN_P, POP_P]
|
||||||
@@ -110,17 +106,18 @@ CONT_CHALLENGES_P = [
|
|||||||
def gen_authzr(authz_status, domain, challs, statuses, combos=True):
|
def gen_authzr(authz_status, domain, challs, statuses, combos=True):
|
||||||
"""Generate an authorization resource.
|
"""Generate an authorization resource.
|
||||||
|
|
||||||
:param str authz_status: "valid", "invalid", "pending"...
|
:param authz_status: Status object
|
||||||
|
:type authz_status: :class:`letsencrypt.acme.messages2.Status`
|
||||||
:param list challs: Challenge objects
|
:param list challs: Challenge objects
|
||||||
:param list statuses: status of each challenge object e.g. "valid"...
|
:param list statuses: status of each challenge object
|
||||||
:param bool combos: Whether or not to add combinations
|
:param bool combos: Whether or not to add combinations
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# pylint: disable=redefined-outer-name
|
# pylint: disable=redefined-outer-name
|
||||||
challbs = [
|
challbs = tuple(
|
||||||
chall_to_challb(chall, status)
|
chall_to_challb(chall, status)
|
||||||
for chall, status in itertools.izip(challs, statuses)
|
for chall, status in itertools.izip(challs, statuses)
|
||||||
]
|
)
|
||||||
authz_kwargs = {
|
authz_kwargs = {
|
||||||
"identifier": messages2.Identifier(
|
"identifier": messages2.Identifier(
|
||||||
typ=messages2.IDENTIFIER_FQDN, value=domain),
|
typ=messages2.IDENTIFIER_FQDN, value=domain),
|
||||||
@@ -128,12 +125,16 @@ def gen_authzr(authz_status, domain, challs, statuses, combos=True):
|
|||||||
}
|
}
|
||||||
if combos:
|
if combos:
|
||||||
authz_kwargs.update({"combinations": gen_combos(challbs)})
|
authz_kwargs.update({"combinations": gen_combos(challbs)})
|
||||||
if authz_status == "valid":
|
if authz_status == messages2.STATUS_VALID:
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
authz_kwargs.update({
|
authz_kwargs.update({
|
||||||
"status": messages2.Status(authz_status),
|
"status": authz_status,
|
||||||
"expires": datetime.datetime(now.year, now.month+1, now.day),
|
"expires": datetime.datetime(now.year, now.month+1, now.day),
|
||||||
})
|
})
|
||||||
|
else:
|
||||||
|
authz_kwargs.update({
|
||||||
|
"status": authz_status,
|
||||||
|
})
|
||||||
|
|
||||||
# pylint: disable=star-args
|
# pylint: disable=star-args
|
||||||
return messages2.AuthorizationResource(
|
return messages2.AuthorizationResource(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import unittest
|
|||||||
import mock
|
import mock
|
||||||
|
|
||||||
from letsencrypt.acme import challenges
|
from letsencrypt.acme import challenges
|
||||||
|
from letsencrypt.acme import messages2
|
||||||
|
|
||||||
from letsencrypt.client import errors
|
from letsencrypt.client import errors
|
||||||
from letsencrypt.client import le_util
|
from letsencrypt.client import le_util
|
||||||
@@ -36,7 +37,8 @@ class ChallengeFactoryTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.dom = "test"
|
self.dom = "test"
|
||||||
self.handler.authzr[self.dom] = acme_util.gen_authzr(
|
self.handler.authzr[self.dom] = acme_util.gen_authzr(
|
||||||
"pending", self.dom, acme_util.CHALLENGES, ["pending"]*6, False)
|
messages2.STATUS_PENDING, self.dom, acme_util.CHALLENGES,
|
||||||
|
[messages2.STATUS_PENDING]*6, False)
|
||||||
|
|
||||||
def test_all(self):
|
def test_all(self):
|
||||||
cont_c, dv_c = self.handler._challenge_factory(self.dom, range(0, 6))
|
cont_c, dv_c = self.handler._challenge_factory(self.dom, range(0, 6))
|
||||||
@@ -55,8 +57,9 @@ class ChallengeFactoryTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_unrecognized(self):
|
def test_unrecognized(self):
|
||||||
self.handler.authzr["failure.com"] = acme_util.gen_authzr(
|
self.handler.authzr["failure.com"] = acme_util.gen_authzr(
|
||||||
"pending", "failure.com",
|
messages2.STATUS_PENDING, "failure.com",
|
||||||
[mock.Mock(chall="chall", typ="unrecognized")], ["pending"])
|
[mock.Mock(chall="chall", typ="unrecognized")],
|
||||||
|
[messages2.STATUS_PENDING])
|
||||||
|
|
||||||
self.assertRaises(errors.LetsEncryptClientError,
|
self.assertRaises(errors.LetsEncryptClientError,
|
||||||
self.handler._challenge_factory, "failure.com", [0])
|
self.handler._challenge_factory, "failure.com", [0])
|
||||||
@@ -145,6 +148,14 @@ class GetAuthorizationsTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(len(authzr), 3)
|
self.assertEqual(len(authzr), 3)
|
||||||
|
|
||||||
|
def test_perform_failure(self):
|
||||||
|
self.mock_net.request_domain_challenges.side_effect = functools.partial(
|
||||||
|
gen_dom_authzr, challs=acme_util.CHALLENGES)
|
||||||
|
self.mock_dv_auth.perform.side_effect = errors.AuthorizationError
|
||||||
|
|
||||||
|
self.assertRaises(errors.AuthorizationError,
|
||||||
|
self.handler.get_authorizations, ["0"])
|
||||||
|
|
||||||
def _get_exp_response(self, domain, path, challs):
|
def _get_exp_response(self, domain, path, challs):
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
exp_resp = [None] * len(challs)
|
exp_resp = [None] * len(challs)
|
||||||
@@ -157,28 +168,129 @@ class GetAuthorizationsTest(unittest.TestCase):
|
|||||||
for dom in self.handler.authzr.keys():
|
for dom in self.handler.authzr.keys():
|
||||||
azr = self.handler.authzr[dom]
|
azr = self.handler.authzr[dom]
|
||||||
self.handler.authzr[dom] = acme_util.gen_authzr(
|
self.handler.authzr[dom] = acme_util.gen_authzr(
|
||||||
"valid", dom, [challb.chall for challb in azr.body.challenges],
|
messages2.STATUS_VALID,
|
||||||
["valid"]*len(azr.body.challenges), azr.body.combinations)
|
dom,
|
||||||
|
[challb.chall for challb in azr.body.challenges],
|
||||||
|
[messages2.STATUS_VALID]*len(azr.body.challenges),
|
||||||
|
azr.body.combinations)
|
||||||
|
|
||||||
|
|
||||||
class PollChallengesTest(unittest.TestCase):
|
class PollChallengesTest(unittest.TestCase):
|
||||||
|
# pylint: disable=protected-access
|
||||||
"""Test poll challenges."""
|
"""Test poll challenges."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
from letsencrypt.client.auth_handler import challb_to_achall
|
||||||
from letsencrypt.client.auth_handler import AuthHandler
|
from letsencrypt.client.auth_handler import AuthHandler
|
||||||
# Account is mocked...
|
|
||||||
|
# Account and network are mocked...
|
||||||
|
self.mock_net = mock.MagicMock()
|
||||||
self.handler = AuthHandler(
|
self.handler = AuthHandler(
|
||||||
None, None, None, mock.Mock(key="mock_key"))
|
None, None, self.mock_net, mock.Mock(key="mock_key"))
|
||||||
|
|
||||||
self.doms = ["0", "1", "2"]
|
self.doms = ["0", "1", "2"]
|
||||||
self.handler.authzr[self.doms[0]] = acme_util.gen_authzr(
|
self.handler.authzr[self.doms[0]] = acme_util.gen_authzr(
|
||||||
"pending", self.doms[0], acme_util.CHALLENGES, ["pending"]*6, False)
|
messages2.STATUS_PENDING, self.doms[0],
|
||||||
|
acme_util.DV_CHALLENGES, [messages2.STATUS_PENDING]*3, False)
|
||||||
|
|
||||||
self.handler.authzr[self.doms[1]] = acme_util.gen_authzr(
|
self.handler.authzr[self.doms[1]] = acme_util.gen_authzr(
|
||||||
"pending", self.doms[1], acme_util.CHALLENGES, ["pending"]*6, False)
|
messages2.STATUS_PENDING, self.doms[1],
|
||||||
|
acme_util.DV_CHALLENGES, [messages2.STATUS_PENDING]*3, False)
|
||||||
|
|
||||||
self.handler.authzr[self.doms[2]] = acme_util.gen_authzr(
|
self.handler.authzr[self.doms[2]] = acme_util.gen_authzr(
|
||||||
"pending", self.doms[2], acme_util.CHALLENGES, ["pending"]*6, False)
|
messages2.STATUS_PENDING, self.doms[2],
|
||||||
|
acme_util.DV_CHALLENGES, [messages2.STATUS_PENDING]*3, False)
|
||||||
|
|
||||||
|
self.chall_update = {}
|
||||||
|
for dom in self.doms:
|
||||||
|
self.chall_update[dom] = [
|
||||||
|
challb_to_achall(challb, "dummy_key", dom)
|
||||||
|
for challb in self.handler.authzr[dom].body.challenges]
|
||||||
|
|
||||||
|
@mock.patch("letsencrypt.client.auth_handler.time")
|
||||||
|
def test_poll_challenges(self, unused_mock_time):
|
||||||
|
self.mock_net.poll.side_effect = self._mock_poll_solve_one_valid
|
||||||
|
self.handler._poll_challenges(self.chall_update, False)
|
||||||
|
|
||||||
|
for authzr in self.handler.authzr.values():
|
||||||
|
self.assertEqual(authzr.body.status, messages2.STATUS_VALID)
|
||||||
|
|
||||||
|
@mock.patch("letsencrypt.client.auth_handler.time")
|
||||||
|
def test_poll_challenges_failure_best_effort(self, unused_mock_time):
|
||||||
|
self.mock_net.poll.side_effect = self._mock_poll_solve_one_invalid
|
||||||
|
self.handler._poll_challenges(self.chall_update, True)
|
||||||
|
|
||||||
|
for authzr in self.handler.authzr.values():
|
||||||
|
self.assertEqual(authzr.body.status, messages2.STATUS_PENDING)
|
||||||
|
|
||||||
|
@mock.patch("letsencrypt.client.auth_handler.time")
|
||||||
|
def test_poll_challenges_failure(self, unused_mock_time):
|
||||||
|
self.mock_net.poll.side_effect = self._mock_poll_solve_one_invalid
|
||||||
|
self.assertRaises(errors.AuthorizationError,
|
||||||
|
self.handler._poll_challenges,
|
||||||
|
self.chall_update, False)
|
||||||
|
|
||||||
|
@mock.patch("letsencrypt.client.auth_handler.time")
|
||||||
|
def test_unable_to_find_challenge_status(self, unused_mock_time):
|
||||||
|
from letsencrypt.client.auth_handler import challb_to_achall
|
||||||
|
self.mock_net.poll.side_effect = self._mock_poll_solve_one_valid
|
||||||
|
self.chall_update[self.doms[0]].append(
|
||||||
|
challb_to_achall(acme_util.RECOVERY_CONTACT_P, "key", self.doms[0]))
|
||||||
|
self.assertRaises(
|
||||||
|
errors.AuthorizationError,
|
||||||
|
self.handler._poll_challenges, self.chall_update, False)
|
||||||
|
|
||||||
|
def test_verify_authzr_failure(self):
|
||||||
|
self.assertRaises(
|
||||||
|
errors.AuthorizationError, self.handler.verify_authzr_complete)
|
||||||
|
|
||||||
|
|
||||||
|
def _mock_poll_solve_one_valid(self, authzr):
|
||||||
|
# Pending here because my dummy script won't change the full status.
|
||||||
|
# Basically it didn't raise an error and it stopped earlier than
|
||||||
|
# Making all challenges invalid which would make mock_poll_solve_one
|
||||||
|
# change authzr to invalid
|
||||||
|
return self._mock_poll_solve_one_chall(authzr, messages2.STATUS_VALID)
|
||||||
|
|
||||||
|
def _mock_poll_solve_one_invalid(self, authzr):
|
||||||
|
return self._mock_poll_solve_one_chall(authzr, messages2.STATUS_INVALID)
|
||||||
|
|
||||||
|
def _mock_poll_solve_one_chall(self, authzr, desired_status):
|
||||||
|
# pylint: disable=no-self-use
|
||||||
|
"""Dummy method that solves one chall at a time to desired_status.
|
||||||
|
|
||||||
|
When all are solved.. it changes authzr.status to desired_status
|
||||||
|
|
||||||
|
"""
|
||||||
|
new_challbs = authzr.body.challenges
|
||||||
|
for challb in authzr.body.challenges:
|
||||||
|
if challb.status != desired_status:
|
||||||
|
new_challbs = tuple(
|
||||||
|
challb_temp if challb_temp != challb
|
||||||
|
else acme_util.chall_to_challb(challb.chall, desired_status)
|
||||||
|
for challb_temp in authzr.body.challenges
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
if all(test_challb.status == desired_status
|
||||||
|
for test_challb in new_challbs):
|
||||||
|
status_ = desired_status
|
||||||
|
else:
|
||||||
|
status_ = authzr.body.status
|
||||||
|
|
||||||
|
new_authzr = messages2.AuthorizationResource(
|
||||||
|
uri=authzr.uri,
|
||||||
|
new_cert_uri=authzr.new_cert_uri,
|
||||||
|
body=messages2.Authorization(
|
||||||
|
identifier=authzr.body.identifier,
|
||||||
|
challenges=new_challbs,
|
||||||
|
combinations=authzr.body.combinations,
|
||||||
|
key=authzr.body.key,
|
||||||
|
contact=authzr.body.contact,
|
||||||
|
status=status_,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return (new_authzr, "response")
|
||||||
|
|
||||||
class GenChallengePathTest(unittest.TestCase):
|
class GenChallengePathTest(unittest.TestCase):
|
||||||
"""Tests for letsencrypt.client.auth_handler.gen_challenge_path.
|
"""Tests for letsencrypt.client.auth_handler.gen_challenge_path.
|
||||||
@@ -328,7 +440,8 @@ def gen_auth_resp(chall_list):
|
|||||||
def gen_dom_authzr(domain, unused_new_authzr_uri, challs):
|
def gen_dom_authzr(domain, unused_new_authzr_uri, challs):
|
||||||
"""Generates new authzr for domains."""
|
"""Generates new authzr for domains."""
|
||||||
return acme_util.gen_authzr(
|
return acme_util.gen_authzr(
|
||||||
"pending", domain, challs, ["pending"]*len(challs))
|
messages2.STATUS_PENDING, domain, challs,
|
||||||
|
[messages2.STATUS_PENDING]*len(challs))
|
||||||
|
|
||||||
|
|
||||||
def gen_path(required, challs):
|
def gen_path(required, challs):
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ class ChooseAccountTest(unittest.TestCase):
|
|||||||
|
|
||||||
@mock.patch("letsencrypt.client.display.ops.util")
|
@mock.patch("letsencrypt.client.display.ops.util")
|
||||||
def test_one(self, mock_util):
|
def test_one(self, mock_util):
|
||||||
print self.acc1
|
|
||||||
mock_util().menu.return_value = (display_util.OK, 0)
|
mock_util().menu.return_value = (display_util.OK, 0)
|
||||||
self.assertEqual(self._call([self.acc1]), self.acc1)
|
self.assertEqual(self._call([self.acc1]), self.acc1)
|
||||||
|
|
||||||
|
|||||||
@@ -233,6 +233,12 @@ class NetworkTest(unittest.TestCase):
|
|||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
errors.UnexpectedUpdate, self.net.update_registration, self.regr)
|
errors.UnexpectedUpdate, self.net.update_registration, self.regr)
|
||||||
|
|
||||||
|
def test_agree_to_tos(self):
|
||||||
|
self.net.update_registration = mock.Mock()
|
||||||
|
self.net.agree_to_tos(self.regr)
|
||||||
|
regr = self.net.update_registration.call_args[0][0]
|
||||||
|
self.assertEqual(self.regr.terms_of_service, regr.body.agreement)
|
||||||
|
|
||||||
def test_request_challenges(self):
|
def test_request_challenges(self):
|
||||||
self.response.status_code = httplib.CREATED
|
self.response.status_code = httplib.CREATED
|
||||||
self.response.headers['Location'] = self.authzr.uri
|
self.response.headers['Location'] = self.authzr.uri
|
||||||
@@ -280,6 +286,7 @@ class NetworkTest(unittest.TestCase):
|
|||||||
|
|
||||||
@unittest.skip("Skip til challenge_resource boulder issue is resolved")
|
@unittest.skip("Skip til challenge_resource boulder issue is resolved")
|
||||||
def test_answer_challenge_missing_next(self):
|
def test_answer_challenge_missing_next(self):
|
||||||
|
# TODO: Change once acme-spec #93 is resolved/boulder issue
|
||||||
self._mock_post_get()
|
self._mock_post_get()
|
||||||
self.assertRaises(errors.NetworkError, self.net.answer_challenge,
|
self.assertRaises(errors.NetworkError, self.net.answer_challenge,
|
||||||
self.challr.body, challenges.DNSResponse())
|
self.challr.body, challenges.DNSResponse())
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ setenv =
|
|||||||
basepython = python2.7
|
basepython = python2.7
|
||||||
commands =
|
commands =
|
||||||
pip install -e .[testing]
|
pip install -e .[testing]
|
||||||
python setup.py nosetests --with-coverage --cover-min-percentage=87
|
python setup.py nosetests --with-coverage --cover-min-percentage=89
|
||||||
|
|
||||||
[testenv:lint]
|
[testenv:lint]
|
||||||
# recent versions of pylint do not support Python 2.6 (#97, #187)
|
# recent versions of pylint do not support Python 2.6 (#97, #187)
|
||||||
|
|||||||
Reference in New Issue
Block a user