Delete empty directories after deleting an account, including symlinks up and down the chain, as appropriate (#6176)

This commit is contained in:
ohemorange 2018-07-10 18:48:09 -07:00 committed by Brad Warren
parent 3f6a908821
commit b7113a35eb
2 changed files with 72 additions and 0 deletions

View file

@ -250,12 +250,50 @@ class AccountFileStorage(interfaces.AccountStorage):
:param account_id: id of account which should be deleted
"""
# Step 1: remove the account itself
account_dir_path = self._account_dir_path(account_id)
if not os.path.isdir(account_dir_path):
raise errors.AccountNotFound(
"Account at %s does not exist" % account_dir_path)
shutil.rmtree(account_dir_path)
# Step 2: remove the directory if it's empty, and linked directories
if not os.listdir(self.config.accounts_dir):
self._delete_accounts_dir_for_server_path(self.config.server_path)
def _delete_accounts_dir_for_server_path(self, server_path):
accounts_dir_path = self.config.accounts_dir_for_server_path(server_path)
# does an appropriate directory link to me? if so, make sure that's gone
reused_servers = {}
for k in constants.LE_REUSE_SERVERS:
reused_servers[constants.LE_REUSE_SERVERS[k]] = k
# is there a next one up? call that and be done
if server_path in reused_servers:
next_server_path = reused_servers[server_path]
next_accounts_dir_path = self.config.accounts_dir_for_server_path(next_server_path)
if os.path.islink(next_accounts_dir_path) \
and os.readlink(next_accounts_dir_path) == accounts_dir_path:
self._delete_accounts_dir_for_server_path(next_server_path)
return
# if there's not a next one up to delete, then delete me
# and whatever I link to if applicable
if os.path.islink(accounts_dir_path):
# save my info then delete me
target = os.readlink(accounts_dir_path)
os.unlink(accounts_dir_path)
# then delete whatever I linked to, if appropriate
if server_path in constants.LE_REUSE_SERVERS:
prev_server_path = constants.LE_REUSE_SERVERS[server_path]
prev_accounts_dir_path = self.config.accounts_dir_for_server_path(prev_server_path)
if target == prev_accounts_dir_path:
self._delete_accounts_dir_for_server_path(prev_server_path)
else:
# just delete me
os.rmdir(accounts_dir_path)
def _save(self, account, acme, regr_only):
account_dir_path = self._account_dir_path(account.id)
util.make_or_verify_dir(account_dir_path, 0o700, os.geteuid(),

View file

@ -273,6 +273,40 @@ class AccountFileStorageTest(test_util.ConfigTestCase):
def test_delete_no_account(self):
self.assertRaises(errors.AccountNotFound, self.storage.delete, self.acc.id)
def _assert_symlinked_account_removed(self):
# create v1 account
self._set_server('https://acme-staging.api.letsencrypt.org/directory')
self.storage.save(self.acc, self.mock_client)
# ensure v2 isn't already linked to it
with mock.patch('certbot.constants.LE_REUSE_SERVERS', {}):
self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory')
self.assertRaises(errors.AccountNotFound, self.storage.load, self.acc.id)
def _test_delete_folders(self, server_url):
# create symlinked servers
self._set_server('https://acme-staging.api.letsencrypt.org/directory')
self.storage.save(self.acc, self.mock_client)
self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory')
self.storage.find_all()
# delete starting at given server_url
self._set_server(server_url)
self.storage.delete(self.acc.id)
# make sure we're gone from both urls
self._set_server('https://acme-staging.api.letsencrypt.org/directory')
self.assertRaises(errors.AccountNotFound, self.storage.load, self.acc.id)
self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory')
self.assertRaises(errors.AccountNotFound, self.storage.load, self.acc.id)
def test_delete_folders_up(self):
self._test_delete_folders('https://acme-staging.api.letsencrypt.org/directory')
self._assert_symlinked_account_removed()
def test_delete_folders_down(self):
self._test_delete_folders('https://acme-staging-v02.api.letsencrypt.org/directory')
self._assert_symlinked_account_removed()
if __name__ == "__main__":
unittest.main() # pragma: no cover