104 lines
6.5 KiB
Python
104 lines
6.5 KiB
Python
"""Run separately: all database, keys and configuration live in a temporary directory."""
|
|
import base64
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
from unittest.mock import patch
|
|
|
|
ROOT=Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0,str(ROOT))
|
|
|
|
def main():
|
|
with tempfile.TemporaryDirectory(prefix='monitor-部署-test-') as tmp:
|
|
os.environ.update(MONITOR_DESKTOP='1',MONITOR_DATA_DIR=tmp,
|
|
DJANGO_SETTINGS_MODULE='framework.settings',
|
|
MONITOR_DEV_PUBLIC_KEY=str(Path(tmp)/'public.pem'))
|
|
from cryptography.hazmat.primitives import serialization
|
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
key=Ed25519PrivateKey.generate()
|
|
Path(tmp,'public.pem').write_bytes(key.public_key().public_bytes(serialization.Encoding.PEM,serialization.PublicFormat.SubjectPublicKeyInfo))
|
|
from monitor_runtime.bootstrap import prepare_files,initialize_database
|
|
prepare_files()
|
|
initialize_database()
|
|
from django.test import Client
|
|
from django.contrib.auth import get_user_model
|
|
from monitor_runtime import web, licensing
|
|
from monitor_runtime.paths import data,atomic_json
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
fingerprint='v1:'+'b'*64
|
|
checks=[]
|
|
with patch.object(licensing,'machine_identity',return_value={'fingerprint':fingerprint,'weak_uuid':False}):
|
|
client=Client(enforce_csrf_checks=True,HTTP_HOST='localhost')
|
|
local={'REMOTE_ADDR':'127.0.0.1'}
|
|
remote={'REMOTE_ADDR':'192.168.1.5'}
|
|
assert client.get('/setup',**remote).status_code==403
|
|
assert client.get('/setup',**local).status_code==200
|
|
csrf=client.cookies['csrftoken'].value
|
|
assert client.post('/license/request',{'setup_token':web.SETUP_TOKEN},**local).status_code==403
|
|
assert client.post('/license/request',{'setup_token':'wrong'},HTTP_X_CSRFTOKEN=csrf,**local).status_code==403
|
|
result=client.post('/license/request',{'setup_token':web.SETUP_TOKEN},HTTP_X_CSRFTOKEN=csrf,**local)
|
|
assert result.status_code==200 and result.json()['fingerprint']==fingerprint
|
|
checks.append('local-only bootstrap, one-time token and CSRF')
|
|
from datetime import datetime,timezone,timedelta
|
|
payload={'schema_version':1,'product':'monitor','license_id':'integration','customer':'integration-test',
|
|
'machine_fingerprint':fingerprint,'issued_at':datetime.now(timezone.utc).isoformat(),'expires_at':None}
|
|
document={'payload':payload,'signature':base64.b64encode(key.sign(licensing.canonical(payload))).decode()}
|
|
uploaded=SimpleUploadedFile('license.json',json.dumps(document).encode(),content_type='application/json')
|
|
result=client.post('/license/import',{'setup_token':web.SETUP_TOKEN,'license':uploaded},HTTP_X_CSRFTOKEN=csrf,**local)
|
|
assert result.status_code==200,result.content[:200]
|
|
assert licensing.check_license()['valid']
|
|
checks.append('signed import and DPAPI clock persistence')
|
|
with patch('monitor_runtime.bootstrap.local_addresses',return_value=['192.168.1.10']):
|
|
result=client.post('/setup',{'setup_token':web.SETUP_TOKEN,'username':'owner',
|
|
'password':'Different!7826Secure','confirm':'Different!7826Secure','address':'192.168.1.10'},
|
|
HTTP_X_CSRFTOKEN=csrf,**local)
|
|
assert result.status_code==200,result.content[:200]
|
|
assert get_user_model().objects.filter(is_superuser=True).count()==1
|
|
assert client.get('/setup',**local).status_code==403
|
|
assert client.post('/license/request',{'setup_token':web.SETUP_TOKEN},HTTP_X_CSRFTOKEN=csrf,**local).status_code==403
|
|
checks.append('first administrator and permanently closed setup')
|
|
snapshot=data('static/storage/alarm/test.jpg');snapshot.parent.mkdir(parents=True,exist_ok=True);snapshot.write_bytes(b'image')
|
|
assert client.get('/static/storage/alarm/test.jpg',**local).status_code==403
|
|
client.force_login(get_user_model().objects.get(username='owner'))
|
|
response=client.get('/static/storage/alarm/test.jpg',**local)
|
|
assert response.status_code==200
|
|
response.close()
|
|
assert client.get('/static/storage/../../.runtime-secrets.json',**local).status_code in (403,404)
|
|
assert client.get('/license/status',**local).status_code==200
|
|
checks.append('authenticated private files and administrator license status')
|
|
old_secret=data('.runtime-secrets.json').read_bytes()
|
|
initialize_database()
|
|
assert data('.runtime-secrets.json').read_bytes()==old_secret
|
|
assert get_user_model().objects.filter(username='owner').count()==1
|
|
checks.append('idempotent database initialization preserves users and encryption key')
|
|
payload['expires_at']=(datetime.now(timezone.utc)-timedelta(seconds=1)).isoformat()
|
|
document={'payload':payload,'signature':base64.b64encode(key.sign(licensing.canonical(payload))).decode()}
|
|
atomic_json(data('license.json'),document)
|
|
assert not licensing.check_license()['valid']
|
|
assert client.get('/analysis/openStatus',**local).status_code==403
|
|
assert client.get('/license/status',**local).status_code==200
|
|
checks.append('expiry blocks APIs and leaves renewal available')
|
|
payload['expires_at']=None
|
|
document={'payload':payload,'signature':base64.b64encode(key.sign(licensing.canonical(payload))).decode()}
|
|
licensing.import_license(json.dumps(document).encode())
|
|
with patch.object(licensing.time,'time',return_value=time.time()-1000):
|
|
assert not licensing.check_license()['valid']
|
|
checks.append('renewal and clock rollback detection')
|
|
get_user_model().objects.all().delete()
|
|
assert client.get('/setup',**local).status_code==403
|
|
checks.append('initialization stays closed even after administrator deletion')
|
|
from django.db import connections
|
|
connections.close_all()
|
|
import logging
|
|
logging.shutdown()
|
|
for handler in list(logging.getLogger().handlers):
|
|
logging.getLogger().removeHandler(handler)
|
|
handler.close()
|
|
print(json.dumps({'passed':checks},ensure_ascii=False,indent=2))
|
|
|
|
if __name__=='__main__':
|
|
main()
|