Skip to content

Commit

Permalink
Add post-login-path tests, session-check tests
Browse files Browse the repository at this point in the history
  • Loading branch information
p53 committed Oct 3, 2023
1 parent 3342914 commit 09aa27f
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 31 deletions.
105 changes: 103 additions & 2 deletions e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@ const (
idpURI = "http://localhost:8081"
testUser = "myuser"
testPass = "baba1234"
testPath = "/test"
umaAllowedPath = "/pets"
umaForbiddenPath = "/pets/1"
umaNonExistentPath = "/cat"
umaMethodAllowedPath = "/horse"
umaFwdMethodAllowedPath = "/turtle"
postLoginRedirectPath = "/post/login/path"
pkceCookieName = "TESTPKCECOOKIE"
)

var idpRealmURI = fmt.Sprintf("%s/realms/%s", idpURI, testRealm)
Expand Down Expand Up @@ -145,7 +148,7 @@ var _ = Describe("NoRedirects Simple login/logout", func() {
})
})

var _ = Describe("Code Flow Simple login/logout", func() {
var _ = Describe("Code Flow login/logout", func() {
var portNum string
var proxyAddress string

Expand All @@ -167,6 +170,7 @@ var _ = Describe("Code Flow Simple login/logout", func() {
"--skip-access-token-issuer-check=true",
"--openid-provider-retry-count=30",
"--secure-cookie=false",
"--post-login-redirect-path=" + postLoginRedirectPath,
}

osArgs = append(osArgs, proxyArgs...)
Expand All @@ -178,6 +182,9 @@ var _ = Describe("Code Flow Simple login/logout", func() {
rClient := resty.New()
resp := codeFlowLogin(rClient, proxyAddress, http.StatusOK)
Expect(resp.Header().Get("Proxy-Accepted")).To(Equal("true"))
body := resp.Body()
Expect(strings.Contains(string(body), postLoginRedirectPath)).To(BeTrue())

resp, err = rClient.R().Get(proxyAddress + "/oauth/logout")
Expect(err).NotTo(HaveOccurred())
Expect(resp.StatusCode()).To(Equal(http.StatusOK))
Expand All @@ -191,7 +198,6 @@ var _ = Describe("Code Flow Simple login/logout", func() {
var _ = Describe("Code Flow PKCE login/logout", func() {
var portNum string
var proxyAddress string
var pkceCookieName = "TESTPKCECOOKIE"

BeforeEach(func() {
server := httptest.NewServer(&testsuite.FakeUpstreamService{})
Expand Down Expand Up @@ -236,3 +242,98 @@ var _ = Describe("Code Flow PKCE login/logout", func() {
Expect(resp.StatusCode()).To(Equal(http.StatusSeeOther))
})
})

var _ = Describe("Code Flow login/logout with session check", func() {
var portNum string
var proxyAddressFirst string
var proxyAddressSec string

BeforeEach(func() {
server := httptest.NewServer(&testsuite.FakeUpstreamService{})
portNum = generateRandomPort()
proxyAddressFirst = "http://127.0.0.1:" + portNum

osArgs := []string{os.Args[0]}
proxyArgs := []string{
"--discovery-url=" + idpRealmURI,
"--openid-provider-timeout=120s",
"--listen=" + "0.0.0.0:" + portNum,
"--client-id=" + testClient,
"--client-secret=" + testClientSecret,
"--upstream-url=" + server.URL,
"--no-redirects=false",
"--skip-access-token-clientid-check=true",
"--skip-access-token-issuer-check=true",
"--openid-provider-retry-count=30",
"--secure-cookie=false",
"--enable-idp-session-check=true",
"--enable-logout-redirect=true",
"--post-logout-redirect-uri=http://google.com",
}

osArgs = append(osArgs, proxyArgs...)
startAndWait(portNum, osArgs)

portNum = generateRandomPort()
proxyAddressSec = "http://localhost:" + portNum
osArgs = []string{os.Args[0]}
proxyArgs = []string{
"--discovery-url=" + idpRealmURI,
"--openid-provider-timeout=120s",
"--listen=" + "0.0.0.0:" + portNum,
"--client-id=" + pkceTestClient,
"--client-secret=" + pkceTestClientSecret,
"--upstream-url=" + server.URL,
"--no-redirects=false",
"--skip-access-token-clientid-check=true",
"--skip-access-token-issuer-check=true",
"--openid-provider-retry-count=30",
"--secure-cookie=false",
"--enable-pkce=true",
"--cookie-pkce-name=" + pkceCookieName,
"--enable-idp-session-check=true",
"--enable-logout-redirect=true",
"--post-logout-redirect-uri=http://google.com",
}

osArgs = append(osArgs, proxyArgs...)
startAndWait(portNum, osArgs)
})

When("Login user with one browser client on two clients/app and logout on one of them", func() {
It("should logout on both successfully", func(ctx context.Context) {
var err error
rClient := resty.New()
resp := codeFlowLogin(rClient, proxyAddressFirst, http.StatusOK)
Expect(resp.Header().Get("Proxy-Accepted")).To(Equal("true"))
resp = codeFlowLogin(rClient, proxyAddressSec, http.StatusOK)
Expect(resp.Header().Get("Proxy-Accepted")).To(Equal("true"))

resp, err = rClient.R().Get(proxyAddressFirst + testPath)
Expect(err).NotTo(HaveOccurred())
body := resp.Body()
Expect(strings.Contains(string(body), testPath)).To(BeTrue())

resp, err = rClient.R().Get(proxyAddressSec + testPath)
Expect(err).NotTo(HaveOccurred())
Expect(resp.StatusCode()).To(Equal(http.StatusOK))
body = resp.Body()
Expect(strings.Contains(string(body), testPath)).To(BeTrue())

By("Logout user on first client")
resp, err = rClient.R().Get(proxyAddressFirst + "/oauth/logout")
Expect(err).NotTo(HaveOccurred())
Expect(resp.StatusCode()).To(Equal(http.StatusOK))

By("Verify logged out on second client")
rClient.SetRedirectPolicy(resty.NoRedirectPolicy())
resp, _ = rClient.R().Get(proxyAddressSec)
Expect(resp.StatusCode()).To(Equal(http.StatusSeeOther))

By("Verify logged out on first client")
rClient.SetRedirectPolicy(resty.NoRedirectPolicy())
resp, _ = rClient.R().Get(proxyAddressFirst)
Expect(resp.StatusCode()).To(Equal(http.StatusSeeOther))
})
})
})
59 changes: 30 additions & 29 deletions pkg/apperrors/apperrors.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,34 @@ import (
)

var (
ErrPermissionNotInToken = errors.New("permissions missing in token")
ErrResourceRetrieve = errors.New("problem getting resources from IDP")
ErrTokenScopeNotMatchResourceScope = errors.New("scopes in token doesn't match scopes in IDP resource")
ErrMissingScopesForResource = errors.New("missing scopes for resource in IDP provider")
ErrNoIDPResourceForPath = errors.New("could not find resource matching path")
ErrTooManyResources = errors.New("too many resources got from IDP (hint: probably you have multiple resources in IDP with same path and scopes combination)")
ErrResourceIDNotPresent = errors.New("resource id not present in token permissions")
ErrPermissionTicketForResourceID = errors.New("problem getting permission ticket for resourceId")
ErrRetrieveRPT = errors.New("problem getting RPT for resource (hint: do you have permissions assigned to resource?)")
ErrAccessMismatchUmaToken = errors.New("access token and uma token user ID don't match")
ErrNoAuthzFound = errors.New("no authz found")
ErrGetIdentityFromUMA = errors.New("problem getting identity from uma token")
ErrFailedAuthzRequest = errors.New("unexpected error occurred during authz request")
ErrSessionNotFound = errors.New("authentication session not found")
ErrNoSessionStateFound = errors.New("no session state found")
ErrZeroLengthToken = errors.New("token has zero length")
ErrInvalidSession = errors.New("invalid session identifier")
ErrRefreshTokenExpired = errors.New("refresh token has expired")
ErrUMATokenExpired = errors.New("uma token expired")
ErrTokenVerificationFailure = errors.New("token verification failed")
ErrDecryption = errors.New("failed to decrypt token")
ErrDefaultDenyWhitelistConflict = errors.New("you've asked for a default denial but whitelisted everything")
ErrDefaultDenyUserDefinedConflict = errors.New("you've enabled default deny and at the same time defined own rules for /*")
ErrBadDiscoveryURIFormat = errors.New("bad discovery url format")
ErrForwardAuthMissingHeaders = errors.New("seems you are using gatekeeper as forward-auth, but you don't forward X-FORWARDED-* headers from front proxy")
ErrPKCEWithCodeOnly = errors.New("pkce can be enabled only with no-redirect=false")
ErrPKCECodeCreation = errors.New("creation of code verifier failed")
ErrPKCECookieEmpty = errors.New("seems that pkce code verifier cookie value is empty string")
ErrInvalidPostLoginRedirectPath = errors.New("post login redirect path invalid, should be only path not absolute url (no hostname, scheme)")
ErrPermissionNotInToken = errors.New("permissions missing in token")
ErrResourceRetrieve = errors.New("problem getting resources from IDP")
ErrTokenScopeNotMatchResourceScope = errors.New("scopes in token doesn't match scopes in IDP resource")
ErrMissingScopesForResource = errors.New("missing scopes for resource in IDP provider")
ErrNoIDPResourceForPath = errors.New("could not find resource matching path")
ErrTooManyResources = errors.New("too many resources got from IDP (hint: probably you have multiple resources in IDP with same path and scopes combination)")
ErrResourceIDNotPresent = errors.New("resource id not present in token permissions")
ErrPermissionTicketForResourceID = errors.New("problem getting permission ticket for resourceId")
ErrRetrieveRPT = errors.New("problem getting RPT for resource (hint: do you have permissions assigned to resource?)")
ErrAccessMismatchUmaToken = errors.New("access token and uma token user ID don't match")
ErrNoAuthzFound = errors.New("no authz found")
ErrGetIdentityFromUMA = errors.New("problem getting identity from uma token")
ErrFailedAuthzRequest = errors.New("unexpected error occurred during authz request")
ErrSessionNotFound = errors.New("authentication session not found")
ErrNoSessionStateFound = errors.New("no session state found")
ErrZeroLengthToken = errors.New("token has zero length")
ErrInvalidSession = errors.New("invalid session identifier")
ErrRefreshTokenExpired = errors.New("refresh token has expired")
ErrUMATokenExpired = errors.New("uma token expired")
ErrTokenVerificationFailure = errors.New("token verification failed")
ErrDecryption = errors.New("failed to decrypt token")
ErrDefaultDenyWhitelistConflict = errors.New("you've asked for a default denial but whitelisted everything")
ErrDefaultDenyUserDefinedConflict = errors.New("you've enabled default deny and at the same time defined own rules for /*")
ErrBadDiscoveryURIFormat = errors.New("bad discovery url format")
ErrForwardAuthMissingHeaders = errors.New("seems you are using gatekeeper as forward-auth, but you don't forward X-FORWARDED-* headers from front proxy")
ErrPKCEWithCodeOnly = errors.New("pkce can be enabled only with no-redirect=false")
ErrPKCECodeCreation = errors.New("creation of code verifier failed")
ErrPKCECookieEmpty = errors.New("seems that pkce code verifier cookie value is empty string")
ErrInvalidPostLoginRedirectPath = errors.New("post login redirect path invalid, should be only path not absolute url (no hostname, scheme)")
ErrPostLoginRedirectPathNoRedirectsInvalid = errors.New("post login redirect path can be enabled only with no-redirect=false")
)
3 changes: 3 additions & 0 deletions pkg/keycloak/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,9 @@ func (r *Config) isPKCEValid() error {
}

func (r *Config) isPostLoginRedirectValid() error {
if r.PostLoginRedirectPath != "" && r.NoRedirects {
return apperrors.ErrPostLoginRedirectPathNoRedirectsInvalid
}
if r.PostLoginRedirectPath != "" {
parsedURI, err := url.ParseRequestURI(r.PostLoginRedirectPath)
if err != nil {
Expand Down
55 changes: 55 additions & 0 deletions pkg/keycloak/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2280,3 +2280,58 @@ func TestIsPKCEValid(t *testing.T) {
)
}
}

func TestIsPostLoginRedirectValid(t *testing.T) {
testCases := []struct {
Name string
Config *Config
Valid bool
}{
{
Name: "OK",
Config: &Config{
PostLoginRedirectPath: "/some/path",
},
Valid: true,
},
{
Name: "OK complex URI",
Config: &Config{
PostLoginRedirectPath: "/some/path?someparam=lala",
},
Valid: true,
},
{
Name: "InvalidPostLoginRedirectPath",
Config: &Config{
PostLoginRedirectPath: "http://somehost/some/path",
},
Valid: false,
},
{
Name: "InvalidCombinationPostLoginRedirectPathWithNoRedirects",
Config: &Config{
PostLoginRedirectPath: "/some/path",
NoRedirects: true,
},
Valid: false,
},
}

for _, testCase := range testCases {
testCase := testCase
t.Run(
testCase.Name,
func(t *testing.T) {
err := testCase.Config.isPostLoginRedirectValid()
if err != nil && testCase.Valid {
t.Fatalf("Expected test not to fail")
}

if err == nil && !testCase.Valid {
t.Fatalf("Expected test to fail")
}
},
)
}
}

0 comments on commit 09aa27f

Please sign in to comment.