github.com/avenga/couper@v1.12.2/eval/lib/oauth2_test.go (about) 1 package lib_test 2 3 import ( 4 "context" 5 "encoding/json" 6 "net/http" 7 "net/http/httptest" 8 "net/url" 9 "testing" 10 11 "github.com/zclconf/go-cty/cty" 12 13 "github.com/avenga/couper/accesscontrol/jwk" 14 "github.com/avenga/couper/cache" 15 "github.com/avenga/couper/config" 16 "github.com/avenga/couper/config/configload" 17 "github.com/avenga/couper/config/request" 18 "github.com/avenga/couper/config/runtime" 19 "github.com/avenga/couper/eval" 20 "github.com/avenga/couper/eval/lib" 21 "github.com/avenga/couper/internal/seetie" 22 "github.com/avenga/couper/internal/test" 23 "github.com/avenga/couper/oauth2/oidc" 24 ) 25 26 func TestNewOAuthAuthorizationURLFunction(t *testing.T) { 27 helper := test.New(t) 28 29 var origin *httptest.Server 30 origin = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 31 var conf interface{} 32 if req.URL.Path == "/.well-known/openid-configuration" { 33 conf = &oidc.OpenidConfiguration{ 34 AuthorizationEndpoint: origin.URL + "/auth", 35 CodeChallengeMethodsSupported: []string{config.CcmS256}, 36 Issuer: "thatsme", 37 JwksURI: origin.URL + "/jwks", 38 TokenEndpoint: origin.URL + "/token", 39 UserinfoEndpoint: origin.URL + "/userinfo", 40 } 41 } else if req.URL.Path == "/jwks" { 42 conf = jwk.JWKSData{} 43 } 44 45 b, err := json.Marshal(conf) 46 helper.Must(err) 47 _, err = rw.Write(b) 48 helper.Must(err) 49 })) 50 defer origin.Close() 51 52 confURL := origin.URL + "/.well-known/openid-configuration" 53 authURL := origin.URL + "/auth" 54 tokenURL := origin.URL + "/token" 55 56 log, _ := test.NewLogger() 57 logger := log.WithContext(context.Background()) 58 59 type testCase struct { 60 name string 61 config string 62 wantRedir string 63 wantScope string 64 wantNonce bool 65 wantState bool 66 wantPKCE bool 67 } 68 69 for _, tc := range []testCase{ 70 { 71 "oidc", 72 `server {} 73 definitions { 74 oidc "auth-ref" { 75 client_id = "test-id" 76 client_secret = "test-s3cr3t" 77 configuration_url = "` + confURL + `" 78 redirect_uri = "/cb" 79 verifier_value = "asdf" 80 } 81 } 82 `, 83 "https://couper.io/cb", 84 "openid", 85 true, 86 false, 87 false, 88 }, 89 { 90 "oidc, redir URI from env var and using function", 91 `server {} 92 definitions { 93 oidc "auth-ref" { 94 client_id = "test-id" 95 client_secret = "test-s3cr3t" 96 configuration_url = "` + confURL + `" 97 redirect_uri = split(" ", env.REDIR_URIS)[0] 98 verifier_value = "asdf" 99 } 100 } 101 defaults { 102 environment_variables = { 103 REDIR_URIS = "/cb /cb2" 104 } 105 } 106 `, 107 "https://couper.io/cb", 108 "openid", 109 true, 110 false, 111 false, 112 }, 113 { 114 "oidc, absolute redir URI", 115 `server {} 116 definitions { 117 oidc "auth-ref" { 118 client_id = "test-id" 119 client_secret = "test-s3cr3t" 120 configuration_url = "` + confURL + `" 121 redirect_uri = "https://example.com/cb" 122 verifier_value = "asdf" 123 } 124 } 125 `, 126 "https://example.com/cb", 127 "openid", 128 true, 129 false, 130 false, 131 }, 132 { 133 "oidc, empty scope", 134 `server {} 135 definitions { 136 oidc "auth-ref" { 137 client_id = "test-id" 138 client_secret = "test-s3cr3t" 139 configuration_url = "` + confURL + `" 140 redirect_uri = "/cb" 141 verifier_value = "asdf" 142 scope = "" 143 } 144 } 145 `, 146 "https://couper.io/cb", 147 "openid", 148 true, 149 false, 150 false, 151 }, 152 { 153 "oidc, email scope", 154 `server {} 155 definitions { 156 oidc "auth-ref" { 157 client_id = "test-id" 158 client_secret = "test-s3cr3t" 159 configuration_url = "` + confURL + `" 160 redirect_uri = "/cb" 161 verifier_value = "asdf" 162 scope = "email" 163 } 164 } 165 `, 166 "https://couper.io/cb", 167 "openid email", 168 true, 169 false, 170 false, 171 }, 172 { 173 "oidc, verifier_method ccm_s256", 174 `server {} 175 definitions { 176 oidc "auth-ref" { 177 client_id = "test-id" 178 client_secret = "test-s3cr3t" 179 configuration_url = "` + confURL + `" 180 redirect_uri = "/cb" 181 verifier_method = "ccm_s256" 182 verifier_value = "asdf" 183 } 184 } 185 `, 186 "https://couper.io/cb", 187 "openid", 188 false, 189 false, 190 true, 191 }, 192 { 193 "oauth2 authorization code", 194 `server {} 195 definitions { 196 beta_oauth2 "auth-ref" { 197 grant_type = "authorization_code" 198 client_id = "test-id" 199 client_secret = "test-s3cr3t" 200 authorization_endpoint = "` + authURL + `" 201 token_endpoint = "` + tokenURL + `" 202 redirect_uri = "/cb" 203 verifier_method = "state" 204 verifier_value = "asdf" 205 } 206 } 207 `, 208 "https://couper.io/cb", 209 "", 210 false, 211 true, 212 false, 213 }, 214 { 215 "oauth2 authorization code, redir URI from env var", 216 `server {} 217 definitions { 218 beta_oauth2 "auth-ref" { 219 grant_type = "authorization_code" 220 client_id = "test-id" 221 client_secret = "test-s3cr3t" 222 authorization_endpoint = "` + authURL + `" 223 token_endpoint = "` + tokenURL + `" 224 redirect_uri = env.REDIR_URI 225 verifier_method = "state" 226 verifier_value = "asdf" 227 } 228 } 229 defaults { 230 environment_variables = { 231 REDIR_URI = "/cb" 232 } 233 } 234 `, 235 "https://couper.io/cb", 236 "", 237 false, 238 true, 239 false, 240 }, 241 { 242 "oauth2 authorization code, absolute redir URI", 243 `server {} 244 definitions { 245 beta_oauth2 "auth-ref" { 246 grant_type = "authorization_code" 247 client_id = "test-id" 248 client_secret = "test-s3cr3t" 249 authorization_endpoint = "` + authURL + `" 250 token_endpoint = "` + tokenURL + `" 251 redirect_uri = "https://example.com/cb" 252 verifier_method = "state" 253 verifier_value = "asdf" 254 } 255 } 256 `, 257 "https://example.com/cb", 258 "", 259 false, 260 true, 261 false, 262 }, 263 { 264 "oauth2 authorization code, empty scope", 265 `server {} 266 definitions { 267 beta_oauth2 "auth-ref" { 268 grant_type = "authorization_code" 269 client_id = "test-id" 270 client_secret = "test-s3cr3t" 271 authorization_endpoint = "` + authURL + `" 272 token_endpoint = "` + tokenURL + `" 273 redirect_uri = "/cb" 274 verifier_method = "state" 275 verifier_value = "asdf" 276 scope = "" 277 } 278 } 279 `, 280 "https://couper.io/cb", 281 "", 282 false, 283 true, 284 false, 285 }, 286 { 287 "oauth2 authorization code, 'foo bar' scope", 288 `server {} 289 definitions { 290 beta_oauth2 "auth-ref" { 291 grant_type = "authorization_code" 292 client_id = "test-id" 293 client_secret = "test-s3cr3t" 294 authorization_endpoint = "` + authURL + `" 295 token_endpoint = "` + tokenURL + `" 296 redirect_uri = "/cb" 297 verifier_method = "state" 298 verifier_value = "asdf" 299 scope = "foo bar" 300 } 301 } 302 `, 303 "https://couper.io/cb", 304 "foo bar", 305 false, 306 true, 307 false, 308 }, 309 { 310 "oauth2 authorization code, verifier_method ccm_s256", 311 `server {} 312 definitions { 313 beta_oauth2 "auth-ref" { 314 grant_type = "authorization_code" 315 client_id = "test-id" 316 client_secret = "test-s3cr3t" 317 authorization_endpoint = "` + authURL + `" 318 token_endpoint = "` + tokenURL + `" 319 redirect_uri = "/cb" 320 verifier_method = "ccm_s256" 321 verifier_value = "asdf" 322 } 323 } 324 `, 325 "https://couper.io/cb", 326 "", 327 false, 328 false, 329 true, 330 }, 331 } { 332 t.Run(tc.name, func(subT *testing.T) { 333 h := test.New(subT) 334 335 couperConf, err := configload.LoadBytes([]byte(tc.config), "test.hcl") 336 h.Must(err) 337 338 quitCh := make(chan struct{}, 1) 339 defer close(quitCh) 340 memStore := cache.New(logger, quitCh) 341 342 ctx, cancel := context.WithCancel(couperConf.Context) 343 couperConf.Context = ctx 344 defer cancel() 345 346 _, err = runtime.NewServerConfiguration(couperConf, logger, memStore) 347 helper.Must(err) 348 349 req, rerr := http.NewRequest(http.MethodGet, "https://couper.io/", nil) 350 helper.Must(rerr) 351 req = req.Clone(context.Background()) 352 353 hclCtx := couperConf.Context.(*eval.Context). 354 WithClientRequest(req). 355 HCLContext() 356 357 val, furr := hclCtx.Functions[lib.FnOAuthAuthorizationURL].Call([]cty.Value{cty.StringVal("auth-ref")}) 358 helper.Must(furr) 359 360 authURL := seetie.ValueToString(val) 361 authURLObj, perr := url.Parse(authURL) 362 helper.Must(perr) 363 364 query := authURLObj.Query() 365 366 if rt := query.Get("response_type"); rt != "code" { 367 subT.Errorf("response_type want: %v; got: %v", "code", rt) 368 } 369 370 if clID := query.Get("client_id"); clID != "test-id" { 371 subT.Errorf("client_id want: %v; got: %v", "test-id", clID) 372 } 373 374 if redir := query.Get("redirect_uri"); redir != tc.wantRedir { 375 subT.Errorf("redirect_uri want: %v; got: %v", tc.wantRedir, redir) 376 } 377 378 if tc.wantScope == "" && query.Has("scope") { 379 subT.Error("scope not expected") 380 } 381 if scope := query.Get("scope"); scope != tc.wantScope { 382 subT.Errorf("scope want: %v; got: %v", tc.wantScope, scope) 383 } 384 385 nonce := query.Get("nonce") 386 if tc.wantNonce { 387 if nonce == "" { 388 subT.Error("missing nonce") 389 } 390 } else { 391 if nonce != "" { 392 subT.Error("nonce not expected") 393 } 394 } 395 396 state := query.Get("state") 397 if tc.wantState { 398 if state == "" { 399 subT.Error("missing state") 400 } 401 } else { 402 if state != "" { 403 subT.Error("state not expected") 404 } 405 } 406 407 codeChallenge := query.Get("code_challenge") 408 codeChallengeMethod := query.Get("code_challenge_method") 409 if tc.wantPKCE { 410 if codeChallenge == "" { 411 subT.Error("missing code_challenge") 412 } 413 if codeChallengeMethod != "S256" { 414 subT.Errorf("code_challenge want: %v; got: %v\n", "S256", codeChallengeMethod) 415 } 416 } else { 417 if codeChallenge != "" { 418 subT.Error("code_challenge not expected") 419 } 420 } 421 }) 422 } 423 } 424 425 func TestOAuthAuthorizationURLError(t *testing.T) { 426 tests := []struct { 427 name string 428 config string 429 label string 430 wantErr string 431 }{ 432 { 433 "missing oidc/beta_oauth2 definitions", 434 ` 435 server {} 436 definitions { 437 } 438 `, 439 "MyLabel", 440 `missing oidc or beta_oauth2 block with referenced label "MyLabel"`, 441 }, 442 { 443 "missing referenced oidc/beta_oauth2", 444 ` 445 server {} 446 definitions { 447 beta_oauth2 "auth-ref" { 448 grant_type = "authorization_code" 449 client_id = "test-id" 450 client_secret = "test-s3cr3t" 451 authorization_endpoint = "https://a.s./auth" 452 token_endpoint = "https://a.s./token" 453 redirect_uri = "/cb" 454 verifier_method = "ccm_s256" 455 verifier_value = "asdf" 456 } 457 } 458 `, 459 "MyLabel", 460 `missing oidc or beta_oauth2 block with referenced label "MyLabel"`, 461 }, 462 } 463 464 for _, tt := range tests { 465 t.Run(tt.name, func(subT *testing.T) { 466 h := test.New(subT) 467 couperConf, err := configload.LoadBytes([]byte(tt.config), "test.hcl") 468 h.Must(err) 469 470 ctx, cancel := context.WithCancel(couperConf.Context) 471 couperConf.Context = ctx 472 defer cancel() 473 474 evalContext := couperConf.Context.Value(request.ContextType).(*eval.Context) 475 req, err := http.NewRequest(http.MethodGet, "https://www.example.com/foo", nil) 476 h.Must(err) 477 evalContext = evalContext.WithClientRequest(req) 478 479 _, err = evalContext.HCLContext().Functions[lib.FnOAuthAuthorizationURL].Call([]cty.Value{cty.StringVal(tt.label)}) 480 if err == nil { 481 subT.Error("expected an error, got nothing") 482 return 483 } 484 if err.Error() != tt.wantErr { 485 subT.Errorf("\nWant:\t%q\nGot:\t%q", tt.wantErr, err.Error()) 486 } 487 }) 488 } 489 }