Skip to content
Open
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
2 changes: 2 additions & 0 deletions docs/users/configuration/reference.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<style>
.wy-nav-content{ max-width: 100%;}
</style>

# Settings

The following table describe all the configuration options you can set for the application. All the settings can be set
Expand Down Expand Up @@ -53,6 +54,7 @@ file in the following way:
| `enable_advanced_full_text_search` | `bool` | No | `True` | If `True` full-text search works on any text-based Jira field; otherwise it searches only on summary and description. |
| `cloud` | `bool` | No | `True` | Set this to False if you are using Jira Data Center (aka. on-premises) |
| `use_bearer_authentication` | `bool` | No | `False` | Set this to True if your Jira instance uses Bearer authentication instead of Basic authentication. |
| `use_cert_authentication` | `bool` | No | `False` | Set this to True if your Jira instance uses certificate-based authentication instead of Bearer authentication or Basic authentication. |
| `search_results_default_order` | `str` | No | `WorkItemsSearchOrderBy.CREATED_DESC` | The default order for search results. |
| `ssl` | `SSLConfiguration` | No | `None` | The settings for SSL. |
| `git_repositories` | `dict` | No | `None` | Configure the Git repos that are available for creating branches from the UI. [See Setting Git Repositories](configuration.md#setting-git-repositories) |
Expand Down
73 changes: 35 additions & 38 deletions src/jiratui/api/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from dataclasses import dataclass
import logging
from typing import Any, Callable, cast
import ssl
from typing import Any, Callable

import httpx

Expand All @@ -25,38 +25,33 @@ def auth_flow(self, request):
yield request


@dataclass
class SSLCertificateSettings:
cert: str | tuple[str, str] | tuple[str, str, str] | None = None
verify_ssl: str | bool = True


def _setup_ssl_certificates(configuration: ApplicationConfiguration) -> SSLCertificateSettings:
cert: str | tuple[str, str] | tuple[str, str, str] | None = None
verify_ssl: str | bool = True

def _setup_ssl_certificates(
configuration: ApplicationConfiguration,
) -> ssl.SSLContext | bool | None:
"""
Returns a value that can be passed to the `verify` kwarg of an httpx client
Either:
False to disable verification,
True if no SSL config block is defined (default behavior),
or an instance of ssl.SSLContext with client cert/key/CA loaded (if specified. Otherwise, an SSLContext with the default CA bundle).
"""
if ssl_certificate_configuration := configuration.ssl:
verify_ssl = ssl_certificate_configuration.verify_ssl
httpx_certificate_configuration: list[str] = []
if certificate_path := ssl_certificate_configuration.certificate_file:
httpx_certificate_configuration.append(certificate_path)
if key_file := ssl_certificate_configuration.key_file:
httpx_certificate_configuration.append(key_file)
if password := ssl_certificate_configuration.password:
httpx_certificate_configuration.append(password.get_secret_value())

if verify_ssl and ssl_certificate_configuration.ca_bundle:
verify_ssl = ssl_certificate_configuration.ca_bundle

# expects:
# (certificate file) or,
# (certificate file, key file) or,
# (certificate file, key file, password)
cert = cast(
str | tuple[str, str] | tuple[str, str, str], tuple(httpx_certificate_configuration)
)
if ssl_certificate_configuration.verify_ssl is False:
return False
ctx = ssl.create_default_context(cafile=ssl_certificate_configuration.ca_bundle)
# Only load the client cert if certificate_file is set
# `load_cert_chain` is safe to run even if key_file is None or password is None
if ssl_certificate_configuration.certificate_file:
ctx.load_cert_chain(
certfile=ssl_certificate_configuration.certificate_file,
keyfile=ssl_certificate_configuration.key_file,
password=ssl_certificate_configuration.password.get_secret_value()
if ssl_certificate_configuration.password
else None,
)

return SSLCertificateSettings(cert=cert, verify_ssl=verify_ssl)
return ctx
return True


class JiraTUIAsyncHTTPClient:
Expand All @@ -72,15 +67,16 @@ def __init__(
api_token: str,
configuration: ApplicationConfiguration,
):
ssl_certificate_settings: SSLCertificateSettings = _setup_ssl_certificates(configuration)
ssl_certificate_settings = _setup_ssl_certificates(configuration)
self.base_url: str = base_url.rstrip('/')
if configuration.use_bearer_authentication:
self.authentication: httpx.Auth = JiraTUIBearerAuth(api_token, api_username)
elif configuration.use_cert_authentication:
self.authentication = None
else:
self.authentication = httpx.BasicAuth(api_username, api_token.strip())
self.client: httpx.AsyncClient = httpx.AsyncClient(
verify=ssl_certificate_settings.verify_ssl,
cert=ssl_certificate_settings.cert,
verify=ssl_certificate_settings,
timeout=None,
)
self.logger = logging.getLogger(LOGGER_NAME)
Expand Down Expand Up @@ -188,15 +184,16 @@ def __init__(
api_token: str,
configuration: ApplicationConfiguration,
):
ssl_certificate_settings: SSLCertificateSettings = _setup_ssl_certificates(configuration)
ssl_certificate_settings = _setup_ssl_certificates(configuration)
self.base_url: str = base_url.rstrip('/')
if configuration.use_bearer_authentication:
self.authentication: httpx.Auth = JiraTUIBearerAuth(api_token, api_username)
elif configuration.use_cert_authentication:
self.authentication = None
else:
self.authentication = httpx.BasicAuth(api_username, api_token.strip())
self.client: httpx.Client = httpx.Client(
verify=ssl_certificate_settings.verify_ssl,
cert=ssl_certificate_settings.cert,
verify=ssl_certificate_settings,
timeout=None,
)
self.logger = logging.getLogger(LOGGER_NAME)
Expand Down
2 changes: 2 additions & 0 deletions src/jiratui/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class ApplicationConfiguration(BaseSettings):
"""Set this to False if your Jira instance run on-premises."""
use_bearer_authentication: bool = False
"""Set this to True if your Jira instance uses Bearer authentication instead of Basic authentication."""
use_cert_authentication: bool = False
"""Set this to True if your Jira instance uses certificate-based authentication instead of Bearer authentication or Basic authentication."""
jira_user_group_id: str | None = None
"""The ID of the group that contains all (or most) of the Jira users in your Jira installation. This value is used
as a fall back mechanism to fetch available users. This is only supported in the Jira Cloud Platform."""
Expand Down
5 changes: 5 additions & 0 deletions src/jiratui/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def config_for_testing() -> ApplicationConfiguration:
jira_api_token=SecretStr('bar'),
jira_api_version=3,
use_bearer_authentication=False,
use_cert_authentication=False,
cloud=True,
ignore_users_without_email=True,
default_project_key_or_id=None,
Expand Down Expand Up @@ -58,6 +59,7 @@ def config_for_testing_jira_dc() -> ApplicationConfiguration:
jira_api_token=SecretStr('bar'),
jira_api_version=3,
use_bearer_authentication=False,
use_cert_authentication=False,
cloud=False,
ignore_users_without_email=True,
default_project_key_or_id=None,
Expand Down Expand Up @@ -85,6 +87,7 @@ def jira_api_controller() -> APIController:
jira_api_token=SecretStr('bar'),
jira_api_version=3,
use_bearer_authentication=False,
use_cert_authentication=False,
cloud=True,
ignore_users_without_email=True,
default_project_key_or_id=None,
Expand Down Expand Up @@ -113,6 +116,7 @@ def jira_api_controller_for_jira_dc() -> APIController:
jira_api_token=SecretStr('bar'),
jira_api_version=3,
use_bearer_authentication=False,
use_cert_authentication=False,
cloud=False,
ignore_users_without_email=True,
default_project_key_or_id=None,
Expand Down Expand Up @@ -141,6 +145,7 @@ def app() -> JiraApp:
jira_api_token=SecretStr('bar'),
jira_api_version=3,
use_bearer_authentication=False,
use_cert_authentication=False,
cloud=True,
ignore_users_without_email=True,
default_project_key_or_id=None,
Expand Down
6 changes: 6 additions & 0 deletions src/jiratui/tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def app_with_unrecognized_config_theme() -> JiraApp:
jira_api_token=SecretStr('bar'),
jira_api_version=3,
use_bearer_authentication=False,
use_cert_authentication=False,
cloud=True,
ignore_users_without_email=True,
default_project_key_or_id=None,
Expand Down Expand Up @@ -54,6 +55,7 @@ def app_with_input_and_config_theme() -> JiraApp:
jira_api_token=SecretStr('bar'),
jira_api_version=3,
use_bearer_authentication=False,
use_cert_authentication=False,
cloud=True,
ignore_users_without_email=True,
default_project_key_or_id=None,
Expand Down Expand Up @@ -85,6 +87,7 @@ def app_with_input_theme() -> JiraApp:
jira_api_token=SecretStr('bar'),
jira_api_version=3,
use_bearer_authentication=False,
use_cert_authentication=False,
cloud=True,
ignore_users_without_email=True,
default_project_key_or_id=None,
Expand Down Expand Up @@ -116,6 +119,7 @@ def app_without_config_theme() -> JiraApp:
jira_api_token=SecretStr('bar'),
jira_api_version=3,
use_bearer_authentication=False,
use_cert_authentication=False,
cloud=True,
ignore_users_without_email=True,
default_project_key_or_id=None,
Expand Down Expand Up @@ -147,6 +151,7 @@ def app() -> JiraApp:
jira_api_token=SecretStr('bar'),
jira_api_version=3,
use_bearer_authentication=False,
use_cert_authentication=False,
cloud=True,
ignore_users_without_email=True,
default_project_key_or_id=None,
Expand Down Expand Up @@ -567,6 +572,7 @@ def config_dict() -> dict:
'cloud': True,
'ssl': None,
'use_bearer_authentication': False,
'use_cert_authentication': False,
'ignore_users_without_email': True,
'default_project_key_or_id': None,
'jira_account_id': None,
Expand Down
1 change: 1 addition & 0 deletions src/jiratui/tests/test_fulltext_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def app() -> JiraApp:
cloud=True,
ssl=None,
use_bearer_authentication=False,
use_cert_authentication=False,
ignore_users_without_email=True,
default_project_key_or_id=None,
jira_account_id=None,
Expand Down
1 change: 1 addition & 0 deletions src/jiratui/tests/test_main_screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def app() -> JiraApp:
jira_api_token=SecretStr('bar'),
jira_api_version=3,
use_bearer_authentication=False,
use_cert_authentication=False,
cloud=True,
ignore_users_without_email=True,
default_project_key_or_id=None,
Expand Down
1 change: 1 addition & 0 deletions src/jiratui/tests/test_search_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def app() -> JiraApp:
jira_api_token=SecretStr('bar'),
jira_api_version=3,
use_bearer_authentication=False,
use_cert_authentication=False,
cloud=True,
ignore_users_without_email=True,
default_project_key_or_id=None,
Expand Down
1 change: 1 addition & 0 deletions src/jiratui/tests/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def app() -> JiraApp:
jira_api_token=SecretStr('bar'),
jira_api_version=3,
use_bearer_authentication=False,
use_cert_authentication=False,
cloud=True,
ignore_users_without_email=True,
default_project_key_or_id=None,
Expand Down
2 changes: 1 addition & 1 deletion src/jiratui/widgets/server_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ async def on_mount(self) -> None:
),
(
Text('Account Type', justify='right', style='yellow'),
Text(user_info.account_type, justify='left'),
Text(str(user_info.account_type), justify='left'),
),
(
Text('Active', justify='right', style='yellow'),
Expand Down