...

Source file src/cloud.google.com/go/storage/conformance_test.go

Documentation: cloud.google.com/go/storage

     1  // Copyright 2019 Google LLC
     2  //
     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  package storage
    16  
    17  import (
    18  	"context"
    19  	"encoding/base64"
    20  	"encoding/json"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"net/url"
    24  	"os"
    25  	"strconv"
    26  	"strings"
    27  	"testing"
    28  	"time"
    29  
    30  	storage_v1_tests "cloud.google.com/go/storage/internal/test/conformance"
    31  	"github.com/google/go-cmp/cmp"
    32  	"google.golang.org/api/option"
    33  	"google.golang.org/protobuf/encoding/protojson"
    34  )
    35  
    36  func TestPostPolicyV4Conformance(t *testing.T) {
    37  	oldUTCNow := utcNow
    38  	defer func() {
    39  		utcNow = oldUTCNow
    40  	}()
    41  
    42  	googleAccessID, privateKey, testFiles := parseFiles(t)
    43  
    44  	for _, testFile := range testFiles {
    45  		for _, tc := range testFile.PostPolicyV4Tests {
    46  			t.Run(tc.Description, func(t *testing.T) {
    47  				pin := tc.PolicyInput
    48  				utcNow = func() time.Time {
    49  					return time.Unix(pin.GetTimestamp().GetSeconds(), 0).UTC()
    50  				}
    51  
    52  				var style URLStyle
    53  				switch pin.UrlStyle {
    54  				case storage_v1_tests.UrlStyle_PATH_STYLE:
    55  					style = PathStyle()
    56  				case storage_v1_tests.UrlStyle_VIRTUAL_HOSTED_STYLE:
    57  					style = VirtualHostedStyle()
    58  				case storage_v1_tests.UrlStyle_BUCKET_BOUND_HOSTNAME:
    59  					style = BucketBoundHostname(pin.BucketBoundHostname)
    60  				}
    61  
    62  				var conditions []PostPolicyV4Condition
    63  				// Build the various conditions.
    64  				pinConds := pin.Conditions
    65  				if pinConds != nil {
    66  					if clr := pinConds.ContentLengthRange; len(clr) > 0 {
    67  						conditions = append(conditions, ConditionContentLengthRange(uint64(clr[0]), uint64(clr[1])))
    68  					}
    69  					if sw := pinConds.StartsWith; len(sw) > 0 {
    70  						conditions = append(conditions, ConditionStartsWith(sw[0], sw[1]))
    71  					}
    72  				}
    73  
    74  				metadata := make(map[string]string, len(pin.Fields))
    75  				for key, value := range pin.Fields {
    76  					if strings.HasPrefix(key, "x-goog-meta") {
    77  						metadata[key] = value
    78  					}
    79  				}
    80  
    81  				got, err := GenerateSignedPostPolicyV4(pin.Bucket, pin.Object, &PostPolicyV4Options{
    82  					GoogleAccessID: googleAccessID,
    83  					PrivateKey:     []byte(privateKey),
    84  					Expires:        utcNow().Add(time.Duration(pin.Expiration) * time.Second),
    85  					Style:          style,
    86  					Insecure:       pin.Scheme == "http",
    87  					Conditions:     conditions,
    88  					Fields: &PolicyV4Fields{
    89  						ACL:                    pin.Fields["acl"],
    90  						CacheControl:           pin.Fields["cache-control"],
    91  						ContentEncoding:        pin.Fields["content-encoding"],
    92  						ContentDisposition:     pin.Fields["content-disposition"],
    93  						ContentType:            pin.Fields["content-type"],
    94  						Metadata:               metadata,
    95  						RedirectToURLOnSuccess: strings.TrimSpace(pin.Fields["success_action_redirect"]),
    96  						StatusCodeOnSuccess:    mustInt(t, pin.Fields["success_action_status"]),
    97  					},
    98  				})
    99  				if err != nil {
   100  					t.Fatal(err)
   101  				}
   102  				want := tc.PolicyOutput
   103  
   104  				switch wantURL, err := url.Parse(want.Url); {
   105  				case err != nil:
   106  					t.Errorf("Failed to parse want.Url: %v", err)
   107  
   108  				default:
   109  					// Sort the headers.
   110  					wantURL.RawQuery = wantURL.Query().Encode()
   111  					if diff := cmp.Diff(got.URL, wantURL.String()); diff != "" {
   112  						t.Errorf("URL mismatch: got - want +\n%s", diff)
   113  					}
   114  				}
   115  
   116  				gotPolicy := b64Decode(t, got.Fields["policy"], "got")
   117  				wantPolicy := want.ExpectedDecodedPolicy
   118  				if diff := cmp.Diff(gotPolicy, wantPolicy); diff != "" {
   119  					t.Fatalf("Policy mismatch: got - want +\n%s", diff)
   120  				}
   121  			})
   122  		}
   123  	}
   124  }
   125  
   126  func b64Decode(t *testing.T, b64Str, name string) string {
   127  	dec, err := base64.StdEncoding.DecodeString(b64Str)
   128  	if err != nil {
   129  		t.Fatalf("%q: Base64 decoding failed: %v", name, err)
   130  	}
   131  	return string(dec)
   132  }
   133  
   134  func mustInt(t *testing.T, s string) int {
   135  	if s == "" {
   136  		return 0
   137  	}
   138  	i, err := strconv.ParseInt(s, 10, 64)
   139  	if err != nil {
   140  		t.Fatal(err)
   141  	}
   142  	return int(i)
   143  }
   144  
   145  func TestSigningV4Conformance(t *testing.T) {
   146  	oldUTCNow := utcNow
   147  	defer func() {
   148  		utcNow = oldUTCNow
   149  	}()
   150  
   151  	googleAccessID, privateKey, testFiles := parseFiles(t)
   152  
   153  	for _, testFile := range testFiles {
   154  		for _, tc := range testFile.SigningV4Tests {
   155  			t.Run(tc.Description, func(t *testing.T) {
   156  				utcNow = func() time.Time {
   157  					return time.Unix(tc.Timestamp.Seconds, 0).UTC()
   158  				}
   159  
   160  				qp := url.Values{}
   161  				if tc.QueryParameters != nil {
   162  					for k, v := range tc.QueryParameters {
   163  						qp.Add(k, v)
   164  					}
   165  				}
   166  
   167  				var style URLStyle
   168  				switch tc.UrlStyle {
   169  				case storage_v1_tests.UrlStyle_PATH_STYLE:
   170  					style = PathStyle()
   171  				case storage_v1_tests.UrlStyle_VIRTUAL_HOSTED_STYLE:
   172  					style = VirtualHostedStyle()
   173  				case storage_v1_tests.UrlStyle_BUCKET_BOUND_HOSTNAME:
   174  					style = BucketBoundHostname(tc.BucketBoundHostname)
   175  				}
   176  
   177  				t.Setenv("STORAGE_EMULATOR_HOST", tc.EmulatorHostname)
   178  
   179  				opts := []option.ClientOption{option.WithoutAuthentication()}
   180  				if tc.ClientEndpoint != "" {
   181  					opts = append(opts, option.WithEndpoint(tc.ClientEndpoint))
   182  				}
   183  				if tc.UniverseDomain != "" {
   184  					opts = append(opts, option.WithUniverseDomain(tc.UniverseDomain))
   185  				}
   186  
   187  				c, err := NewClient(context.Background(), opts...)
   188  				if err != nil {
   189  					t.Fatalf("NewClient: %v", err)
   190  				}
   191  
   192  				gotURL, err := c.Bucket(tc.Bucket).SignedURL(tc.Object, &SignedURLOptions{
   193  					GoogleAccessID:  googleAccessID,
   194  					PrivateKey:      []byte(privateKey),
   195  					Method:          tc.Method,
   196  					Expires:         utcNow().Add(time.Duration(tc.Expiration) * time.Second),
   197  					Scheme:          SigningSchemeV4,
   198  					Headers:         headersAsSlice(tc.Headers),
   199  					QueryParameters: qp,
   200  					Style:           style,
   201  					Insecure:        tc.Scheme == "http",
   202  					Hostname:        tc.Hostname,
   203  				})
   204  				if err != nil {
   205  					t.Fatal(err)
   206  				}
   207  				wantURL, err := url.Parse(tc.ExpectedUrl)
   208  				if err != nil {
   209  					t.Fatal(err)
   210  				}
   211  				// Sort the headers.
   212  				wantURL.RawQuery = wantURL.Query().Encode()
   213  
   214  				if gotURL != wantURL.String() {
   215  					t.Fatalf("\nwant:\t%s\ngot:\t%s", wantURL.String(), gotURL)
   216  				}
   217  			})
   218  		}
   219  	}
   220  }
   221  
   222  func headersAsSlice(m map[string]string) []string {
   223  	var s []string
   224  	for k, v := range m {
   225  		s = append(s, fmt.Sprintf("%s:%s", k, v))
   226  	}
   227  	return s
   228  }
   229  
   230  func parseFiles(t *testing.T) (googleAccessID, privateKey string, testFiles []*storage_v1_tests.TestFile) {
   231  	dir := "internal/test/conformance"
   232  
   233  	inBytes, err := ioutil.ReadFile(dir + "/service-account")
   234  	if err != nil {
   235  		t.Fatal(err)
   236  	}
   237  	serviceAccount := map[string]string{}
   238  	if err := json.Unmarshal(inBytes, &serviceAccount); err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	googleAccessID = serviceAccount["client_email"]
   242  	privateKey = serviceAccount["private_key"]
   243  
   244  	files, err := os.ReadDir(dir)
   245  	if err != nil {
   246  		t.Fatal(err)
   247  	}
   248  
   249  	for _, f := range files {
   250  		if !strings.HasSuffix(f.Name(), ".json") {
   251  			continue
   252  		}
   253  
   254  		inBytes, err := os.ReadFile(dir + "/" + f.Name())
   255  		if err != nil {
   256  			t.Fatalf("%s: %v", f.Name(), err)
   257  		}
   258  
   259  		testFile := new(storage_v1_tests.TestFile)
   260  		if err := protojson.Unmarshal(inBytes, testFile); err != nil {
   261  			t.Fatalf("unmarshalling %s: %v", f.Name(), err)
   262  		}
   263  		testFiles = append(testFiles, testFile)
   264  	}
   265  	return
   266  }
   267  

View as plain text