Merge pull request #987 from vmware-tanzu/chrome_cors
Add CORS request handling to CLI's localhost listener
This commit is contained in:
commit
f6f188565b
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
window.onload = () => {
|
window.onload = () => {
|
||||||
@ -48,7 +48,14 @@ window.onload = () => {
|
|||||||
headers: {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'},
|
headers: {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'},
|
||||||
body: responseParams['encoded_params'].value,
|
body: responseParams['encoded_params'].value,
|
||||||
})
|
})
|
||||||
.then(() => clearTimeout(timeout))
|
.then(response => {
|
||||||
.then(() => transitionToState('success'))
|
clearTimeout(timeout);
|
||||||
|
if (response.ok) {
|
||||||
|
transitionToState('success');
|
||||||
|
} else {
|
||||||
|
// Got non-2XX http response status.
|
||||||
|
transitionToState('manual');
|
||||||
|
}
|
||||||
|
})
|
||||||
.catch(() => transitionToState('manual'));
|
.catch(() => transitionToState('manual'));
|
||||||
};
|
};
|
||||||
|
@ -30,7 +30,7 @@ var (
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<style>body{font-family:metropolis-light,Helvetica,sans-serif}h1{font-size:20px}.state{position:absolute;top:100px;left:50%;width:400px;height:80px;margin-top:-40px;margin-left:-200px;font-size:14px;line-height:24px}button{margin:-10px;padding:10px;text-align:left;width:100%;display:inline;border:none;background:0 0;cursor:pointer;transition:all .1s}button:hover{background-color:#eee;transform:scale(1.01)}button:active{background-color:#ddd;transform:scale(.99)}code{display:block;word-wrap:break-word;word-break:break-all;font-size:12px;font-family:monospace;color:#333}.copy-icon{float:left;width:36px;height:36px;margin-top:-3px;margin-right:10px;background-size:contain;background-repeat:no-repeat;background-image:url("data:image/svg+xml,%3Csvg width=%2236%22 height=%2236%22 viewBox=%220 0 36 36%22 xmlns=%22http://www.w3.org/2000/svg%22 xmlns:xlink=%22http://www.w3.org/1999/xlink%22%3E%3Ctitle%3Ecopy-to-clipboard-line%3C/title%3E%3Cpath d=%22M22.6 4H21.55a3.89 3.89.0 00-7.31.0H13.4A2.41 2.41.0 0011 6.4V10H25V6.4A2.41 2.41.0 0022.6 4zM23 8H13V6.25A.25.25.0 0113.25 6h2.69l.12-1.11A1.24 1.24.0 0116.61 4a2 2 0 013.15 1.18l.09.84h2.9a.25.25.0 01.25.25z%22 class=%22clr-i-outline clr-i-outline-path-1%22/%3E%3Cpath d=%22M33.25 18.06H21.33l2.84-2.83a1 1 0 10-1.42-1.42L17.5 19.06l5.25 5.25a1 1 0 00.71.29 1 1 0 00.71-1.7l-2.84-2.84H33.25a1 1 0 000-2z%22 class=%22clr-i-outline clr-i-outline-path-2%22/%3E%3Cpath d=%22M29 16h2V6.68A1.66 1.66.0 0029.35 5H27.08V7H29z%22 class=%22clr-i-outline clr-i-outline-path-3%22/%3E%3Cpath d=%22M29 31H7V7H9V5H6.64A1.66 1.66.0 005 6.67V31.32A1.66 1.66.0 006.65 33H29.36A1.66 1.66.0 0031 31.33V22.06H29z%22 class=%22clr-i-outline clr-i-outline-path-4%22/%3E%3Crect x=%220%22 y=%220%22 width=%2236%22 height=%2236%22 fill-opacity=%220%22/%3E%3C/svg%3E")}@keyframes loader{to{transform:rotate(360deg)}}#loading{content:'';box-sizing:border-box;width:80px;height:80px;margin-top:-40px;margin-left:-40px;border-radius:50%;border:2px solid #fff;border-top-color:#1b3951;animation:loader .6s linear infinite}</style>
|
<style>body{font-family:metropolis-light,Helvetica,sans-serif}h1{font-size:20px}.state{position:absolute;top:100px;left:50%;width:400px;height:80px;margin-top:-40px;margin-left:-200px;font-size:14px;line-height:24px}button{margin:-10px;padding:10px;text-align:left;width:100%;display:inline;border:none;background:0 0;cursor:pointer;transition:all .1s}button:hover{background-color:#eee;transform:scale(1.01)}button:active{background-color:#ddd;transform:scale(.99)}code{display:block;word-wrap:break-word;word-break:break-all;font-size:12px;font-family:monospace;color:#333}.copy-icon{float:left;width:36px;height:36px;margin-top:-3px;margin-right:10px;background-size:contain;background-repeat:no-repeat;background-image:url("data:image/svg+xml,%3Csvg width=%2236%22 height=%2236%22 viewBox=%220 0 36 36%22 xmlns=%22http://www.w3.org/2000/svg%22 xmlns:xlink=%22http://www.w3.org/1999/xlink%22%3E%3Ctitle%3Ecopy-to-clipboard-line%3C/title%3E%3Cpath d=%22M22.6 4H21.55a3.89 3.89.0 00-7.31.0H13.4A2.41 2.41.0 0011 6.4V10H25V6.4A2.41 2.41.0 0022.6 4zM23 8H13V6.25A.25.25.0 0113.25 6h2.69l.12-1.11A1.24 1.24.0 0116.61 4a2 2 0 013.15 1.18l.09.84h2.9a.25.25.0 01.25.25z%22 class=%22clr-i-outline clr-i-outline-path-1%22/%3E%3Cpath d=%22M33.25 18.06H21.33l2.84-2.83a1 1 0 10-1.42-1.42L17.5 19.06l5.25 5.25a1 1 0 00.71.29 1 1 0 00.71-1.7l-2.84-2.84H33.25a1 1 0 000-2z%22 class=%22clr-i-outline clr-i-outline-path-2%22/%3E%3Cpath d=%22M29 16h2V6.68A1.66 1.66.0 0029.35 5H27.08V7H29z%22 class=%22clr-i-outline clr-i-outline-path-3%22/%3E%3Cpath d=%22M29 31H7V7H9V5H6.64A1.66 1.66.0 005 6.67V31.32A1.66 1.66.0 006.65 33H29.36A1.66 1.66.0 0031 31.33V22.06H29z%22 class=%22clr-i-outline clr-i-outline-path-4%22/%3E%3Crect x=%220%22 y=%220%22 width=%2236%22 height=%2236%22 fill-opacity=%220%22/%3E%3C/svg%3E")}@keyframes loader{to{transform:rotate(360deg)}}#loading{content:'';box-sizing:border-box;width:80px;height:80px;margin-top:-40px;margin-left:-40px;border-radius:50%;border:2px solid #fff;border-top-color:#1b3951;animation:loader .6s linear infinite}</style>
|
||||||
<script>window.onload=()=>{const e=t=>{Array.from(document.querySelectorAll('.state')).forEach(e=>e.hidden=!0);const e=document.getElementById(t);e.hidden=!1,document.title=e.dataset.title,document.getElementById('favicon').setAttribute('href','data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>'+e.dataset.favicon+'</text></svg>')};e('loading'),window.history.replaceState(null,'','./'),document.getElementById('manual-copy-button').onclick=()=>{const e=document.getElementById('manual-copy-button').innerText;navigator.clipboard.writeText(e).then(()=>console.info('copied authorization code '+e+' to clipboard')).catch(t=>console.error('failed to copy code '+e+' to clipboard: '+t))};const n=setTimeout(()=>e('manual'),2e3),t=document.forms[0].elements;fetch(t.redirect_uri.value,{method:'POST',mode:'no-cors',headers:{'Content-Type':'application/x-www-form-urlencoded;charset=UTF-8'},body:t.encoded_params.value}).then(()=>clearTimeout(n)).then(()=>e('success')).catch(()=>e('manual'))}</script>
|
<script>window.onload=()=>{const e=t=>{Array.from(document.querySelectorAll('.state')).forEach(e=>e.hidden=!0);const e=document.getElementById(t);e.hidden=!1,document.title=e.dataset.title,document.getElementById('favicon').setAttribute('href','data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>'+e.dataset.favicon+'</text></svg>')};e('loading'),window.history.replaceState(null,'','./'),document.getElementById('manual-copy-button').onclick=()=>{const e=document.getElementById('manual-copy-button').innerText;navigator.clipboard.writeText(e).then(()=>console.info('copied authorization code '+e+' to clipboard')).catch(t=>console.error('failed to copy code '+e+' to clipboard: '+t))};const n=setTimeout(()=>e('manual'),2e3),t=document.forms[0].elements;fetch(t.redirect_uri.value,{method:'POST',mode:'no-cors',headers:{'Content-Type':'application/x-www-form-urlencoded;charset=UTF-8'},body:t.encoded_params.value}).then(t=>{clearTimeout(n),t.ok?e('success'):e('manual')}).catch(()=>e('manual'))}</script>
|
||||||
<link id="favicon" rel="icon"/>
|
<link id="favicon" rel="icon"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -61,7 +61,7 @@ var (
|
|||||||
// It's okay if this changes in the future, but this gives us a chance to eyeball the formatting.
|
// It's okay if this changes in the future, but this gives us a chance to eyeball the formatting.
|
||||||
// Our browser-based integration tests should find any incompatibilities.
|
// Our browser-based integration tests should find any incompatibilities.
|
||||||
testExpectedCSP = `default-src 'none'; ` +
|
testExpectedCSP = `default-src 'none'; ` +
|
||||||
`script-src 'sha256-cjTdJmRvuz5EHNb/cw6pFk9iWyjegU9Ihx7Fb9tlqRg='; ` +
|
`script-src 'sha256-Lon+X41NoXuVGPqi3LsAPmBqlDmwbu3lGhQii7/Zjrc='; ` +
|
||||||
`style-src 'sha256-CtfkX7m8x2UdGYvGgDq+6b6yIAQsASW9pbQK+sG8fNA='; ` +
|
`style-src 'sha256-CtfkX7m8x2UdGYvGgDq+6b6yIAQsASW9pbQK+sG8fNA='; ` +
|
||||||
`img-src data:; ` +
|
`img-src data:; ` +
|
||||||
`connect-src *; ` +
|
`connect-src *; ` +
|
||||||
|
@ -834,13 +834,46 @@ func (h *handlerState) handleAuthCodeCallback(w http.ResponseWriter, r *http.Req
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
var params url.Values
|
var params url.Values
|
||||||
if h.useFormPost {
|
if h.useFormPost { // nolint:nestif
|
||||||
// Return HTTP 405 for anything that's not a POST.
|
if r.Method == http.MethodOptions {
|
||||||
if r.Method != http.MethodPost {
|
// Google Chrome decided that it should do CORS preflight checks for this Javascript form submission POST request.
|
||||||
return httperr.Newf(http.StatusMethodNotAllowed, "wanted POST")
|
// See https://developer.chrome.com/blog/private-network-access-preflight/
|
||||||
|
origin := r.Header.Get("Origin")
|
||||||
|
if origin == "" {
|
||||||
|
// The CORS preflight request should have an origin.
|
||||||
|
h.logger.V(debugLogLevel).Info("Pinniped: Got OPTIONS request without origin header")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return nil // keep listening for more requests
|
||||||
|
}
|
||||||
|
h.logger.V(debugLogLevel).Info("Pinniped: Got CORS preflight request from browser", "origin", origin)
|
||||||
|
issuerURL, parseErr := url.Parse(h.issuer)
|
||||||
|
if parseErr != nil {
|
||||||
|
return httperr.Wrap(http.StatusInternalServerError, "invalid issuer url", parseErr)
|
||||||
|
}
|
||||||
|
// To tell the browser that it is okay to make the real POST request, return the following response.
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", issuerURL.Scheme+"://"+issuerURL.Host)
|
||||||
|
w.Header().Set("Access-Control-Allow-Credentials", "false")
|
||||||
|
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
|
||||||
|
w.Header().Set("Access-Control-Allow-Private-Network", "true")
|
||||||
|
// If the browser would like to send some headers on the real request, allow them. Chrome doesn't
|
||||||
|
// currently send this header at the moment. This is in case some browser in the future decides to
|
||||||
|
// request to be allowed to send specific headers by using Access-Control-Request-Headers.
|
||||||
|
requestedHeaders := r.Header.Get("Access-Control-Request-Headers")
|
||||||
|
if requestedHeaders != "" {
|
||||||
|
w.Header().Set("Access-Control-Allow-Headers", requestedHeaders)
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return nil // keep listening for more requests
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse and pull the response parameters from a application/x-www-form-urlencoded request body.
|
// Return HTTP 405 for anything that's not a POST.
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
h.logger.V(debugLogLevel).Info("Pinniped: Got unexpected request on callback listener", "method", r.Method)
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
return nil // keep listening for more requests
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse and pull the response parameters from an application/x-www-form-urlencoded request body.
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
return httperr.Wrap(http.StatusBadRequest, "invalid form", err)
|
return httperr.Wrap(http.StatusBadRequest, "invalid form", err)
|
||||||
}
|
}
|
||||||
@ -848,7 +881,9 @@ func (h *handlerState) handleAuthCodeCallback(w http.ResponseWriter, r *http.Req
|
|||||||
} else {
|
} else {
|
||||||
// Return HTTP 405 for anything that's not a GET.
|
// Return HTTP 405 for anything that's not a GET.
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
return httperr.Newf(http.StatusMethodNotAllowed, "wanted GET")
|
h.logger.V(debugLogLevel).Info("Pinniped: Got unexpected request on callback listener", "method", r.Method)
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
return nil // keep listening for more requests
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull response parameters from the URL query string.
|
// Pull response parameters from the URL query string.
|
||||||
|
@ -1825,6 +1825,8 @@ func TestHandlePasteCallback(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
tt := tt
|
tt := tt
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
h := &handlerState{
|
h := &handlerState{
|
||||||
callbacks: make(chan callbackResult, 1),
|
callbacks: make(chan callbackResult, 1),
|
||||||
state: state.State("test-state"),
|
state: state.State("test-state"),
|
||||||
@ -1866,35 +1868,38 @@ func TestHandleAuthCodeCallback(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
method string
|
method string
|
||||||
query string
|
query string
|
||||||
body []byte
|
body []byte
|
||||||
contentType string
|
headers http.Header
|
||||||
opt func(t *testing.T) Option
|
opt func(t *testing.T) Option
|
||||||
wantErr string
|
|
||||||
wantHTTPStatus int
|
wantErr string
|
||||||
|
wantHTTPStatus int
|
||||||
|
wantNoCallbacks bool
|
||||||
|
wantHeaders http.Header
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "wrong method",
|
name: "wrong method returns an error but keeps listening",
|
||||||
method: "POST",
|
method: http.MethodPost,
|
||||||
query: "",
|
query: "",
|
||||||
wantErr: "wanted GET",
|
wantNoCallbacks: true,
|
||||||
wantHTTPStatus: http.StatusMethodNotAllowed,
|
wantHTTPStatus: http.StatusMethodNotAllowed,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "wrong method for form_post",
|
name: "wrong method for form_post returns an error but keeps listening",
|
||||||
method: "GET",
|
method: http.MethodGet,
|
||||||
query: "",
|
query: "",
|
||||||
opt: withFormPostMode,
|
opt: withFormPostMode,
|
||||||
wantErr: "wanted POST",
|
wantNoCallbacks: true,
|
||||||
wantHTTPStatus: http.StatusMethodNotAllowed,
|
wantHTTPStatus: http.StatusMethodNotAllowed,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid form for form_post",
|
name: "invalid form for form_post",
|
||||||
method: "POST",
|
method: http.MethodPost,
|
||||||
query: "",
|
query: "",
|
||||||
contentType: "application/x-www-form-urlencoded",
|
headers: map[string][]string{"Content-Type": {"application/x-www-form-urlencoded"}},
|
||||||
body: []byte(`%`),
|
body: []byte(`%`),
|
||||||
opt: withFormPostMode,
|
opt: withFormPostMode,
|
||||||
wantErr: `invalid form: invalid URL escape "%"`,
|
wantErr: `invalid form: invalid URL escape "%"`,
|
||||||
@ -1918,6 +1923,75 @@ func TestHandleAuthCodeCallback(t *testing.T) {
|
|||||||
wantErr: `login failed with code "some_error": optional error description`,
|
wantErr: `login failed with code "some_error": optional error description`,
|
||||||
wantHTTPStatus: http.StatusBadRequest,
|
wantHTTPStatus: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "in form post mode, invalid issuer url config during CORS preflight request returns an error",
|
||||||
|
method: http.MethodOptions,
|
||||||
|
query: "",
|
||||||
|
headers: map[string][]string{"Origin": {"https://some-origin.com"}},
|
||||||
|
wantErr: `invalid issuer url: parse "://bad-url": missing protocol scheme`,
|
||||||
|
wantHTTPStatus: http.StatusInternalServerError,
|
||||||
|
opt: func(t *testing.T) Option {
|
||||||
|
return func(h *handlerState) error {
|
||||||
|
h.useFormPost = true
|
||||||
|
h.issuer = "://bad-url"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "in form post mode, options request is missing origin header results in 400 and keeps listener running",
|
||||||
|
method: http.MethodOptions,
|
||||||
|
query: "",
|
||||||
|
opt: withFormPostMode,
|
||||||
|
wantNoCallbacks: true,
|
||||||
|
wantHTTPStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "in form post mode, valid CORS request responds with 402 and CORS headers and keeps listener running",
|
||||||
|
method: http.MethodOptions,
|
||||||
|
query: "",
|
||||||
|
headers: map[string][]string{"Origin": {"https://some-origin.com"}},
|
||||||
|
wantNoCallbacks: true,
|
||||||
|
wantHTTPStatus: http.StatusNoContent,
|
||||||
|
wantHeaders: map[string][]string{
|
||||||
|
"Access-Control-Allow-Credentials": {"false"},
|
||||||
|
"Access-Control-Allow-Methods": {"POST, OPTIONS"},
|
||||||
|
"Access-Control-Allow-Origin": {"https://valid-issuer.com"},
|
||||||
|
"Access-Control-Allow-Private-Network": {"true"},
|
||||||
|
},
|
||||||
|
opt: func(t *testing.T) Option {
|
||||||
|
return func(h *handlerState) error {
|
||||||
|
h.useFormPost = true
|
||||||
|
h.issuer = "https://valid-issuer.com/with/some/path"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "in form post mode, valid CORS request with Access-Control-Request-Headers responds with 402 and CORS headers including Access-Control-Allow-Headers and keeps listener running",
|
||||||
|
method: http.MethodOptions,
|
||||||
|
query: "",
|
||||||
|
headers: map[string][]string{
|
||||||
|
"Origin": {"https://some-origin.com"},
|
||||||
|
"Access-Control-Request-Headers": {"header1, header2, header3"},
|
||||||
|
},
|
||||||
|
wantNoCallbacks: true,
|
||||||
|
wantHTTPStatus: http.StatusNoContent,
|
||||||
|
wantHeaders: map[string][]string{
|
||||||
|
"Access-Control-Allow-Credentials": {"false"},
|
||||||
|
"Access-Control-Allow-Methods": {"POST, OPTIONS"},
|
||||||
|
"Access-Control-Allow-Origin": {"https://valid-issuer.com"},
|
||||||
|
"Access-Control-Allow-Private-Network": {"true"},
|
||||||
|
"Access-Control-Allow-Headers": {"header1, header2, header3"},
|
||||||
|
},
|
||||||
|
opt: func(t *testing.T) Option {
|
||||||
|
return func(h *handlerState) error {
|
||||||
|
h.useFormPost = true
|
||||||
|
h.issuer = "https://valid-issuer.com/with/some/path"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "invalid code",
|
name: "invalid code",
|
||||||
query: "state=test-state&code=invalid",
|
query: "state=test-state&code=invalid",
|
||||||
@ -1938,8 +2012,9 @@ func TestHandleAuthCodeCallback(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid",
|
name: "valid",
|
||||||
query: "state=test-state&code=valid",
|
query: "state=test-state&code=valid",
|
||||||
|
wantHTTPStatus: http.StatusOK,
|
||||||
opt: func(t *testing.T) Option {
|
opt: func(t *testing.T) Option {
|
||||||
return func(h *handlerState) error {
|
return func(h *handlerState) error {
|
||||||
h.oauth2Config = &oauth2.Config{RedirectURL: testRedirectURI}
|
h.oauth2Config = &oauth2.Config{RedirectURL: testRedirectURI}
|
||||||
@ -1955,10 +2030,11 @@ func TestHandleAuthCodeCallback(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid form_post",
|
name: "valid form_post",
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
contentType: "application/x-www-form-urlencoded",
|
headers: map[string][]string{"Content-Type": {"application/x-www-form-urlencoded"}},
|
||||||
body: []byte(`state=test-state&code=valid`),
|
body: []byte(`state=test-state&code=valid`),
|
||||||
|
wantHTTPStatus: http.StatusOK,
|
||||||
opt: func(t *testing.T) Option {
|
opt: func(t *testing.T) Option {
|
||||||
return func(h *handlerState) error {
|
return func(h *handlerState) error {
|
||||||
h.useFormPost = true
|
h.useFormPost = true
|
||||||
@ -1978,11 +2054,14 @@ func TestHandleAuthCodeCallback(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
tt := tt
|
tt := tt
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
h := &handlerState{
|
h := &handlerState{
|
||||||
callbacks: make(chan callbackResult, 1),
|
callbacks: make(chan callbackResult, 1),
|
||||||
state: state.State("test-state"),
|
state: state.State("test-state"),
|
||||||
pkce: pkce.Code("test-pkce"),
|
pkce: pkce.Code("test-pkce"),
|
||||||
nonce: nonce.Nonce("test-nonce"),
|
nonce: nonce.Nonce("test-nonce"),
|
||||||
|
logger: testlogger.New(t).Logger,
|
||||||
}
|
}
|
||||||
if tt.opt != nil {
|
if tt.opt != nil {
|
||||||
require.NoError(t, tt.opt(t)(h))
|
require.NoError(t, tt.opt(t)(h))
|
||||||
@ -1998,8 +2077,8 @@ func TestHandleAuthCodeCallback(t *testing.T) {
|
|||||||
if tt.method != "" {
|
if tt.method != "" {
|
||||||
req.Method = tt.method
|
req.Method = tt.method
|
||||||
}
|
}
|
||||||
if tt.contentType != "" {
|
if tt.headers != nil {
|
||||||
req.Header.Set("Content-Type", tt.contentType)
|
req.Header = tt.headers
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.handleAuthCodeCallback(resp, req)
|
err = h.handleAuthCodeCallback(resp, req)
|
||||||
@ -2012,11 +2091,19 @@ func TestHandleAuthCodeCallback(t *testing.T) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tt.wantHTTPStatus, resp.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tt.wantHeaders != nil {
|
||||||
|
require.Equal(t, tt.wantHeaders, resp.Header())
|
||||||
|
}
|
||||||
|
|
||||||
|
gotCallback := false
|
||||||
select {
|
select {
|
||||||
case <-time.After(1 * time.Second):
|
case <-time.After(1 * time.Second):
|
||||||
require.Fail(t, "timed out waiting to receive from callbacks channel")
|
if !tt.wantNoCallbacks {
|
||||||
|
require.Fail(t, "timed out waiting to receive from callbacks channel")
|
||||||
|
}
|
||||||
case result := <-h.callbacks:
|
case result := <-h.callbacks:
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
require.EqualError(t, result.err, tt.wantErr)
|
require.EqualError(t, result.err, tt.wantErr)
|
||||||
@ -2025,7 +2112,9 @@ func TestHandleAuthCodeCallback(t *testing.T) {
|
|||||||
require.NoError(t, result.err)
|
require.NoError(t, result.err)
|
||||||
require.NotNil(t, result.token)
|
require.NotNil(t, result.token)
|
||||||
require.Equal(t, result.token.IDToken.Token, "test-id-token")
|
require.Equal(t, result.token.IDToken.Token, "test-id-token")
|
||||||
|
gotCallback = true
|
||||||
}
|
}
|
||||||
|
require.Equal(t, tt.wantNoCallbacks, !gotCallback)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user