Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce confluence api token option #992

Merged
merged 3 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 59 additions & 19 deletions doc/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -128,42 +128,39 @@ Essential configuration
(or)
confluence_server_user = 'myawesomeuser'

.. |confluence_server_pass| replace:: ``confluence_server_pass``
.. _confluence_server_pass:
.. |confluence_api_token| replace:: ``confluence_api_token``
.. _confluence_api_token:

.. confval:: confluence_server_pass
.. confval:: confluence_api_token

.. versionadded:: 2.6

.. tip::

Use this option for Confluence Cloud.

.. caution::

It is never recommended to store an API token or raw password into a
committed/shared repository holding documentation.
It is never recommended to store an API token into a committed/shared
repository holding documentation.

A documentation's configuration can modified various ways with Python
to pull an authentication token for a publishing event such as
:ref:`reading from an environment variable <tip_manage_publish_subset>`,
reading from a local file or acquiring a password from ``getpass``. If
desired, this extension provides a method for prompting for a
password (see |confluence_ask_password|_).
reading from a local file or acquiring a token from ``getpass``.

.. note::

If attempting to use a personal access token (PAT), use the
|confluence_publish_token|_ option instead.

The password value used to authenticate with the Confluence instance. If
using Confluence Cloud, it is recommended to use an API token for the
configured username value (see `API tokens`_):
The API token value used to authenticate with the Confluence instance. Set
this option to an API token for the configured username value
(see `API tokens`_):

.. code-block:: python

confluence_server_pass = 'vsUsrSZ6Z4kmrQMapSXBYkJh'

If `API tokens`_ are not being used, the plaintext password for the
configured username value can be used:

.. code-block:: python

confluence_server_pass = 'myawesomepassword'
confluence_api_token = 'YDYDD3qVvKV0FbkErSxaQ2olmy...AMGwaPe8=02381T9A'

.. |confluence_publish_token| replace:: ``confluence_publish_token``
.. _confluence_publish_token:
Expand All @@ -172,6 +169,10 @@ Essential configuration

.. versionadded:: 1.8

.. tip::

Use this option for Confluence Data Center.

.. caution::

It is never recommended to store a personal access tokens (PAT) into a
Expand All @@ -194,6 +195,45 @@ Essential configuration

confluence_publish_token = 'AbCdEfGhIjKlMnOpQrStUvWxY/z1234567890aBc'

.. |confluence_server_pass| replace:: ``confluence_server_pass``
.. _confluence_server_pass:

.. confval:: confluence_server_pass

.. warning::

It is not recommended to use this option when authenticating with
an API token or a personal access token.

.. note::

Functionally, this option is the same as |confluence_api_token|_.
It is recommended to use the API token variant solely for naming
convention. Only limited cases can use a password value for
publication over API tokens or personal access tokens (specifically,
cases using Confluence Data Center). Unless users are expected to
interact directly with their Confluence instance with user passwords,
users should instead use either one of the following options instead:

- |confluence_api_token|_
- |confluence_publish_token|_

.. caution::

It is never recommended to store a raw password into a
committed/shared repository holding documentation. If desired, this
extension provides a method for prompting for a password
(see |confluence_ask_password|_).

Future versions *may* deprecate this option.

The password value used to authenticate with the Confluence instance. This
value expects the plaintext password for the configured username value:

.. code-block:: python

confluence_server_pass = 'myawesomepassword'

Generic configuration
---------------------

Expand Down
20 changes: 11 additions & 9 deletions doc/guide-ci.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ Before demonstrating these methods, please note which type of authentication
is required for the target Confluence instance. For example, if
authenticating with an API key (Confluence Cloud; see `API tokens`_), users
will need to configure both ``confluence_server_user``
(:ref:`ref<confluence_server_user>`) and ``confluence_server_pass``
(:ref:`ref<confluence_server_pass>`) options. However, if using a personal
(:ref:`ref<confluence_server_user>`) and ``confluence_api_token``
(:ref:`ref<confluence_api_token>`) options. However, if using a personal
access token (see `Using Personal Access Tokens`_), users will need to
configure only the ``confluence_publish_token``
(:ref:`ref<confluence_publish_token>`) option.
Expand All @@ -65,9 +65,10 @@ environment if the option is not already set in ``conf.py``.
Confluence Cloud API Key
~~~~~~~~~~~~~~~~~~~~~~~~

If using a Confluence Cloud API key, ensure both the following variables are
If using a Confluence Cloud API key, ensure the following variables are
*not set* inside ``conf.py``:

- ``confluence_api_token``
- ``confluence_publish_token``
- ``confluence_server_pass``

Expand All @@ -76,15 +77,15 @@ published with a single API token. If the environment plans to use multiple
tokens, ensure ``confluence_server_user`` is not set as well.

Next, if the CI environment supports defining custom CI variables, create a
new entry for ``CONFLUENCE_SERVER_PASS``, holding the API token value to use
new entry for ``CONFLUENCE_API_TOKEN``, holding the API token value to use
when publishing. If the API token is stored in another manner that can be
exposed when running a build, ensure the token is set into a
``CONFLUENCE_SERVER_PASS`` environment variable before running Sphinx. For
``CONFLUENCE_API_TOKEN`` environment variable before running Sphinx. For
example:

.. code-block:: shell-session

$ export CONFLUENCE_SERVER_PASS="<my-token-value>"
$ export CONFLUENCE_API_TOKEN="<my-token-value>"
$ sphinx-build ...
Running Sphinx
...
Expand All @@ -93,7 +94,7 @@ Or, when using a Windows command line:

.. code-block:: doscon

> set CONFLUENCE_SERVER_PASS="<my-token-value>"
> set CONFLUENCE_API_TOKEN="<my-token-value>"
> sphinx-build ...
Running Sphinx
...
Expand All @@ -107,6 +108,7 @@ Confluence Data Center PAT
If using a PAT, ensure the following variables are *not set* inside
``conf.py``:

- ``confluence_api_token``
- ``confluence_publish_token``
- ``confluence_server_pass``
- ``confluence_server_user``
Expand Down Expand Up @@ -146,7 +148,7 @@ The following can be used to configure an API token for Confluence Cloud:

.. code-block:: shell

sphinx-build ... -Dconfluence_server_pass="<my-token-value>"
sphinx-build ... -Dconfluence_api_token="<my-token-value>"

Confluence Data Center PAT
~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -178,7 +180,7 @@ If using an API token, the following can be used:
...

confluence_server_user = 'api-key-uid'
confluence_server_pass = os.getenv('SECRET_KEY')
confluence_api_token = os.getenv('SECRET_KEY')


Confluence Data Center PAT
Expand Down
4 changes: 3 additions & 1 deletion sphinxcontrib/confluencebuilder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,11 @@ def setup(app):
# (configuration - essential)
# Enablement of publishing.
cm.add_conf_bool('confluence_publish')
# API token to authenticate to Confluence API with.
cm.add_conf('confluence_api_token')
# PAT to authenticate to Confluence API with.
cm.add_conf('confluence_publish_token')
# API key/password to login to Confluence API with.
# Password to login to Confluence API with.
cm.add_conf('confluence_server_pass')
# URL of the Confluence instance to publish to.
cm.add_conf('confluence_server_url')
Expand Down
15 changes: 12 additions & 3 deletions sphinxcontrib/confluencebuilder/cmd/conntest.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def conntest_main(args_parser):

opts = [
'confluence_adv_embedded_certs',
'confluence_api_token',
'confluence_ca_cert',
'confluence_client_cert',
'confluence_client_cert_pass',
Expand Down Expand Up @@ -272,13 +273,19 @@ def conntest_main(args_parser):
if config.get('confluence_request_session_override'):
print('(note) Custom session overrides detected!')

# confluence_publish_token
# confluence_server_pass + pat
confluence_server_pass = config.get('confluence_server_pass')
if confluence_server_pass and pat:
print('(warning) Both server password/API token configured at the '
'same time with a personal access token (PAT)!')

# confluence_publish_token
# confluence_api_token + confluence_server_pass
confluence_api_token = config.get('confluence_api_token')
if confluence_api_token and confluence_server_pass:
print('(warning) API token configured at the '
'same time with a password!')

# !confluence_server_user + confluence_server_pass
confluence_server_user = config.get('confluence_server_user')
if not confluence_server_user and confluence_server_pass:
print('(warning) No user configured, but a server password/API '
Expand Down Expand Up @@ -363,13 +370,15 @@ class ProbeModes(Enum):


def conntest_probe(config):
api_token = config.confluence_api_token
ca_cert = config.confluence_ca_cert or True
pat = config.confluence_publish_token
publish_headers = config.confluence_publish_headers
server_pass = config.confluence_server_pass or ''
server_user = config.confluence_server_user
space_key = config.confluence_space_key
url = config.confluence_server_url
auth_value = api_token or server_pass

space_queries = {
'v1': {
Expand Down Expand Up @@ -399,7 +408,7 @@ def conntest_probe(config):

if mode == ProbeModes.auth:
if server_user:
auth = (server_user, server_pass)
auth = (server_user, auth_value)
headers = publish_headers
else:
print('skipped (no username configured).')
Expand Down
1 change: 1 addition & 0 deletions sphinxcontrib/confluencebuilder/cmd/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ def sensitive_config(key):
config[key] = '(set; empty)'

# always sanitize out sensitive information
sensitive_config('confluence_api_token')
sensitive_config('confluence_client_cert_pass')
sensitive_config('confluence_publish_headers')
sensitive_config('confluence_publish_token')
Expand Down
13 changes: 11 additions & 2 deletions sphinxcontrib/confluencebuilder/config/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from sphinxcontrib.confluencebuilder.config.exceptions import ConfluencePublishMissingServerUrlConfigError
from sphinxcontrib.confluencebuilder.config.exceptions import ConfluencePublishMissingSpaceKeyConfigError
from sphinxcontrib.confluencebuilder.config.exceptions import ConfluencePublishMissingUsernameAskConfigError
from sphinxcontrib.confluencebuilder.config.exceptions import ConfluencePublishMissingUsernameAuthConfigError
from sphinxcontrib.confluencebuilder.config.exceptions import ConfluencePublishMissingUsernamePassConfigError
from sphinxcontrib.confluencebuilder.config.exceptions import ConfluenceServerAuthConfigError
from sphinxcontrib.confluencebuilder.config.exceptions import ConfluenceSourcelinkRequiredConfigError
Expand Down Expand Up @@ -127,6 +128,12 @@ def validate_configuration(builder):

# ##################################################################

# confluence_api_token
validator.conf('confluence_api_token') \
.string()

# ##################################################################

# confluence_append_labels
validator.conf('confluence_append_labels') \
.bool()
Expand Down Expand Up @@ -744,8 +751,10 @@ def conf_translate(value):
and not config.confluence_ask_user):
raise ConfluencePublishMissingUsernameAskConfigError

if config.confluence_server_pass:
if not config.confluence_server_user:
if not config.confluence_server_user:
if config.confluence_api_token:
raise ConfluencePublishMissingUsernameAuthConfigError
if config.confluence_server_pass:
raise ConfluencePublishMissingUsernamePassConfigError

if config.confluence_parent_page_id_check:
Expand Down
11 changes: 11 additions & 0 deletions sphinxcontrib/confluencebuilder/config/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,17 @@ def __init__(self):
''')


class ConfluencePublishMissingUsernameAuthConfigError(ConfluenceConfigError):
def __init__(self):
super().__init__('''\
confluence username not provided

A publishing password has been configured with 'confluence_api_token';
however, no username has been configured. Ensure 'confluence_server_user' is
properly set with the publisher's Confluence username.
''')


class ConfluencePublishMissingUsernamePassConfigError(ConfluenceConfigError):
def __init__(self):
super().__init__('''\
Expand Down
17 changes: 17 additions & 0 deletions sphinxcontrib/confluencebuilder/config/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ def warnings(validator):

config = validator.config

# check that only a single authentication key is configured
auth_keys = [
'confluence_api_token',
'confluence_publish_token',
'confluence_server_pass',
]

auth_key_count = 0
for option in auth_keys:
value = getattr(config, option)
if value:
auth_key_count += 1

if auth_key_count > 1:
logger.warn('multiple authentication options configured')

# check if any user defined mime types are unknown
if config.confluence_additional_mime_types is not None:
for mime_type in config.confluence_additional_mime_types:
Expand Down Expand Up @@ -115,6 +131,7 @@ def warnings(validator):
# quotes a password/token value -- provide a warning if we believe that
# has been detected
quote_wrap_check = [
'confluence_api_token',
'confluence_publish_token',
'confluence_server_pass',
]
Expand Down
12 changes: 7 additions & 5 deletions sphinxcontrib/confluencebuilder/publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,10 @@ def connect(self):
# https://sphinxcontrib-confluencebuilder.atlassian.net/wiki/rest/api/space/STABLE
# https://sphinxcontrib-confluencebuilder.atlassian.net/wiki/api/v2/spaces?keys=STABLE

api_token_set = bool(self.config.confluence_api_token)
pw_set = bool(self.config.confluence_server_pass)
token_set = bool(self.config.confluence_publish_token)
auth_set = api_token_set or pw_set
pat_set = bool(self.config.confluence_publish_token)

try:
if self.api_mode == 'v2':
Expand All @@ -120,8 +122,8 @@ def connect(self):
server_url,
self.space_key,
self.config.confluence_server_user,
pw_set,
token_set,
auth_set,
pat_set,
)
else:
rsp = self.rest.get(f'{self.APIV1}space/{self.space_key}')
Expand All @@ -136,8 +138,8 @@ def connect(self):
server_url,
self.space_key,
self.config.confluence_server_user,
pw_set,
token_set,
auth_set,
pat_set,
) from ex

raise ConfluenceBadServerUrlError(server_url, ex) from ex
Expand Down
6 changes: 4 additions & 2 deletions sphinxcontrib/confluencebuilder/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,10 @@ def _setup_session(self, config):

if config.confluence_server_auth:
session.auth = config.confluence_server_auth
elif config.confluence_server_user:
passwd = config.confluence_server_pass
elif config.confluence_api_token or config.confluence_server_user:
passwd = config.confluence_api_token
if passwd is None:
passwd = config.confluence_server_pass
if passwd is None:
passwd = ''
session.auth = (config.confluence_server_user, passwd)
Expand Down
Loading