Skip to content

Commit f85b508

Browse files
committed
Merge PR oras-project#1038: feat!: remove ForceAttemptOAuth2 add SetLegacyMode to auth client
Signed-off-by: Terry Howe <terrylhowe@gmail.com>
2 parents 856f5c2 + 2b4e299 commit f85b508

File tree

5 files changed

+126
-20
lines changed

5 files changed

+126
-20
lines changed

CLAUDE.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
ORAS Go is a Go library for managing OCI (Open Container Initiative) artifacts, providing unified APIs for pushing, pulling, and managing artifacts across OCI-compliant registries, local file systems, and in-memory stores. It's compliant with the OCI Image Format Specification and OCI Distribution Specification.
8+
9+
## Development Commands
10+
11+
### Testing
12+
```bash
13+
make test # Run tests with race detection and coverage
14+
go test ./... # Run all tests without coverage
15+
go test ./content # Run tests for specific package
16+
```
17+
18+
### Building and Validation
19+
```bash
20+
make check-encoding # Check for CRLF line endings
21+
make fix-encoding # Fix CRLF line endings
22+
make vendor # Update vendor directory
23+
make clean # Clean ignored files
24+
```
25+
26+
### Coverage
27+
```bash
28+
make covhtml # Open coverage report in browser (requires prior test run)
29+
```
30+
31+
## Code Architecture
32+
33+
### Core Package Structure
34+
35+
- **Root package (`oras`)** - Main APIs for copying, packing, and content operations
36+
- **`content/`** - Content management and storage interfaces
37+
- `content/file/` - File-based content storage
38+
- `content/memory/` - In-memory content storage
39+
- `content/oci/` - OCI layout storage
40+
- **`registry/`** - Registry interfaces and operations
41+
- `registry/remote/` - Remote registry client implementations
42+
- `registry/remote/auth/` - Authentication handling
43+
- `registry/remote/credentials/` - Credential management
44+
- **`internal/`** - Internal packages not part of the public API
45+
- `internal/cas/` - Content-addressable storage utilities
46+
- `internal/syncutil/` - Synchronization utilities
47+
- `internal/platform/` - Platform-specific utilities
48+
49+
### Key Concepts
50+
51+
- **Targets** - Abstractions for different storage backends (registry, file system, memory)
52+
- **Content Stores** - Manage content storage and retrieval with content-addressable storage
53+
- **Descriptors** - OCI descriptors that identify and describe content
54+
- **Copy Operations** - Core functionality for copying artifacts between different targets
55+
- **Graph Operations** - Handle dependencies between artifacts and their manifests
56+
57+
### Testing Patterns
58+
59+
Tests follow Go conventions with `*_test.go` files. The codebase includes:
60+
- Unit tests for individual packages
61+
- Example tests demonstrating API usage
62+
- Integration-style tests for end-to-end operations
63+
64+
### Go Version Support
65+
66+
The project supports the two latest Go versions (currently 1.23 and 1.24) following Go's security policy. The minimum Go version is specified in `go.mod`.
67+
68+
## Key Files for Understanding
69+
70+
- `content.go` - Core content operations and interfaces
71+
- `copy.go` - Primary copy functionality between targets
72+
- `pack.go` - Artifact packing operations
73+
- `registry/registry.go` - Registry interface definitions
74+
- `registry/remote/repository.go` - Remote repository implementation

registry/remote/auth/client.go

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,25 @@ type Client struct {
9999
// Reference: https://distribution.github.io/distribution/spec/auth/oauth/#getting-a-token
100100
ClientID string
101101

102-
// ForceAttemptOAuth2 controls whether to follow OAuth2 with password grant
103-
// instead the distribution spec when authenticating using username and
104-
// password.
102+
// legacyMode controls whether to use the legacy distribution spec
103+
// instead of OAuth2 with password grant when authenticating using
104+
// username and password.
105+
// Default is false (OAuth2 is used).
106+
//
107+
// When legacyMode is true, the client uses the legacy distribution spec for
108+
// authentication. If the registry says it supports bearer authentication,
109+
// basic authentication will be performed unless there are no credentials
110+
// or a refresh token is provided. This is the approach used in oras-go
111+
// v1 and v2.
112+
//
113+
// When legacyMode is false (default), the client uses OAuth2 with password
114+
// grant. If the registry says it supports bearer authentication, bearer
115+
// authentication will be performed.
116+
//
105117
// References:
106118
// - https://distribution.github.io/distribution/spec/auth/jwt/
107119
// - https://distribution.github.io/distribution/spec/auth/oauth/
108-
ForceAttemptOAuth2 bool
120+
legacyMode bool
109121
}
110122

111123
// client returns an HTTP client used to access the remote registry.
@@ -150,6 +162,27 @@ func (c *Client) SetUserAgent(userAgent string) {
150162
c.Header.Set(headerUserAgent, userAgent)
151163
}
152164

165+
// SetLegacyMode sets whether to use legacy distribution spec authentication
166+
// instead of OAuth2 with password grant when authenticating using username
167+
// and password.
168+
//
169+
// When legacy is true, the client uses the legacy distribution spec for
170+
// authentication. If the registry says it supports bearer authentication,
171+
// basic authentication will be performed unless there are no credentials
172+
// or a refresh token is provided. This is the approach used in oras-go
173+
// v1 and v2.
174+
//
175+
// When legacy is false (default), the client uses OAuth2 with password
176+
// grant. If the registry says it supports bearer authentication, bearer
177+
// authentication will be performed.
178+
//
179+
// References:
180+
// - https://distribution.github.io/distribution/spec/auth/jwt/
181+
// - https://distribution.github.io/distribution/spec/auth/oauth/
182+
func (c *Client) SetLegacyMode(legacy bool) {
183+
c.legacyMode = legacy
184+
}
185+
153186
// Do sends the request to the remote server, attempting to resolve
154187
// authentication if 'Authorization' header is not set.
155188
//
@@ -288,7 +321,7 @@ func (c *Client) fetchBearerToken(ctx context.Context, registry, realm, service
288321
if cred.AccessToken != "" {
289322
return cred.AccessToken, nil
290323
}
291-
if cred == credentials.EmptyCredential || (cred.RefreshToken == "" && !c.ForceAttemptOAuth2) {
324+
if cred == credentials.EmptyCredential || (cred.RefreshToken == "" && c.legacyMode) {
292325
return c.fetchDistributionToken(ctx, realm, service, scopes, cred.Username, cred.Password)
293326
}
294327
return c.fetchOAuth2Token(ctx, realm, service, scopes, cred)

registry/remote/auth/client_test.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,7 @@ func TestClient_Do_Bearer_Auth(t *testing.T) {
726726
}, nil
727727
},
728728
}
729+
client.SetLegacyMode(true)
729730

730731
// first request
731732
req, err := http.NewRequest(http.MethodGet, ts.URL, nil)
@@ -853,6 +854,7 @@ func TestClient_Do_Bearer_Auth_Cached(t *testing.T) {
853854
},
854855
Cache: NewCache(),
855856
}
857+
client.SetLegacyMode(true)
856858

857859
// first request
858860
ctx := WithScopes(context.Background(), scopes...)
@@ -995,6 +997,7 @@ func TestClient_Do_Bearer_Auth_Cached_PerHost(t *testing.T) {
995997
}),
996998
Cache: NewCache(),
997999
}
1000+
client1.SetLegacyMode(true)
9981001

9991002
// set up server 2
10001003
username2 := "test_user2"
@@ -1065,6 +1068,7 @@ func TestClient_Do_Bearer_Auth_Cached_PerHost(t *testing.T) {
10651068
}),
10661069
Cache: NewCache(),
10671070
}
1071+
client2.SetLegacyMode(true)
10681072

10691073
ctx := context.Background()
10701074
ctx = WithScopesForHost(ctx, uri1.Host, scopes1...)
@@ -1312,7 +1316,6 @@ func TestClient_Do_Bearer_OAuth2_Password(t *testing.T) {
13121316
Password: password,
13131317
}, nil
13141318
},
1315-
ForceAttemptOAuth2: true,
13161319
}
13171320

13181321
// first request
@@ -1459,8 +1462,7 @@ func TestClient_Do_Bearer_OAuth2_Password_Cached(t *testing.T) {
14591462
Password: password,
14601463
}, nil
14611464
},
1462-
ForceAttemptOAuth2: true,
1463-
Cache: NewCache(),
1465+
Cache: NewCache(),
14641466
}
14651467

14661468
// first request
@@ -1622,8 +1624,7 @@ func TestClient_Do_Bearer_OAuth2_Password_Cached_PerHost(t *testing.T) {
16221624
Username: username1,
16231625
Password: password1,
16241626
}),
1625-
ForceAttemptOAuth2: true,
1626-
Cache: NewCache(),
1627+
Cache: NewCache(),
16271628
}
16281629
// set up server 2
16291630
username2 := "test_user2"
@@ -1712,8 +1713,7 @@ func TestClient_Do_Bearer_OAuth2_Password_Cached_PerHost(t *testing.T) {
17121713
Username: username2,
17131714
Password: password2,
17141715
}),
1715-
ForceAttemptOAuth2: true,
1716-
Cache: NewCache(),
1716+
Cache: NewCache(),
17171717
}
17181718

17191719
ctx := context.Background()
@@ -2970,8 +2970,7 @@ func TestClient_Do_Scope_Hint_Mismatch(t *testing.T) {
29702970
Password: password,
29712971
}, nil
29722972
},
2973-
ForceAttemptOAuth2: true,
2974-
Cache: NewCache(),
2973+
Cache: NewCache(),
29752974
}
29762975

29772976
// first request
@@ -3114,8 +3113,7 @@ func TestClient_Do_Scope_Hint_Mismatch_PerHost(t *testing.T) {
31143113
Username: username1,
31153114
Password: password1,
31163115
}),
3117-
ForceAttemptOAuth2: true,
3118-
Cache: NewCache(),
3116+
Cache: NewCache(),
31193117
}
31203118

31213119
// set up server 1
@@ -3208,8 +3206,7 @@ func TestClient_Do_Scope_Hint_Mismatch_PerHost(t *testing.T) {
32083206
Username: username2,
32093207
Password: password2,
32103208
}),
3211-
ForceAttemptOAuth2: true,
3212-
Cache: NewCache(),
3209+
Cache: NewCache(),
32133210
}
32143211

32153212
ctx := context.Background()
@@ -3350,6 +3347,7 @@ func TestClient_Do_Invalid_Credential_Basic(t *testing.T) {
33503347
}, nil
33513348
},
33523349
}
3350+
client.SetLegacyMode(true)
33533351

33543352
// request should fail
33553353
req, err := http.NewRequest(http.MethodGet, ts.URL, nil)
@@ -3435,6 +3433,7 @@ func TestClient_Do_Invalid_Credential_Bearer(t *testing.T) {
34353433
}, nil
34363434
},
34373435
}
3436+
client.SetLegacyMode(true)
34383437

34393438
// request should fail
34403439
req, err := http.NewRequest(http.MethodGet, ts.URL, nil)
@@ -3620,6 +3619,7 @@ func TestClient_Do_Scheme_Change(t *testing.T) {
36203619
},
36213620
Cache: NewCache(),
36223621
}
3622+
client.SetLegacyMode(true)
36233623

36243624
// request with bearer auth
36253625
req, err := http.NewRequest(http.MethodGet, ts.URL, nil)

registry/remote/auth/example_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,6 @@ func ExampleClient_Do_clientConfigurations() {
190190
Username: username,
191191
Password: password,
192192
}),
193-
// ForceAttemptOAuth2 controls whether to follow OAuth2 with password grant.
194-
ForceAttemptOAuth2: true,
195193
// Cache caches credentials for accessing the remote registry.
196194
Cache: NewCache(),
197195
}

tools

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../bashed/tools

0 commit comments

Comments
 (0)