From fb7a2ea13b8328f40ed3e877d39813da02fbcb1c Mon Sep 17 00:00:00 2001 From: wangxiaoxuan273 Date: Thu, 30 Jun 2022 07:37:20 +0000 Subject: [PATCH 1/2] added a refresh token example Signed-off-by: wangxiaoxuan273 --- registry/remote/auth/example_test.go | 124 +++++++++++++++++++-------- 1 file changed, 86 insertions(+), 38 deletions(-) diff --git a/registry/remote/auth/example_test.go b/registry/remote/auth/example_test.go index 59070ac1..94850ab0 100644 --- a/registry/remote/auth/example_test.go +++ b/registry/remote/auth/example_test.go @@ -22,7 +22,6 @@ import ( "net/http/httptest" "net/url" "os" - "reflect" "strings" "testing" @@ -30,18 +29,20 @@ import ( ) const ( - username = "test_user" - password = "test_password" - accessToken = "test/access/token" + username = "test_user" + password = "test_password" + accessToken = "test/access/token" + refreshToken = "test/access/refreshToken" ) 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", @@ -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) } @@ -86,6 +96,13 @@ 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 { @@ -93,11 +110,11 @@ func TestMain(m *testing.M) { 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": @@ -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()) @@ -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{ @@ -199,41 +265,23 @@ 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() { +func ExampleClient_Do_withRefreshToken() { 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) } From 2996de69f0970b7987d7bb8e5230a7699f9e389d Mon Sep 17 00:00:00 2001 From: wangxiaoxuan273 Date: Tue, 5 Jul 2022 05:06:47 +0000 Subject: [PATCH 2/2] addressed the comments Signed-off-by: wangxiaoxuan273 --- registry/remote/auth/example_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/registry/remote/auth/example_test.go b/registry/remote/auth/example_test.go index 94850ab0..3a5d6f14 100644 --- a/registry/remote/auth/example_test.go +++ b/registry/remote/auth/example_test.go @@ -32,7 +32,7 @@ const ( username = "test_user" password = "test_password" accessToken = "test/access/token" - refreshToken = "test/access/refreshToken" + refreshToken = "test/refresh/token" ) var ( @@ -265,6 +265,7 @@ func ExampleClient_Do_withAccessToken() { // 200 } +// ExampleClient_Do_withRefreshToken gives an example of using client with a refresh token. func ExampleClient_Do_withRefreshToken() { client := &auth.Client{ Credential: func(ctx context.Context, reg string) (auth.Credential, error) {