feat(ssh): change SSH key uniqueness to per-user scope
Previously, SSH key fingerprints were globally unique across all users, preventing the same key from being registered by different users. This change makes fingerprint uniqueness scoped to individual users. - Remove global unique constraints on payload and fingerprint columns - Add composite unique constraint on (user_id, fingerprint) - Make add_ssh_key operation idempotent for same user - Return tuple (SSHKey, is_new) from service to indicate creation status - Update API to return 200 for existing keys, 201 for new keys BREAKING CHANGE: API behavior changed - duplicate key addition now returns 200 OK instead of 409 Conflict. Service method signature changed from returning SSHKey to tuple[SSHKey, bool].
This commit is contained in:
@@ -106,23 +106,67 @@ class TestSSHKeyManagement:
|
||||
|
||||
assert_error(exc_info.value, 400)
|
||||
|
||||
def test_add_duplicate_key_negative(self, integration_client, create_test_user):
|
||||
"""TEST: SSH-KEY-03 — Reject duplicate SSH key.
|
||||
def test_add_duplicate_key_idempotent_positive(self, integration_client, create_test_user):
|
||||
"""TEST: SSH-KEY-03 — Add duplicate SSH key is idempotent for same user.
|
||||
|
||||
WHAT: User adds TEST_PUBLIC_KEY, then tries to add it again.
|
||||
WHY: Fingerprints must be unique per database to avoid
|
||||
ambiguity in key-to-user mappings.
|
||||
EXPECTED: 409 Conflict with error_type SSH_KEY_ALREADY_EXISTS.
|
||||
WHY: Fingerprints are unique per user, not globally. Adding the
|
||||
same key twice by the same user should succeed both times.
|
||||
EXPECTED: Both calls succeed (201 then 200). Both return same key id.
|
||||
"""
|
||||
user = create_test_user(password="MyPassword123!")
|
||||
integration_client.auth.login(email=user["email"], password="MyPassword123!")
|
||||
|
||||
integration_client.ssh.add_key(TEST_PUBLIC_KEY, "First")
|
||||
# First add should succeed with 201
|
||||
result1 = integration_client.ssh.add_key(TEST_PUBLIC_KEY, "First")
|
||||
data1 = assert_success(result1, "added")
|
||||
assert result1.get("code") == 201, f"Expected status 201 but got {result1.get('code')}"
|
||||
|
||||
# Second add should succeed with 200 (idempotent)
|
||||
result2 = integration_client.ssh.add_key(TEST_PUBLIC_KEY, "Duplicate")
|
||||
data2 = assert_success(result2, "exists")
|
||||
assert result2.get("code") == 200, f"Expected status 200 but got {result2.get('code')}"
|
||||
|
||||
# Both calls should return the same key id
|
||||
assert data1.get("id") == data2.get("id"), "Key IDs should match for idempotent operation"
|
||||
|
||||
with pytest.raises(ApiError) as exc_info:
|
||||
integration_client.ssh.add_key(TEST_PUBLIC_KEY, "Duplicate")
|
||||
def test_add_same_key_different_user_positive(self, integration_client, create_test_user):
|
||||
"""TEST: SSH-KEY-03b — Same key can be added by different users.
|
||||
|
||||
assert_error(exc_info.value, 409, "SSH_KEY_ALREADY_EXISTS")
|
||||
WHAT: User A adds TEST_PUBLIC_KEY. User B adds the SAME key.
|
||||
WHY: Fingerprint uniqueness is per-user, not global.
|
||||
EXPECTED: Both calls succeed (201). Each user sees the key in their list.
|
||||
"""
|
||||
# Create and login as user A
|
||||
user_a = create_test_user(password="PassA123!")
|
||||
integration_client.auth.login(email=user_a["email"], password="PassA123!")
|
||||
|
||||
# User A adds the key
|
||||
result_a = integration_client.ssh.add_key(TEST_PUBLIC_KEY, "User A Key")
|
||||
assert_success(result_a, "added")
|
||||
assert result_a.get("code") == 201, f"Expected status 201 but got {result_a.get('code')}"
|
||||
|
||||
# Create and login as user B
|
||||
user_b = create_test_user(password="PassB123!")
|
||||
integration_client.auth.logout()
|
||||
integration_client.auth.login(email=user_b["email"], password="PassB123!")
|
||||
|
||||
# User B adds the same key
|
||||
result_b = integration_client.ssh.add_key(TEST_PUBLIC_KEY, "User B Key")
|
||||
assert_success(result_b, "added")
|
||||
assert result_b.get("code") == 201, f"Expected status 201 but got {result_b.get('code')}"
|
||||
|
||||
# Verify user B sees exactly one key in their list
|
||||
list_result_b = integration_client.ssh.list_keys()
|
||||
list_data_b = assert_success(list_result_b)
|
||||
assert list_data_b.get("count") == 1, f"User B should see 1 key but saw {list_data_b.get('count')}"
|
||||
|
||||
# Log back in as user A and verify they still see exactly one key
|
||||
integration_client.auth.logout()
|
||||
integration_client.auth.login(email=user_a["email"], password="PassA123!")
|
||||
list_result_a = integration_client.ssh.list_keys()
|
||||
list_data_a = assert_success(list_result_a)
|
||||
assert list_data_a.get("count") == 1, f"User A should see 1 key but saw {list_data_a.get('count')}"
|
||||
|
||||
def test_add_key_without_auth_negative(self, integration_client):
|
||||
"""TEST: SSH-KEY-04 — Reject key upload without authentication.
|
||||
|
||||
Reference in New Issue
Block a user