AUTH-5682 Org token flow in Access logins should pass CF_AppSession cookie
- Refactor HandleRedirects function and add unit tests - Move signal test to its own file because of OS specific instructions
This commit is contained in:
parent
33baad35b8
commit
652df22831
|
@ -0,0 +1,54 @@
|
||||||
|
//go:build linux || darwin
|
||||||
|
|
||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSignalHandler(t *testing.T) {
|
||||||
|
sigHandler := signalHandler{signals: []os.Signal{syscall.SIGUSR1}}
|
||||||
|
handlerRan := false
|
||||||
|
done := make(chan struct{})
|
||||||
|
timer := time.NewTimer(time.Second)
|
||||||
|
sigHandler.register(func() {
|
||||||
|
handlerRan = true
|
||||||
|
done <- struct{}{}
|
||||||
|
})
|
||||||
|
|
||||||
|
p, err := os.FindProcess(os.Getpid())
|
||||||
|
require.Nil(t, err)
|
||||||
|
p.Signal(syscall.SIGUSR1)
|
||||||
|
|
||||||
|
// Blocks for up to one second to make sure the handler callback runs before the assert.
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
assert.True(t, handlerRan)
|
||||||
|
case <-timer.C:
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
sigHandler.deregister()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignalHandlerClose(t *testing.T) {
|
||||||
|
sigHandler := signalHandler{signals: []os.Signal{syscall.SIGUSR1}}
|
||||||
|
done := make(chan struct{})
|
||||||
|
timer := time.NewTimer(time.Second)
|
||||||
|
sigHandler.register(func() { done <- struct{}{} })
|
||||||
|
sigHandler.deregister()
|
||||||
|
|
||||||
|
p, err := os.FindProcess(os.Getpid())
|
||||||
|
require.Nil(t, err)
|
||||||
|
p.Signal(syscall.SIGUSR1)
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
t.Fail()
|
||||||
|
case <-timer.C:
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,11 +21,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
keyName = "token"
|
keyName = "token"
|
||||||
tokenCookie = "CF_Authorization"
|
tokenCookie = "CF_Authorization"
|
||||||
appDomainHeader = "CF-Access-Domain"
|
appSessionCookie = "CF_AppSession"
|
||||||
appAUDHeader = "CF-Access-Aud"
|
appDomainHeader = "CF-Access-Domain"
|
||||||
AccessLoginWorkerPath = "/cdn-cgi/access/login"
|
appAUDHeader = "CF-Access-Aud"
|
||||||
|
AccessLoginWorkerPath = "/cdn-cgi/access/login"
|
||||||
|
AccessAuthorizedWorkerPath = "/cdn-cgi/access/authorized"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -297,20 +299,41 @@ func GetAppInfo(reqURL *url.URL) (*AppInfo, error) {
|
||||||
return &AppInfo{location.Hostname(), aud, domain}, nil
|
return &AppInfo{location.Hostname(), aud, domain}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleRedirects(req *http.Request, via []*http.Request, orgToken string) error {
|
||||||
|
// attach org token to login request
|
||||||
|
if strings.Contains(req.URL.Path, AccessLoginWorkerPath) {
|
||||||
|
req.AddCookie(&http.Cookie{Name: tokenCookie, Value: orgToken})
|
||||||
|
}
|
||||||
|
|
||||||
|
// attach app session cookie to authorized request
|
||||||
|
if strings.Contains(req.URL.Path, AccessAuthorizedWorkerPath) {
|
||||||
|
// We need to check and see if the CF_APP_SESSION cookie was set
|
||||||
|
for _, prevReq := range via {
|
||||||
|
if prevReq != nil && prevReq.Response != nil {
|
||||||
|
for _, c := range prevReq.Response.Cookies() {
|
||||||
|
if c.Name == appSessionCookie {
|
||||||
|
req.AddCookie(&http.Cookie{Name: appSessionCookie, Value: c.Value})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop after hitting authorized endpoint since it will contain the app token
|
||||||
|
if len(via) > 0 && strings.Contains(via[len(via)-1].URL.Path, AccessAuthorizedWorkerPath) {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// exchangeOrgToken attaches an org token to a request to the appURL and returns an app token. This uses the Access SSO
|
// exchangeOrgToken attaches an org token to a request to the appURL and returns an app token. This uses the Access SSO
|
||||||
// flow to automatically generate and return an app token without the login page.
|
// flow to automatically generate and return an app token without the login page.
|
||||||
func exchangeOrgToken(appURL *url.URL, orgToken string) (string, error) {
|
func exchangeOrgToken(appURL *url.URL, orgToken string) (string, error) {
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
// attach org token to login request
|
return handleRedirects(req, via, orgToken)
|
||||||
if strings.Contains(req.URL.Path, AccessLoginWorkerPath) {
|
|
||||||
req.AddCookie(&http.Cookie{Name: tokenCookie, Value: orgToken})
|
|
||||||
}
|
|
||||||
// stop after hitting authorized endpoint since it will contain the app token
|
|
||||||
if strings.Contains(via[len(via)-1].URL.Path, "cdn-cgi/access/authorized") {
|
|
||||||
return http.ErrUseLastResponse
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
Timeout: time.Second * 7,
|
Timeout: time.Second * 7,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,82 @@
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
package token
|
package token
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"net/http"
|
||||||
"syscall"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSignalHandler(t *testing.T) {
|
func TestHandleRedirects_AttachOrgToken(t *testing.T) {
|
||||||
sigHandler := signalHandler{signals: []os.Signal{syscall.SIGUSR1}}
|
req, _ := http.NewRequest("GET", "http://example.com/cdn-cgi/access/login", nil)
|
||||||
handlerRan := false
|
via := []*http.Request{}
|
||||||
done := make(chan struct{})
|
orgToken := "orgTokenValue"
|
||||||
timer := time.NewTimer(time.Second)
|
|
||||||
sigHandler.register(func() {
|
|
||||||
handlerRan = true
|
|
||||||
done <- struct{}{}
|
|
||||||
})
|
|
||||||
|
|
||||||
p, err := os.FindProcess(os.Getpid())
|
handleRedirects(req, via, orgToken)
|
||||||
require.Nil(t, err)
|
|
||||||
p.Signal(syscall.SIGUSR1)
|
|
||||||
|
|
||||||
// Blocks for up to one second to make sure the handler callback runs before the assert.
|
// Check if the orgToken cookie is attached
|
||||||
select {
|
cookies := req.Cookies()
|
||||||
case <-done:
|
found := false
|
||||||
assert.True(t, handlerRan)
|
for _, cookie := range cookies {
|
||||||
case <-timer.C:
|
if cookie.Name == tokenCookie && cookie.Value == orgToken {
|
||||||
t.Fail()
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sigHandler.deregister()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSignalHandlerClose(t *testing.T) {
|
if !found {
|
||||||
sigHandler := signalHandler{signals: []os.Signal{syscall.SIGUSR1}}
|
t.Errorf("OrgToken cookie not attached to the request.")
|
||||||
done := make(chan struct{})
|
}
|
||||||
timer := time.NewTimer(time.Second)
|
}
|
||||||
sigHandler.register(func() { done <- struct{}{} })
|
|
||||||
sigHandler.deregister()
|
func TestHandleRedirects_AttachAppSessionCookie(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("GET", "http://example.com/cdn-cgi/access/authorized", nil)
|
||||||
p, err := os.FindProcess(os.Getpid())
|
via := []*http.Request{
|
||||||
require.Nil(t, err)
|
{
|
||||||
p.Signal(syscall.SIGUSR1)
|
URL: &url.URL{Path: "/cdn-cgi/access/login"},
|
||||||
select {
|
Response: &http.Response{
|
||||||
case <-done:
|
Header: http.Header{"Set-Cookie": {"CF_AppSession=appSessionValue"}},
|
||||||
t.Fail()
|
},
|
||||||
case <-timer.C:
|
},
|
||||||
|
}
|
||||||
|
orgToken := "orgTokenValue"
|
||||||
|
|
||||||
|
err := handleRedirects(req, via, orgToken)
|
||||||
|
|
||||||
|
// Check if the appSessionCookie is attached to the request
|
||||||
|
cookies := req.Cookies()
|
||||||
|
found := false
|
||||||
|
for _, cookie := range cookies {
|
||||||
|
if cookie.Name == appSessionCookie && cookie.Value == "appSessionValue" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Errorf("AppSessionCookie not attached to the request.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleRedirects_StopAtAuthorizedEndpoint(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("GET", "http://example.com/cdn-cgi/access/authorized", nil)
|
||||||
|
via := []*http.Request{
|
||||||
|
{
|
||||||
|
URL: &url.URL{Path: "other"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: &url.URL{Path: AccessAuthorizedWorkerPath},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
orgToken := "orgTokenValue"
|
||||||
|
|
||||||
|
err := handleRedirects(req, via, orgToken)
|
||||||
|
|
||||||
|
// Check if ErrUseLastResponse is returned
|
||||||
|
if err != http.ErrUseLastResponse {
|
||||||
|
t.Errorf("Expected ErrUseLastResponse, got %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue