feat: allow admins to bypass approval flow when joining networks

This commit is contained in:
Ubuntu
2026-05-07 20:04:08 +00:00
parent 32d517ea08
commit d100fdff3b
34 changed files with 2523 additions and 1637 deletions
+15
View File
@@ -48,6 +48,21 @@ class AdminClient:
data={"confirm": confirm},
)
def get_user_ssh_certificates(self, user_id: str, **params) -> dict:
"""List all SSH certificates for a user (admin view).
Args:
user_id: Target user ID
**params: Optional query parameters — status, active, cert_type, page, per_page
"""
path = f"/admin/users/{user_id}/ssh-certificates"
if params:
from urllib.parse import urlencode
query = urlencode({k: v for k, v in params.items() if v is not None})
if query:
path = f"{path}?{query}"
return self._client.get(path)
def list_audit_logs(self) -> dict:
"""List system-wide audit logs."""
return self._client.get("/audit-logs")
+306
View File
@@ -211,3 +211,309 @@ class TestAdminUserManagement:
with pytest.raises(ApiError) as exc_info:
integration_client.auth.login(email=victim["email"], password="VictimPass123!")
assert exc_info.value.status_code in (400, 401)
class TestAdminSSHCertificates:
"""Test admin SSH certificate listing endpoints."""
def _create_test_cert(
self, integration_app, user_id: str, ca_id: str, *, ssh_key_id=None,
status="issued", revoked=False, valid_after=None, valid_before=None,
cert_type="user", principals=None,
):
"""Create a test SSH certificate record."""
from datetime import datetime, timezone, timedelta
from gatehouse_app.models.ssh_ca.ssh_certificate import SSHCertificate, CertificateStatus
from gatehouse_app.models.ssh_ca.ca import CertType
now = datetime.now(timezone.utc)
valid_after = valid_after or (now - timedelta(hours=1))
valid_before = valid_before or (now + timedelta(hours=23))
principals = principals or ["prod-servers"]
with integration_app.app_context():
cert = SSHCertificate(
ca_id=ca_id,
user_id=user_id,
ssh_key_id=ssh_key_id,
certificate=f"ssh-ed25519-cert-v01@openssh.com AAAA...test_serial_{uuid.uuid4().hex[:8]}",
serial=str(uuid.uuid4().int)[:20],
key_id=f"test@example.com-{uuid.uuid4().hex[:8]}",
cert_type=CertType(cert_type),
principals=principals,
valid_after=valid_after,
valid_before=valid_before,
revoked=revoked,
status=CertificateStatus(status),
request_ip="192.168.1.100",
request_user_agent="OpenSSH_9.0",
)
if revoked:
cert.revoked_at = now
cert.revoke_reason = "test revocation"
db.session.add(cert)
db.session.commit()
return str(cert.id)
def _create_test_ssh_key(self, integration_app, user_id: str, fingerprint: str = None):
"""Create a test SSH key record."""
from gatehouse_app.models.ssh_ca.ssh_key import SSHKey
fingerprint = fingerprint or f"SHA256:{uuid.uuid4().hex[:43]}"
with integration_app.app_context():
key = SSHKey(
user_id=user_id,
payload=f"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...test",
fingerprint=fingerprint,
description="Test laptop key",
verified=True,
key_type="ssh-ed25519",
key_bits=256,
key_comment="test@laptop",
)
db.session.add(key)
db.session.commit()
return str(key.id)
def test_list_user_ssh_certs_positive(self, integration_app, integration_client, create_test_user, create_test_org, create_test_membership, create_test_ca):
"""TEST: ADMIN-SSH-01 — List all SSH certificates for a user as admin.
WHAT: Create a user with two certs (one active, one expired),
admin lists all certs via the new endpoint.
WHY: Admin needs full visibility of user SSH certificate history.
EXPECTED: 200 OK with certificates array containing both certs.
"""
admin = create_test_user(password="AdminPass123!")
victim = create_test_user(password="VictimPass123!")
org = create_test_org()
create_test_membership(admin["id"], org["id"], OrganizationRole.OWNER)
create_test_membership(victim["id"], org["id"], OrganizationRole.MEMBER)
ca = create_test_ca(org_id=org["id"])
from datetime import datetime, timezone, timedelta
now = datetime.now(timezone.utc)
# Create an active cert
self._create_test_cert(
integration_app, victim["id"], ca["id"],
status="issued", valid_after=now - timedelta(hours=1),
valid_before=now + timedelta(hours=23),
)
# Create an expired cert
self._create_test_cert(
integration_app, victim["id"], ca["id"],
status="expired", valid_after=now - timedelta(days=7),
valid_before=now - timedelta(days=1),
)
integration_client.auth.login(email=admin["email"], password="AdminPass123!")
result = integration_client.admin.get_user_ssh_certificates(victim["id"])
data = assert_success(result)
assert "certificates" in data
assert data["count"] == 2
assert len(data["certificates"]) == 2
def test_list_user_ssh_certs_with_key_metadata(self, integration_app, integration_client, create_test_user, create_test_org, create_test_membership, create_test_ca):
"""TEST: ADMIN-SSH-02 — Certificate includes SSH key metadata.
WHAT: Create a cert linked to an SSH key, verify key details
appear in the response.
WHY: Admin needs to see which key was used to request the cert.
EXPECTED: ssh_key object with fingerprint, key_type, key_bits.
"""
admin = create_test_user(password="AdminPass123!")
victim = create_test_user(password="VictimPass123!")
org = create_test_org()
create_test_membership(admin["id"], org["id"], OrganizationRole.OWNER)
create_test_membership(victim["id"], org["id"], OrganizationRole.MEMBER)
ca = create_test_ca(org_id=org["id"])
key_id = self._create_test_ssh_key(integration_app, victim["id"])
self._create_test_cert(integration_app, victim["id"], ca["id"], ssh_key_id=key_id)
integration_client.auth.login(email=admin["email"], password="AdminPass123!")
result = integration_client.admin.get_user_ssh_certificates(victim["id"])
data = assert_success(result)
cert = data["certificates"][0]
assert cert["ssh_key"] is not None
assert cert["ssh_key"]["key_type"] == "ssh-ed25519"
assert cert["ssh_key"]["fingerprint"] is not None
assert cert["ssh_key"]["description"] == "Test laptop key"
def test_list_user_ssh_certs_non_admin_negative(self, integration_app, integration_client, create_test_user, create_test_org, create_test_membership, create_test_ca):
"""TEST: ADMIN-SSH-03 — Non-admin cannot list another user's certs.
WHAT: Regular member tries to list admin's certs.
WHY: Certificate data is sensitive and admin-only.
EXPECTED: 403 Forbidden.
"""
member = create_test_user(password="MemberPass123!")
admin_user = create_test_user(password="AdminPass123!")
org = create_test_org()
create_test_membership(member["id"], org["id"], OrganizationRole.MEMBER)
create_test_membership(admin_user["id"], org["id"], OrganizationRole.OWNER)
integration_client.auth.login(email=member["email"], password="MemberPass123!")
with pytest.raises(ApiError) as exc_info:
integration_client.admin.get_user_ssh_certificates(admin_user["id"])
assert exc_info.value.status_code == 403
def test_list_user_ssh_certs_filter_by_status(self, integration_app, integration_client, create_test_user, create_test_org, create_test_membership, create_test_ca):
"""TEST: ADMIN-SSH-04 — Filter certificates by status.
WHAT: Create certs with different statuses, filter by status=revoked.
WHY: Admin may want to see only revoked certs to audit access.
EXPECTED: Only revoked certs returned.
"""
admin = create_test_user(password="AdminPass123!")
victim = create_test_user(password="VictimPass123!")
org = create_test_org()
create_test_membership(admin["id"], org["id"], OrganizationRole.OWNER)
create_test_membership(victim["id"], org["id"], OrganizationRole.MEMBER)
ca = create_test_ca(org_id=org["id"])
from datetime import datetime, timezone, timedelta
now = datetime.now(timezone.utc)
self._create_test_cert(integration_app, victim["id"], ca["id"], status="issued")
self._create_test_cert(integration_app, victim["id"], ca["id"], status="revoked", revoked=True)
self._create_test_cert(integration_app, victim["id"], ca["id"], status="expired")
integration_client.auth.login(email=admin["email"], password="AdminPass123!")
result = integration_client.admin.get_user_ssh_certificates(victim["id"], status="revoked")
data = assert_success(result)
assert data["count"] == 1
assert data["certificates"][0]["status"] == "revoked"
assert data["certificates"][0]["revoked"] is True
def test_list_user_ssh_certs_filter_active_only(self, integration_app, integration_client, create_test_user, create_test_org, create_test_membership, create_test_ca):
"""TEST: ADMIN-SSH-05 — Filter for only currently valid certificates.
WHAT: Create active and expired certs, filter by active=true.
WHY: Admin needs quick view of currently active certs.
EXPECTED: Only valid (non-revoked, non-expired) certs returned.
"""
admin = create_test_user(password="AdminPass123!")
victim = create_test_user(password="VictimPass123!")
org = create_test_org()
create_test_membership(admin["id"], org["id"], OrganizationRole.OWNER)
create_test_membership(victim["id"], org["id"], OrganizationRole.MEMBER)
ca = create_test_ca(org_id=org["id"])
from datetime import datetime, timezone, timedelta
now = datetime.now(timezone.utc)
self._create_test_cert(
integration_app, victim["id"], ca["id"], status="issued",
valid_after=now - timedelta(hours=1), valid_before=now + timedelta(hours=23),
)
self._create_test_cert(
integration_app, victim["id"], ca["id"], status="expired",
valid_after=now - timedelta(days=7), valid_before=now - timedelta(days=1),
)
self._create_test_cert(
integration_app, victim["id"], ca["id"], status="revoked", revoked=True,
valid_after=now - timedelta(hours=1), valid_before=now + timedelta(hours=23),
)
integration_client.auth.login(email=admin["email"], password="AdminPass123!")
result = integration_client.admin.get_user_ssh_certificates(victim["id"], active="true")
data = assert_success(result)
assert data["count"] == 1
cert = data["certificates"][0]
assert cert["is_valid"] is True
assert cert["revoked"] is False
def test_list_user_ssh_certs_user_not_found(self, integration_app, integration_client, create_test_user, create_test_org, create_test_membership):
"""TEST: ADMIN-SSH-06 — Return 404 for non-existent user.
WHAT: Admin requests certs for a user ID that doesn't exist.
WHY: Clear error for missing resources.
EXPECTED: 404 NOT_FOUND.
"""
admin = create_test_user(password="AdminPass123!")
org = create_test_org()
create_test_membership(admin["id"], org["id"], OrganizationRole.OWNER)
integration_client.auth.login(email=admin["email"], password="AdminPass123!")
with pytest.raises(ApiError) as exc_info:
integration_client.admin.get_user_ssh_certificates("non-existent-user-id")
assert exc_info.value.status_code == 404
assert exc_info.value.error_type == "NOT_FOUND"
def test_list_user_ssh_certs_empty_result(self, integration_app, integration_client, create_test_user, create_test_org, create_test_membership):
"""TEST: ADMIN-SSH-07 — Empty result when user has no certs.
WHAT: Admin lists certs for a user who has never requested one.
WHY: Endpoint should handle gracefully, not error.
EXPECTED: 200 OK with empty certificates array and count=0.
"""
admin = create_test_user(password="AdminPass123!")
victim = create_test_user(password="VictimPass123!")
org = create_test_org()
create_test_membership(admin["id"], org["id"], OrganizationRole.OWNER)
create_test_membership(victim["id"], org["id"], OrganizationRole.MEMBER)
integration_client.auth.login(email=admin["email"], password="AdminPass123!")
result = integration_client.admin.get_user_ssh_certificates(victim["id"])
data = assert_success(result)
assert data["certificates"] == []
assert data["count"] == 0
def test_list_user_ssh_certs_revoked_cert_details(self, integration_app, integration_client, create_test_user, create_test_org, create_test_membership, create_test_ca):
"""TEST: ADMIN-SSH-08 — Revoked certificate shows revocation details.
WHAT: Create a revoked cert, verify revoke metadata is present.
WHY: Admin needs to know when and why a cert was revoked.
EXPECTED: revoked=True, revoked_at populated, revoke_reason present.
"""
admin = create_test_user(password="AdminPass123!")
victim = create_test_user(password="VictimPass123!")
org = create_test_org()
create_test_membership(admin["id"], org["id"], OrganizationRole.OWNER)
create_test_membership(victim["id"], org["id"], OrganizationRole.MEMBER)
ca = create_test_ca(org_id=org["id"])
self._create_test_cert(
integration_app, victim["id"], ca["id"],
status="revoked", revoked=True,
)
integration_client.auth.login(email=admin["email"], password="AdminPass123!")
result = integration_client.admin.get_user_ssh_certificates(victim["id"])
data = assert_success(result)
cert = data["certificates"][0]
assert cert["revoked"] is True
assert cert["revoked_at"] is not None
assert cert["revoke_reason"] == "test revocation"
assert cert["status"] == "revoked"
def test_list_user_ssh_certs_invalid_status_filter(self, integration_app, integration_client, create_test_user, create_test_org, create_test_membership):
"""TEST: ADMIN-SSH-09 — Invalid status filter returns 400.
WHAT: Admin passes an invalid status value.
WHY: Input validation prevents confusing queries.
EXPECTED: 400 VALIDATION_ERROR.
"""
admin = create_test_user(password="AdminPass123!")
victim = create_test_user(password="VictimPass123!")
org = create_test_org()
create_test_membership(admin["id"], org["id"], OrganizationRole.OWNER)
create_test_membership(victim["id"], org["id"], OrganizationRole.MEMBER)
integration_client.auth.login(email=admin["email"], password="AdminPass123!")
with pytest.raises(ApiError) as exc_info:
integration_client.admin.get_user_ssh_certificates(victim["id"], status="bogus")
assert exc_info.value.status_code == 400
assert exc_info.value.error_type == "VALIDATION_ERROR"
+142
View File
@@ -201,3 +201,145 @@ class TestZeroTierMembership:
except ApiError as exc:
# Accept errors when no active memberships to kill
assert exc.status_code in (400, 500)
class TestAdminUserDevices:
"""Test admin endpoint to list devices for a specific user."""
def test_list_user_devices_positive(
self, integration_client, create_test_user, create_test_org, create_test_membership, integration_app
):
"""TEST: ZT-10 — Admin lists devices for a user with devices.
WHAT: Admin GET /organizations/<id>/users/<user_id>/devices.
WHY: Admins need to see what devices a user has registered.
EXPECTED: 200 OK with devices array.
"""
from gatehouse_app.models.zerotier.device import Device
admin = create_test_user(password="AdminPass123!")
member = create_test_user(password="MemberPass123!")
org = create_test_org()
create_test_membership(admin["id"], org["id"], OrganizationRole.ADMIN)
create_test_membership(member["id"], org["id"], OrganizationRole.MEMBER)
# Create test devices for the member
from gatehouse_app.extensions import db as _db
with integration_app.app_context():
device1 = Device(
user_id=member["id"],
organization_id=org["id"],
node_id="1234567890",
device_nickname="Member Laptop",
hostname="member-laptop",
)
device2 = Device(
user_id=member["id"],
organization_id=org["id"],
node_id="0987654321",
device_nickname="Member Phone",
hostname="member-phone",
)
_db.session.add_all([device1, device2])
_db.session.commit()
integration_client.auth.login(email=admin["email"], password="AdminPass123!")
result = integration_client.get(f"/organizations/{org['id']}/users/{member['id']}/devices")
data = assert_success(result, "devices retrieved")
assert "devices" in data
assert data["count"] == 2
assert data["user_id"] == member["id"]
assert data["organization_id"] == org["id"]
device_node_ids = [d["node_id"] for d in data["devices"]]
assert "1234567890" in device_node_ids
assert "0987654321" in device_node_ids
def test_list_user_devices_no_devices(
self, integration_client, create_test_user, create_test_org, create_test_membership
):
"""TEST: ZT-11 — Admin lists devices for a user with no devices.
WHAT: Admin GET /organizations/<id>/users/<user_id>/devices for user with no devices.
WHY: Endpoint should return empty list, not error.
EXPECTED: 200 OK with empty devices array.
"""
admin = create_test_user(password="AdminPass123!")
member = create_test_user(password="MemberPass123!")
org = create_test_org()
create_test_membership(admin["id"], org["id"], OrganizationRole.ADMIN)
create_test_membership(member["id"], org["id"], OrganizationRole.MEMBER)
integration_client.auth.login(email=admin["email"], password="AdminPass123!")
result = integration_client.get(f"/organizations/{org['id']}/users/{member['id']}/devices")
data = assert_success(result)
assert data["count"] == 0
assert data["devices"] == []
def test_list_user_devices_non_admin_negative(
self, integration_client, create_test_user, create_test_org, create_test_membership
):
"""TEST: ZT-12 — Non-admin cannot list another user's devices.
WHAT: Member attempts GET /organizations/<id>/users/<user_id>/devices.
WHY: This endpoint is admin-only.
EXPECTED: 403 Forbidden.
"""
member1 = create_test_user(password="Member1Pass123!")
member2 = create_test_user(password="Member2Pass123!")
org = create_test_org()
create_test_membership(member1["id"], org["id"], OrganizationRole.MEMBER)
create_test_membership(member2["id"], org["id"], OrganizationRole.MEMBER)
integration_client.auth.login(email=member1["email"], password="Member1Pass123!")
with pytest.raises(ApiError) as exc_info:
integration_client.get(f"/organizations/{org['id']}/users/{member2['id']}/devices")
assert exc_info.value.status_code == 403
def test_list_user_devices_user_not_in_org_negative(
self, integration_client, create_test_user, create_test_org, create_test_membership
):
"""TEST: ZT-13 — Cannot list devices for user not in organization.
WHAT: Admin GET /organizations/<id>/users/<user_id>/devices for user not in org.
WHY: User must be a member of the organization.
EXPECTED: 404 Not Found.
"""
admin = create_test_user(password="AdminPass123!")
outside_user = create_test_user(password="OutsidePass123!")
org = create_test_org()
create_test_membership(admin["id"], org["id"], OrganizationRole.ADMIN)
# outside_user is NOT added to the org
integration_client.auth.login(email=admin["email"], password="AdminPass123!")
with pytest.raises(ApiError) as exc_info:
integration_client.get(f"/organizations/{org['id']}/users/{outside_user['id']}/devices")
assert exc_info.value.status_code == 404
def test_list_user_devices_user_not_found_negative(
self, integration_client, create_test_user, create_test_org, create_test_membership
):
"""TEST: ZT-14 — Cannot list devices for non-existent user.
WHAT: Admin GET /organizations/<id>/users/<non_existent_id>/devices.
WHY: User must exist.
EXPECTED: 404 Not Found.
"""
import uuid
admin = create_test_user(password="AdminPass123!")
org = create_test_org()
create_test_membership(admin["id"], org["id"], OrganizationRole.ADMIN)
non_existent_id = str(uuid.uuid4())
integration_client.auth.login(email=admin["email"], password="AdminPass123!")
with pytest.raises(ApiError) as exc_info:
integration_client.get(f"/organizations/{org['id']}/users/{non_existent_id}/devices")
assert exc_info.value.status_code == 404