oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/registry/remote/credentials/native_store_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 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package credentials 17 18 import ( 19 "bytes" 20 "context" 21 "encoding/json" 22 "fmt" 23 "io" 24 "strings" 25 "testing" 26 27 "oras.land/oras-go/v2/registry/remote/auth" 28 "oras.land/oras-go/v2/registry/remote/credentials/trace" 29 ) 30 31 const ( 32 basicAuthHost = "localhost:2333" 33 bearerAuthHost = "localhost:666" 34 exeErrorHost = "localhost:500/exeError" 35 jsonErrorHost = "localhost:500/jsonError" 36 noCredentialsHost = "localhost:404" 37 traceHost = "localhost:808" 38 testUsername = "test_username" 39 testPassword = "test_password" 40 testRefreshToken = "test_token" 41 ) 42 43 var ( 44 errCommandExited = fmt.Errorf("exited with error") 45 errExecute = fmt.Errorf("Execute failed") 46 errCredentialsNotFound = fmt.Errorf(errCredentialsNotFoundMessage) 47 ) 48 49 // testExecuter implements the Executer interface for testing purpose. 50 // It simulates interactions between the docker client and a remote 51 // credentials helper. 52 type testExecuter struct{} 53 54 // Execute mocks the behavior of a credential helper binary. It returns responses 55 // and errors based on the input. 56 func (e *testExecuter) Execute(ctx context.Context, input io.Reader, action string) ([]byte, error) { 57 in, err := io.ReadAll(input) 58 if err != nil { 59 return nil, err 60 } 61 inS := string(in) 62 switch action { 63 case "get": 64 switch inS { 65 case basicAuthHost: 66 return []byte(`{"Username": "test_username", "Secret": "test_password"}`), nil 67 case bearerAuthHost: 68 return []byte(`{"Username": "<token>", "Secret": "test_token"}`), nil 69 case exeErrorHost: 70 return []byte("Execute failed"), errExecute 71 case jsonErrorHost: 72 return []byte("json.Unmarshal failed"), nil 73 case noCredentialsHost: 74 return []byte("credentials not found"), errCredentialsNotFound 75 case traceHost: 76 traceHook := trace.ContextExecutableTrace(ctx) 77 if traceHook != nil { 78 if traceHook.ExecuteStart != nil { 79 traceHook.ExecuteStart("testExecuter", "get") 80 } 81 if traceHook.ExecuteDone != nil { 82 traceHook.ExecuteDone("testExecuter", "get", nil) 83 } 84 } 85 return []byte(`{"Username": "test_username", "Secret": "test_password"}`), nil 86 default: 87 return []byte("program failed"), errCommandExited 88 } 89 case "store": 90 var c dockerCredentials 91 err := json.NewDecoder(strings.NewReader(inS)).Decode(&c) 92 if err != nil { 93 return []byte("program failed"), errCommandExited 94 } 95 switch c.ServerURL { 96 case basicAuthHost, bearerAuthHost, exeErrorHost: 97 return nil, nil 98 case traceHost: 99 traceHook := trace.ContextExecutableTrace(ctx) 100 if traceHook != nil { 101 if traceHook.ExecuteStart != nil { 102 traceHook.ExecuteStart("testExecuter", "store") 103 } 104 if traceHook.ExecuteDone != nil { 105 traceHook.ExecuteDone("testExecuter", "store", nil) 106 } 107 } 108 return nil, nil 109 default: 110 return []byte("program failed"), errCommandExited 111 } 112 case "erase": 113 switch inS { 114 case basicAuthHost, bearerAuthHost: 115 return nil, nil 116 case traceHost: 117 traceHook := trace.ContextExecutableTrace(ctx) 118 if traceHook != nil { 119 if traceHook.ExecuteStart != nil { 120 traceHook.ExecuteStart("testExecuter", "erase") 121 } 122 if traceHook.ExecuteDone != nil { 123 traceHook.ExecuteDone("testExecuter", "erase", nil) 124 } 125 } 126 return nil, nil 127 default: 128 return []byte("program failed"), errCommandExited 129 } 130 } 131 return []byte(fmt.Sprintf("unknown argument %q with %q", action, inS)), errCommandExited 132 } 133 134 func TestNativeStore_interface(t *testing.T) { 135 var ns interface{} = &nativeStore{} 136 if _, ok := ns.(Store); !ok { 137 t.Error("&NativeStore{} does not conform Store") 138 } 139 } 140 141 func TestNativeStore_basicAuth(t *testing.T) { 142 ns := &nativeStore{ 143 &testExecuter{}, 144 } 145 // Put 146 err := ns.Put(context.Background(), basicAuthHost, auth.Credential{Username: testUsername, Password: testPassword}) 147 if err != nil { 148 t.Fatalf("basic auth test ns.Put fails: %v", err) 149 } 150 // Get 151 cred, err := ns.Get(context.Background(), basicAuthHost) 152 if err != nil { 153 t.Fatalf("basic auth test ns.Get fails: %v", err) 154 } 155 if cred.Username != testUsername { 156 t.Fatal("incorrect username") 157 } 158 if cred.Password != testPassword { 159 t.Fatal("incorrect password") 160 } 161 // Delete 162 err = ns.Delete(context.Background(), basicAuthHost) 163 if err != nil { 164 t.Fatalf("basic auth test ns.Delete fails: %v", err) 165 } 166 } 167 168 func TestNativeStore_refreshToken(t *testing.T) { 169 ns := &nativeStore{ 170 &testExecuter{}, 171 } 172 // Put 173 err := ns.Put(context.Background(), bearerAuthHost, auth.Credential{RefreshToken: testRefreshToken}) 174 if err != nil { 175 t.Fatalf("refresh token test ns.Put fails: %v", err) 176 } 177 // Get 178 cred, err := ns.Get(context.Background(), bearerAuthHost) 179 if err != nil { 180 t.Fatalf("refresh token test ns.Get fails: %v", err) 181 } 182 if cred.Username != "" { 183 t.Fatalf("expect username to be empty, got %s", cred.Username) 184 } 185 if cred.RefreshToken != testRefreshToken { 186 t.Fatal("incorrect refresh token") 187 } 188 // Delete 189 err = ns.Delete(context.Background(), basicAuthHost) 190 if err != nil { 191 t.Fatalf("refresh token test ns.Delete fails: %v", err) 192 } 193 } 194 195 func TestNativeStore_errorHandling(t *testing.T) { 196 ns := &nativeStore{ 197 &testExecuter{}, 198 } 199 // Get Error: Execute error 200 _, err := ns.Get(context.Background(), exeErrorHost) 201 if err != errExecute { 202 t.Fatalf("got error: %v, should get exeErr", err) 203 } 204 // Get Error: json.Unmarshal 205 _, err = ns.Get(context.Background(), jsonErrorHost) 206 if err == nil { 207 t.Fatalf("should get error from json.Unmarshal") 208 } 209 // Get: Should not return error when credentials are not found 210 _, err = ns.Get(context.Background(), noCredentialsHost) 211 if err != nil { 212 t.Fatalf("should not get error when no credentials are found") 213 } 214 } 215 216 func TestNewDefaultNativeStore(t *testing.T) { 217 defaultHelper := getDefaultHelperSuffix() 218 wantOK := (defaultHelper != "") 219 220 if _, ok := NewDefaultNativeStore(); ok != wantOK { 221 t.Errorf("NewDefaultNativeStore() = %v, want %v", ok, wantOK) 222 } 223 } 224 225 func TestNativeStore_trace(t *testing.T) { 226 ns := &nativeStore{ 227 &testExecuter{}, 228 } 229 // create trace hooks that write to buffer 230 buffer := bytes.Buffer{} 231 traceHook := &trace.ExecutableTrace{ 232 ExecuteStart: func(executableName string, action string) { 233 buffer.WriteString(fmt.Sprintf("test trace, start the execution of executable %s with action %s ", executableName, action)) 234 }, 235 ExecuteDone: func(executableName string, action string, err error) { 236 buffer.WriteString(fmt.Sprintf("test trace, completed the execution of executable %s with action %s and got err %v", executableName, action, err)) 237 }, 238 } 239 ctx := trace.WithExecutableTrace(context.Background(), traceHook) 240 // Test ns.Put trace 241 err := ns.Put(ctx, traceHost, auth.Credential{Username: testUsername, Password: testPassword}) 242 if err != nil { 243 t.Fatalf("trace test ns.Put fails: %v", err) 244 } 245 bufferContent := buffer.String() 246 if bufferContent != "test trace, start the execution of executable testExecuter with action store test trace, completed the execution of executable testExecuter with action store and got err <nil>" { 247 t.Fatalf("incorrect buffer content: %s", bufferContent) 248 } 249 buffer.Reset() 250 // Test ns.Get trace 251 _, err = ns.Get(ctx, traceHost) 252 if err != nil { 253 t.Fatalf("trace test ns.Get fails: %v", err) 254 } 255 bufferContent = buffer.String() 256 if bufferContent != "test trace, start the execution of executable testExecuter with action get test trace, completed the execution of executable testExecuter with action get and got err <nil>" { 257 t.Fatalf("incorrect buffer content: %s", bufferContent) 258 } 259 buffer.Reset() 260 // Test ns.Delete trace 261 err = ns.Delete(ctx, traceHost) 262 if err != nil { 263 t.Fatalf("trace test ns.Delete fails: %v", err) 264 } 265 bufferContent = buffer.String() 266 if bufferContent != "test trace, start the execution of executable testExecuter with action erase test trace, completed the execution of executable testExecuter with action erase and got err <nil>" { 267 t.Fatalf("incorrect buffer content: %s", bufferContent) 268 } 269 } 270 271 // This test ensures that a nil trace will not cause an error. 272 func TestNativeStore_noTrace(t *testing.T) { 273 ns := &nativeStore{ 274 &testExecuter{}, 275 } 276 // Put 277 err := ns.Put(context.Background(), traceHost, auth.Credential{Username: testUsername, Password: testPassword}) 278 if err != nil { 279 t.Fatalf("basic auth test ns.Put fails: %v", err) 280 } 281 // Get 282 cred, err := ns.Get(context.Background(), traceHost) 283 if err != nil { 284 t.Fatalf("basic auth test ns.Get fails: %v", err) 285 } 286 if cred.Username != testUsername { 287 t.Fatal("incorrect username") 288 } 289 if cred.Password != testPassword { 290 t.Fatal("incorrect password") 291 } 292 // Delete 293 err = ns.Delete(context.Background(), traceHost) 294 if err != nil { 295 t.Fatalf("basic auth test ns.Delete fails: %v", err) 296 } 297 } 298 299 // This test ensures that an empty trace will not cause an error. 300 func TestNativeStore_emptyTrace(t *testing.T) { 301 ns := &nativeStore{ 302 &testExecuter{}, 303 } 304 traceHook := &trace.ExecutableTrace{} 305 ctx := trace.WithExecutableTrace(context.Background(), traceHook) 306 // Put 307 err := ns.Put(ctx, traceHost, auth.Credential{Username: testUsername, Password: testPassword}) 308 if err != nil { 309 t.Fatalf("basic auth test ns.Put fails: %v", err) 310 } 311 // Get 312 cred, err := ns.Get(ctx, traceHost) 313 if err != nil { 314 t.Fatalf("basic auth test ns.Get fails: %v", err) 315 } 316 if cred.Username != testUsername { 317 t.Fatal("incorrect username") 318 } 319 if cred.Password != testPassword { 320 t.Fatal("incorrect password") 321 } 322 // Delete 323 err = ns.Delete(ctx, traceHost) 324 if err != nil { 325 t.Fatalf("basic auth test ns.Delete fails: %v", err) 326 } 327 } 328 329 func TestNativeStore_multipleTrace(t *testing.T) { 330 ns := &nativeStore{ 331 &testExecuter{}, 332 } 333 // create trace hooks that write to buffer 334 buffer := bytes.Buffer{} 335 trace1 := &trace.ExecutableTrace{ 336 ExecuteStart: func(executableName string, action string) { 337 buffer.WriteString(fmt.Sprintf("trace 1 start %s, %s ", executableName, action)) 338 }, 339 ExecuteDone: func(executableName string, action string, err error) { 340 buffer.WriteString(fmt.Sprintf("trace 1 done %s, %s, %v ", executableName, action, err)) 341 }, 342 } 343 ctx := context.Background() 344 ctx = trace.WithExecutableTrace(ctx, trace1) 345 trace2 := &trace.ExecutableTrace{ 346 ExecuteStart: func(executableName string, action string) { 347 buffer.WriteString(fmt.Sprintf("trace 2 start %s, %s ", executableName, action)) 348 }, 349 ExecuteDone: func(executableName string, action string, err error) { 350 buffer.WriteString(fmt.Sprintf("trace 2 done %s, %s, %v ", executableName, action, err)) 351 }, 352 } 353 ctx = trace.WithExecutableTrace(ctx, trace2) 354 trace3 := &trace.ExecutableTrace{} 355 ctx = trace.WithExecutableTrace(ctx, trace3) 356 // Test ns.Put trace 357 err := ns.Put(ctx, traceHost, auth.Credential{Username: testUsername, Password: testPassword}) 358 if err != nil { 359 t.Fatalf("trace test ns.Put fails: %v", err) 360 } 361 bufferContent := buffer.String() 362 if bufferContent != "trace 2 start testExecuter, store trace 1 start testExecuter, store trace 2 done testExecuter, store, <nil> trace 1 done testExecuter, store, <nil> " { 363 t.Fatalf("incorrect buffer content: %s", bufferContent) 364 } 365 buffer.Reset() 366 // Test ns.Get trace 367 _, err = ns.Get(ctx, traceHost) 368 if err != nil { 369 t.Fatalf("trace test ns.Get fails: %v", err) 370 } 371 bufferContent = buffer.String() 372 if bufferContent != "trace 2 start testExecuter, get trace 1 start testExecuter, get trace 2 done testExecuter, get, <nil> trace 1 done testExecuter, get, <nil> " { 373 t.Fatalf("incorrect buffer content: %s", bufferContent) 374 } 375 buffer.Reset() 376 // Test ns.Delete trace 377 err = ns.Delete(ctx, traceHost) 378 if err != nil { 379 t.Fatalf("trace test ns.Delete fails: %v", err) 380 } 381 bufferContent = buffer.String() 382 if bufferContent != "trace 2 start testExecuter, erase trace 1 start testExecuter, erase trace 2 done testExecuter, erase, <nil> trace 1 done testExecuter, erase, <nil> " { 383 t.Fatalf("incorrect buffer content: %s", bufferContent) 384 } 385 }