github.com/opcr-io/oras-go/v2@v2.0.0-20231122155130-eb4260d8a0ae/registry/remote/auth/example_test.go (about)

     1  /*
     2  Copyright The ORAS Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  http://www.apache.org/licenses/LICENSE-2.0
     7  Unless required by applicable law or agreed to in writing, software
     8  distributed under the License is distributed on an "AS IS" BASIS,
     9  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    10  See the License for the specific language governing permissions and
    11  limitations under the License.
    12  */
    13  
    14  // Package auth_test includes the testable examples for the http client.
    15  package auth_test
    16  
    17  import (
    18  	"context"
    19  	"encoding/base64"
    20  	"fmt"
    21  	"net/http"
    22  	"net/http/httptest"
    23  	"net/url"
    24  	"os"
    25  	"strings"
    26  	"testing"
    27  
    28  	. "github.com/opcr-io/oras-go/v2/registry/internal/doc"
    29  	"github.com/opcr-io/oras-go/v2/registry/remote/auth"
    30  )
    31  
    32  const (
    33  	username     = "test_user"
    34  	password     = "test_password"
    35  	accessToken  = "test/access/token"
    36  	refreshToken = "test/refresh/token"
    37  	_            = ExampleUnplayable
    38  )
    39  
    40  var (
    41  	host                  string
    42  	expectedHostAddress   string
    43  	targetURL             string
    44  	clientConfigTargetURL string
    45  	basicAuthTargetURL    string
    46  	accessTokenTargetURL  string
    47  	refreshTokenTargetURL string
    48  	tokenScopes           = []string{
    49  		"repository:dst:pull,push",
    50  		"repository:src:pull",
    51  	}
    52  )
    53  
    54  func TestMain(m *testing.M) {
    55  	// create an authorization server
    56  	as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    57  		if r.Method != http.MethodGet && r.Method != http.MethodPost {
    58  			w.WriteHeader(http.StatusUnauthorized)
    59  			panic("unexecuted attempt of authorization service")
    60  		}
    61  		if err := r.ParseForm(); err != nil {
    62  			w.WriteHeader(http.StatusUnauthorized)
    63  			panic("failed to parse form")
    64  		}
    65  		if got := r.PostForm.Get("service"); got != host {
    66  			w.WriteHeader(http.StatusUnauthorized)
    67  		}
    68  		// handles refresh token requests
    69  		if got := r.PostForm.Get("grant_type"); got != "refresh_token" {
    70  			w.WriteHeader(http.StatusUnauthorized)
    71  		}
    72  		scope := strings.Join(tokenScopes, " ")
    73  		if got := r.PostForm.Get("scope"); got != scope {
    74  			w.WriteHeader(http.StatusUnauthorized)
    75  		}
    76  		if got := r.PostForm.Get("refresh_token"); got != refreshToken {
    77  			w.WriteHeader(http.StatusUnauthorized)
    78  		}
    79  		// writes back access token
    80  		if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil {
    81  			panic(err)
    82  		}
    83  	}))
    84  	defer as.Close()
    85  
    86  	// create a test server
    87  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    88  		path := r.URL.Path
    89  		if r.Method != http.MethodGet {
    90  			w.WriteHeader(http.StatusNotFound)
    91  			panic("unexpected access")
    92  		}
    93  		switch path {
    94  		case "/basicAuth":
    95  			wantedAuthHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password))
    96  			authHeader := r.Header.Get("Authorization")
    97  			if authHeader != wantedAuthHeader {
    98  				w.Header().Set("Www-Authenticate", `Basic realm="Test Server"`)
    99  				w.WriteHeader(http.StatusUnauthorized)
   100  			}
   101  		case "/clientConfig":
   102  			wantedAuthHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password))
   103  			authHeader := r.Header.Get("Authorization")
   104  			if authHeader != wantedAuthHeader {
   105  				w.Header().Set("Www-Authenticate", `Basic realm="Test Server"`)
   106  				w.WriteHeader(http.StatusUnauthorized)
   107  			}
   108  		case "/accessToken":
   109  			wantedAuthHeader := "Bearer " + accessToken
   110  			if auth := r.Header.Get("Authorization"); auth != wantedAuthHeader {
   111  				challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, host, strings.Join(tokenScopes, " "))
   112  				w.Header().Set("Www-Authenticate", challenge)
   113  				w.WriteHeader(http.StatusUnauthorized)
   114  			}
   115  		case "/refreshToken":
   116  			wantedAuthHeader := "Bearer " + accessToken
   117  			if auth := r.Header.Get("Authorization"); auth != wantedAuthHeader {
   118  				challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, host, strings.Join(tokenScopes, " "))
   119  				w.Header().Set("Www-Authenticate", challenge)
   120  				w.WriteHeader(http.StatusUnauthorized)
   121  			}
   122  		case "/simple":
   123  			w.WriteHeader(http.StatusOK)
   124  		default:
   125  			w.WriteHeader(http.StatusNotAcceptable)
   126  		}
   127  	}))
   128  	defer ts.Close()
   129  	host = ts.URL
   130  	uri, _ := url.Parse(host)
   131  	expectedHostAddress = uri.Host
   132  	targetURL = fmt.Sprintf("%s/simple", host)
   133  	basicAuthTargetURL = fmt.Sprintf("%s/basicAuth", host)
   134  	clientConfigTargetURL = fmt.Sprintf("%s/clientConfig", host)
   135  	accessTokenTargetURL = fmt.Sprintf("%s/accessToken", host)
   136  	refreshTokenTargetURL = fmt.Sprintf("%s/refreshToken", host)
   137  	http.DefaultClient = ts.Client()
   138  
   139  	os.Exit(m.Run())
   140  }
   141  
   142  // ExampleClient_Do_minimalClient gives an example of a minimal working client.
   143  func ExampleClient_Do_minimalClient() {
   144  	var client auth.Client
   145  	// targetURL can be any URL. For example, https://registry.wabbit-networks.io/v2/
   146  	req, err := http.NewRequest(http.MethodGet, targetURL, nil)
   147  	if err != nil {
   148  		panic(err)
   149  	}
   150  	resp, err := client.Do(req)
   151  	if err != nil {
   152  		panic(err)
   153  	}
   154  
   155  	fmt.Println(resp.StatusCode)
   156  	// Output:
   157  	// 200
   158  }
   159  
   160  // ExampleClient_Do_basicAuth gives an example of using client with credentials.
   161  func ExampleClient_Do_basicAuth() {
   162  	client := &auth.Client{
   163  		// expectedHostAddress is of form ipaddr:port
   164  		Credential: auth.StaticCredential(expectedHostAddress, auth.Credential{
   165  			Username: username,
   166  			Password: password,
   167  		}),
   168  	}
   169  	// basicAuthTargetURL can be any URL. For example, https://registry.wabbit-networks.io/v2/
   170  	req, err := http.NewRequest(http.MethodGet, basicAuthTargetURL, nil)
   171  	if err != nil {
   172  		panic(err)
   173  	}
   174  	resp, err := client.Do(req)
   175  	if err != nil {
   176  		panic(err)
   177  	}
   178  
   179  	fmt.Println(resp.StatusCode)
   180  	// Output:
   181  	// 200
   182  }
   183  
   184  // ExampleClient_Do_clientConfigurations shows the client configurations available,
   185  // including using cache, setting user agent and configuring OAuth2.
   186  func ExampleClient_Do_clientConfigurations() {
   187  	client := &auth.Client{
   188  		// expectedHostAddress is of form ipaddr:port
   189  		Credential: auth.StaticCredential(expectedHostAddress, auth.Credential{
   190  			Username: username,
   191  			Password: password,
   192  		}),
   193  		// ForceAttemptOAuth2 controls whether to follow OAuth2 with password grant.
   194  		ForceAttemptOAuth2: true,
   195  		// Cache caches credentials for accessing the remote registry.
   196  		Cache: auth.NewCache(),
   197  	}
   198  	// SetUserAgent sets the user agent for all out-going requests.
   199  	client.SetUserAgent("example user agent")
   200  	// Tokens carry restrictions about what resources they can access and how.
   201  	// Such restrictions are represented and enforced as Scopes.
   202  	// Reference: https://docs.docker.com/registry/spec/auth/scope/
   203  	scopes := []string{
   204  		"repository:dst:pull,push",
   205  		"repository:src:pull",
   206  	}
   207  	// WithScopes returns a context with scopes added.
   208  	ctx := auth.WithScopes(context.Background(), scopes...)
   209  
   210  	// clientConfigTargetURL can be any URL. For example, https://registry.wabbit-networks.io/v2/
   211  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, clientConfigTargetURL, nil)
   212  	if err != nil {
   213  		panic(err)
   214  	}
   215  	resp, err := client.Do(req)
   216  	if err != nil {
   217  		panic(err)
   218  	}
   219  
   220  	fmt.Println(resp.StatusCode)
   221  	// Output:
   222  	// 200
   223  }
   224  
   225  // ExampleClient_Do_withAccessToken gives an example of using client with an access token.
   226  func ExampleClient_Do_withAccessToken() {
   227  	client := &auth.Client{
   228  		// expectedHostAddress is of form ipaddr:port
   229  		Credential: auth.StaticCredential(expectedHostAddress, auth.Credential{
   230  			AccessToken: accessToken,
   231  		}),
   232  	}
   233  	// accessTokenTargetURL can be any URL. For example, https://registry.wabbit-networks.io/v2/
   234  	req, err := http.NewRequest(http.MethodGet, accessTokenTargetURL, nil)
   235  	if err != nil {
   236  		panic(err)
   237  	}
   238  	resp, err := client.Do(req)
   239  	if err != nil {
   240  		panic(err)
   241  	}
   242  
   243  	fmt.Println(resp.StatusCode)
   244  	// Output:
   245  	// 200
   246  }
   247  
   248  // ExampleClient_Do_withRefreshToken gives an example of using client with a refresh token.
   249  func ExampleClient_Do_withRefreshToken() {
   250  	client := &auth.Client{
   251  		// expectedHostAddress is of form ipaddr:port
   252  		Credential: auth.StaticCredential(expectedHostAddress, auth.Credential{
   253  			RefreshToken: refreshToken,
   254  		}),
   255  	}
   256  
   257  	// refreshTokenTargetURL can be any URL. For example, https://registry.wabbit-networks.io/v2/
   258  	req, err := http.NewRequest(http.MethodGet, refreshTokenTargetURL, nil)
   259  	if err != nil {
   260  		panic(err)
   261  	}
   262  	resp, err := client.Do(req)
   263  	if err != nil {
   264  		panic(err)
   265  	}
   266  
   267  	fmt.Println(resp.StatusCode)
   268  	// Output:
   269  	// 200
   270  }