@@ -527,6 +527,7 @@ class PackageBackend {
527527 final repository = githubConfig.repository? .trim () ?? '' ;
528528 githubConfig.repository = repository.isEmpty ? null : repository;
529529 final tagPattern = githubConfig.tagPattern? .trim () ?? '' ;
530+ verifyTagPattern (tagPattern: tagPattern);
530531 githubConfig.tagPattern = tagPattern.isEmpty ? null : tagPattern;
531532 final environment = githubConfig.environment? .trim () ?? '' ;
532533 githubConfig.environment = environment.isEmpty ? null : environment;
@@ -544,15 +545,6 @@ class PackageBackend {
544545 'The `repository` field has invalid characters.' );
545546 }
546547
547- final tagPatternParts = tagPattern.split ('{{version}}' );
548- InvalidInputException .check (tagPatternParts.length == 2 ,
549- 'The `tagPattern` field must contain a single `{{version}}` part.' );
550- InvalidInputException .check (
551- tagPatternParts
552- .where ((e) => e.isNotEmpty)
553- .every (_validGitHubVersionPattern.hasMatch),
554- 'The `tagPattern` field has invalid characters.' );
555-
556548 InvalidInputException .check (
557549 ! githubConfig.requireEnvironment || environment.isNotEmpty,
558550 'The `environment` field must not be empty when enabled.' );
@@ -1339,11 +1331,6 @@ class PackageBackend {
13391331 if (repository == null || repository.isEmpty) {
13401332 throw AssertionError ('Missing or empty repository.' );
13411333 }
1342- final tagPattern = githubConfig.tagPattern ?? '' ;
1343- if (! tagPattern.contains ('{{version}}' )) {
1344- throw AssertionError (
1345- 'Configured tag pattern does not include `{{version}}`' );
1346- }
13471334 final requireEnvironment = githubConfig.requireEnvironment;
13481335 final environment = githubConfig.environment;
13491336 if (requireEnvironment && (environment == null || environment.isEmpty)) {
@@ -1375,25 +1362,11 @@ class PackageBackend {
13751362 throw AuthorizationException .githubActionIssue (
13761363 'publishing is only allowed from "tag" refType, this token has "${agent .payload .refType }" refType' );
13771364 }
1378- final expectedRefStart = 'refs/tags/' ;
1379- if (! agent.payload.ref.startsWith (expectedRefStart)) {
1380- throw AuthorizationException .githubActionIssue (
1381- 'publishing is only allowed from "refs/tags/*" ref, this token has "${agent .payload .ref }" ref' );
1382- }
1383- final expectedTagValue = tagPattern.replaceFirst ('{{version}}' , newVersion);
1384- if (agent.payload.ref != 'refs/tags/$expectedTagValue ' ) {
1385- // At this point we have concluded that the agent has push rights to the repository,
1386- // however, the tag pattern they have used is not the one we expect.
1387- //
1388- // By revealing the expected tag pattern, we are serving the users with better
1389- // error message, while not exposing much information to an assumed attacker.
1390- // With the current access level, an attacker would have access to past tags, and
1391- // figuring out the tag pattern from those should be straightforward anyway.
1392- throw AuthorizationException .githubActionIssue (
1393- 'publishing is configured to only be allowed from actions with specific ref pattern, '
1394- 'this token has "${agent .payload .ref }" ref for which publishing is not allowed. '
1395- 'Expected tag "$expectedTagValue ". Check that the version in the tag matches the version in "pubspec.yaml"' );
1396- }
1365+ verifyTagPatternWithRef (
1366+ tagPattern: githubConfig.tagPattern ?? '' ,
1367+ ref: agent.payload.ref,
1368+ newVersion: newVersion,
1369+ );
13971370
13981371 // When environment is configured, it must match the action's environment.
13991372 if (requireEnvironment && environment != agent.payload.environment) {
@@ -1759,6 +1732,54 @@ Future<void> purgePackageCache(String package) async {
17591732 ]);
17601733}
17611734
1735+ /// Verifies the [tagPattern] before storing it on the automated publishing
1736+ /// settings object.
1737+ @visibleForTesting
1738+ void verifyTagPattern ({required String tagPattern}) {
1739+ final tagPatternParts = tagPattern.split ('{{version}}' );
1740+ InvalidInputException .check (tagPatternParts.length == 2 ,
1741+ 'The `tagPattern` field must contain a single `{{version}}` part.' );
1742+ InvalidInputException .check (
1743+ tagPatternParts
1744+ .where ((e) => e.isNotEmpty)
1745+ .every (_validGitHubVersionPattern.hasMatch),
1746+ 'The `tagPattern` field has invalid characters.' );
1747+ }
1748+
1749+ /// Verifies the user-settings [tagPattern] with the authentication-provided
1750+ /// [ref] value, and throws if the ref is not allowed or not recognized as
1751+ /// valid pattern.
1752+ @visibleForTesting
1753+ void verifyTagPatternWithRef ({
1754+ required String tagPattern,
1755+ required String ref,
1756+ required String newVersion,
1757+ }) {
1758+ if (! tagPattern.contains ('{{version}}' )) {
1759+ throw AssertionError (
1760+ 'Configured tag pattern does not include `{{version}}`' );
1761+ }
1762+ final expectedRefStart = 'refs/tags/' ;
1763+ if (! ref.startsWith (expectedRefStart)) {
1764+ throw AuthorizationException .githubActionIssue (
1765+ 'publishing is only allowed from "refs/tags/*" ref, this token has "$ref " ref' );
1766+ }
1767+ final expectedTagValue = tagPattern.replaceFirst ('{{version}}' , newVersion);
1768+ if (ref != 'refs/tags/$expectedTagValue ' ) {
1769+ // At this point we have concluded that the agent has push rights to the repository,
1770+ // however, the tag pattern they have used is not the one we expect.
1771+ //
1772+ // By revealing the expected tag pattern, we are serving the users with better
1773+ // error message, while not exposing much information to an assumed attacker.
1774+ // With the current access level, an attacker would have access to past tags, and
1775+ // figuring out the tag pattern from those should be straightforward anyway.
1776+ throw AuthorizationException .githubActionIssue (
1777+ 'publishing is configured to only be allowed from actions with specific ref pattern, '
1778+ 'this token has "$ref " ref for which publishing is not allowed. '
1779+ 'Expected tag "$expectedTagValue ". Check that the version in the tag matches the version in "pubspec.yaml"' );
1780+ }
1781+ }
1782+
17621783/// The status of an invite after being created or updated.
17631784class InviteStatus {
17641785 final String ? urlNonce;
0 commit comments