summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/cli-usage.rst23
-rw-r--r--gitlab/config.py25
-rw-r--r--gitlab/tests/test_config.py36
3 files changed, 63 insertions, 21 deletions
diff --git a/docs/cli-usage.rst b/docs/cli-usage.rst
index 71c8577..c27e6c5 100644
--- a/docs/cli-usage.rst
+++ b/docs/cli-usage.rst
@@ -48,7 +48,7 @@ example:
[elsewhere]
url = http://else.whe.re:8080
- private_token = lookup: pass show path/to/password | head -n1
+ private_token = helper: path/to/helper.sh
timeout = 1
The ``default`` option of the ``[global]`` section defines the GitLab server to
@@ -119,6 +119,27 @@ server, with very limited permissions.
* - ``http_password``
- Password for optional HTTP authentication
+For all settings, which contain secrets (``http_password``,
+``personal_token``, ``oauth_token``, ``job_token``), you can specify
+a helper program to retrieve the secret indicated by ``helper:``
+prefix. You can only specify a path to a program without any
+parameters. It is expected, that the program prints the secret to
+standard output.
+
+Example for a `keyring <https://github.com/jaraco/keyring>`_ helper:
+
+.. code-block:: bash
+
+ #!/bin/bash
+ keyring get Service Username
+
+Example for a `pass <https://www.passwordstore.org>`_ helper:
+
+.. code-block:: bash
+
+ #!/bin/bash
+ pass show path/to/password | head -n 1
+
CLI
===
diff --git a/gitlab/config.py b/gitlab/config.py
index a6f25ac..67f5082 100644
--- a/gitlab/config.py
+++ b/gitlab/config.py
@@ -34,6 +34,11 @@ _DEFAULT_FILES: List[str] = _env_config() + [
os.path.expanduser("~/.python-gitlab.cfg"),
]
+HELPER_PREFIX = "helper:"
+
+HELPER_ATTRIBUTES = [
+ "job_token", "http_password", "private_token", "oauth_token"
+]
class ConfigError(Exception):
pass
@@ -151,15 +156,7 @@ class GitlabConfigParser(object):
except Exception:
pass
- for attr in ("job_token", "http_password", "private_token", "oauth_token"):
- value = getattr(self, attr)
- prefix = "lookup:"
- if isinstance(value, str) and value.lower().strip().startswith(prefix):
- helper = value[len(prefix) :].strip()
- value = (
- subprocess.check_output(helper, shell=True).decode("utf-8").strip()
- )
- setattr(self, attr, value)
+ self._get_values_from_helper()
self.api_version = "4"
try:
@@ -203,3 +200,13 @@ class GitlabConfigParser(object):
self.user_agent = self._config.get(self.gitlab_id, "user_agent")
except Exception:
pass
+
+ def _get_values_from_helper(self):
+ """Update attributes, which may get values from an external helper program
+ """
+ for attr in HELPER_ATTRIBUTES:
+ value = getattr(self, attr)
+ if isinstance(value, str) and value.lower().strip().startswith(HELPER_PREFIX):
+ helper = value[len(HELPER_PREFIX) :].strip()
+ value = subprocess.check_output([helper]).decode("utf-8").strip()
+ setattr(self, attr, value) \ No newline at end of file
diff --git a/gitlab/tests/test_config.py b/gitlab/tests/test_config.py
index 644b0c1..60c8853 100644
--- a/gitlab/tests/test_config.py
+++ b/gitlab/tests/test_config.py
@@ -17,6 +17,7 @@
import os
import unittest
+from textwrap import dedent
import mock
import io
@@ -51,10 +52,6 @@ per_page = 50
[four]
url = https://four.url
oauth_token = STUV
-
-[five]
-url = https://five.url
-oauth_token = lookup: echo "foobar"
"""
custom_user_agent_config = """[global]
@@ -196,16 +193,33 @@ def test_valid_data(m_open, path_exists):
assert 2 == cp.timeout
assert True == cp.ssl_verify
- fd = io.StringIO(valid_config)
+
+@mock.patch("os.path.exists")
+@mock.patch("builtins.open")
+def test_data_from_helper(m_open, path_exists, tmp_path):
+ helper = (tmp_path / "helper.sh")
+ helper.write_text(dedent("""\
+ #!/bin/sh
+ echo "secret"
+ """))
+ helper.chmod(0o755)
+
+ fd = io.StringIO(dedent("""\
+ [global]
+ default = helper
+
+ [helper]
+ url = https://helper.url
+ oauth_token = helper: %s
+ """) % helper)
+
fd.close = mock.Mock(return_value=None)
m_open.return_value = fd
- cp = config.GitlabConfigParser(gitlab_id="five")
- assert "five" == cp.gitlab_id
- assert "https://five.url" == cp.url
+ cp = config.GitlabConfigParser(gitlab_id="helper")
+ assert "helper" == cp.gitlab_id
+ assert "https://helper.url" == cp.url
assert None == cp.private_token
- assert "foobar" == cp.oauth_token
- assert 2 == cp.timeout
- assert True == cp.ssl_verify
+ assert "secret" == cp.oauth_token
@mock.patch("os.path.exists")