#!/usr/bin/env python3 """通过真实 HTTP 验证应用接入,不打印或记录密码、Cookie 与 Token。""" import copy import http.cookiejar import json import os from pathlib import Path import urllib.error import urllib.request import uuid base = os.environ['ORBIT_E2E_URL'].rstrip('/') user = os.environ['ORBIT_E2E_USER'] password = Path(os.environ['ORBIT_E2E_PASSWORD_FILE']).read_text().strip() jar = http.cookiejar.CookieJar() opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar)) def call(path, body=None, token=None, headers=None, expected=200, session=True): hdr = {'Accept': 'application/json', **(headers or {})} if body is not None: hdr['Content-Type'] = 'application/json' if path.startswith('/console/applications'): hdr.setdefault('X-Orbit-Console', '1') hdr.setdefault('Origin', base) if token: hdr['Authorization'] = 'Bearer ' + token req = urllib.request.Request(base + path, data=None if body is None else json.dumps(body).encode(), headers=hdr) try: response = (opener.open if session else urllib.request.urlopen)(req, timeout=15) except urllib.error.HTTPError as error: response = error with response: status = response.status value = json.load(response) assert status == expected, f'{path}: HTTP {status}, expected {expected}; code={value.get("error", {}).get("code")}' return value.get('data', value) call('/console/applications', expected=401, session=False) call('/console/auth/login', {'username': user, 'password': password}) assert call('/console/auth/session')['authenticated'] call('/console/applications/prepare', {}, headers={'Origin': 'https://other.invalid'}, expected=403) name = 'access-demo-' + uuid.uuid4().hex[:8] request = {'kind': 'CREATE', 'application': name, 'namespace': 'default', 'environment': 'test', 'queue': 'integration', 'capabilities': ['worker', 'events', 'schedules', 'workflows'], 'valid_days': 30, 'expected_revision': '0', 'reason': 'application onboarding acceptance'} op = call('/console/applications/prepare', request) # 固定操作在提交前写入测试私密目录,以便提交结果不明时核对。 private = os.environ.get('ORBIT_E2E_APPLICATION_DIR') if private: os.umask(0o077) Path(private).mkdir(parents=True, exist_ok=True) Path(private, 'create.operation.json').write_text(json.dumps(op)) first = call('/console/applications/apply', op) assert not first['deduplicated'] assert call('/console/applications/apply', op)['deduplicated'] changed = copy.deepcopy(op) changed['input']['reason'] = 'changed' call('/console/applications/apply', changed, expected=409) page = call('/console/applications') assert any(x['namespace_id'] == op['namespace_id'] for x in page['items']) detail = call('/console/applications/' + op['namespace_id']) assert op['token'] not in json.dumps(detail) queue = detail['queues'][0]['id'] assert queue == op['queue_id'] call('/console/applications', token=op['token'], expected=401, session=False) call('/v1/queues/' + queue, token=op['token'], session=False) sub = call('/v1/subscriptions', {'name': 'result-example', 'queue_id': queue}, token=op['token'], headers={'Idempotency-Key': 'onboarding-subscription'}, expected=201, session=False) subscription_id = sub['subscription']['id'] task = call('/v1/tasks', {'queue_id': queue, 'handler': 'onboarding.noop', 'handler_version': 'v1', 'payload_version': 'v1', 'payload': {}}, token=op['token'], headers={'Idempotency-Key': 'onboarding-task'}, expected=201, session=False) task_id = task['task']['id'] call('/v1/tasks/' + task_id + ':cancel', {'expected_revision': task['task']['revision'], 'reason': 'acceptance complete'}, token=op['token'], headers={'Idempotency-Key': 'onboarding-cancel'}, session=False) expiry = call('/console/applications/prepare', {'kind': 'SET_EXPIRY', 'namespace_id': op['namespace_id'], 'credential_id': op['credential_id'], 'expected_revision': '1', 'valid_days': 0, 'reason': 'long-lived service credential'}) assert 'token' not in expiry and 'expires_at' not in expiry if private: Path(private, 'expiry.operation.json').write_text(json.dumps(expiry)) assert call('/console/applications/apply', expiry)['revision'] == '2' assert call('/console/applications/apply', expiry)['deduplicated'] assert 'expires_at' not in call('/console/applications/' + op['namespace_id'])['credentials'][0] call('/v1/queues/' + queue, token=op['token'], session=False) rotate = call('/console/applications/prepare', {'kind': 'ROTATE', 'namespace_id': op['namespace_id'], 'credential_id': op['credential_id'], 'expected_revision': '2', 'valid_days': 0, 'grace_days': 7, 'reason': 'rotation acceptance'}) if private: Path(private, 'rotate.operation.json').write_text(json.dumps(rotate)) assert call('/console/applications/apply', rotate)['revision'] == '3' assert call('/console/applications/apply', rotate)['deduplicated'] call('/v1/queues/' + queue, token=op['token'], session=False) call('/v1/queues/' + queue, token=rotate['token'], session=False) assert call('/console/applications/' + op['namespace_id'])['credentials'][0]['previous_valid_until'] finalize = call('/console/applications/prepare', {'kind': 'FINALIZE', 'namespace_id': op['namespace_id'], 'credential_id': op['credential_id'], 'expected_revision': '3', 'valid_days': 0, 'reason': 'all services switched'}) if private: Path(private, 'finalize.operation.json').write_text(json.dumps(finalize)) assert 'token' not in finalize assert call('/console/applications/apply', finalize)['revision'] == '4' assert call('/console/applications/apply', finalize)['deduplicated'] call('/v1/queues/' + queue, token=op['token'], expected=401, session=False) call('/v1/queues/' + queue, token=rotate['token'], session=False) # 重放旧轮换回执不能重新启用已经结束过渡的秘密。 assert call('/console/applications/apply', rotate)['deduplicated'] call('/v1/queues/' + queue, token=op['token'], expected=401, session=False) current_task = call('/v1/tasks/' + task_id, token=rotate['token'], session=False) assert current_task['state'] == 'CANCELLED' current_sub = call('/v1/subscriptions/' + subscription_id, token=rotate['token'], session=False) assert current_sub['id'] == subscription_id # 不让联调示例遗留启用的订阅,历史和稳定凭据身份保持不变。 call('/v1/subscriptions/' + subscription_id + ':configure', {'expected_revision': current_sub['revision'], 'enabled': False, 'reason': 'acceptance complete'}, token=rotate['token'], headers={'Idempotency-Key': 'onboarding-pause-subscription'}, session=False) if private: Path(private, 'host.env').write_text(f"ORBIT_URL={base}\nORBIT_TOKEN={rotate['token']}\nORBIT_NAMESPACE_ID={op['namespace_id']}\nORBIT_QUEUE_ID={queue}\n") Path(private, 'result.json').write_text(json.dumps({'application': name, 'namespace_id': op['namespace_id'], 'queue_id': queue, 'credential_id': op['credential_id'], 'task_id': task_id, 'subscription_id': subscription_id})) call('/console/auth/logout', {}) call('/console/applications', expected=401) print('PASS: application create/replay, CSRF/session isolation, permanent expiry, overlapping rotation/finalization, original task/subscription preserved, logout') print('Test application: ' + name)