Skip to content
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
60 changes: 57 additions & 3 deletions jipdate/cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,56 @@ def get_config_file():
return config_path + "/" + config_filename


def validate_yaml_structure(config):
errors = []
server_fields = ["url", "token"]
expected_top_level = [
"version",
"server",
"test_server",
"comments",
"header",
"use_combined_issue_header",
"separator",
"text-editor",
]

def check_server_section(section_name):
if section_name in config:
section = config[section_name]
if not isinstance(section, dict):
errors.append(
f"'{section_name}' must be a dictionary/mapping with nested fields"
)
elif not set(section.keys()).intersection(server_fields):
errors.append(
f"'{section_name}' section exists but contains no expected fields - they should be indented under '{section_name}:'"
)

check_server_section("server")
check_server_section("test_server")

for field in server_fields:
if field in config:
if "server" not in config and "test_server" not in config:
errors.append(
f"Found '{field}' at root level - it should be indented under 'server:' or 'test_server:'"
)
elif "server" in config:
errors.append(
f"Found '{field}' at root level - it should be indented under 'server:'"
)

config_keys = set(config.keys()) if config else set()
unexpected_keys = config_keys - set(expected_top_level) - set(server_fields)

for field_name in ["comments", "header"]:
if field_name in config and not isinstance(config[field_name], list):
errors.append(f"'{field_name}' should be a list")

return errors


def get_server(use_test_server=False):
# Get Jira Server details. Check first if using the test server
# then try user config file, then default from cfg.py
Expand All @@ -105,9 +155,6 @@ def get_server(use_test_server=False):


def initiate_config():
"""Reads the config file (yaml format) and returns the sets the global
instance.
"""
global yml_config
global config_file

Expand All @@ -118,3 +165,10 @@ def initiate_config():
log.debug("Using config file: %s" % config_file)
with open(config_file, "r") as yml:
yml_config = yaml.load(yml, Loader=yaml.FullLoader)

validation_errors = validate_yaml_structure(yml_config)
if validation_errors:
log.error(f"Configuration validation failed for {config_file}:")
for error in validation_errors:
log.error(f" - {error}")
sys.exit(1)
33 changes: 25 additions & 8 deletions jipdate/jiralogin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@
from jipdate import cfg
from jira import JIRA
from jira import JIRAError
from requests.exceptions import JSONDecodeError


def _get_auth_error_message(response_text, url):
"""Return a user-friendly authentication error message."""
if not response_text:
hint = "Empty response - check network connectivity"
elif any(word in response_text.lower() for word in ["login", "sign in", "captcha"]):
hint = "Authentication required - complete web login/CAPTCHA first"
elif any(word in response_text.lower() for word in ["forbidden", "access denied"]):
hint = "Access denied - check credentials and permissions"
elif any(word in response_text.lower() for word in ["maintenance", "unavailable"]):
hint = "Server maintenance - try again later"
else:
hint = "Received HTML instead of JSON - likely authentication issue"

return (
f"Authentication failed: {hint}\n\n"
f"To fix: Open {url} in browser, logout/login, complete any CAPTCHA, then retry."
)


def get_username_from_config():
Expand Down Expand Up @@ -135,15 +155,12 @@ def get_jira_instance(use_test_server):
),
username,
)
except JSONDecodeError as e:
log.error(_get_auth_error_message(getattr(e, "doc", ""), url))
sys.exit(os.EX_NOPERM)
except JIRAError as e:
if e.text.find("CAPTCHA_CHALLENGE") != -1:
log.error(
"Captcha verification has been triggered by "
"JIRA - please go to JIRA using your web "
"browser, log out of JIRA, log back in "
"entering the captcha; after that is done, "
"please re-run the script"
)
if "CAPTCHA_CHALLENGE" in e.text:
log.error(_get_auth_error_message("captcha", url))
sys.exit(os.EX_NOPERM)
else:
raise
Expand Down