feat:日志敏感信息过滤
This commit is contained in:
1 parent
82800373ca
commit
ede0f9bc4c
10 files changed
+517
-18
No files matched your search
@@ -0,0 +1,164 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/testing.dart';
|
||||
import 'package:pj_supermarket_cloud/common/utils/app/app_network_tool.dart';
|
||||
import 'package:pj_supermarket_cloud/common/utils/app/file_access.dart';
|
||||
import 'package:pj_supermarket_cloud/common/values/constants.dart';
|
||||
|
||||
void main() {
|
||||
final testKey = base64Encode(List.generate(32, (index) => index));
|
||||
|
||||
FileAccessMode select({
|
||||
String mode = 'auto',
|
||||
bool debug = true,
|
||||
bool configured = false,
|
||||
String appId = 'flutter',
|
||||
String kid = 'v1',
|
||||
String key = '',
|
||||
}) =>
|
||||
resolveFileAccessMode(
|
||||
mode: mode,
|
||||
debugBuild: debug,
|
||||
hasConfiguration: configured,
|
||||
appId: appId,
|
||||
kid: kid,
|
||||
key: key,
|
||||
);
|
||||
|
||||
test('an ordinary Debug launch without configuration simulates old clients',
|
||||
() {
|
||||
expect(select(), FileAccessMode.legacy);
|
||||
});
|
||||
|
||||
test('valid configuration selects signing in Debug and production builds',
|
||||
() {
|
||||
for (final debug in [true, false]) {
|
||||
for (final mode in ['auto', 'signed']) {
|
||||
expect(
|
||||
select(mode: mode, debug: debug, configured: true, key: testKey),
|
||||
FileAccessMode.signed,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('partial or invalid configuration never silently disables signing', () {
|
||||
for (final key in [
|
||||
'',
|
||||
'not-base64!',
|
||||
base64Encode([1, 2, 3])
|
||||
]) {
|
||||
expect(() => select(configured: true, key: key), throwsStateError);
|
||||
}
|
||||
expect(
|
||||
() => select(configured: true, appId: 'admin', key: testKey),
|
||||
throwsStateError,
|
||||
);
|
||||
expect(
|
||||
() => select(configured: true, kid: 'invalid key id', key: testKey),
|
||||
throwsStateError,
|
||||
);
|
||||
});
|
||||
|
||||
test('production and explicit signed launches reject missing credentials',
|
||||
() {
|
||||
expect(() => select(debug: false), throwsStateError);
|
||||
expect(() => select(mode: 'signed'), throwsStateError);
|
||||
});
|
||||
|
||||
test(
|
||||
'explicit legacy testing stays unsigned even when credentials are present',
|
||||
() {
|
||||
expect(
|
||||
select(mode: 'legacy', configured: true, key: testKey),
|
||||
FileAccessMode.legacy,
|
||||
);
|
||||
expect(() => select(mode: 'legacy', debug: false), throwsStateError);
|
||||
expect(() => select(mode: 'typo'), throwsStateError);
|
||||
});
|
||||
|
||||
test(
|
||||
'legacy image requests omit all signature fields and preserve file names',
|
||||
() {
|
||||
final client = FileAccessClient(
|
||||
baseUrl: 'https://files.example/api',
|
||||
appId: 'flutter',
|
||||
kid: 'v1',
|
||||
key: '',
|
||||
signingEnabled: false,
|
||||
);
|
||||
const source = '/admin/sys-file/oss/file?appId=flutter&kid=v1&ts=1&sign=old'
|
||||
'&width=20&fileName=literal%252Fname.png';
|
||||
final url = Uri.parse(client.url(source));
|
||||
expect(url.path, '/api/admin/sys-file/oss/file');
|
||||
expect(
|
||||
url.queryParameters, {'width': '20', 'fileName': 'literal%2Fname.png'});
|
||||
expect(client.url(source, force: true), url.toString());
|
||||
expect(client.url('photo.jpg'),
|
||||
'https://files.example/api/admin/sys-file/oss/file?fileName=photo.jpg');
|
||||
const external = 'https://other.example/image.png?sign=third-party';
|
||||
expect(client.url(external), external);
|
||||
expect(client.url('assets/logo.png'), 'assets/logo.png');
|
||||
});
|
||||
|
||||
test('legacy uploads carry no app signature headers and need no key', () {
|
||||
final client = FileAccessClient(
|
||||
baseUrl: 'https://files.example/api',
|
||||
appId: 'flutter',
|
||||
kid: 'v1',
|
||||
key: '',
|
||||
signingEnabled: false,
|
||||
);
|
||||
for (final target in ['upload', 'inner-upload', 'qn/upload']) {
|
||||
expect(client.uploadHeaders('/admin/sys-file/$target'), isEmpty);
|
||||
}
|
||||
});
|
||||
|
||||
test('the compiled launch configuration controls image and upload signing',
|
||||
() {
|
||||
final expectedSigning = Constants.fileAccessMode != 'legacy' &&
|
||||
Constants.hasFileAccessConfiguration;
|
||||
expect(FileAccess.signingEnabled, expectedSigning);
|
||||
final uri = Uri.parse(FileAccess.client.url('photo.jpg'));
|
||||
expect(uri.queryParameters.containsKey('sign'), expectedSigning);
|
||||
expect(
|
||||
FileAccess.client.uploadHeaders('/admin/sys-file/upload').isNotEmpty,
|
||||
expectedSigning,
|
||||
);
|
||||
// Old clients can still consume package links signed by the server.
|
||||
const package = 'https://files.example/admin/sys-file/oss/file'
|
||||
'?appId=package&kid=v1&ts=1&sign=server&fileName=app.apk';
|
||||
expect(FileAccess.packageUrl(package), package);
|
||||
});
|
||||
|
||||
test(
|
||||
'legacy image downloads send bare requests and do not retry a rejected signature',
|
||||
() async {
|
||||
final requests = <http.Request>[];
|
||||
final client = MockClient((request) async {
|
||||
requests.add(request);
|
||||
return http.Response('', 403);
|
||||
});
|
||||
addTearDown(client.close);
|
||||
expect(await AppNetworkTool.downloadImage('photo.jpg', client: client),
|
||||
isNull);
|
||||
expect(requests, hasLength(1));
|
||||
expect(requests.single.url.queryParameters, {'fileName': 'photo.jpg'});
|
||||
}, skip: FileAccess.signingEnabled);
|
||||
|
||||
testWidgets(
|
||||
'unsigned startup avoids platform initialization and renewal timers',
|
||||
(tester) async {
|
||||
FileAccess.initialize();
|
||||
FileAccess.initialize();
|
||||
final before = FileAccess.revision.value;
|
||||
FileAccess.resume();
|
||||
await FileAccess.refreshClock(force: true);
|
||||
expect(await FileAccess.retryImage('photo.jpg'), isFalse);
|
||||
await tester.pump(const Duration(minutes: 2));
|
||||
expect(FileAccess.revision.value, before);
|
||||
FileAccess.pause();
|
||||
}, skip: FileAccess.signingEnabled);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pj_supermarket_cloud/common/utils/app/file_access.dart';
|
||||
|
||||
void main() {
|
||||
final vectors =
|
||||
jsonDecode(File('test-vectors/file-upload.json').readAsStringSync())
|
||||
as List;
|
||||
for (final v in vectors) {
|
||||
test('shared upload signature: ${v['target']}', () {
|
||||
expect(
|
||||
fileUploadCanonical(
|
||||
v['target'], v['appId'], v['kid'], v['ts'], v['nonce']),
|
||||
v['canonical']);
|
||||
expect(
|
||||
fileUploadSignature(
|
||||
v['target'], v['appId'], v['kid'], v['ts'], v['nonce'], v['key']),
|
||||
v['sign']);
|
||||
});
|
||||
}
|
||||
test(
|
||||
'upload headers are fresh, target-bound and restricted to owned endpoints',
|
||||
() {
|
||||
final client = FileAccessClient(
|
||||
baseUrl: 'https://files.example/api',
|
||||
appId: 'flutter',
|
||||
kid: 'v1',
|
||||
key: vectors[0]['key'],
|
||||
now: () => DateTime.fromMillisecondsSinceEpoch(1793232000000),
|
||||
allowedOrigins: ['https://legacy.example']);
|
||||
for (final entry in {
|
||||
'/admin/sys-file/upload': 'upload',
|
||||
'/sys-file/upload': 'upload',
|
||||
'/api/admin/sys-file/qn/upload': 'qn-upload',
|
||||
'https://legacy.example/admin/sys-file/inner-upload': 'inner-upload'
|
||||
}.entries) {
|
||||
final headers =
|
||||
client.uploadHeaders(entry.key, nonce: vectors[0]['nonce']);
|
||||
expect(
|
||||
headers['X-File-Signature'],
|
||||
fileUploadSignature(entry.value, 'flutter', 'v1', 1793232000,
|
||||
vectors[0]['nonce'], vectors[0]['key']));
|
||||
}
|
||||
final first = client.uploadHeaders('/admin/sys-file/upload'),
|
||||
second = client.uploadHeaders('/admin/sys-file/upload');
|
||||
expect(first['X-File-Nonce'], isNot(second['X-File-Nonce']));
|
||||
expect(first['X-File-Signature'], isNot(second['X-File-Signature']));
|
||||
for (final source in [
|
||||
'https://other.example/admin/sys-file/upload',
|
||||
'https:///admin/sys-file/upload',
|
||||
'/admin/user/import',
|
||||
'/admin/sys-file/oss/file?fileName=x.jpg',
|
||||
'file:///local.png'
|
||||
]) {
|
||||
expect(client.uploadHeaders(source), isEmpty);
|
||||
}
|
||||
});
|
||||
test(
|
||||
'the Java numeric-string clock response corrects upload and image signatures',
|
||||
() {
|
||||
final client = FileAccessClient(
|
||||
baseUrl: 'https://files.example/api',
|
||||
appId: 'flutter',
|
||||
kid: 'v1',
|
||||
key: vectors[0]['key'],
|
||||
now: () => DateTime.fromMillisecondsSinceEpoch(1793318400000));
|
||||
expect(
|
||||
client.synchronizeInfo(
|
||||
{'serverTime': '1793232000000', 'ttlSeconds': '1800'}, 0),
|
||||
isTrue);
|
||||
expect(client.uploadHeaders('/admin/sys-file/upload')['X-File-Timestamp'],
|
||||
'1793232000');
|
||||
expect(
|
||||
Uri.parse(client.url('photo.jpg')).queryParameters['ts'], '1793232000');
|
||||
expect(client.synchronizeInfo({'serverTime': 'not-a-time'}, 0), isFalse);
|
||||
expect(client.uploadHeaders('/admin/sys-file/upload')['X-File-Timestamp'],
|
||||
'1793232000');
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user