fix(privacy): the instance's side of the reading of private studysets (ADR 0019)
An upload said to be encrypted must start as a sealed file does (HLKF, version 1), so a readable file can't skip the PDF check by saying so. A person's room counts the files a live asset row names and those claimed within the week of grace, so a private studyset's rewrite near the limit no longer fails for a week while pruning keeps the replaced files. Copy reports are limited to 60 an hour per person. Tests: what goes with the person, `counted` refused to anonymous people. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
678a3b5bde
commit
d9b5e06618
2 changed files with 204 additions and 13 deletions
|
|
@ -191,7 +191,9 @@ void main() {
|
|||
'look into it, a little larger than 50 MB too, and no larger',
|
||||
() async {
|
||||
const size = AssetEndpoint.maxOtherBytes + 1;
|
||||
final sealed = Uint8List(size)..fillRange(0, size, 7);
|
||||
final sealed = Uint8List(size)
|
||||
..fillRange(0, size, 7)
|
||||
..setAll(0, [...utf8.encode('HLKF'), 1]);
|
||||
final readable = Uint8List(size)..fillRange(0, size, 8);
|
||||
const most =
|
||||
AssetEndpoint.maxBytes + AssetEndpoint.encryptionOverhead;
|
||||
|
|
@ -228,6 +230,25 @@ void main() {
|
|||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'keeps no file said to be encrypted that is no sealed file, as a '
|
||||
'readable one would skip the PDF check',
|
||||
() async {
|
||||
const size = AssetEndpoint.maxOtherBytes + 1;
|
||||
final readable = Uint8List(size)..fillRange(0, size, 9);
|
||||
final wrongVersion = Uint8List(size)
|
||||
..fillRange(0, size, 9)
|
||||
..setAll(0, [...utf8.encode('HLKF'), 2]);
|
||||
|
||||
expect(await add(alice, readable, encrypted: true), isFalse);
|
||||
expect(await add(alice, wrongVersion, encrypted: true), isFalse);
|
||||
expect(
|
||||
await add(alice, utf8.encode('HLKF'), encrypted: true),
|
||||
isFalse,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('finishes no upload that never came', () async {
|
||||
expect(
|
||||
await endpoints.asset.finishUpload(signedIn(alice), imageHash),
|
||||
|
|
@ -488,6 +509,40 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'counts no file a live row no longer names once its week of grace '
|
||||
'is over, as one a rewrite replaced (ADR 0019)',
|
||||
() async {
|
||||
final replaced = List.filled(6, 3);
|
||||
final named = List.filled(3, 4);
|
||||
String hash(List<int> bytes) => sha256.convert(bytes).toString();
|
||||
final gone = await _assetIn(session, alice, hash(replaced));
|
||||
await add(alice, replaced);
|
||||
await _deleteAsset(session, alice, gone);
|
||||
await _assetIn(session, alice, hash(named));
|
||||
await add(alice, named);
|
||||
final old = DateTime.now().toUtc().subtract(
|
||||
const Duration(days: 8),
|
||||
);
|
||||
await _claimed(session, alice, hash(replaced), old);
|
||||
await _claimed(session, alice, hash(named), old);
|
||||
|
||||
final usage = await endpoints.asset.usage(signedIn(alice));
|
||||
expect(usage.usedBytes, 3);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'counts a file no row names yet while it is recent, so uploads '
|
||||
"without rows can't fill the room unnoticed",
|
||||
() async {
|
||||
await add(alice, List.filled(4, 5));
|
||||
|
||||
final usage = await endpoints.asset.usage(signedIn(alice));
|
||||
expect(usage.usedBytes, 4);
|
||||
},
|
||||
);
|
||||
|
||||
test('tells how much room the files take, and may take', () async {
|
||||
await add(alice, List.filled(4, 1));
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:typed_data';
|
|||
|
||||
import 'package:halkyon_base_server/base.dart';
|
||||
import 'package:halkyon_learn_server/src/explore/learner_count.dart';
|
||||
import 'package:halkyon_learn_server/src/explore/learning_count_endpoint.dart';
|
||||
import 'package:halkyon_learn_server/src/generated/protocol.dart';
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
import 'package:serverpod_auth_idp_server/core.dart';
|
||||
|
|
@ -222,8 +223,51 @@ void main() {
|
|||
|
||||
await learner();
|
||||
expect(await counted(), 1);
|
||||
final gone = people.last;
|
||||
await deletePerson(session, people.removeLast());
|
||||
expect(await counted(), 0);
|
||||
// Nothing of theirs stays behind, whatever the count says.
|
||||
expect(
|
||||
await CountedLearner.db.count(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(gone),
|
||||
),
|
||||
0,
|
||||
);
|
||||
expect(
|
||||
await LearningCopy.db.count(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(gone),
|
||||
),
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
test('a person reports at most an hour\'s worth of times', () async {
|
||||
final copy = await learner();
|
||||
final person = people.last;
|
||||
final report = [
|
||||
CopyReport(
|
||||
publicationId: japanese.publicId.uuid,
|
||||
studysetIds: [copy],
|
||||
),
|
||||
];
|
||||
// The learner reported once already.
|
||||
for (var i = 1; i < copyReportsPerHour; i++) {
|
||||
expect(
|
||||
await endpoints.learningCount.reportCopies(
|
||||
signedIn(person),
|
||||
report,
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
}
|
||||
|
||||
expect(
|
||||
await endpoints.learningCount.reportCopies(signedIn(person), []),
|
||||
isFalse,
|
||||
);
|
||||
expect(await counted(), 1);
|
||||
});
|
||||
|
||||
test('reports are kept only while the person chose to be counted, '
|
||||
|
|
@ -274,6 +318,10 @@ void main() {
|
|||
),
|
||||
throwsA(isA<ServerpodInsufficientAccessException>()),
|
||||
);
|
||||
await expectLater(
|
||||
endpoints.learningCount.counted(signedIn(person, anonymous: true)),
|
||||
throwsA(isA<ServerpodInsufficientAccessException>()),
|
||||
);
|
||||
expect(await CountedLearner.db.count(session), 0);
|
||||
});
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue