@@ -169,6 +169,22 @@ func GetAcrCLIClientWithAuth(loginURL string, username string, password string,
169169 return & acrClient , nil
170170}
171171
172+ // buildAbacScope constructs a token scope string from a set of repository names.
173+ // "catalog" is a sentinel value mapped to "registry:catalog:*" (access to the
174+ // catalog API for listing repositories). All other names are mapped to
175+ // "repository:<name>:pull,delete,metadata_read,metadata_write".
176+ func buildAbacScope (repositories []string ) string {
177+ var scopeParts []string
178+ for _ , repo := range repositories {
179+ if repo == "catalog" {
180+ scopeParts = append (scopeParts , "registry:catalog:*" )
181+ } else {
182+ scopeParts = append (scopeParts , fmt .Sprintf ("repository:%s:pull,delete,metadata_read,metadata_write" , repo ))
183+ }
184+ }
185+ return strings .Join (scopeParts , " " )
186+ }
187+
172188// refreshAcrCLIClientToken obtains a new token and gets its expiration time.
173189// For non-ABAC registries, this uses the wildcard scope.
174190// For ABAC registries, this uses the currentRepositories to refresh with the appropriate scope.
@@ -180,27 +196,17 @@ func refreshAcrCLIClientToken(ctx context.Context, c *AcrCLIClient, repoName str
180196 for _ , repo := range c .currentRepositories {
181197 repoSet [repo ] = true
182198 }
183- // Ensure the current repoName is in the set
184199 if repoName != "" {
185200 repoSet [repoName ] = true
186201 }
187- var scopeParts []string
188- // "catalog" is a sentinel value meaning "include registry:catalog:* scope"
189- // (access to the catalog API for listing repositories). It must NOT be
190- // treated as a repository name, since "repository:catalog:..." would try
191- // to access a repository literally named "catalog".
192- if repoSet ["catalog" ] {
193- scopeParts = append (scopeParts , "registry:catalog:*" )
194- delete (repoSet , "catalog" )
195- }
202+ repos := make ([]string , 0 , len (repoSet ))
196203 for repo := range repoSet {
197- scopeParts = append (scopeParts , fmt . Sprintf ( "repository:%s:pull,delete,metadata_read,metadata_write" , repo ) )
204+ repos = append (repos , repo )
198205 }
199- if len ( scopeParts ) == 0 {
200- // Fallback: if no repositories specified, return error for ABAC
206+ scope = buildAbacScope ( repos )
207+ if scope == "" {
201208 return errors .New ("ABAC registry requires repository scope but none specified" )
202209 }
203- scope = strings .Join (scopeParts , " " )
204210 } else {
205211 // For non-ABAC registries, use the wildcard scope
206212 scope = "repository:*:*"
@@ -258,21 +264,7 @@ func (c *AcrCLIClient) RefreshTokenForAbac(ctx context.Context, repositories []s
258264 // Update the current repositories so automatic refreshes use the same scope
259265 c .currentRepositories = repositories
260266
261- // Build the scope string for all requested repositories.
262- // Each repository needs pull, delete, and metadata permissions for purge operations.
263- // "catalog" is a sentinel value meaning "include registry:catalog:* scope"
264- // (access to the catalog API for listing repositories). It must NOT be
265- // treated as a repository name, since "repository:catalog:..." would try
266- // to access a repository literally named "catalog".
267- var scopeParts []string
268- for _ , repo := range repositories {
269- if repo == "catalog" {
270- scopeParts = append (scopeParts , "registry:catalog:*" )
271- } else {
272- scopeParts = append (scopeParts , fmt .Sprintf ("repository:%s:pull,delete,metadata_read,metadata_write" , repo ))
273- }
274- }
275- scope := strings .Join (scopeParts , " " )
267+ scope := buildAbacScope (repositories )
276268
277269 accessTokenResponse , err := c .AutorestClient .GetAcrAccessToken (ctx , c .loginURL , scope , c .token .RefreshToken )
278270 if err != nil {
0 commit comments