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
47 changes: 47 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: CI

on:
push:
branches: [master]
pull_request:
branches: [master]

permissions:
contents: read
id-token: write

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r tests/requirements.txt
pip install pytest pytest-cov ruff
pip install -e .

- name: Ruff
run: ruff check tpm.py tests

- name: Tests with coverage
run: pytest --cov=tpm --cov-report=xml tests/

- name: Upload coverage
if: github.event_name == 'push'
uses: codecov/codecov-action@v4
with:
use_oidc: true
files: ./coverage.xml
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> Modern Python SDK groundwork for TeamPasswordManager with safer API boundaries and maintainable packaging.

[![Build Status](https://travis-ci.org/peshay/tpm.svg?branch=master)](https://travis-ci.org/peshay/tpm)
[![CI](https://github.com/peshay/tpm/actions/workflows/ci.yml/badge.svg)](https://github.com/peshay/tpm/actions/workflows/ci.yml)
[![Python version](https://img.shields.io/pypi/pyversions/tpm.svg)](https://pypi.python.org/pypi/tpm)
[![license](https://img.shields.io/github/license/peshay/tpm.svg)](https://github.com/peshay/tpm/blob/master/LICENSE)

Expand Down
21 changes: 21 additions & 0 deletions docs/audits/repository-structure-packaging-tests-ci.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Repository structure, packaging, tests, CI audit

Scope: `peshay/tpm`

## Findings
- Repo is a single-module package: `tpm.py`, `setup.py`, `requirements.txt`, `tests/`.
- No `pyproject.toml` yet; packaging still uses `distutils.core.setup` in `setup.py`.
- Runtime dependencies are legacy-pinned (`requests<=2.26.0`, `future`, `urllib3`).
- CI is Travis-based, targets Python 3.6-3.9 plus nightly, installs with `python setup.py -q install`, and runs `nose2 -v --with-coverage`.
- Tests are fixture-driven under `tests/resources/` and cover API v4/v5 behavior.
- Cross-cutting gap: the request layer disables TLS verification by default (`verify=False`).

## Risks / gaps
- Packaging path is outdated for current Python tooling.
- CI/runtime support matrix is stale and likely misses current supported versions.
- TLS verification default is unsafe for production use.

## Follow-up
- Modernize packaging to `pyproject.toml`.
- Refresh the test/CI matrix.
- Revisit transport security defaults in a separate hardening card.
26 changes: 12 additions & 14 deletions tests/test_tpm_v4.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import requests
import requests_mock
import unittest
import os.path
Expand All @@ -8,7 +7,6 @@
import hmac
import hashlib
import time
import random

log = logging.getLogger(__name__)

Expand All @@ -24,7 +22,7 @@ def fake_data(url, m, altpath=False):
"""
# Map path from url to a file
path_parts = url.split('/')[6:]
if altpath == False:
if not altpath:
path = '/'.join(path_parts)
else:
path = altpath
Expand Down Expand Up @@ -845,12 +843,12 @@ def test_provide_unlock_reason(self):
request_path = local_path + path_to_mock
resource_file = os.path.normpath(request_path)
data_file = open(resource_file)
data = json.load(data_file)
json.load(data_file)
unlock_reason = 'because I can'
client = tpm.TpmApiv4('https://tpm.example.com', username='USER', password='PASS', unlock_reason=unlock_reason)
with requests_mock.Mocker() as m:
m.get(request_url, request_headers={'X-Unlock-Reason': unlock_reason})
response = client.show_password('14')
client.show_password('14')
history = m.request_history
request_unlock_reason = history[0].headers.get('X-Unlock-Reason')
self.assertEqual(request_unlock_reason, unlock_reason)
Expand All @@ -864,12 +862,12 @@ def test_key_authentciation(self):
request_url = api_url + path_to_mock
with requests_mock.Mocker() as m:
fake_data(request_url, m)
response = client.get_version()
client.get_version()
history = m.request_history
request_hash = history[0].headers.get('X-Request-Hash')
request_pubkey = history[0].headers.get('X-Public-Key')
history[0].headers.get('X-Public-Key')
request_timestamp = history[0].headers.get('X-Request-Timestamp')
timestamp = str(int(time.time()))
str(int(time.time()))
unhashed = 'api/v4/' + path_to_mock + request_timestamp
hashed = hmac.new(str.encode(private_key),
msg=unhashed.encode('utf-8'),
Expand Down Expand Up @@ -936,7 +934,7 @@ def test_function_up_to_date_true(self):
request_path = local_path + path_to_mock
resource_file = os.path.normpath(request_path)
data_file = open(resource_file)
data = json.load(data_file)
json.load(data_file)
with requests_mock.Mocker() as m:
fake_data(request_url, m)
response_up_to_date_true = self.client.up_to_date()
Expand Down Expand Up @@ -1014,7 +1012,7 @@ def test_value_error_exception(self):
with self.assertRaises(ValueError) as context:
with requests_mock.Mocker() as m:
m.get(request_url, text=str(data))
response = self.client.show_password('value_error')
self.client.show_password('value_error')
log.debug("context exception: {}".format(context.exception))
self.assertTrue(str(context.exception).startswith(exception_error) or str(context.exception).startswith(exception_error3))

Expand All @@ -1027,7 +1025,7 @@ def test_exception_on_error_in_result(self):
with self.assertRaises(tpm.TPMException) as context:
with requests_mock.Mocker() as m:
m.get(request_url, json=error_json)
response = self.client.show_password('json_error')
self.client.show_password('json_error')
log.debug("context exception: {}".format(context.exception))
self.assertTrue(exception_error in str(context.exception))

Expand All @@ -1039,7 +1037,7 @@ def test_exception_on_403(self):
with self.assertRaises(tpm.TPMException) as context:
with requests_mock.Mocker() as m:
m.get(request_url, text='forbidden', status_code=403)
response = self.client.list_passwords()
self.client.list_passwords()
log.debug("context exception: {}".format(context.exception))
self.assertTrue(exception_error in str(context.exception))

Expand All @@ -1051,7 +1049,7 @@ def test_exception_on_404(self):
with self.assertRaises(tpm.TPMException) as context:
with requests_mock.Mocker() as m:
m.get(request_url, text='not found', status_code=404)
response = self.client.list_passwords()
self.client.list_passwords()
log.debug("context exception: {}".format(context.exception))
self.assertTrue(exception_error in str(context.exception))

Expand All @@ -1063,6 +1061,6 @@ def test_exception_on_405(self):
with self.assertRaises(ValueError) as context:
with requests_mock.Mocker() as m:
m.get(request_url, text='Method Not Allowed', status_code=405)
response = self.client.list_passwords()
self.client.list_passwords()
log.debug("context exception: {}".format(context.exception))
self.assertTrue(str(context.exception).endswith(exception_error))
26 changes: 12 additions & 14 deletions tests/test_tpm_v5.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import requests
import requests_mock
import unittest
import os.path
Expand All @@ -8,7 +7,6 @@
import hmac
import hashlib
import time
import random

log = logging.getLogger(__name__)

Expand All @@ -24,7 +22,7 @@ def fake_data(url, m, altpath=False):
"""
# Map path from url to a file
path_parts = url.split('/')[6:]
if altpath == False:
if not altpath:
path = '/'.join(path_parts)
else:
path = altpath
Expand Down Expand Up @@ -920,12 +918,12 @@ def test_provide_unlock_reason(self):
request_path = local_path + path_to_mock
resource_file = os.path.normpath(request_path)
data_file = open(resource_file)
data = json.load(data_file)
json.load(data_file)
unlock_reason = 'because I can'
client = tpm.TpmApiv5('https://tpm.example.com', username='USER', password='PASS', unlock_reason=unlock_reason)
with requests_mock.Mocker() as m:
m.get(request_url, request_headers={'X-Unlock-Reason': unlock_reason})
response = client.show_password('14')
client.show_password('14')
history = m.request_history
request_unlock_reason = history[0].headers.get('X-Unlock-Reason')
self.assertEqual(request_unlock_reason, unlock_reason)
Expand All @@ -939,12 +937,12 @@ def test_key_authentciation(self):
request_url = api_url + path_to_mock
with requests_mock.Mocker() as m:
fake_data(request_url, m)
response = client.get_version()
client.get_version()
history = m.request_history
request_hash = history[0].headers.get('X-Request-Hash')
request_pubkey = history[0].headers.get('X-Public-Key')
history[0].headers.get('X-Public-Key')
request_timestamp = history[0].headers.get('X-Request-Timestamp')
timestamp = str(int(time.time()))
str(int(time.time()))
unhashed = 'api/v5/' + path_to_mock + request_timestamp
hashed = hmac.new(str.encode(private_key),
msg=unhashed.encode('utf-8'),
Expand Down Expand Up @@ -1011,7 +1009,7 @@ def test_function_up_to_date_true(self):
request_path = local_path + path_to_mock
resource_file = os.path.normpath(request_path)
data_file = open(resource_file)
data = json.load(data_file)
json.load(data_file)
with requests_mock.Mocker() as m:
fake_data(request_url, m)
response_up_to_date_true = self.client.up_to_date()
Expand Down Expand Up @@ -1089,7 +1087,7 @@ def test_value_error_exception(self):
with self.assertRaises(ValueError) as context:
with requests_mock.Mocker() as m:
m.get(request_url, text=str(data))
response = self.client.show_password('value_error')
self.client.show_password('value_error')
log.debug("context exception: {}".format(context.exception))
self.assertTrue(str(context.exception).startswith(exception_error) or str(context.exception).startswith(exception_error3))

Expand All @@ -1102,7 +1100,7 @@ def test_exception_on_error_in_result(self):
with self.assertRaises(tpm.TPMException) as context:
with requests_mock.Mocker() as m:
m.get(request_url, json=error_json)
response = self.client.show_password('json_error')
self.client.show_password('json_error')
log.debug("context exception: {}".format(context.exception))
self.assertTrue(exception_error in str(context.exception))

Expand All @@ -1114,7 +1112,7 @@ def test_exception_on_403(self):
with self.assertRaises(tpm.TPMException) as context:
with requests_mock.Mocker() as m:
m.get(request_url, text='forbidden', status_code=403)
response = self.client.list_passwords()
self.client.list_passwords()
log.debug("context exception: {}".format(context.exception))
self.assertTrue(exception_error in str(context.exception))

Expand All @@ -1126,7 +1124,7 @@ def test_exception_on_404(self):
with self.assertRaises(tpm.TPMException) as context:
with requests_mock.Mocker() as m:
m.get(request_url, text='not found', status_code=404)
response = self.client.list_passwords()
self.client.list_passwords()
log.debug("context exception: {}".format(context.exception))
self.assertTrue(exception_error in str(context.exception))

Expand All @@ -1138,7 +1136,7 @@ def test_exception_on_405(self):
with self.assertRaises(ValueError) as context:
with requests_mock.Mocker() as m:
m.get(request_url, text='Method Not Allowed', status_code=405)
response = self.client.list_passwords()
self.client.list_passwords()
log.debug("context exception: {}".format(context.exception))
self.assertTrue(str(context.exception).endswith(exception_error))

Expand Down
Loading