feat: add single-admin application onboarding and token rotation

This commit is contained in:
2026-09-27 10:18:31 +08:00
parent 98ff028228
commit 87aa710013
24 changed files with 1341 additions and 25 deletions

View File

@@ -0,0 +1,91 @@
#!/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)
rotate = call('/console/applications/prepare', {'kind': 'ROTATE', 'namespace_id': op['namespace_id'], 'credential_id': op['credential_id'], 'expected_revision': '1', 'valid_days': 30, 'reason': 'rotation acceptance'})
if private:
Path(private, 'rotate.operation.json').write_text(json.dumps(rotate))
assert call('/console/applications/apply', rotate)['revision'] == '2'
assert call('/console/applications/apply', rotate)['deduplicated']
call('/v1/queues/' + queue, token=op['token'], expected=401, session=False)
call('/v1/queues/' + queue, token=rotate['token'], 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, token rotation, original task/subscription preserved, logout')
print('Test application: ' + name)