@@ -38,6 +38,7 @@ import 'package:pub_dev/task/global_lock.dart';
3838import 'package:pub_dev/task/handlers.dart' ;
3939import 'package:pub_dev/task/models.dart'
4040 show
41+ AbortedTokenInfo,
4142 PackageState,
4243 PackageStateInfo,
4344 PackageVersionStateInfo,
@@ -454,6 +455,20 @@ class TaskBackend {
454455 return false ;
455456 }
456457
458+ state.abortedTokens = [
459+ ...state.versions! .entries
460+ .where ((e) => deselectedVersions.contains (e.key))
461+ .map ((e) => e.value)
462+ .where ((vs) => vs.secretToken != null )
463+ .map (
464+ (vs) => AbortedTokenInfo (
465+ token: vs.secretToken! ,
466+ expires: vs.scheduled.add (maxTaskExecutionTime),
467+ ),
468+ ),
469+ ...? state.abortedTokens,
470+ ].where ((t) => t.isNotExpired).take (50 ).toList ();
471+
457472 // Make changes!
458473 state.versions!
459474 // Remove versions that have been deselected
@@ -467,6 +482,7 @@ class TaskBackend {
467482 ),
468483 });
469484 state.derivePendingAt ();
485+ state.abortedTokens? .removeWhere ((t) => t.expires.isAfter (clock.now ()));
470486
471487 _log.info ('Update state tracking for $packageName ' );
472488 tx.insert (state);
@@ -609,20 +625,18 @@ class TaskBackend {
609625 InvalidInputException .checkPackageName (package);
610626 version = InvalidInputException .checkSemanticVersion (version);
611627
628+ final token = _extractBearerToken (request);
629+ if (token == null ) {
630+ throw AuthenticationException .authenticationRequired ();
631+ }
632+
612633 final key = PackageState .createKey (_db, runtimeVersion, package);
613634 final state = await _db.lookupOrNull <PackageState >(key);
614- if (state == null || state.versions ! [version] == null ) {
635+ if (state == null ) {
615636 throw NotFoundException .resource ('$package /$version ' );
616637 }
617- final versionState = state.versions! [version]! ;
618-
619- // Check the secret token
620- if (! versionState.isAuthorized (_extractBearerToken (request))) {
621- throw AuthenticationException .authenticationRequired ();
622- }
623- assert (versionState.scheduled != initialTimestamp);
624- assert (versionState.instance != null );
625- assert (versionState.zone != null );
638+ final versionState =
639+ _authorizeWorkerCallback (package, version, state, token);
626640
627641 // Set expiration of signed URLs to remaining execution time + 5 min to
628642 // allow for clock skew.
@@ -669,6 +683,11 @@ class TaskBackend {
669683 InvalidInputException .checkPackageName (package);
670684 version = InvalidInputException .checkSemanticVersion (version);
671685
686+ final token = _extractBearerToken (request);
687+ if (token == null ) {
688+ throw AuthenticationException .authenticationRequired ();
689+ }
690+
672691 String ? zone, instance;
673692 bool isInstanceDone = false ;
674693 final index = await _loadTaskResultIndex (
@@ -685,18 +704,11 @@ class TaskBackend {
685704 await withRetryTransaction (_db, (tx) async {
686705 final key = PackageState .createKey (_db, runtimeVersion, package);
687706 final state = await tx.lookupOrNull <PackageState >(key);
688- if (state == null || state.versions ! [version] == null ) {
707+ if (state == null ) {
689708 throw NotFoundException .resource ('$package /$version ' );
690709 }
691- final versionState = state.versions! [version]! ;
692-
693- // Check the secret token
694- if (! versionState.isAuthorized (_extractBearerToken (request))) {
695- throw AuthenticationException .authenticationRequired ();
696- }
697- assert (versionState.scheduled != initialTimestamp);
698- assert (versionState.instance != null );
699- assert (versionState.zone != null );
710+ final versionState =
711+ _authorizeWorkerCallback (package, version, state, token);
700712
701713 // Update dependencies, if pana summary has dependencies
702714 if (summary != null && summary.allDependencies != null ) {
@@ -1169,6 +1181,38 @@ String? _extractBearerToken(shelf.Request request) {
11691181 return parts.last.trim ();
11701182}
11711183
1184+ /// Authorize a worker callback for [package] / [version] .
1185+ ///
1186+ /// Returns the [PackageVersionStateInfo] that the worker is authenticated for.
1187+ /// Or throw [ResponseException] if authorization is not possible.
1188+ PackageVersionStateInfo _authorizeWorkerCallback (
1189+ String package,
1190+ String version,
1191+ PackageState state,
1192+ String token,
1193+ ) {
1194+ // fixed-time verification of aborted tokens
1195+ final isKnownAbortedToken = state.abortedTokens
1196+ ? .map ((t) => t.isAuthorized (token))
1197+ .fold <bool >(false , (a, b) => a || b);
1198+ if (isKnownAbortedToken ?? false ) {
1199+ throw TaskAbortedException ('$package /$version has been aborted.' );
1200+ }
1201+
1202+ final versionState = state.versions! [version];
1203+ if (versionState == null ) {
1204+ throw NotFoundException .resource ('$package /$version ' );
1205+ }
1206+ // Check the secret token
1207+ if (! versionState.isAuthorized (token)) {
1208+ throw AuthenticationException .authenticationRequired ();
1209+ }
1210+ assert (versionState.scheduled != initialTimestamp);
1211+ assert (versionState.instance != null );
1212+ assert (versionState.zone != null );
1213+ return versionState;
1214+ }
1215+
11721216/// Given a list of versions return the list of versions that should be
11731217/// tracked for analysis.
11741218///
0 commit comments