Hello community,

here is the log from the commit of package python-certbot-dns-cloudflare for 
openSUSE:Leap:15.2 checked in at 2020-02-27 06:41:49
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Leap:15.2/python-certbot-dns-cloudflare (Old)
 and      /work/SRC/openSUSE:Leap:15.2/.python-certbot-dns-cloudflare.new.26092 
(New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-certbot-dns-cloudflare"

Thu Feb 27 06:41:49 2020 rev:6 rq:779447 version:1.2.0

Changes:
--------
--- 
/work/SRC/openSUSE:Leap:15.2/python-certbot-dns-cloudflare/python-certbot-dns-cloudflare.changes
    2020-02-19 18:48:30.223017941 +0100
+++ 
/work/SRC/openSUSE:Leap:15.2/.python-certbot-dns-cloudflare.new.26092/python-certbot-dns-cloudflare.changes
 2020-02-27 06:41:50.961652849 +0100
@@ -1,0 +2,6 @@
+Fri Feb 21 15:32:45 UTC 2020 - Marketa Calabkova <mcalabk...@suse.com>
+
+- update to version 1.2.0
+  * Added support for Cloudflare's limited-scope API Tokens
+
+-------------------------------------------------------------------

Old:
----
  certbot-dns-cloudflare-1.1.0.tar.gz

New:
----
  certbot-dns-cloudflare-1.2.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-certbot-dns-cloudflare.spec ++++++
--- /var/tmp/diff_new_pack.AAPEQA/_old  2020-02-27 06:41:51.241653432 +0100
+++ /var/tmp/diff_new_pack.AAPEQA/_new  2020-02-27 06:41:51.241653432 +0100
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-certbot-dns-cloudflare
-Version:        1.1.0
+Version:        1.2.0
 Release:        0
 Summary:        Cloudflare Authenticator plugin for Certbot
 License:        Apache-2.0

++++++ certbot-dns-cloudflare-1.1.0.tar.gz -> 
certbot-dns-cloudflare-1.2.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-dns-cloudflare-1.1.0/PKG-INFO 
new/certbot-dns-cloudflare-1.2.0/PKG-INFO
--- old/certbot-dns-cloudflare-1.1.0/PKG-INFO   2020-01-14 19:41:51.000000000 
+0100
+++ new/certbot-dns-cloudflare-1.2.0/PKG-INFO   2020-02-04 22:47:10.000000000 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: certbot-dns-cloudflare
-Version: 1.1.0
+Version: 1.2.0
 Summary: Cloudflare DNS Authenticator plugin for Certbot
 Home-page: https://github.com/certbot/certbot
 Author: Certbot Project
@@ -17,7 +17,6 @@
 Classifier: Programming Language :: Python :: 2
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
@@ -28,5 +27,5 @@
 Classifier: Topic :: System :: Networking
 Classifier: Topic :: System :: Systems Administration
 Classifier: Topic :: Utilities
-Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
 Provides-Extra: docs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/certbot-dns-cloudflare-1.1.0/certbot_dns_cloudflare/__init__.py 
new/certbot-dns-cloudflare-1.2.0/certbot_dns_cloudflare/__init__.py
--- old/certbot-dns-cloudflare-1.1.0/certbot_dns_cloudflare/__init__.py 
2020-01-14 19:41:31.000000000 +0100
+++ new/certbot-dns-cloudflare-1.2.0/certbot_dns_cloudflare/__init__.py 
2020-02-04 22:46:57.000000000 +0100
@@ -22,17 +22,40 @@
 
 Use of this plugin requires a configuration file containing Cloudflare API
 credentials, obtained from your Cloudflare
-`account page <https://www.cloudflare.com/a/account/my-account>`_. This plugin
-does not currently support Cloudflare's "API Tokens", so please ensure you use
-the "Global API Key" for authentication.
+`account page <https://dash.cloudflare.com/profile/api-tokens>`_.
+
+Previously, Cloudflare's "Global API Key" was used for authentication, however
+this key can access the entire Cloudflare API for all domains in your account,
+meaning it could cause a lot of damage if leaked.
+
+Cloudflare's newer API Tokens can be restricted to specific domains and
+operations, and are therefore now the recommended authentication option.
+
+However, due to some shortcomings in Cloudflare's implementation of Tokens,
+Tokens created for Certbot currently require ``Zone:Zone:Read`` and 
``Zone:DNS:Edit``
+permissions for **all** zones in your account. While this is not ideal, your 
Token
+will still have fewer permission than the Global key, so it's still worth 
doing.
+Hopefully Cloudflare will improve this in the future.
+
+Using Cloudflare Tokens also requires at least version 2.3.1 of the 
``cloudflare``
+python module. If the version that automatically installed with this plugin is
+older than that, and you can't upgrade it on your system, you'll have to stick 
to
+the Global key.
+
+.. code-block:: ini
+   :name: certbot_cloudflare_token.ini
+   :caption: Example credentials file using restricted API Token (recommended):
+
+   # Cloudflare API token used by Certbot
+   dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567
 
 .. code-block:: ini
-   :name: credentials.ini
-   :caption: Example credentials file:
+   :name: certbot_cloudflare_key.ini
+   :caption: Example credentials file using Global API Key (not recommended):
 
    # Cloudflare API credentials used by Certbot
    dns_cloudflare_email = cloudfl...@example.com
-   dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234567
+   dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234
 
 The path to this file can be provided interactively or using the
 ``--dns-cloudflare-credentials`` command-line argument. Certbot records the 
path
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/certbot-dns-cloudflare-1.1.0/certbot_dns_cloudflare/_internal/dns_cloudflare.py
 
new/certbot-dns-cloudflare-1.2.0/certbot_dns_cloudflare/_internal/dns_cloudflare.py
--- 
old/certbot-dns-cloudflare-1.1.0/certbot_dns_cloudflare/_internal/dns_cloudflare.py
 2020-01-14 19:41:31.000000000 +0100
+++ 
new/certbot-dns-cloudflare-1.2.0/certbot_dns_cloudflare/_internal/dns_cloudflare.py
 2020-02-04 22:46:57.000000000 +0100
@@ -4,6 +4,10 @@
 import CloudFlare
 import zope.interface
 
+from acme.magic_typing import Any
+from acme.magic_typing import Dict
+from acme.magic_typing import List
+
 from certbot import errors
 from certbot import interfaces
 from certbot.plugins import dns_common
@@ -38,14 +42,35 @@
         return 'This plugin configures a DNS TXT record to respond to a dns-01 
challenge using ' + \
                'the Cloudflare API.'
 
+    def _validate_credentials(self, credentials):
+        token = credentials.conf('api-token')
+        email = credentials.conf('email')
+        key = credentials.conf('api-key')
+        if token:
+            if email or key:
+                raise errors.PluginError('{}: dns_cloudflare_email and 
dns_cloudflare_api_key are '
+                                         'not needed when using an API Token'
+                                         .format(credentials.confobj.filename))
+        elif email or key:
+            if not email:
+                raise errors.PluginError('{}: dns_cloudflare_email is required 
when using a Global '
+                                         'API Key. (should be email address 
associated with '
+                                         'Cloudflare 
account)'.format(credentials.confobj.filename))
+            if not key:
+                raise errors.PluginError('{}: dns_cloudflare_api_key is 
required when using a '
+                                         'Global API Key. (see {})'
+                                         .format(credentials.confobj.filename, 
ACCOUNT_URL))
+        else:
+            raise errors.PluginError('{}: Either dns_cloudflare_api_token 
(recommended), or '
+                                     'dns_cloudflare_email and 
dns_cloudflare_api_key are required.'
+                                     ' (see 
{})'.format(credentials.confobj.filename, ACCOUNT_URL))
+
     def _setup_credentials(self):
         self.credentials = self._configure_credentials(
             'credentials',
             'Cloudflare credentials INI file',
-            {
-                'email': 'email address associated with Cloudflare account',
-                'api-key': 'API key for Cloudflare account, obtained from 
{0}'.format(ACCOUNT_URL)
-            }
+            None,
+            self._validate_credentials
         )
 
     def _perform(self, domain, validation_name, validation):
@@ -55,6 +80,8 @@
         self._get_cloudflare_client().del_txt_record(domain, validation_name, 
validation)
 
     def _get_cloudflare_client(self):
+        if self.credentials.conf('api-token'):
+            return _CloudflareClient(None, self.credentials.conf('api-token'))
         return _CloudflareClient(self.credentials.conf('email'), 
self.credentials.conf('api-key'))
 
 
@@ -88,8 +115,15 @@
             logger.debug('Attempting to add record to zone %s: %s', zone_id, 
data)
             self.cf.zones.dns_records.post(zone_id, data=data)  # zones | 
pylint: disable=no-member
         except CloudFlare.exceptions.CloudFlareAPIError as e:
+            code = int(e)
+            hint = None
+
+            if code == 9109:
+                hint = 'Does your API token have "Zone:DNS:Edit" permissions?'
+
             logger.error('Encountered CloudFlareAPIError adding TXT record: %d 
%s', e, e)
-            raise errors.PluginError('Error communicating with the Cloudflare 
API: {0}'.format(e))
+            raise errors.PluginError('Error communicating with the Cloudflare 
API: {0}{1}'
+                                     .format(e, ' ({0})'.format(hint) if hint 
else ''))
 
         record_id = self._find_txt_record_id(zone_id, record_name, 
record_content)
         logger.debug('Successfully added TXT record with record_id: %s', 
record_id)
@@ -139,6 +173,8 @@
         """
 
         zone_name_guesses = dns_common.base_domain_name_guesses(domain)
+        zones = []  # type: List[Dict[str, Any]]
+        code = msg = None
 
         for zone_name in zone_name_guesses:
             params = {'name': zone_name,
@@ -148,16 +184,26 @@
                 zones = self.cf.zones.get(params=params)  # zones | pylint: 
disable=no-member
             except CloudFlare.exceptions.CloudFlareAPIError as e:
                 code = int(e)
+                msg = str(e)
                 hint = None
 
                 if code == 6003:
-                    hint = 'Did you copy your entire API key?'
+                    hint = ('Did you copy your entire API token/key? To use 
Cloudflare tokens, '
+                            'you\'ll need the python package 
cloudflare>=2.3.1.{}'
+                    .format(' This certbot is running cloudflare ' + 
str(CloudFlare.__version__)
+                    if hasattr(CloudFlare, '__version__') else ''))
                 elif code == 9103:
-                    hint = 'Did you enter the correct email address?'
-
-                raise errors.PluginError('Error determining zone_id: {0} {1}. 
Please confirm that '
-                                         'you have supplied valid Cloudflare 
API credentials.{2}'
-                                         .format(code, e, ' 
({0})'.format(hint) if hint else ''))
+                    hint = 'Did you enter the correct email address and Global 
key?'
+                elif code == 9109:
+                    hint = 'Did you enter a valid Cloudflare Token?'
+
+                if hint:
+                    raise errors.PluginError('Error determining zone_id: {0} 
{1}. Please confirm '
+                                  'that you have supplied valid Cloudflare API 
credentials. ({2})'
+                                                                         
.format(code, msg, hint))
+                else:
+                    logger.debug('Unrecognised CloudFlareAPIError while 
finding zone_id: %d %s. '
+                                 'Continuing with next zone guess...', e, e)
 
             if zones:
                 zone_id = zones[0]['id']
@@ -165,9 +211,10 @@
                 return zone_id
 
         raise errors.PluginError('Unable to determine zone_id for {0} using 
zone names: {1}. '
-                                 'Please confirm that the domain name has been 
entered correctly '
-                                 'and is already associated with the supplied 
Cloudflare account.'
-                                 .format(domain, zone_name_guesses))
+                                'Please confirm that the domain name has been 
entered correctly '
+                                'and is already associated with the supplied 
Cloudflare account.{2}'
+                                .format(domain, zone_name_guesses, ' The error 
from Cloudflare was:'
+                                ' {0} {1}'.format(code, msg) if code is not 
None else ''))
 
     def _find_txt_record_id(self, zone_id, record_name, record_content):
         """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/certbot-dns-cloudflare-1.1.0/certbot_dns_cloudflare.egg-info/PKG-INFO 
new/certbot-dns-cloudflare-1.2.0/certbot_dns_cloudflare.egg-info/PKG-INFO
--- old/certbot-dns-cloudflare-1.1.0/certbot_dns_cloudflare.egg-info/PKG-INFO   
2020-01-14 19:41:51.000000000 +0100
+++ new/certbot-dns-cloudflare-1.2.0/certbot_dns_cloudflare.egg-info/PKG-INFO   
2020-02-04 22:47:10.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: certbot-dns-cloudflare
-Version: 1.1.0
+Version: 1.2.0
 Summary: Cloudflare DNS Authenticator plugin for Certbot
 Home-page: https://github.com/certbot/certbot
 Author: Certbot Project
@@ -17,7 +17,6 @@
 Classifier: Programming Language :: Python :: 2
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
@@ -28,5 +27,5 @@
 Classifier: Topic :: System :: Networking
 Classifier: Topic :: System :: Systems Administration
 Classifier: Topic :: Utilities
-Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
 Provides-Extra: docs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/certbot-dns-cloudflare-1.1.0/certbot_dns_cloudflare.egg-info/requires.txt 
new/certbot-dns-cloudflare-1.2.0/certbot_dns_cloudflare.egg-info/requires.txt
--- 
old/certbot-dns-cloudflare-1.1.0/certbot_dns_cloudflare.egg-info/requires.txt   
    2020-01-14 19:41:51.000000000 +0100
+++ 
new/certbot-dns-cloudflare-1.2.0/certbot_dns_cloudflare.egg-info/requires.txt   
    2020-02-04 22:47:10.000000000 +0100
@@ -1,5 +1,5 @@
 acme>=0.29.0
-certbot>=1.0.0
+certbot>=1.1.0
 cloudflare>=1.5.1
 mock
 setuptools
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-dns-cloudflare-1.1.0/setup.py 
new/certbot-dns-cloudflare-1.2.0/setup.py
--- old/certbot-dns-cloudflare-1.1.0/setup.py   2020-01-14 19:41:33.000000000 
+0100
+++ new/certbot-dns-cloudflare-1.2.0/setup.py   2020-02-04 22:46:58.000000000 
+0100
@@ -4,13 +4,13 @@
 from setuptools import setup
 from setuptools.command.test import test as TestCommand
 
-version = '1.1.0'
+version = '1.2.0'
 
 # Remember to update local-oldest-requirements.txt when changing the minimum
 # acme/certbot version.
 install_requires = [
     'acme>=0.29.0',
-    'certbot>=1.0.0',
+    'certbot>=1.1.0',
     'cloudflare>=1.5.1',
     'mock',
     'setuptools',
@@ -44,7 +44,7 @@
     author="Certbot Project",
     author_email='client-...@letsencrypt.org',
     license='Apache License 2.0',
-    python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
+    python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
     classifiers=[
         'Development Status :: 5 - Production/Stable',
         'Environment :: Plugins',
@@ -55,7 +55,6 @@
         'Programming Language :: Python :: 2',
         'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.4',
         'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/certbot-dns-cloudflare-1.1.0/tests/dns_cloudflare_test.py 
new/certbot-dns-cloudflare-1.2.0/tests/dns_cloudflare_test.py
--- old/certbot-dns-cloudflare-1.1.0/tests/dns_cloudflare_test.py       
2020-01-14 19:41:31.000000000 +0100
+++ new/certbot-dns-cloudflare-1.2.0/tests/dns_cloudflare_test.py       
2020-02-04 22:46:57.000000000 +0100
@@ -12,6 +12,9 @@
 from certbot.tests import util as test_util
 
 API_ERROR = CloudFlare.exceptions.CloudFlareAPIError(1000, '', '')
+
+API_TOKEN = 'an-api-token'
+
 API_KEY = 'an-api-key'
 EMAIL = 'exam...@example.com'
 
@@ -49,6 +52,50 @@
         expected = [mock.call.del_txt_record(DOMAIN, 
'_acme-challenge.'+DOMAIN, mock.ANY)]
         self.assertEqual(expected, self.mock_client.mock_calls)
 
+    def test_api_token(self):
+        dns_test_common.write({"cloudflare_api_token": API_TOKEN},
+                              self.config.cloudflare_credentials)
+        self.auth.perform([self.achall])
+
+        expected = [mock.call.add_txt_record(DOMAIN, 
'_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)]
+        self.assertEqual(expected, self.mock_client.mock_calls)
+
+    def test_no_creds(self):
+        dns_test_common.write({}, self.config.cloudflare_credentials)
+        self.assertRaises(errors.PluginError,
+                          self.auth.perform,
+                          [self.achall])
+
+    def test_missing_email_or_key(self):
+        dns_test_common.write({"cloudflare_api_key": API_KEY}, 
self.config.cloudflare_credentials)
+        self.assertRaises(errors.PluginError,
+                          self.auth.perform,
+                          [self.achall])
+
+        dns_test_common.write({"cloudflare_email": EMAIL}, 
self.config.cloudflare_credentials)
+        self.assertRaises(errors.PluginError,
+                          self.auth.perform,
+                          [self.achall])
+
+    def test_email_or_key_with_token(self):
+        dns_test_common.write({"cloudflare_api_token": API_TOKEN, 
"cloudflare_email": EMAIL},
+                              self.config.cloudflare_credentials)
+        self.assertRaises(errors.PluginError,
+                          self.auth.perform,
+                          [self.achall])
+
+        dns_test_common.write({"cloudflare_api_token": API_TOKEN, 
"cloudflare_api_key": API_KEY},
+                              self.config.cloudflare_credentials)
+        self.assertRaises(errors.PluginError,
+                          self.auth.perform,
+                          [self.achall])
+
+        dns_test_common.write({"cloudflare_api_token": API_TOKEN, 
"cloudflare_email": EMAIL,
+                               "cloudflare_api_key": API_KEY}, 
self.config.cloudflare_credentials)
+        self.assertRaises(errors.PluginError,
+                          self.auth.perform,
+                          [self.achall])
+
 
 class CloudflareClientTest(unittest.TestCase):
     record_name = "foo"
@@ -83,7 +130,7 @@
     def test_add_txt_record_error(self):
         self.cf.zones.get.return_value = [{'id': self.zone_id}]
 
-        self.cf.zones.dns_records.post.side_effect = API_ERROR
+        self.cf.zones.dns_records.post.side_effect = 
CloudFlare.exceptions.CloudFlareAPIError(9109, '', '')
 
         self.assertRaises(
             errors.PluginError,
@@ -104,6 +151,25 @@
         self.assertRaises(
             errors.PluginError,
             self.cloudflare_client.add_txt_record,
+            DOMAIN, self.record_name, self.record_content, self.record_ttl)
+
+    def test_add_txt_record_bad_creds(self):
+        self.cf.zones.get.side_effect = 
CloudFlare.exceptions.CloudFlareAPIError(6003, '', '')
+        self.assertRaises(
+            errors.PluginError,
+            self.cloudflare_client.add_txt_record,
+            DOMAIN, self.record_name, self.record_content, self.record_ttl)
+
+        self.cf.zones.get.side_effect = 
CloudFlare.exceptions.CloudFlareAPIError(9103, '', '')
+        self.assertRaises(
+            errors.PluginError,
+            self.cloudflare_client.add_txt_record,
+            DOMAIN, self.record_name, self.record_content, self.record_ttl)
+
+        self.cf.zones.get.side_effect = 
CloudFlare.exceptions.CloudFlareAPIError(9109, '', '')
+        self.assertRaises(
+            errors.PluginError,
+            self.cloudflare_client.add_txt_record,
             DOMAIN, self.record_name, self.record_content, self.record_ttl)
 
     def test_del_txt_record(self):


Reply via email to