228 lines
7.3 KiB
Python
228 lines
7.3 KiB
Python
"""Mobile / Seaca API."""
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
from app.models.mobile_device import MobileDevice
|
|
from app.models.mobile_enrollment_code import MobileEnrollmentCode
|
|
from app.models.mobile_settings import MOBILE_SETTINGS_ROW_ID, MobileSettings
|
|
from app.services.mobile_tokens import hash_mobile_secret
|
|
|
|
|
|
def _enable_mobile(db_session):
|
|
row = db_session.get(MobileSettings, MOBILE_SETTINGS_ROW_ID)
|
|
if row is None:
|
|
row = MobileSettings(id=MOBILE_SETTINGS_ROW_ID, devices_allowed=True, max_devices_per_user=3)
|
|
db_session.add(row)
|
|
else:
|
|
row.devices_allowed = True
|
|
db_session.commit()
|
|
|
|
|
|
def test_mobile_settings_admin(jwt_headers, client, db_session):
|
|
_enable_mobile(db_session)
|
|
response = client.get("/api/v1/mobile/settings", headers=jwt_headers)
|
|
assert response.status_code == 200
|
|
body = response.json()
|
|
assert body["devices_allowed"] is True
|
|
assert body["max_devices_per_user"] == 3
|
|
|
|
|
|
def test_create_enrollment_code_and_enroll(jwt_headers, client, db_session):
|
|
_enable_mobile(db_session)
|
|
create = client.post(
|
|
"/api/v1/mobile/enrollment-codes",
|
|
headers=jwt_headers,
|
|
json={"label": "test phone", "login_mode": "password", "expires_in_hours": 1},
|
|
)
|
|
assert create.status_code == 200
|
|
created = create.json()
|
|
assert created["enrollment_code"].startswith("sacmob_")
|
|
assert created["code_prefix"]
|
|
|
|
enroll = client.post(
|
|
"/api/v1/mobile/enroll",
|
|
json={
|
|
"enrollment_code": created["enrollment_code"],
|
|
"username": "test-monitor",
|
|
"password": "test-monitor-password",
|
|
"device_uuid": "uuid-test-device-001",
|
|
"display_name": "Pixel Test",
|
|
"fcm_token": "fcm-token-test",
|
|
},
|
|
)
|
|
assert enroll.status_code == 200
|
|
body = enroll.json()
|
|
assert body["username"] == "test-monitor"
|
|
assert body["role"] == "monitor"
|
|
assert body["device_id"] > 0
|
|
assert body["access_token"]
|
|
assert body["refresh_token"]
|
|
|
|
devices = client.get("/api/v1/mobile/devices", headers=jwt_headers)
|
|
assert devices.status_code == 200
|
|
assert len(devices.json()) == 1
|
|
assert devices.json()[0]["display_name"] == "Pixel Test"
|
|
|
|
|
|
def test_enroll_blocked_when_disabled(client, db_session, jwt_headers):
|
|
code_plain = "sacmob_testcode123456"
|
|
db_session.add(
|
|
MobileEnrollmentCode(
|
|
label="x",
|
|
code_hash=hash_mobile_secret(code_plain),
|
|
code_prefix=code_plain[:12],
|
|
login_mode="password",
|
|
max_uses=1,
|
|
use_count=0,
|
|
expires_at=datetime.now(timezone.utc) + timedelta(hours=1),
|
|
)
|
|
)
|
|
db_session.commit()
|
|
|
|
response = client.post(
|
|
"/api/v1/mobile/enroll",
|
|
json={
|
|
"enrollment_code": code_plain,
|
|
"username": "test-monitor",
|
|
"password": "test-monitor-password",
|
|
"device_uuid": "uuid-disabled-001",
|
|
},
|
|
)
|
|
assert response.status_code == 403
|
|
|
|
|
|
def test_refresh_token_rotation(client, db_session, jwt_headers):
|
|
_enable_mobile(db_session)
|
|
create = client.post(
|
|
"/api/v1/mobile/enrollment-codes",
|
|
headers=jwt_headers,
|
|
json={"login_mode": "password"},
|
|
)
|
|
code = create.json()["enrollment_code"]
|
|
enroll = client.post(
|
|
"/api/v1/mobile/enroll",
|
|
json={
|
|
"enrollment_code": code,
|
|
"username": "test-admin",
|
|
"password": "test-admin-password",
|
|
"device_uuid": "uuid-refresh-001",
|
|
},
|
|
)
|
|
data = enroll.json()
|
|
refresh = client.post(
|
|
"/api/v1/mobile/auth/refresh",
|
|
json={"refresh_token": data["refresh_token"], "device_uuid": "uuid-refresh-001"},
|
|
)
|
|
assert refresh.status_code == 200
|
|
refreshed = refresh.json()
|
|
assert refreshed["refresh_token"] != data["refresh_token"]
|
|
assert refreshed["device_id"] == data["device_id"]
|
|
|
|
|
|
def test_revoke_device_blocks_token(client, db_session, jwt_headers):
|
|
_enable_mobile(db_session)
|
|
create = client.post(
|
|
"/api/v1/mobile/enrollment-codes",
|
|
headers=jwt_headers,
|
|
json={"login_mode": "password"},
|
|
)
|
|
code = create.json()["enrollment_code"]
|
|
enroll = client.post(
|
|
"/api/v1/mobile/enroll",
|
|
json={
|
|
"enrollment_code": code,
|
|
"username": "test-monitor",
|
|
"password": "test-monitor-password",
|
|
"device_uuid": "uuid-revoke-001",
|
|
},
|
|
)
|
|
data = enroll.json()
|
|
headers = {"Authorization": f"Bearer {data['access_token']}"}
|
|
|
|
revoke = client.post(f"/api/v1/mobile/devices/{data['device_id']}/revoke", headers=jwt_headers)
|
|
assert revoke.status_code == 200
|
|
|
|
me = client.put(
|
|
"/api/v1/mobile/devices/me/fcm",
|
|
headers=headers,
|
|
json={"fcm_token": "new-token"},
|
|
)
|
|
assert me.status_code == 401
|
|
|
|
|
|
def test_reenroll_same_device_after_revoke(client, db_session, jwt_headers):
|
|
_enable_mobile(db_session)
|
|
device_uuid = "uuid-reenroll-001"
|
|
|
|
first_code = client.post(
|
|
"/api/v1/mobile/enrollment-codes",
|
|
headers=jwt_headers,
|
|
json={"login_mode": "password"},
|
|
).json()["enrollment_code"]
|
|
first = client.post(
|
|
"/api/v1/mobile/enroll",
|
|
json={
|
|
"enrollment_code": first_code,
|
|
"username": "test-monitor",
|
|
"password": "test-monitor-password",
|
|
"device_uuid": device_uuid,
|
|
"display_name": "Phone A",
|
|
},
|
|
)
|
|
assert first.status_code == 200
|
|
first_device_id = first.json()["device_id"]
|
|
|
|
revoke = client.post(f"/api/v1/mobile/devices/{first_device_id}/revoke", headers=jwt_headers)
|
|
assert revoke.status_code == 200
|
|
|
|
second_code = client.post(
|
|
"/api/v1/mobile/enrollment-codes",
|
|
headers=jwt_headers,
|
|
json={"login_mode": "password"},
|
|
).json()["enrollment_code"]
|
|
second = client.post(
|
|
"/api/v1/mobile/enroll",
|
|
json={
|
|
"enrollment_code": second_code,
|
|
"username": "test-monitor",
|
|
"password": "test-monitor-password",
|
|
"device_uuid": device_uuid,
|
|
"display_name": "Phone B",
|
|
},
|
|
)
|
|
assert second.status_code == 200
|
|
assert second.json()["device_id"] == first_device_id
|
|
|
|
devices = client.get("/api/v1/mobile/devices", headers=jwt_headers)
|
|
assert devices.status_code == 200
|
|
assert len(devices.json()) == 1
|
|
assert devices.json()[0]["display_name"] == "Phone B"
|
|
assert devices.json()[0]["is_active"] is True
|
|
|
|
|
|
def test_delete_device_record(client, db_session, jwt_headers):
|
|
_enable_mobile(db_session)
|
|
create = client.post(
|
|
"/api/v1/mobile/enrollment-codes",
|
|
headers=jwt_headers,
|
|
json={"login_mode": "password"},
|
|
)
|
|
code = create.json()["enrollment_code"]
|
|
enroll = client.post(
|
|
"/api/v1/mobile/enroll",
|
|
json={
|
|
"enrollment_code": code,
|
|
"username": "test-monitor",
|
|
"password": "test-monitor-password",
|
|
"device_uuid": "uuid-delete-001",
|
|
},
|
|
)
|
|
device_id = enroll.json()["device_id"]
|
|
|
|
deleted = client.delete(f"/api/v1/mobile/devices/{device_id}", headers=jwt_headers)
|
|
assert deleted.status_code == 204
|
|
|
|
devices = client.get("/api/v1/mobile/devices", headers=jwt_headers)
|
|
assert devices.status_code == 200
|
|
assert devices.json() == []
|