From af81daf60c64f01611a1f6a34ef141dc1cf020d0 Mon Sep 17 00:00:00 2001
From: Corentin Forler <10946971+cogk@users.noreply.github.com>
Date: Wed, 20 Sep 2023 11:01:23 +0200
Subject: [PATCH 1/2] feat: Add authentication with Personal Access Token

---
 certbot_plugin_gandi/gandi_api.py | 12 ++++++++----
 certbot_plugin_gandi/main.py      | 24 ++++++++++++++++++------
 2 files changed, 26 insertions(+), 10 deletions(-)

diff --git a/certbot_plugin_gandi/gandi_api.py b/certbot_plugin_gandi/gandi_api.py
index 3e1ef84..67c9908 100644
--- a/certbot_plugin_gandi/gandi_api.py
+++ b/certbot_plugin_gandi/gandi_api.py
@@ -4,11 +4,11 @@
 from collections import namedtuple
 from certbot.plugins import dns_common
 
-_GandiConfig = namedtuple('_GandiConfig', ('api_key', 'sharing_id',))
+_GandiConfig = namedtuple('_GandiConfig', ('api_key', 'sharing_id', 'personal_access_token',))
 _BaseDomain = namedtuple('_BaseDomain', ('fqdn'))
 
-def get_config(api_key, sharing_id):
-    return _GandiConfig(api_key=api_key, sharing_id=sharing_id)
+def get_config(api_key, sharing_id, personal_access_token=None):
+    return _GandiConfig(api_key=api_key, sharing_id=sharing_id, personal_access_token=personal_access_token)
 
 
 def _get_json(response):
@@ -24,9 +24,13 @@ def _get_response_message(response, default='<No reason given>'):
 
 
 def _headers(cfg):
+    if cfg.personal_access_token:
+        auth = 'Bearer ' + cfg.personal_access_token
+    else:
+        auth = 'Apikey ' + cfg.api_key
     return {
         'Content-Type': 'application/json',
-        'Authorization': 'Apikey ' + cfg.api_key
+        'Authorization': auth,
     }
 
 
diff --git a/certbot_plugin_gandi/main.py b/certbot_plugin_gandi/main.py
index c547d1a..6930f0c 100644
--- a/certbot_plugin_gandi/main.py
+++ b/certbot_plugin_gandi/main.py
@@ -15,7 +15,7 @@ def register_authenticator(cls):
         import zope.interface
         zope.interface.implementer(interfaces.IAuthenticator)(cls)
         zope.interface.provider(interfaces.IPluginFactory)(cls)
-    return cls 
+    return cls
 
 @register_authenticator
 class Authenticator(dns_common.DNSAuthenticator):
@@ -47,14 +47,22 @@ def _validate_sharing_id(self, credentials):
             except ValueError:
                 raise errors.PluginError("Invalid sharing_id: {0}.".format(sharing_id))
 
+    def _validate(self, credentials):
+        self._validate_sharing_id(credentials)
+
+        # Either api-key or token must be set
+        if not credentials.conf('api-key') and not credentials.conf('token'):
+            raise errors.PluginError(
+                'Missing property in credentials configuration file {0}: {1}'.format(
+                    credentials.confobj.filename, 'dns_gandi_api_key or dns_gandi_token must be set')
+                )
+
     def _setup_credentials(self):
         self.credentials = self._configure_credentials(
             'credentials',
             'Gandi credentials INI file',
-            {
-                'api-key': 'API key for Gandi account',
-            },
-            self._validate_sharing_id
+            {},
+            self._validate
         )
 
 
@@ -71,4 +79,8 @@ def _cleanup(self, domain, validation_name, validation):
 
 
     def _get_gandi_config(self):
-        return gandi_api.get_config(api_key = self.credentials.conf('api-key'), sharing_id = self.credentials.conf('sharing-id'))
+        return gandi_api.get_config(
+            api_key = self.credentials.conf('api-key'),
+            sharing_id = self.credentials.conf('sharing-id'),
+            personal_access_token = self.credentials.conf('token'),
+        )

From da164a8360a6cceeb5fc9b45000d37d75682fb45 Mon Sep 17 00:00:00 2001
From: Corentin Forler <10946971+cogk@users.noreply.github.com>
Date: Wed, 20 Sep 2023 11:08:02 +0200
Subject: [PATCH 2/2] doc: Update readme to showcase personal access token

---
 README.md | 27 +++++++++++++++++++--------
 1 file changed, 19 insertions(+), 8 deletions(-)

diff --git a/README.md b/README.md
index ede080d..68f982e 100644
--- a/README.md
+++ b/README.md
@@ -13,15 +13,12 @@ customers to prove control of a domain name.
 2. Install the plugin using `pip install certbot-plugin-gandi`
 
 3. Create a `gandi.ini` config file with the following contents and apply `chmod 600 gandi.ini` on it:
+   ```conf
+   # Gandi personal access token
+   dns_gandi_token=PERSONAL_ACCESS_TOKEN
    ```
-   # live dns v5 api key
-   dns_gandi_api_key=APIKEY
-
-   # optional organization id, remove it if not used
-   dns_gandi_sharing_id=SHARINGID
-   ```
-   Replace `APIKEY` with your Gandi API key and ensure permissions are set
-   to disallow access to other users.
+   Replace `PERSONAL_ACCESS_TOKEN` with your Gandi personal access token and ensure permissions are set
+   to disallow access to other users. You can also use a Gandi LiveDNS API Key instead, see FAQ below.
 
 4. Run `certbot` and direct it to use the plugin for authentication and to use
    the config file previously created:
@@ -70,6 +67,20 @@ You can setup automatic renewal using `crontab` with the following job for weekl
 
 ## FAQ
 
+> I don't have a personal access token, only a Gandi LiveDNS API Key
+
+Use the following configuration in your `gandi.ini` file instead:
+
+```conf
+# live dns v5 api key
+dns_gandi_api_key=APIKEY
+
+# optional organization id, remove it if not used
+dns_gandi_sharing_id=SHARINGID
+```
+Replace `APIKEY` with your Gandi API key and ensure permissions are set
+to disallow access to other users.
+
 > I have a warning telling me `Plugin legacy name certbot-plugin-gandi:dns may be removed in a future version. Please use dns instead.`
 
 Certbot had moved to remove 3rd party plugins prefixes since v1.7.0. Please switch to the new configuration format and remove any used prefix-based configuration.
