diff --git a/app/lib/admin/actions/actions.dart b/app/lib/admin/actions/actions.dart index 5fd6be94f2..58653c560f 100644 --- a/app/lib/admin/actions/actions.dart +++ b/app/lib/admin/actions/actions.dart @@ -27,7 +27,6 @@ import 'package_reservation_delete.dart'; import 'package_reservation_list.dart'; import 'package_version_info.dart'; import 'package_version_retraction.dart'; -import 'publisher_block.dart'; import 'publisher_create.dart'; import 'publisher_delete.dart'; import 'publisher_info.dart'; @@ -114,7 +113,6 @@ final class AdminAction { packageReservationList, packageVersionInfo, packageVersionRetraction, - publisherBlock, publisherCreate, publisherDelete, publisherInfo, diff --git a/app/lib/admin/actions/publisher_block.dart b/app/lib/admin/actions/publisher_block.dart deleted file mode 100644 index aeef7b8946..0000000000 --- a/app/lib/admin/actions/publisher_block.dart +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'package:pub_dev/account/backend.dart'; -import 'package:pub_dev/admin/actions/actions.dart'; -import 'package:pub_dev/publisher/backend.dart'; -import 'package:pub_dev/publisher/models.dart'; -import 'package:pub_dev/shared/datastore.dart'; - -final publisherBlock = AdminAction( - name: 'publisher-block', - summary: 'Block publisher and block all members', - description: ''' -Get information about publisher and list all members. -''', - options: { - 'publisher': 'Publisher to be blocked', - }, - invoke: (options) async { - final publisherId = options['publisher'] ?? - (throw InvalidInputException('Missing --publisher argument.')); - InvalidInputException.check( - publisherId.isNotEmpty, - 'publisher must be given', - ); - - final publisher = await publisherBackend.getPublisher(publisherId); - if (publisher == null) { - throw NotFoundException.resource(publisherId); - } - final members = await publisherBackend.listPublisherMembers(publisherId); - - for (final m in members) { - await accountBackend.updateBlockedFlag(m.userId, true); - } - - final publisherKey = dbService.emptyKey.append(Publisher, id: publisherId); - await withRetryTransaction(dbService, (tx) async { - final p = await tx.lookupValue<Publisher>(publisherKey); - p.markForBlocked(); - tx.insert(p); - }); - - return { - 'publisher': publisher.publisherId, - 'description': publisher.description, - 'website': publisher.websiteUrl, - 'contact': publisher.contactEmail, - 'created': publisher.created, - 'blocked': true, - 'members': members - .map((m) => { - 'email': m.email, - 'role': m.role, - 'userId': m.userId, - 'blocked': true, - }) - .toList(), - }; - }, -); diff --git a/app/lib/admin/backend.dart b/app/lib/admin/backend.dart index 058769157e..38c4e69eb1 100644 --- a/app/lib/admin/backend.dart +++ b/app/lib/admin/backend.dart @@ -35,14 +35,11 @@ import '../shared/versions.dart'; import '../task/backend.dart'; import 'actions/actions.dart' show AdminAction; import 'tools/delete_all_staging.dart'; -import 'tools/list_package_blocked.dart'; import 'tools/list_tools.dart'; import 'tools/notify_service.dart'; import 'tools/package_publisher.dart'; import 'tools/publisher_member.dart'; import 'tools/recent_uploaders.dart'; -import 'tools/set_package_blocked.dart'; -import 'tools/set_user_blocked.dart'; import 'tools/user_merger.dart'; final _logger = Logger('pub.admin.backend'); @@ -59,14 +56,11 @@ typedef Tool = Future<String> Function(List<String> args); final Map<String, Tool> availableTools = { 'delete-all-staging': executeDeleteAllStaging, - 'list-package-blocked': executeListPackageBlocked, 'notify-service': executeNotifyService, 'package-publisher': executeSetPackagePublisher, 'recent-uploaders': executeRecentUploaders, 'publisher-member': executePublisherMember, 'publisher-invite-member': executePublisherInviteMember, - 'set-package-blocked': executeSetPackageBlocked, - 'set-user-blocked': executeSetUserBlocked, 'user-merger': executeUserMergerTool, 'list-tools': executeListTools, }; diff --git a/app/lib/admin/tools/list_package_blocked.dart b/app/lib/admin/tools/list_package_blocked.dart deleted file mode 100644 index d86cf4c21e..0000000000 --- a/app/lib/admin/tools/list_package_blocked.dart +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'package:args/args.dart'; - -import '../../package/models.dart'; -import '../../shared/datastore.dart'; - -final _argParser = ArgParser() - ..addFlag('help', abbr: 'h', defaultsTo: false, help: 'Show help.'); - -Future<String> executeListPackageBlocked(List<String> args) async { - final argv = _argParser.parse(args); - - if (argv['help'] as bool) { - return 'List packages that are blocked.\n' - '${_argParser.usage}'; - } - - final query = dbService.query<Package>()..filter('isBlocked =', true); - final output = StringBuffer(); - await for (final p in query.run()) { - output.writeln(p.name!); - } - return output.toString(); -} diff --git a/app/lib/admin/tools/set_package_blocked.dart b/app/lib/admin/tools/set_package_blocked.dart deleted file mode 100644 index 3eec0e40e1..0000000000 --- a/app/lib/admin/tools/set_package_blocked.dart +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'package:args/args.dart'; - -import 'package:pub_dev/account/backend.dart'; -import 'package:pub_dev/package/backend.dart'; -import 'package:pub_dev/package/models.dart'; -import 'package:pub_dev/shared/datastore.dart'; -import 'package:pub_dev/shared/exceptions.dart'; - -final _argParser = ArgParser() - ..addOption('lookup', allowed: ['package', 'publisher', 'userid', 'email']) - ..addOption('update', allowed: ['true', 'false']) - ..addOption('reason', help: 'The reason of blocked status.') - ..addFlag('help', abbr: 'h', defaultsTo: false, help: 'Show help.'); - -Future<String> executeSetPackageBlocked(List<String> args) async { - final argv = _argParser.parse(args); - final blockedStatus = _parseValue(argv['update'] as String?); - final blockedReason = argv['reason'] as String?; - final lookupKey = argv['lookup'] as String?; - - if (argv['help'] as bool || lookupKey == null) { - return 'Usage: <tool> --lookup package [pkg1] [pkg2] -- list status of packages\n' - 'Usage: <tool> --lookup publisher [publisher1] [publisher2] -- list status of packages from publishers\n' - 'Usage: <tool> --lookup userid [user1] [user2] -- list status of packages from uploaders\n' - 'Usage: <tool> --lookup email [email1] [email2] -- list status of packages from uploaders\n' - 'Usage: <tool> --update true --lookup email [email1] [email2] -- update status of packages from uploaders\n' - '${_argParser.usage}'; - } - - final packages = <String, Package>{}; - - Future<void> loadPackages(String name, String? value) async { - final query = dbService.query<Package>()..filter('$name =', value); - await for (final p in query.run()) { - packages[p.name!] = p; - } - } - - InvalidInputException.checkAnyOf( - lookupKey, 'lookupKey', ['package', 'publisher', 'userid', 'email']); - if (lookupKey == 'package') { - for (final name in argv.rest) { - final p = (await packageBackend.lookupPackage(name))!; - packages[p.name!] = p; - } - } else if (lookupKey == 'publisher') { - for (final publisherId in argv.rest) { - await loadPackages('publisherId', publisherId); - } - } else if (lookupKey == 'userid') { - for (final userId in argv.rest) { - await loadPackages('uploaders', userId); - } - } else if (lookupKey == 'email') { - for (final email in argv.rest) { - final users = await accountBackend.lookupUsersByEmail(email); - InvalidInputException.check( - users.isNotEmpty, 'Email lookup failed: $email'); - for (final u in users) { - await loadPackages('uploaders', u.userId); - } - } - } - - final output = StringBuffer(); - output.writeln('Found ${packages.length} packages.'); - final orderedNames = packages.keys.toList()..sort(); - for (final name in orderedNames) { - final p = packages[name]!; - output.writeln('${p.name!.padRight(40)} - ${p.isBlocked}'); - } - - if (blockedStatus != null) { - for (final name in orderedNames) { - final p = packages[name]!; - final out = await _updateStatus(p, blockedStatus, blockedReason); - if (out.isNotEmpty) { - output.writeln(out); - } - } - } - return output.toString(); -} - -Future<String> _updateStatus(Package pkg, bool status, String? reason) async { - if (pkg.isBlocked == status) { - return ''; - } - await withRetryTransaction(dbService, (tx) async { - final p = await tx.lookupValue<Package>(pkg.key); - p.updateIsBlocked(isBlocked: status, reason: reason); - tx.insert(p); - }); - await purgePackageCache(pkg.name!); - return ('Updating ${pkg.name!.padRight(40)} - ${pkg.isBlocked} -> $status'); -} - -bool? _parseValue(String? value) { - if (value == null) return null; - if (value == 'true') return true; - if (value == 'false') return false; - throw ArgumentError('Unknown bool value: $value'); -} diff --git a/app/lib/admin/tools/set_user_blocked.dart b/app/lib/admin/tools/set_user_blocked.dart deleted file mode 100644 index 10107fb396..0000000000 --- a/app/lib/admin/tools/set_user_blocked.dart +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:async'; - -import 'package:pub_dev/account/backend.dart'; -import 'package:pub_dev/account/models.dart'; -import 'package:pub_dev/service/email/email_templates.dart'; - -Future<String> executeSetUserBlocked(List<String> args) async { - if (args.isEmpty || args.length > 2) { - return 'Sets the blocked flag on a User.\n' - ' <tools-command> <id|email> - get status\n' - ' <tools-command> <id|email> <true|false> - set status\n'; - } - final idOrEmail = args[0]; - final valueAsString = args.length == 2 ? args[1] : null; - final blockedStatus = _parseValue(valueAsString); - - final List<User> users; - if (isValidEmail(idOrEmail)) { - users = await accountBackend.lookupUsersByEmail(idOrEmail); - } else { - final user = await accountBackend.lookupUserById(idOrEmail); - users = user == null ? [] : [user]; - } - if (users.isEmpty) { - return 'No user found.'; - } - final output = StringBuffer(); - for (final user in users) { - if (blockedStatus == null) { - output.write('userId: ${user.userId}\n' - 'email: ${user.email}\n' - 'isBlocked: ${user.isBlocked}\n'); - } else { - await accountBackend.updateBlockedFlag(user.userId, blockedStatus); - output.writeln('Updated: ${user.userId} -> $blockedStatus'); - } - } - return output.toString(); -} - -bool? _parseValue(String? value) { - if (value == null) return null; - if (value == 'true') return true; - if (value == 'false') return false; - throw ArgumentError('Unknown bool value: $value'); -} diff --git a/app/lib/package/models.dart b/app/lib/package/models.dart index 47d65c2c63..bffe4783c0 100644 --- a/app/lib/package/models.dart +++ b/app/lib/package/models.dart @@ -413,16 +413,6 @@ class Package extends db.ExpandoModel<String> { ); } - void updateIsBlocked({ - required bool isBlocked, - String? reason, - }) { - this.isBlocked = isBlocked; - blockedReason = reason; - blocked = isBlocked ? clock.now().toUtc() : null; - updated = clock.now().toUtc(); - } - void updateIsModerated({ required bool isModerated, }) { diff --git a/app/lib/publisher/models.dart b/app/lib/publisher/models.dart index 5a7895f7e1..79a0929d6f 100644 --- a/app/lib/publisher/models.dart +++ b/app/lib/publisher/models.dart @@ -91,15 +91,6 @@ class Publisher extends db.ExpandoModel<String> { isModerated = false; } - /// Clears most properties on the entity and sets the [isBlocked] flag. - void markForBlocked() { - isBlocked = true; - isAbandoned = true; - contactEmail = null; - description = ''; - updated = clock.now().toUtc(); - } - /// Whether the publisher has a displayable description. bool get hasDescription => description != null && description!.isNotEmpty; diff --git a/app/test/admin/api_test.dart b/app/test/admin/api_test.dart index a07ae9f659..8e56d3cbc4 100644 --- a/app/test/admin/api_test.dart +++ b/app/test/admin/api_test.dart @@ -712,26 +712,6 @@ void main() { expect(rs3.isRetracted, v4.isRetracted); }); }); - - group('block user', () { - setupTestsWithAdminTokenIssues( - (client) => client.adminExecuteTool( - 'set-user-blocked', - 'user@pub.dev/true', - ), - ); - - testWithProfile('block and unblock user', fn: () async { - final client = createPubApiClient(authToken: siteAdminToken); - await client.adminExecuteTool('set-user-blocked', 'user@pub.dev/true'); - final user = await accountBackend.lookupUserByEmail('user@pub.dev'); - expect(user.isBlocked, true); - await client.adminExecuteTool( - 'set-user-blocked', '${user.userId}/false'); - final user2 = await accountBackend.lookupUserByEmail('user@pub.dev'); - expect(user2.isBlocked, false); - }); - }); }); } diff --git a/app/test/frontend/handlers/custom_api_test.dart b/app/test/frontend/handlers/custom_api_test.dart index 8b197bf1e1..20597377e7 100644 --- a/app/test/frontend/handlers/custom_api_test.dart +++ b/app/test/frontend/handlers/custom_api_test.dart @@ -147,7 +147,7 @@ void main() { }, ); final p = await packageBackend.lookupPackage('neon'); - p!.updateIsBlocked(isBlocked: true, reason: 'spam'); + p!.updateIsModerated(isModerated: true); expect(p.isVisible, isFalse); await dbService.commit(inserts: [p]); await nameTracker.reloadFromDatastore(); @@ -163,7 +163,7 @@ void main() { ); // reverting to make sure integrity check is passing - p.updateIsBlocked(isBlocked: false); + p.updateIsModerated(isModerated: false); await dbService.commit(inserts: [p]); }); }); diff --git a/app/test/frontend/handlers/documentation_test.dart b/app/test/frontend/handlers/documentation_test.dart index 259991f442..438bb2abf0 100644 --- a/app/test/frontend/handlers/documentation_test.dart +++ b/app/test/frontend/handlers/documentation_test.dart @@ -2,9 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'package:gcloud/db.dart'; import 'package:pub_dev/frontend/handlers/documentation.dart'; -import 'package:pub_dev/package/backend.dart'; import 'package:pub_dev/shared/urls.dart'; import 'package:test/test.dart'; @@ -143,15 +141,5 @@ void main() { }, processJobsWithFakeRunners: true, ); - - testWithProfile('withheld package gets rejected', fn: () async { - final pkg = await packageBackend.lookupPackage('oxygen'); - await dbService.commit(inserts: [pkg!..updateIsBlocked(isBlocked: true)]); - await expectNotFoundResponse( - await issueGet('/documentation/oxygen/latest/')); - - // reverting to make sure integrity check is passing - await dbService.commit(inserts: [pkg..updateIsBlocked(isBlocked: false)]); - }); }); } diff --git a/app/test/frontend/handlers/package_test.dart b/app/test/frontend/handlers/package_test.dart index 1f0d507ad9..773ebe58fd 100644 --- a/app/test/frontend/handlers/package_test.dart +++ b/app/test/frontend/handlers/package_test.dart @@ -2,8 +2,6 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'package:gcloud/db.dart'; -import 'package:pub_dev/package/models.dart'; import 'package:pub_dev/tool/test_profile/models.dart'; import 'package:test/test.dart'; @@ -43,22 +41,6 @@ void main() { '/packages/oxygen/versions/1.2.0/changelog'); }); - testWithProfile('withheld package - not found', fn: () async { - final pkg = await dbService.lookupValue<Package>( - dbService.emptyKey.append(Package, id: 'oxygen')); - await dbService.commit(inserts: [pkg..updateIsBlocked(isBlocked: true)]); - await expectNotFoundResponse(await issueGet('/packages/oxygen')); - await expectNotFoundResponse(await issueGet('/packages/oxygen/score')); - await expectNotFoundResponse(await issueGet('/packages/oxygen/versions')); - await expectNotFoundResponse( - await issueGet('/packages/oxygen/versions/${pkg.latestVersion}')); - await expectNotFoundResponse(await issueGet( - '/packages/oxygen/versions/${pkg.latestVersion}/score')); - - // reverting to make sure integrity check is passing - await dbService.commit(inserts: [pkg..updateIsBlocked(isBlocked: false)]); - }); - testWithProfile('/packages/foobar_not_found - not found', fn: () async { await expectNotFoundResponse( await issueGet('/packages/foobar_not_found')); diff --git a/app/test/package/backend_test.dart b/app/test/package/backend_test.dart index 12d5a76986..29153fee58 100644 --- a/app/test/package/backend_test.dart +++ b/app/test/package/backend_test.dart @@ -165,19 +165,6 @@ void main() { }); }); - testWithProfile('blocked user', fn: () async { - final user = await accountBackend.lookupUserByEmail('admin@pub.dev'); - await dbService.commit(inserts: [user..isBlocked = true]); - final rs = withFakeAuthRequestContext( - adminAtPubDevEmail, - () async { - return packageBackend.inviteUploader( - 'oxygen', InviteUploaderRequest(email: 'a@b.com')); - }, - ); - await expectLater(rs, throwsA(isA<AuthenticationException>())); - }); - testWithProfile('package does not exist', fn: () async { await withFakeAuthRequestContext(adminAtPubDevEmail, () async { final rs = packageBackend.inviteUploader( diff --git a/app/test/package/upload_test.dart b/app/test/package/upload_test.dart index d1f851fd63..608114a243 100644 --- a/app/test/package/upload_test.dart +++ b/app/test/package/upload_test.dart @@ -937,17 +937,6 @@ void main() { expect(p2!.versionCount, 3); }); - testWithProfile('user is blocked', fn: () async { - final user = await accountBackend.lookupUserByEmail('user@pub.dev'); - await dbService.commit(inserts: [user..isBlocked = true]); - final tarball = await packageArchiveBytes( - pubspecContent: generatePubspecYaml('pkg', '1.2.3')); - final rs = createPubApiClient(authToken: userClientToken) - .uploadPackageBytes(tarball); - await expectApiException(rs, - status: 403, code: 'InsufficientPermissions'); - }); - testWithProfile('upload restriction - no uploads', fn: () async { (secretBackend as FakeSecretBackend) .update(SecretKey.uploadRestriction, 'no-uploads'); diff --git a/app/test/shared/test_services.dart b/app/test/shared/test_services.dart index 44444561a4..ace8d243b2 100644 --- a/app/test/shared/test_services.dart +++ b/app/test/shared/test_services.dart @@ -240,11 +240,15 @@ void setupTestsWithCallerAuthorizationIssues( await expectApiException(rs, status: 403, code: 'InsufficientPermissions'); }); - testWithProfile('Active user is blocked', fn: () async { + testWithProfile('Active user is moderated', fn: () async { final users = await dbService.query<User>().run().toList(); final user = users.firstWhere((u) => u.email == 'admin@pub.dev'); final client = await createFakeAuthPubApiClient(email: adminAtPubDevEmail); - await dbService.commit(inserts: [user..isBlocked = true]); + await dbService.commit(inserts: [ + user + ..isModerated = true + ..moderatedAt = clock.now() + ]); final rs = fn(client); await expectApiException(rs, status: 401, code: 'MissingAuthentication', message: 'failed'); diff --git a/app/test/tool/maintenance/migrate_isblocked_test.dart b/app/test/tool/maintenance/migrate_isblocked_test.dart index 3d59e13329..7b0c3f7ba6 100644 --- a/app/test/tool/maintenance/migrate_isblocked_test.dart +++ b/app/test/tool/maintenance/migrate_isblocked_test.dart @@ -2,6 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'package:clock/clock.dart'; import 'package:pub_dev/account/backend.dart'; import 'package:pub_dev/package/backend.dart'; import 'package:pub_dev/publisher/backend.dart'; @@ -15,8 +16,12 @@ void main() { group('Migrate isBlocked', () { testWithProfile('package', fn: () async { final p1 = await packageBackend.lookupPackage('oxygen'); - await dbService.commit( - inserts: [p1!..updateIsBlocked(isBlocked: true, reason: 'abc')]); + await dbService.commit(inserts: [ + p1! + ..isBlocked = true + ..blocked = clock.now() + ..blockedReason = 'abc' + ]); await migrateIsBlocked(); final p2 = await packageBackend.lookupPackage('oxygen'); @@ -25,7 +30,7 @@ void main() { testWithProfile('publisher', fn: () async { final p1 = await publisherBackend.getPublisher('example.com'); - await dbService.commit(inserts: [p1!..markForBlocked()]); + await dbService.commit(inserts: [p1!..isBlocked = true]); final members = await publisherBackend.listPublisherMembers('example.com'); for (final m in members) {