...

Source file src/cloud.google.com/go/auth/credentials/downscope/downscope_test.go

Documentation: cloud.google.com/go/auth/credentials/downscope

     1  // Copyright 2023 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 downscope
    16  
    17  import (
    18  	"context"
    19  	"io"
    20  	"net/http"
    21  	"net/http/httptest"
    22  	"testing"
    23  
    24  	"cloud.google.com/go/auth"
    25  )
    26  
    27  var (
    28  	standardReqBody  = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&options=%7B%22accessBoundary%22%3A%7B%22accessBoundaryRules%22%3A%5B%7B%22availableResource%22%3A%22test1%22%2C%22availablePermissions%22%3A%5B%22Perm1%22%2C%22Perm2%22%5D%7D%5D%7D%7D&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&subject_token=token_base&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token"
    29  	standardRespBody = `{"access_token":"fake_token","expires_in":42,"token_type":"Bearer"}`
    30  )
    31  
    32  func staticCredentials(tok string) *auth.Credentials {
    33  	return auth.NewCredentials(&auth.CredentialsOptions{
    34  		TokenProvider: staticTokenProvider(tok),
    35  	})
    36  }
    37  
    38  type staticTokenProvider string
    39  
    40  func (s staticTokenProvider) Token(context.Context) (*auth.Token, error) {
    41  	return &auth.Token{Value: string(s)}, nil
    42  }
    43  
    44  func TestNewTokenProvider(t *testing.T) {
    45  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    46  		if r.Method != "POST" {
    47  			t.Errorf("Unexpected request method, %v is found", r.Method)
    48  		}
    49  		if r.URL.String() != "/" {
    50  			t.Errorf("Unexpected request URL, %v is found", r.URL)
    51  		}
    52  		body, err := io.ReadAll(r.Body)
    53  		if err != nil {
    54  			t.Fatalf("Failed to read request body: %v", err)
    55  		}
    56  		if got, want := string(body), standardReqBody; got != want {
    57  			t.Errorf("Unexpected exchange payload: got %v but want %v,", got, want)
    58  		}
    59  		w.Header().Set("Content-Type", "application/json")
    60  		w.Write([]byte(standardRespBody))
    61  
    62  	}))
    63  	defer ts.Close()
    64  	creds, err := NewCredentials(&Options{
    65  		Credentials: staticCredentials("token_base"),
    66  		Rules: []AccessBoundaryRule{
    67  			{
    68  				AvailableResource:    "test1",
    69  				AvailablePermissions: []string{"Perm1", "Perm2"},
    70  			},
    71  		},
    72  	})
    73  	if err != nil {
    74  		t.Fatalf("NewTokenProvider() = %v", err)
    75  	}
    76  	// Replace the default STS endpoint on the TokenProvider with the test server URL.
    77  	creds.TokenProvider.(*downscopedTokenProvider).identityBindingEndpoint = ts.URL
    78  
    79  	tok, err := creds.Token(context.Background())
    80  	if err != nil {
    81  		t.Fatalf("Token failed with error: %v", err)
    82  	}
    83  	if want := "fake_token"; tok.Value != want {
    84  		t.Fatalf("got %v, want %v", tok.Value, want)
    85  	}
    86  }
    87  
    88  func TestNewCredentials_Validations(t *testing.T) {
    89  	tests := []struct {
    90  		name string
    91  		opts *Options
    92  	}{
    93  		{
    94  			name: "no opts",
    95  			opts: nil,
    96  		},
    97  		{
    98  			name: "no provider",
    99  			opts: &Options{},
   100  		},
   101  		{
   102  			name: "no rules",
   103  			opts: &Options{
   104  				Credentials: staticCredentials("token_base"),
   105  			},
   106  		},
   107  		{
   108  			name: "too many rules",
   109  			opts: &Options{
   110  				Credentials: staticCredentials("token_base"),
   111  				Rules:       []AccessBoundaryRule{{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}},
   112  			},
   113  		},
   114  		{
   115  			name: "no resource",
   116  			opts: &Options{
   117  				Credentials: staticCredentials("token_base"),
   118  				Rules:       []AccessBoundaryRule{{}},
   119  			},
   120  		},
   121  		{
   122  			name: "no perm",
   123  			opts: &Options{
   124  				Credentials: staticCredentials("token_base"),
   125  				Rules: []AccessBoundaryRule{{
   126  					AvailableResource: "resource",
   127  				}},
   128  			},
   129  		},
   130  	}
   131  	for _, test := range tests {
   132  		t.Run(test.name, func(t *testing.T) {
   133  			if _, err := NewCredentials(test.opts); err == nil {
   134  				t.Fatal("want non-nil err")
   135  			}
   136  		})
   137  	}
   138  }
   139  
   140  func TestOptions_UniverseDomain(t *testing.T) {
   141  	tests := []struct {
   142  		universeDomain string
   143  		want           string
   144  	}{
   145  		{"", "https://sts.googleapis.com/v1/token"},
   146  		{"googleapis.com", "https://sts.googleapis.com/v1/token"},
   147  		{"example.com", "https://sts.example.com/v1/token"},
   148  	}
   149  	for _, tt := range tests {
   150  		c := Options{
   151  			UniverseDomain: tt.universeDomain,
   152  		}
   153  		if got := c.identityBindingEndpoint(); got != tt.want {
   154  			t.Errorf("got %q, want %q", got, tt.want)
   155  		}
   156  	}
   157  }
   158  

View as plain text