1
0
mirror of https://github.com/certbot/certbot.git synced 2026-01-23 07:20:55 +03:00

Merge pull request #242 from letsencrypt/interface_specification

Clarify authenticator interface
This commit is contained in:
James Kasten
2015-02-13 02:14:07 -08:00
4 changed files with 90 additions and 43 deletions

View File

@@ -129,6 +129,7 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes
.. todo:: It might be worth it to try different challenges to
find one that doesn't throw an exception
.. todo:: separate into more functions
"""
logging.info("Performing the following challenges:")
@@ -145,14 +146,19 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes
# Order is important here as we will not expose the outside
# Authenticator to our own indices.
flat_client = []
flat_auth = []
flat_dv = []
for dom in self.domains:
flat_client.extend(ichall.chall for ichall in self.client_c[dom])
flat_auth.extend(ichall.chall for ichall in self.dv_c[dom])
flat_dv.extend(ichall.chall for ichall in self.dv_c[dom])
client_resp = []
dv_resp = []
try:
client_resp = self.client_auth.perform(flat_client)
dv_resp = self.dv_auth.perform(flat_auth)
if flat_client:
client_resp = self.client_auth.perform(flat_client)
if flat_dv:
dv_resp = self.dv_auth.perform(flat_dv)
# This will catch both specific types of errors.
except errors.LetsEncryptAuthHandlerError as err:
logging.critical("Failure in setting up challenges:")
@@ -167,8 +173,10 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes
logging.info("Ready for verification...")
# Assemble Responses
self._assign_responses(client_resp, self.client_c)
self._assign_responses(dv_resp, self.dv_c)
if client_resp:
self._assign_responses(client_resp, self.client_c)
if dv_resp:
self._assign_responses(dv_resp, self.dv_c)
def _assign_responses(self, flat_list, ichall_dict):
"""Assign responses from flat_list back to the IndexedChall dicts.
@@ -212,9 +220,13 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes
# These are indexed challenges... give just the challenges to the auth
# Chose to make these lists instead of a generator to make it easier to
# work with...
self.dv_auth.cleanup([ichall.chall for ichall in self.dv_c[domain]])
self.client_auth.cleanup(
[ichall.chall for ichall in self.client_c[domain]])
dv_list = [ichall.chall for ichall in self.dv_c[domain]]
client_list = [ichall.chall for ichall in self.client_c[domain]]
if dv_list:
self.dv_auth.cleanup(dv_list)
if client_list:
self.client_auth.cleanup(client_list)
def _cleanup_state(self, delete_list):
"""Cleanup state after an authorization is received.

View File

@@ -30,6 +30,9 @@ class IAuthenticator(zope.interface.Interface):
:param list chall_list: List of namedtuple types defined in
:mod:`letsencrypt.client.challenge_util` (``DvsniChall``, etc.).
- chall_list will never be empty
- chall_list will only contain types found within
:func:`get_chall_pref`
:returns: ACME Challenge responses or if it cannot be completed then:
@@ -43,20 +46,16 @@ class IAuthenticator(zope.interface.Interface):
"""
def cleanup(chall_list):
"""Revert changes and shutdown after challenges complete."""
"""Revert changes and shutdown after challenges complete.
:param list chall_list: List of namedtuple types defined in
:mod:`letsencrypt.client.challenge_util` (``DvsniChall``, etc.)
class IChallenge(zope.interface.Interface):
"""Let's Encrypt challenge."""
- Only challenges given previously in the perform function will be
found in chall_list.
- chall_list will never be empty
def perform():
"""Perform the challenge."""
def generate_response():
"""Generate response."""
def cleanup():
"""Cleanup."""
"""
class IConfig(zope.interface.Interface):

View File

@@ -26,7 +26,7 @@ CHALLENGES = {
"successURL": "https://example.ca/confirmrecovery/bb1b9928932",
"contact": "c********n@example.com"
},
"recoveryTokent":
"recoveryToken":
{
"type": "recoveryToken"
},

View File

@@ -25,8 +25,8 @@ class SatisfyChallengesTest(unittest.TestCase):
def setUp(self):
from letsencrypt.client.auth_handler import AuthHandler
self.mock_dv_auth = mock.MagicMock(name='ApacheConfigurator')
self.mock_client_auth = mock.MagicMock(name='ClientAuthenticator')
self.mock_dv_auth = mock.MagicMock(name="ApacheConfigurator")
self.mock_client_auth = mock.MagicMock(name="ClientAuthenticator")
self.mock_dv_auth.get_chall_pref.return_value = ["dvsni"]
self.mock_client_auth.get_chall_pref.return_value = ["recoveryToken"]
@@ -59,6 +59,29 @@ class SatisfyChallengesTest(unittest.TestCase):
self.assertEqual(len(self.handler.dv_c[dom]), 1)
self.assertEqual(len(self.handler.client_c[dom]), 0)
def test_name1_rectok1(self):
dom = "0"
challenge = [acme_util.CHALLENGES["recoveryToken"]]
msg = acme_util.get_chall_msg(dom, "nonce0", challenge)
self.handler.add_chall_msg(dom, msg, "dummy_key")
self.handler._satisfy_challenges() # pylint: disable=protected-access
self.assertEqual(len(self.handler.responses), 1)
self.assertEqual(len(self.handler.responses[dom]), 1)
# Test if statement for dv_auth perform
self.assertEqual(self.mock_client_auth.perform.call_count, 1)
self.assertEqual(self.mock_dv_auth.perform.call_count, 0)
self.assertEqual("RecTokenChall0", self.handler.responses[dom][0])
# Assert 1 domain
self.assertEqual(len(self.handler.dv_c), 1)
self.assertEqual(len(self.handler.client_c), 1)
# Assert 1 auth challenge, 0 dv
self.assertEqual(len(self.handler.dv_c[dom]), 0)
self.assertEqual(len(self.handler.client_c[dom]), 1)
def test_name5_dvsni5(self):
challenge = [acme_util.CHALLENGES["dvsni"]]
for i in xrange(5):
@@ -74,6 +97,10 @@ class SatisfyChallengesTest(unittest.TestCase):
self.assertEqual(len(self.handler.client_c), 5)
# Each message contains 1 auth, 0 client
# Test proper call count for methods
self.assertEqual(self.mock_client_auth.perform.call_count, 0)
self.assertEqual(self.mock_dv_auth.perform.call_count, 1)
for i in xrange(5):
dom = str(i)
self.assertEqual(len(self.handler.responses[dom]), 1)
@@ -103,6 +130,10 @@ class SatisfyChallengesTest(unittest.TestCase):
self.assertEqual(len(self.handler.dv_c), 1)
self.assertEqual(len(self.handler.client_c), 1)
# Test if statement for client_auth perform
self.assertEqual(self.mock_client_auth.perform.call_count, 0)
self.assertEqual(self.mock_dv_auth.perform.call_count, 1)
self.assertEqual(
self.handler.responses[dom],
self._get_exp_response(dom, path, challenges))
@@ -251,33 +282,38 @@ class SatisfyChallengesTest(unittest.TestCase):
str(i), "nonce%d" % i, challenges, combos),
"dummy_key")
mock_chall_path.return_value = gen_path(
["dvsni", "proofOfPossession"], challenges)
mock_chall_path.side_effect = [
gen_path(["dvsni", "proofOfPossession"], challenges),
gen_path(["proofOfPossession"], challenges),
gen_path(["dvsni"], challenges),
]
# This may change in the future... but for now catch the error
self.assertRaises(errors.LetsEncryptAuthHandlerError,
self.handler._satisfy_challenges)
# Verify cleanup is actually run correctly
self.assertEqual(self.mock_dv_auth.cleanup.call_count, 3)
self.assertEqual(self.mock_client_auth.cleanup.call_count, 3)
self.assertEqual(self.mock_dv_auth.cleanup.call_count, 2)
self.assertEqual(self.mock_client_auth.cleanup.call_count, 2)
dv_cleanup_args = self.mock_dv_auth.cleanup.call_args_list
client_cleanup_args = self.mock_client_auth.cleanup.call_args_list
# Check DV cleanup
mock_cleanup_args = self.mock_dv_auth.cleanup.call_args_list
for i in xrange(3):
# Assert length of arg list was 1
arg_chall_list = mock_cleanup_args[i][0][0]
self.assertEqual(len(arg_chall_list), 1)
self.assertTrue(isinstance(arg_chall_list[0],
challenge_util.DvsniChall))
for i in xrange(2):
dv_chall_list = dv_cleanup_args[i][0][0]
self.assertEqual(len(dv_chall_list), 1)
self.assertTrue(
isinstance(dv_chall_list[0], challenge_util.DvsniChall))
# Check Auth cleanup
mock_cleanup_args = self.mock_client_auth.cleanup.call_args_list
for i in xrange(3):
arg_chall_list = mock_cleanup_args[i][0][0]
self.assertEqual(len(arg_chall_list), 1)
self.assertTrue(isinstance(arg_chall_list[0],
challenge_util.PopChall))
for i in xrange(2):
client_chall_list = client_cleanup_args[i][0][0]
self.assertEqual(len(client_chall_list), 1)
self.assertTrue(
isinstance(client_chall_list[0], challenge_util.PopChall))
def _get_exp_response(self, domain, path, challenges): # pylint: disable=no-self-use
@@ -293,8 +329,8 @@ class GetAuthorizationsTest(unittest.TestCase):
def setUp(self):
from letsencrypt.client.auth_handler import AuthHandler
self.mock_dv_auth = mock.MagicMock(name='ApacheConfigurator')
self.mock_client_auth = mock.MagicMock(name='ClientAuthenticator')
self.mock_dv_auth = mock.MagicMock(name="ApacheConfigurator")
self.mock_client_auth = mock.MagicMock(name="ClientAuthenticator")
self.mock_sat_chall = mock.MagicMock(name="_satisfy_challenges")
self.mock_acme_auth = mock.MagicMock(name="acme_authorization")
@@ -484,5 +520,5 @@ def gen_path(str_list, challenges):
return path
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()