1// Copyright 2021 Google LLC. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package downscope 6 7import ( 8 "context" 9 "flag" 10 "fmt" 11 "io/ioutil" 12 "log" 13 "os" 14 "testing" 15 "time" 16 17 "google.golang.org/api/option" 18 19 "golang.org/x/oauth2" 20 "golang.org/x/oauth2/google" 21 "golang.org/x/oauth2/google/downscope" 22 storage "google.golang.org/api/storage/v1" 23 "google.golang.org/api/transport" 24) 25 26const ( 27 rootTokenScope = "https://www.googleapis.com/auth/cloud-platform" 28 envServiceAccountFile = "GCLOUD_TESTS_GOLANG_KEY" 29 object1 = "cab-first-c45wknuy.txt" 30 object2 = "cab-second-c45wknuy.txt" 31 bucket = "dulcet-port-762" 32) 33 34var ( 35 rootCredential *google.Credentials 36) 37 38// TestMain contains all of the setup code that needs to be run once before any of the tests are run 39func TestMain(m *testing.M) { 40 flag.Parse() 41 if testing.Short() { 42 // This line runs all of our individual tests 43 os.Exit(m.Run()) 44 } 45 ctx := context.Background() 46 credentialFileName := os.Getenv(envServiceAccountFile) 47 48 var err error 49 rootCredential, err = transport.Creds(ctx, option.WithCredentialsFile(credentialFileName), option.WithScopes(rootTokenScope)) 50 51 if err != nil { 52 log.Fatalf("failed to construct root credential: %v", err) 53 } 54 55 // This line runs all of our individual tests 56 os.Exit(m.Run()) 57 58} 59 60// downscopeTest holds the parameters necessary for running a test of the token downscoping capabilities implemented in `oauth2/google/downscope` 61type downscopeTest struct { 62 name string 63 availableResource string 64 availablePermissions []string 65 condition downscope.AvailabilityCondition 66 objectName string 67 rootSource oauth2.TokenSource 68 expectError bool 69} 70 71func TestDownscopedToken(t *testing.T) { 72 if testing.Short() { 73 t.Skip("skipping integration test") 74 } 75 76 var downscopeTests = []downscopeTest{ 77 { 78 name: "successfulDownscopedRead", 79 availableResource: "//storage.googleapis.com/projects/_/buckets/" + bucket, 80 availablePermissions: []string{"inRole:roles/storage.objectViewer"}, 81 condition: downscope.AvailabilityCondition{ 82 Expression: "resource.name.startsWith('projects/_/buckets/" + bucket + "/objects/" + object1 + "')", 83 }, 84 rootSource: rootCredential.TokenSource, 85 objectName: object1, 86 expectError: false, 87 }, 88 { 89 name: "readWithoutPermission", 90 availableResource: "//storage.googleapis.com/projects/_/buckets/" + bucket, 91 availablePermissions: []string{"inRole:roles/storage.objectViewer"}, 92 condition: downscope.AvailabilityCondition{ 93 Expression: "resource.name.startsWith('projects/_/buckets/" + bucket + "/objects/" + object1 + "')", 94 }, 95 rootSource: rootCredential.TokenSource, 96 objectName: object2, 97 expectError: true, 98 }, 99 } 100 101 for _, tt := range downscopeTests { 102 t.Run(tt.name, func(t *testing.T) { 103 err := downscopeQuery(t, tt) 104 // If a test isn't supposed to fail, it shouldn't fail. 105 if !tt.expectError && err != nil { 106 t.Errorf("test case %v should have succeeded, but instead returned %v", tt.name, err) 107 } else if tt.expectError && err == nil { // If a test is supposed to fail, it should return a non-nil error. 108 t.Errorf(" test case %v should have returned an error, but instead returned nil", tt.name) 109 } 110 }) 111 } 112} 113 114// I'm not sure what I should name this according to convention. 115func downscopeQuery(t *testing.T, tt downscopeTest) error { 116 t.Helper() 117 ctx := context.Background() 118 119 // Initializes an accessBoundary 120 var AccessBoundaryRules []downscope.AccessBoundaryRule 121 AccessBoundaryRules = append(AccessBoundaryRules, downscope.AccessBoundaryRule{AvailableResource: tt.availableResource, AvailablePermissions: tt.availablePermissions, Condition: &tt.condition}) 122 123 downscopedTokenSource, err := downscope.NewTokenSource(context.Background(), downscope.DownscopingConfig{RootSource: tt.rootSource, Rules: AccessBoundaryRules}) 124 if err != nil { 125 return fmt.Errorf("failed to create the initial token source: %v", err) 126 } 127 downscopedTokenSource = oauth2.ReuseTokenSource(nil, downscopedTokenSource) 128 129 ctx, cancel := context.WithTimeout(ctx, time.Second*30) 130 defer cancel() 131 storageService, err := storage.NewService(ctx, option.WithTokenSource(downscopedTokenSource)) 132 if err != nil { 133 return fmt.Errorf("failed to create the storage service: %v", err) 134 } 135 resp, err := storageService.Objects.Get(bucket, tt.objectName).Download() 136 if err != nil { 137 return fmt.Errorf("failed to retrieve object from GCP project with error: %v", err) 138 } 139 defer resp.Body.Close() 140 _, err = ioutil.ReadAll(resp.Body) 141 if err != nil { 142 return fmt.Errorf("ioutil.ReadAll: %v", err) 143 } 144 return nil 145} 146