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
3 changes: 2 additions & 1 deletion gh-pages-template/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
</select>
</div>
<div class="col-12 col-md-6 col-lg-5">
<input type="text" class="form-control rounded-0" placeholder="Search" id="search_term">
<input type="text" class="form-control rounded-0" placeholder="Search" id="search_term"
aria-label="Search">
</div>
<button type="button" class="btn btn-warning col-12 col-md-auto rounded-0" onclick="run_search()">
<i class="fa-fw fas fa-search"></i>Search</button>
Expand Down
53 changes: 50 additions & 3 deletions src/queue_eligibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,24 +52,71 @@ def normalize_user_id(user_id: object) -> str:
return str(user_id).strip()


def load_auto_approved_user_ids(auto_approved_users_file: Path) -> frozenset[str]:
def resolve_auto_approved_users_file(
auto_approved_users_file: Path,
base_dir: Path | None = None,
) -> Path:
"""
Resolve an auto-approved users file path within the allowed base directory.

Parameters
----------
auto_approved_users_file : pathlib.Path
Candidate JSON file path.
base_dir : pathlib.Path, optional
Directory that may contain the allowlist file.

Returns
-------
pathlib.Path
Canonical allowlist file path.

Raises
------
ValueError
If the resolved path escapes the allowed base directory.
"""
allowed_base_dir = (Path.cwd() if base_dir is None else base_dir).resolve()
candidate_file = auto_approved_users_file
if not candidate_file.is_absolute():
candidate_file = allowed_base_dir / candidate_file

resolved_file = candidate_file.resolve()
try:
resolved_file.relative_to(allowed_base_dir)
except ValueError as error:
raise ValueError('auto-approved users file must be inside the working directory') from error

return resolved_file


def load_auto_approved_user_ids(
auto_approved_users_file: Path,
base_dir: Path | None = None,
) -> frozenset[str]:
"""
Load auto-approved GitHub user ids from a JSON file.

Parameters
----------
auto_approved_users_file : pathlib.Path
JSON file containing auto-approved user objects.
base_dir : pathlib.Path, optional
Directory that may contain the allowlist file.

Returns
-------
frozenset[str]
Normalized GitHub user ids.
"""
try:
with auto_approved_users_file.open(encoding='utf-8') as auto_approved_users_f:
safe_auto_approved_users_file = resolve_auto_approved_users_file(
auto_approved_users_file=auto_approved_users_file,
base_dir=base_dir,
)
with safe_auto_approved_users_file.open(encoding='utf-8') as auto_approved_users_f:
auto_approved_users = json.load(auto_approved_users_f)
except (OSError, json.JSONDecodeError):
except (OSError, ValueError):
return frozenset()

if not isinstance(auto_approved_users, list):
Expand Down
34 changes: 31 additions & 3 deletions tests/unit/test_queue_eligibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def test_load_auto_approved_user_ids_loads_user_ids(tmp_path):

auto_approved_user_ids = queue_eligibility.load_auto_approved_user_ids(
auto_approved_users_file=auto_approved_users_file,
base_dir=tmp_path,
)

assert auto_approved_user_ids == frozenset({'42013603', '88998541'})
Expand Down Expand Up @@ -61,6 +62,7 @@ def test_load_auto_approved_user_ids_ignores_malformed_entries(tmp_path):

auto_approved_user_ids = queue_eligibility.load_auto_approved_user_ids(
auto_approved_users_file=auto_approved_users_file,
base_dir=tmp_path,
)

assert auto_approved_user_ids == frozenset({'1234'})
Expand All @@ -71,8 +73,14 @@ def test_load_auto_approved_user_ids_fails_closed_for_missing_or_bad_file(tmp_pa
bad_file = tmp_path / 'bad.json'
bad_file.write_text('{', encoding='utf-8')

assert queue_eligibility.load_auto_approved_user_ids(auto_approved_users_file=missing_file) == frozenset()
assert queue_eligibility.load_auto_approved_user_ids(auto_approved_users_file=bad_file) == frozenset()
assert queue_eligibility.load_auto_approved_user_ids(
auto_approved_users_file=missing_file,
base_dir=tmp_path,
) == frozenset()
assert queue_eligibility.load_auto_approved_user_ids(
auto_approved_users_file=bad_file,
base_dir=tmp_path,
) == frozenset()


def test_load_auto_approved_user_ids_fails_closed_for_non_list_json(tmp_path):
Expand All @@ -82,6 +90,25 @@ def test_load_auto_approved_user_ids_fails_closed_for_non_list_json(tmp_path):

auto_approved_user_ids = queue_eligibility.load_auto_approved_user_ids(
auto_approved_users_file=auto_approved_users_file,
base_dir=tmp_path,
)

assert auto_approved_user_ids == frozenset()


def test_load_auto_approved_user_ids_rejects_path_outside_base_dir(tmp_path):
base_dir = tmp_path / 'base'
base_dir.mkdir()
outside_file = write_auto_approved_users_file(tmp_path, [
{
'user_id': 42013603,
'username': 'ReenigneArcher',
},
])

auto_approved_user_ids = queue_eligibility.load_auto_approved_user_ids(
auto_approved_users_file=Path('..') / outside_file.name,
base_dir=base_dir,
)

assert auto_approved_user_ids == frozenset()
Expand Down Expand Up @@ -213,10 +240,11 @@ def test_main_writes_queue_eligible_result(tmp_path, monkeypatch):
])
output_file = tmp_path / 'github_output'
monkeypatch.setenv('GITHUB_OUTPUT', str(output_file))
monkeypatch.chdir(tmp_path)
monkeypatch.setattr(sys, 'argv', [
'queue_eligibility',
'--auto-approved-users-file',
str(auto_approved_users_file),
auto_approved_users_file.name,
'--user-id',
'42013603',
])
Expand Down