@@ -3,20 +3,13 @@ package login
33import (
44 "context"
55 "encoding/json"
6- "io"
7- "net/http"
8- "net/http/httptest"
9- "net/http/httputil"
10- "net/url"
11- "strings"
126 "testing"
137 "time"
148
159 "github.com/golang-jwt/jwt/v5"
1610 "github.com/google/go-cmp/cmp"
1711 "github.com/google/go-cmp/cmp/cmpopts"
1812 "github.com/google/uuid"
19- "github.com/stackitcloud/stackit-cli/internal/pkg/cache"
2013 "github.com/stackitcloud/stackit-cli/internal/pkg/utils"
2114 "github.com/stackitcloud/stackit-sdk-go/services/ske"
2215 v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -31,9 +24,6 @@ var testClient = &ske.APIClient{}
3124var testProjectId = uuid .NewString ()
3225var testClusterName = "cluster"
3326var testOrganization = uuid .NewString ()
34- var testAccessToken = "access-token-test-" + uuid .NewString ()
35- var testExchangedToken = "access-token-exchanged-" + uuid .NewString ()
36- var testTokenEndpoint = "https://accounts.stackit.cloud/test/endpoint" //nolint:gosec // Actually just a URL
3727
3828const testRegion = "eu01"
3929
@@ -60,40 +50,6 @@ func fixtureLoginRequest(mods ...func(request *ske.ApiCreateKubeconfigRequest))
6050 return request
6151}
6252
63- func fixtureTokenExchangeRequest (tokenEndpoint string ) * http.Request {
64- form := url.Values {}
65- form .Set ("grant_type" , "urn:ietf:params:oauth:grant-type:token-exchange" )
66- form .Set ("client_id" , "stackit-cli-0000-0000-000000000001" )
67- form .Set ("subject_token_type" , "urn:ietf:params:oauth:token-type:access_token" )
68- form .Set ("requested_token_type" , "urn:ietf:params:oauth:token-type:id_token" )
69- form .Set ("scope" , "openid profile email groups" )
70- form .Set ("subject_token" , testAccessToken )
71- form .Set ("resource" , "resource://organizations/" + testOrganization + "/projects/" + testProjectId + "/regions/" + testRegion + "/ske/" + testClusterName )
72-
73- req , _ := http .NewRequestWithContext (
74- testCtx ,
75- http .MethodPost ,
76- tokenEndpoint ,
77- strings .NewReader (form .Encode ()),
78- )
79- req .Header .Add ("Content-Type" , "application/x-www-form-urlencoded" )
80- return req
81- }
82-
83- func fixtureTokenExchangeResponse () string {
84- type exchangeReponse struct {
85- AccessToken string `json:"access_token"`
86- IssuedTokeType string `json:"issued_token_type"`
87- TokenType string `json:"token_type"`
88- }
89- response , _ := json .Marshal (exchangeReponse {
90- AccessToken : testExchangedToken ,
91- IssuedTokeType : "urn:ietf:params:oauth:token-type:id_token" ,
92- TokenType : "Bearer" ,
93- })
94- return string (response )
95- }
96-
9753func TestBuildRequest (t * testing.T ) {
9854 tests := []struct {
9955 description string
@@ -191,135 +147,6 @@ zbRjZmli7cnenEnfnNoFIGbgkbjGXRUCIC5zFtWXFK7kA+B2vDxD0DlLcQodNwi4
191147 }
192148}
193149
194- func TestBuildTokenExchangeRequest (t * testing.T ) {
195- cfg := fixtureClusterConfig ()
196- expectedRequest := fixtureTokenExchangeRequest (testTokenEndpoint )
197- req , err := buildRequestToExchangeTokens (testCtx , testTokenEndpoint , testAccessToken , cfg )
198- if err != nil {
199- t .Fatalf ("func returned error: %s" , err )
200- }
201- // directly using cmp.Diff is not possible, so dump the requests first
202- expected , err := httputil .DumpRequest (expectedRequest , true )
203- if err != nil {
204- t .Fatalf ("fail to dump expected: %s" , err )
205- }
206- actual , err := httputil .DumpRequest (req , true )
207- if err != nil {
208- t .Fatalf ("fail to dump actual: %s" , err )
209- }
210- diff := cmp .Diff (actual , expected )
211- if diff != "" {
212- t .Fatalf ("Data does not match: %s" , diff )
213- }
214- }
215-
216- func TestParseTokenExchangeResponse (t * testing.T ) {
217- response := fixtureTokenExchangeResponse ()
218-
219- tests := []struct {
220- description string
221- response string
222- status int
223- expectError bool
224- }{
225- {
226- description : "valid response" ,
227- response : response ,
228- status : http .StatusOK ,
229- },
230- {
231- description : "error status" ,
232- response : response , // valid response to make sure the status code is checked
233- status : http .StatusForbidden ,
234- expectError : true ,
235- },
236- {
237- description : "error content" ,
238- response : "{}" ,
239- status : http .StatusOK ,
240- expectError : true ,
241- },
242- }
243-
244- for _ , tt := range tests {
245- t .Run (tt .description , func (t * testing.T ) {
246- w := httptest .NewRecorder ()
247- w .WriteHeader (tt .status )
248- _ , _ = w .WriteString (tt .response )
249- resp := w .Result ()
250-
251- defer func () {
252- tempErr := resp .Body .Close ()
253- if tempErr != nil {
254- t .Fatalf ("failed to close response body: %v" , tempErr )
255- }
256- }()
257- accessToken , err := parseTokenExchangeResponse (resp )
258- if tt .expectError {
259- if err == nil {
260- t .Fatal ("expected error got nil" )
261- }
262- } else {
263- if err != nil {
264- t .Fatalf ("func returned error: %s" , err )
265- }
266- diff := cmp .Diff (accessToken , testExchangedToken )
267- if diff != "" {
268- t .Fatalf ("Token does not match: %s" , diff )
269- }
270- }
271- })
272- }
273- }
274-
275- func TestExchangeToken (t * testing.T ) {
276- config := fixtureClusterConfig (func (clusterConfig * clusterConfig ) {
277- clusterConfig .cacheKey = "test-exchange-token-" + uuid .NewString ()
278- })
279- var request * http.Request
280- response := fixtureTokenExchangeResponse ()
281- defer cache .OverwriteCacheDir (t )()
282- if err := cache .Init (); err != nil {
283- t .Fatalf ("cache init failed: %s" , err )
284- }
285-
286- handler := http .HandlerFunc (func (w http.ResponseWriter , req * http.Request ) {
287- // only compare body as the headers will differ
288- expected , err := io .ReadAll (request .Body )
289- if err != nil {
290- t .Errorf ("fail to dump expected: %s" , err )
291- }
292- actual , err := io .ReadAll (req .Body )
293- if err != nil {
294- t .Errorf ("fail to dump actual: %s" , err )
295- }
296- diff := cmp .Diff (actual , expected )
297- if diff != "" {
298- w .WriteHeader (http .StatusBadRequest )
299- t .Errorf ("request mismatch: %v" , diff )
300- return
301- }
302-
303- w .Header ().Set ("Content-Type" , "application/json" )
304- _ , err = w .Write ([]byte (response ))
305- if err != nil {
306- t .Errorf ("Failed to write response: %v" , err )
307- }
308- })
309- server := httptest .NewServer (handler )
310- defer server .Close ()
311-
312- request = fixtureTokenExchangeRequest (server .URL )
313- idToken , err := exchangeToken (testCtx , server .Client (), server .URL , testAccessToken , config )
314- if err != nil {
315- t .Fatalf ("func returned error: %s" , err )
316- }
317- diff := cmp .Diff (idToken , testExchangedToken )
318- if diff != "" {
319- t .Fatalf ("Exchanged token does not match: %s" , diff )
320- }
321- }
322-
323150func TestParseTokenToExecCredential (t * testing.T ) {
324151 expirationTime := time .Now ().Add (30 * time .Minute )
325152 expectedTime := expirationTime .Add (- 5 * time .Minute )
@@ -369,3 +196,13 @@ func TestParseTokenToExecCredential(t *testing.T) {
369196 })
370197 }
371198}
199+
200+ func TestResourceForCluster (t * testing.T ) {
201+ cc := fixtureClusterConfig ()
202+ resource := resourceForCluster (cc )
203+ // somewhat redundant, but the resource string must not change unexpectedly
204+ expectedResource := "resource://organizations/" + testOrganization + "/projects/" + testProjectId + "/regions/" + testRegion + "/ske/" + testClusterName
205+ if resource != expectedResource {
206+ t .Fatalf ("unexpected resource, got %v expected %v" , resource , expectedResource )
207+ }
208+ }
0 commit comments