Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added a refresh token example #219

Merged
merged 2 commits into from
Jul 5, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 87 additions & 38 deletions registry/remote/auth/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,27 @@ import (
"net/http/httptest"
"net/url"
"os"
"reflect"
"strings"
"testing"

"oras.land/oras-go/v2/registry/remote/auth"
)

const (
username = "test_user"
password = "test_password"
accessToken = "test/access/token"
username = "test_user"
password = "test_password"
accessToken = "test/access/token"
refreshToken = "test/refresh/token"
)

var (
host string
expectedHostAddress string
targetURL string
clientConfigTargetURL string
basicAuthTargetURL string
accessTokenTargetURL string
clientConfigTargetURL string
refreshTokenTargetURL string
tokenScopes = []string{
"repository:dst:pull,push",
"repository:src:pull",
Expand All @@ -51,20 +52,29 @@ var (
func TestMain(m *testing.M) {
// create an authorization server
as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
if r.Method != http.MethodGet && r.Method != http.MethodPost {
w.WriteHeader(http.StatusUnauthorized)
panic("unexecuted attempt of authorization service")
}
header := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password))
if auth := r.Header.Get("Authorization"); auth != header {
if err := r.ParseForm(); err != nil {
w.WriteHeader(http.StatusUnauthorized)
panic("failed to parse form")
}
if got := r.PostForm.Get("service"); got != host {
w.WriteHeader(http.StatusUnauthorized)
}
// handles refresh token requests
if got := r.PostForm.Get("grant_type"); got != "refresh_token" {
w.WriteHeader(http.StatusUnauthorized)
}
if got := r.URL.Query().Get("service"); got != host {
scope := strings.Join(tokenScopes, " ")
if got := r.PostForm.Get("scope"); got != scope {
w.WriteHeader(http.StatusUnauthorized)
}
if got := r.URL.Query()["scope"]; !reflect.DeepEqual(got, tokenScopes) {
if got := r.PostForm.Get("refresh_token"); got != refreshToken {
w.WriteHeader(http.StatusUnauthorized)
}
// writes back access token
if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil {
panic(err)
}
Expand All @@ -86,18 +96,25 @@ func TestMain(m *testing.M) {
w.Header().Set("Www-Authenticate", `Basic realm="Test Server"`)
w.WriteHeader(http.StatusUnauthorized)
}
case "/clientConfig":
wantedAuthHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password))
authHeader := r.Header.Get("Authorization")
if authHeader != wantedAuthHeader {
w.Header().Set("Www-Authenticate", `Basic realm="Test Server"`)
w.WriteHeader(http.StatusUnauthorized)
}
case "/accessToken":
wantedAuthHeader := "Bearer " + accessToken
if auth := r.Header.Get("Authorization"); auth != wantedAuthHeader {
challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, host, strings.Join(tokenScopes, " "))
w.Header().Set("Www-Authenticate", challenge)
w.WriteHeader(http.StatusUnauthorized)
}
case "/clientConfig":
wantedAuthHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password))
authHeader := r.Header.Get("Authorization")
if authHeader != wantedAuthHeader {
w.Header().Set("Www-Authenticate", `Basic realm="Test Server"`)
case "/refreshToken":
wantedAuthHeader := "Bearer " + accessToken
if auth := r.Header.Get("Authorization"); auth != wantedAuthHeader {
challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, host, strings.Join(tokenScopes, " "))
w.Header().Set("Www-Authenticate", challenge)
w.WriteHeader(http.StatusUnauthorized)
}
case "/simple":
Expand All @@ -112,8 +129,9 @@ func TestMain(m *testing.M) {
expectedHostAddress = uri.Host
targetURL = fmt.Sprintf("%s/simple", host)
basicAuthTargetURL = fmt.Sprintf("%s/basicAuth", host)
accessTokenTargetURL = fmt.Sprintf("%s/accessToken", host)
clientConfigTargetURL = fmt.Sprintf("%s/clientConfig", host)
accessTokenTargetURL = fmt.Sprintf("%s/accessToken", host)
refreshTokenTargetURL = fmt.Sprintf("%s/refreshToken", host)
http.DefaultClient = ts.Client()

os.Exit(m.Run())
Expand Down Expand Up @@ -169,6 +187,54 @@ func ExampleClient_Do_basicAuth() {
// 200
}

// ExampleClient_Do_clientConfigurations shows the client configurations available,
// including using cache, setting user agent and configuring OAuth2.
func ExampleClient_Do_clientConfigurations() {
client := &auth.Client{
Credential: func(ctx context.Context, reg string) (auth.Credential, error) {
switch reg {
// expectedHostAddress is of form ipaddr:port
case expectedHostAddress:
return auth.Credential{
Username: username,
Password: password,
}, nil
default:
return auth.EmptyCredential, fmt.Errorf("credential not found for %v", reg)
}
},
// ForceAttemptOAuth2 controls whether to follow OAuth2 with password grant.
ForceAttemptOAuth2: true,
// Cache caches credentials for accessing the remote registry.
Cache: auth.NewCache(),
}
// SetUserAgent sets the user agent for all out-going requests.
client.SetUserAgent("example user agent")
// Tokens carry restrictions about what resources they can access and how.
// Such restrictions are represented and enforced as Scopes.
// Reference: https://docs.docker.com/registry/spec/auth/scope/
scopes := []string{
"repository:dst:pull,push",
"repository:src:pull",
}
// WithScopes returns a context with scopes added.
ctx := auth.WithScopes(context.Background(), scopes...)

// clientConfigTargetURL can be any URL. For example, https://registry.wabbit-networks.io/v2/
req, err := http.NewRequestWithContext(ctx, http.MethodGet, clientConfigTargetURL, nil)
if err != nil {
panic(err)
}
resp, err := client.Do(req)
if err != nil {
panic(err)
}

fmt.Println(resp.StatusCode)
// Output:
// 200
}

// ExampleClient_Do_withAccessToken gives an example of using client with an access token.
func ExampleClient_Do_withAccessToken() {
client := &auth.Client{
Expand Down Expand Up @@ -199,41 +265,24 @@ func ExampleClient_Do_withAccessToken() {
// 200
}

// ExampleClient_Do_clientConfiguration shows the client configurations available,
// including using cache, setting user agent and configuring OAuth2.
func ExampleClient_Do_clientConfiguration() {
// ExampleClient_Do_withRefreshToken gives an example of using client with a refresh token.
func ExampleClient_Do_withRefreshToken() {
wangxiaoxuan273 marked this conversation as resolved.
Show resolved Hide resolved
client := &auth.Client{
Credential: func(ctx context.Context, reg string) (auth.Credential, error) {
switch reg {
// expectedHostAddress is of form ipaddr:port
case expectedHostAddress:
return auth.Credential{
Username: username,
Password: password,
RefreshToken: refreshToken,
}, nil
default:
return auth.EmptyCredential, fmt.Errorf("credential not found for %v", reg)
}
},
// ForceAttemptOAuth2 controls whether to follow OAuth2 with password grant.
ForceAttemptOAuth2: true,
// Cache caches credentials for accessing the remote registry.
Cache: auth.NewCache(),
}
// SetUserAgent sets the user agent for all out-going requests.
client.SetUserAgent("example user agent")
// Tokens carry restrictions about what resources they can access and how.
// Such restrictions are represented and enforced as Scopes.
// Reference: https://docs.docker.com/registry/spec/auth/scope/
scopes := []string{
"repository:dst:pull,push",
"repository:src:pull",
}
// WithScopes returns a context with scopes added.
ctx := auth.WithScopes(context.Background(), scopes...)

// clientConfigTargetURL can be any URL. For example, https://registry.wabbit-networks.io/v2/
req, err := http.NewRequestWithContext(ctx, http.MethodGet, clientConfigTargetURL, nil)
// refreshTokenTargetURL can be any URL. For example, https://registry.wabbit-networks.io/v2/
req, err := http.NewRequest(http.MethodGet, refreshTokenTargetURL, nil)
if err != nil {
panic(err)
}
Expand Down