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 }