mirror of
https://github.com/openziti/ziti.git
synced 2026-09-10 00:35:41 +00:00
86092a8640
Bumps the sdk-golang dependency from v1 to the v2 module (`github.com/openziti/sdk-golang/v2` at v2.0.0-pre1) and updates all import paths. This is a no-behavior-change precursor that isolates the dependency migration from the Connect-V2 feature work in #3884. - Rewrites `github.com/openziti/sdk-golang/...` imports to `github.com/openziti/sdk-golang/v2/...` across the main and zititest modules. - Pins both modules to `github.com/openziti/sdk-golang/v2 v2.0.0-pre1`. - Adapts `edgeXgressConn.AcceptMessage` to the v2 `MsgSink` signature, which now takes an `edge.SdkChannel` argument. - Replaces the removed `edge.Conn.GetRouterId()` with `RemoteAddr().String()` in the loop4 traffic-test logging. For openziti/sdk-golang#936.
519 lines
18 KiB
Go
519 lines
18 KiB
Go
//go:build apitests
|
|
|
|
package tests
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/google/uuid"
|
|
edge_apis "github.com/openziti/sdk-golang/v2/edge-apis"
|
|
"github.com/openziti/ziti/v2/common"
|
|
"github.com/openziti/ziti/v2/controller/change"
|
|
"github.com/openziti/ziti/v2/controller/model"
|
|
"github.com/openziti/ziti/v2/controller/models"
|
|
"github.com/openziti/ziti/v2/controller/oidc_auth"
|
|
"github.com/zitadel/oidc/v3/pkg/oidc"
|
|
)
|
|
|
|
// oidcErrorResponse matches the JSON structure returned by the zitadel/oidc library's
|
|
// WriteError handler for OAuth/OIDC error responses.
|
|
type oidcErrorResponse struct {
|
|
Error string `json:"error"`
|
|
ErrorDescription string `json:"error_description"`
|
|
}
|
|
|
|
func Test_OIDC_ErrorCodes(t *testing.T) {
|
|
ctx := NewTestContext(t)
|
|
defer ctx.Teardown()
|
|
ctx.StartServer()
|
|
|
|
clientHelper := ctx.NewEdgeClientApi(nil)
|
|
adminCreds := ctx.NewAdminCredentials()
|
|
tokenUrl := "https://" + ctx.ApiHost + "/oidc/oauth/token"
|
|
|
|
// Authenticate once to get valid tokens for reuse in sub-tests.
|
|
tokens, _, err := clientHelper.RawOidcAuthRequest(adminCreds)
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.NotNil(tokens)
|
|
|
|
t.Run("token endpoint returns invalid_client for unknown client_id", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
resp, err := ctx.newAnonymousClientApiRequest().
|
|
SetHeader("content-type", oidc_auth.FormContentType).
|
|
SetFormData(map[string]string{
|
|
"grant_type": string(oidc.GrantTypeRefreshToken),
|
|
"refresh_token": tokens.RefreshToken,
|
|
"client_id": "nonexistent-client",
|
|
}).
|
|
Post(tokenUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Equal(http.StatusBadRequest, resp.StatusCode(),
|
|
"expected 400 for unknown client, got %d: %s", resp.StatusCode(), resp.String())
|
|
|
|
errResp := parseOidcError(t, resp.Body())
|
|
ctx.Req.Equal("invalid_client", errResp.Error)
|
|
ctx.Req.NotEmpty(errResp.ErrorDescription, "expected non-empty error_description")
|
|
})
|
|
|
|
t.Run("token endpoint returns invalid_grant for garbage refresh_token", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
resp, err := ctx.newAnonymousClientApiRequest().
|
|
SetHeader("content-type", oidc_auth.FormContentType).
|
|
SetFormData(map[string]string{
|
|
"grant_type": string(oidc.GrantTypeRefreshToken),
|
|
"refresh_token": "not-a-valid-jwt",
|
|
"client_id": tokens.IDTokenClaims.ClientID,
|
|
}).
|
|
Post(tokenUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Equal(http.StatusBadRequest, resp.StatusCode(),
|
|
"expected 400 for bad refresh token, got %d: %s", resp.StatusCode(), resp.String())
|
|
|
|
errResp := parseOidcError(t, resp.Body())
|
|
ctx.Req.Equal("invalid_grant", errResp.Error)
|
|
ctx.Req.NotEmpty(errResp.ErrorDescription, "expected non-empty error_description")
|
|
})
|
|
|
|
t.Run("token endpoint returns invalid_grant for expired or revoked refresh_token", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
// Use an access token (wrong token type) as the refresh_token value.
|
|
// parseRefreshToken will reject this because the token type claim is not "refresh".
|
|
resp, err := ctx.newAnonymousClientApiRequest().
|
|
SetHeader("content-type", oidc_auth.FormContentType).
|
|
SetFormData(map[string]string{
|
|
"grant_type": string(oidc.GrantTypeRefreshToken),
|
|
"refresh_token": tokens.AccessToken,
|
|
"client_id": tokens.IDTokenClaims.ClientID,
|
|
}).
|
|
Post(tokenUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Equal(http.StatusBadRequest, resp.StatusCode(),
|
|
"expected 400 for wrong token type, got %d: %s", resp.StatusCode(), resp.String())
|
|
|
|
errResp := parseOidcError(t, resp.Body())
|
|
ctx.Req.Equal("invalid_grant", errResp.Error)
|
|
})
|
|
|
|
t.Run("token endpoint returns invalid_grant for missing grant_type", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
resp, err := ctx.newAnonymousClientApiRequest().
|
|
SetHeader("content-type", oidc_auth.FormContentType).
|
|
SetFormData(map[string]string{
|
|
"refresh_token": tokens.RefreshToken,
|
|
"client_id": tokens.IDTokenClaims.ClientID,
|
|
}).
|
|
Post(tokenUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Equal(http.StatusBadRequest, resp.StatusCode(),
|
|
"expected 400 for missing grant_type, got %d: %s", resp.StatusCode(), resp.String())
|
|
|
|
errResp := parseOidcError(t, resp.Body())
|
|
ctx.Req.Equal("invalid_request", errResp.Error)
|
|
})
|
|
|
|
t.Run("token endpoint returns error for unsupported grant_type", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
resp, err := ctx.newAnonymousClientApiRequest().
|
|
SetHeader("content-type", oidc_auth.FormContentType).
|
|
SetFormData(map[string]string{
|
|
"grant_type": "urn:fake:unsupported",
|
|
"client_id": tokens.IDTokenClaims.ClientID,
|
|
}).
|
|
Post(tokenUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Equal(http.StatusBadRequest, resp.StatusCode(),
|
|
"expected 400 for unsupported grant_type, got %d: %s", resp.StatusCode(), resp.String())
|
|
|
|
errResp := parseOidcError(t, resp.Body())
|
|
ctx.Req.Equal("unsupported_grant_type", errResp.Error)
|
|
})
|
|
|
|
t.Run("token endpoint returns invalid_grant for code exchange with invalid code", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
resp, err := ctx.newAnonymousClientApiRequest().
|
|
SetHeader("content-type", oidc_auth.FormContentType).
|
|
SetFormData(map[string]string{
|
|
"grant_type": string(oidc.GrantTypeCode),
|
|
"code": "bogus-auth-code",
|
|
"redirect_uri": "http://localhost:18643/auth/callback",
|
|
"client_id": tokens.IDTokenClaims.ClientID,
|
|
"code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
|
|
}).
|
|
Post(tokenUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Equal(http.StatusBadRequest, resp.StatusCode(),
|
|
"expected 400 for invalid code, got %d: %s", resp.StatusCode(), resp.String())
|
|
|
|
errResp := parseOidcError(t, resp.Body())
|
|
ctx.Req.Equal("invalid_grant", errResp.Error)
|
|
})
|
|
|
|
t.Run("login endpoint returns 401 for invalid credentials", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
result, err := clientHelper.OidcAuthorize(adminCreds)
|
|
ctx.Req.NoError(err)
|
|
|
|
loginUrl := "https://" + ctx.ApiHost + "/oidc/login/username?id=" + result.AuthRequestId
|
|
|
|
resp, err := result.Client.R().
|
|
SetHeader("Accept", "application/json").
|
|
SetFormData(map[string]string{
|
|
"username": ctx.AdminAuthenticator.Username,
|
|
"password": "wrong-password",
|
|
}).
|
|
Post(loginUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Equal(http.StatusUnauthorized, resp.StatusCode(),
|
|
"expected 401 for bad password, got %d: %s", resp.StatusCode(), resp.String())
|
|
})
|
|
|
|
t.Run("login endpoint returns 401 for unknown username", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
result, err := clientHelper.OidcAuthorize(adminCreds)
|
|
ctx.Req.NoError(err)
|
|
|
|
loginUrl := "https://" + ctx.ApiHost + "/oidc/login/username?id=" + result.AuthRequestId
|
|
|
|
resp, err := result.Client.R().
|
|
SetHeader("Accept", "application/json").
|
|
SetFormData(map[string]string{
|
|
"username": "nonexistent-user",
|
|
"password": "irrelevant",
|
|
}).
|
|
Post(loginUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Equal(http.StatusUnauthorized, resp.StatusCode(),
|
|
"expected 401 for unknown user, got %d: %s", resp.StatusCode(), resp.String())
|
|
})
|
|
|
|
t.Run("userinfo endpoint returns 401 for invalid access token", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
userinfoUrl := "https://" + ctx.ApiHost + "/oidc/userinfo"
|
|
|
|
resp, err := ctx.newAnonymousClientApiRequest().
|
|
SetHeader("Authorization", "Bearer not-a-valid-jwt").
|
|
Get(userinfoUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Equal(http.StatusUnauthorized, resp.StatusCode(),
|
|
"expected 401 for bad access token, got %d: %s", resp.StatusCode(), resp.String())
|
|
})
|
|
|
|
t.Run("userinfo endpoint returns 401 for missing access token", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
userinfoUrl := "https://" + ctx.ApiHost + "/oidc/userinfo"
|
|
|
|
resp, err := ctx.newAnonymousClientApiRequest().
|
|
Get(userinfoUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Equal(http.StatusUnauthorized, resp.StatusCode(),
|
|
"expected 401 for missing token, got %d: %s", resp.StatusCode(), resp.String())
|
|
})
|
|
|
|
t.Run("token refresh preserves correct error types after successful auth", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
// Authenticate fresh to get a known-good refresh token
|
|
freshTokens, _, err := clientHelper.RawOidcAuthRequest(adminCreds)
|
|
ctx.Req.NoError(err)
|
|
|
|
// Refresh with invalid scopes should return invalid_scope
|
|
enc := oidc.NewEncoder()
|
|
dst := map[string][]string{}
|
|
req := &oidc.RefreshTokenRequest{
|
|
RefreshToken: freshTokens.RefreshToken,
|
|
ClientID: freshTokens.IDTokenClaims.ClientID,
|
|
Scopes: []string{"bogus-scope-not-in-original"},
|
|
}
|
|
err = enc.Encode(req, dst)
|
|
ctx.Req.NoError(err)
|
|
dst["grant_type"] = []string{string(req.GrantType())}
|
|
|
|
resp, err := ctx.newAnonymousClientApiRequest().
|
|
SetHeader("content-type", oidc_auth.FormContentType).
|
|
SetMultiValueFormData(dst).
|
|
Post(tokenUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Equal(http.StatusBadRequest, resp.StatusCode(),
|
|
"expected 400 for invalid scope, got %d: %s", resp.StatusCode(), resp.String())
|
|
|
|
errResp := parseOidcError(t, resp.Body())
|
|
ctx.Req.Equal("invalid_scope", errResp.Error)
|
|
})
|
|
|
|
t.Run("token endpoint returns invalid_grant with expired description for expired refresh_token", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
// Manufacture a refresh token that is already expired by signing it
|
|
// with the controller's own key.
|
|
signer := ctx.EdgeController.AppEnv.GetRootTlsJwtSigner()
|
|
now := time.Now()
|
|
expiredClaims := &common.RefreshClaims{
|
|
IDTokenClaims: oidc.IDTokenClaims{
|
|
TokenClaims: oidc.TokenClaims{
|
|
JWTID: uuid.NewString(),
|
|
Issuer: "https://" + ctx.ApiHost + "/oidc",
|
|
Subject: "fake-identity",
|
|
Expiration: oidc.Time(now.Add(-1 * time.Hour).Unix()),
|
|
IssuedAt: oidc.Time(now.Add(-2 * time.Hour).Unix()),
|
|
NotBefore: oidc.Time(now.Add(-2 * time.Hour).Unix()),
|
|
},
|
|
},
|
|
CustomClaims: common.CustomClaims{
|
|
Type: common.TokenTypeRefresh,
|
|
},
|
|
}
|
|
|
|
expiredToken, err := signer.Generate(expiredClaims)
|
|
ctx.Req.NoError(err)
|
|
|
|
resp, err := ctx.newAnonymousClientApiRequest().
|
|
SetHeader("content-type", oidc_auth.FormContentType).
|
|
SetFormData(map[string]string{
|
|
"grant_type": string(oidc.GrantTypeRefreshToken),
|
|
"refresh_token": expiredToken,
|
|
"client_id": tokens.IDTokenClaims.ClientID,
|
|
}).
|
|
Post(tokenUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Equal(http.StatusBadRequest, resp.StatusCode(),
|
|
"expected 400 for expired refresh token, got %d: %s", resp.StatusCode(), resp.String())
|
|
|
|
errResp := parseOidcError(t, resp.Body())
|
|
ctx.Req.Equal("invalid_grant", errResp.Error)
|
|
ctx.Req.Contains(errResp.ErrorDescription, "expired")
|
|
})
|
|
|
|
t.Run("token endpoint returns invalid_grant for revoked refresh_token", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
// Authenticate to get a valid refresh token, then revoke it via the DB
|
|
freshTokens, _, err := clientHelper.RawOidcAuthRequest(adminCreds)
|
|
ctx.Req.NoError(err)
|
|
|
|
// Parse the refresh token to get its JWTID
|
|
refreshClaims := &common.RefreshClaims{}
|
|
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
|
|
_, _, err = parser.ParseUnverified(freshTokens.RefreshToken, refreshClaims)
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.NotEmpty(refreshClaims.JWTID)
|
|
|
|
// Plant a revocation record for this token's JWTID
|
|
changeCtx := change.New().SetSourceType("test").SetChangeAuthorType(change.AuthorTypeController)
|
|
err = ctx.EdgeController.AppEnv.Managers.Revocation.Create(&model.Revocation{
|
|
BaseEntity: models.BaseEntity{Id: refreshClaims.JWTID},
|
|
ExpiresAt: time.Now().Add(1 * time.Hour),
|
|
}, changeCtx)
|
|
ctx.Req.NoError(err)
|
|
|
|
// Try to refresh with the revoked token
|
|
resp, err := ctx.newAnonymousClientApiRequest().
|
|
SetHeader("content-type", oidc_auth.FormContentType).
|
|
SetFormData(map[string]string{
|
|
"grant_type": string(oidc.GrantTypeRefreshToken),
|
|
"refresh_token": freshTokens.RefreshToken,
|
|
"client_id": freshTokens.IDTokenClaims.ClientID,
|
|
}).
|
|
Post(tokenUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Equal(http.StatusBadRequest, resp.StatusCode(),
|
|
"expected 400 for revoked refresh token, got %d: %s", resp.StatusCode(), resp.String())
|
|
|
|
errResp := parseOidcError(t, resp.Body())
|
|
ctx.Req.Equal("invalid_grant", errResp.Error)
|
|
ctx.Req.Contains(errResp.ErrorDescription, "revoked")
|
|
})
|
|
|
|
t.Run("userinfo endpoint returns error for revoked access token", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
// Authenticate to get a valid access token, then revoke it
|
|
freshTokens, _, err := clientHelper.RawOidcAuthRequest(adminCreds)
|
|
ctx.Req.NoError(err)
|
|
|
|
// Parse the access token to get its JWTID
|
|
accessClaims := &common.AccessClaims{}
|
|
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
|
|
_, _, err = parser.ParseUnverified(freshTokens.AccessToken, accessClaims)
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.NotEmpty(accessClaims.JWTID)
|
|
|
|
// Plant a revocation record for this token's JWTID
|
|
changeCtx := change.New().SetSourceType("test").SetChangeAuthorType(change.AuthorTypeController)
|
|
err = ctx.EdgeController.AppEnv.Managers.Revocation.Create(&model.Revocation{
|
|
BaseEntity: models.BaseEntity{Id: accessClaims.JWTID},
|
|
ExpiresAt: time.Now().Add(1 * time.Hour),
|
|
}, changeCtx)
|
|
ctx.Req.NoError(err)
|
|
|
|
// Try to use the revoked access token on userinfo
|
|
userinfoUrl := "https://" + ctx.ApiHost + "/oidc/userinfo"
|
|
resp, err := ctx.newAnonymousClientApiRequest().
|
|
SetHeader("Authorization", "Bearer "+freshTokens.AccessToken).
|
|
Get(userinfoUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.NotEqual(http.StatusOK, resp.StatusCode(),
|
|
"expected error for revoked access token, got %d: %s", resp.StatusCode(), resp.String())
|
|
})
|
|
|
|
t.Run("end_session endpoint is reachable", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
endSessionUrl := "https://" + ctx.ApiHost + "/oidc/end_session"
|
|
|
|
// POST with no id_token_hint. The endpoint redirects to /oidc/logged-out
|
|
// on success (even with no hint), so disable redirects and check the
|
|
// status directly.
|
|
resp, err := ctx.DefaultClientApiClient().
|
|
SetRedirectPolicy().R().
|
|
SetHeader("content-type", oidc_auth.FormContentType).
|
|
Post(endSessionUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Less(resp.StatusCode(), 500,
|
|
"expected non-server-error, got %d: %s", resp.StatusCode(), resp.String())
|
|
})
|
|
|
|
t.Run("well-known openid-configuration is accessible", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
discoveryUrl := "https://" + ctx.ApiHost + "/oidc/.well-known/openid-configuration"
|
|
|
|
resp, err := ctx.newAnonymousClientApiRequest().Get(discoveryUrl)
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Equal(http.StatusOK, resp.StatusCode(),
|
|
"expected 200 for discovery, got %d: %s", resp.StatusCode(), resp.String())
|
|
|
|
// Verify it's valid JSON with expected fields
|
|
var discovery map[string]interface{}
|
|
err = json.Unmarshal(resp.Body(), &discovery)
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Contains(discovery, "issuer")
|
|
ctx.Req.Contains(discovery, "token_endpoint")
|
|
ctx.Req.Contains(discovery, "authorization_endpoint")
|
|
})
|
|
|
|
t.Run("successful OIDC auth still works end to end", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
// Full auth flow should still succeed after all the error handling changes
|
|
freshTokens, _, err := clientHelper.RawOidcAuthRequest(adminCreds)
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.NotEmpty(freshTokens.AccessToken)
|
|
ctx.Req.NotEmpty(freshTokens.RefreshToken)
|
|
ctx.Req.NotEmpty(freshTokens.IDToken)
|
|
|
|
// Use the access token to hit the userinfo endpoint
|
|
userinfoUrl := "https://" + ctx.ApiHost + "/oidc/userinfo"
|
|
resp, err := ctx.newAnonymousClientApiRequest().
|
|
SetHeader("Authorization", "Bearer "+freshTokens.AccessToken).
|
|
Get(userinfoUrl)
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Equal(http.StatusOK, resp.StatusCode(),
|
|
"expected 200 for valid userinfo request, got %d: %s", resp.StatusCode(), resp.String())
|
|
|
|
// Refresh should work
|
|
enc := oidc.NewEncoder()
|
|
dst := map[string][]string{}
|
|
req := &oidc.RefreshTokenRequest{
|
|
RefreshToken: freshTokens.RefreshToken,
|
|
ClientID: freshTokens.IDTokenClaims.ClientID,
|
|
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeOfflineAccess},
|
|
}
|
|
err = enc.Encode(req, dst)
|
|
ctx.Req.NoError(err)
|
|
dst["grant_type"] = []string{string(req.GrantType())}
|
|
|
|
resp, err = ctx.newAnonymousClientApiRequest().
|
|
SetHeader("content-type", oidc_auth.FormContentType).
|
|
SetMultiValueFormData(dst).
|
|
Post(tokenUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.Equal(http.StatusOK, resp.StatusCode(),
|
|
"expected 200 for valid refresh, got %d: %s", resp.StatusCode(), resp.String())
|
|
})
|
|
}
|
|
|
|
func Test_OIDC_ErrorCodes_CertAuth(t *testing.T) {
|
|
ctx := NewTestContext(t)
|
|
defer ctx.Teardown()
|
|
ctx.StartServer()
|
|
ctx.RequireAdminManagementApiLogin()
|
|
|
|
clientHelper := ctx.NewEdgeClientApi(nil)
|
|
adminCreds := ctx.NewAdminCredentials()
|
|
|
|
_, certAuth := ctx.AdminManagementSession.requireCreateIdentityOttEnrollment("oidc-err-cert-test", false)
|
|
certCreds := edge_apis.NewCertCredentials(certAuth.certs, certAuth.key)
|
|
certCreds.CaPool = ctx.ControllerCaPool()
|
|
|
|
t.Run("cert auth OIDC flow succeeds", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
tokens, _, err := clientHelper.RawOidcAuthRequest(certCreds)
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.NotEmpty(tokens.AccessToken)
|
|
})
|
|
|
|
t.Run("login endpoint returns error for invalid auth request id", func(t *testing.T) {
|
|
ctx.NextTest(t)
|
|
|
|
result, err := clientHelper.OidcAuthorize(adminCreds)
|
|
ctx.Req.NoError(err)
|
|
|
|
loginUrl := "https://" + ctx.ApiHost + "/oidc/login/username?id=bogus-request-id"
|
|
|
|
resp, err := result.Client.R().
|
|
SetHeader("Accept", "application/json").
|
|
SetFormData(map[string]string{
|
|
"username": ctx.AdminAuthenticator.Username,
|
|
"password": ctx.AdminAuthenticator.Password,
|
|
}).
|
|
Post(loginUrl)
|
|
|
|
ctx.Req.NoError(err)
|
|
ctx.Req.GreaterOrEqual(resp.StatusCode(), 400,
|
|
"expected error for bogus request id, got %d: %s", resp.StatusCode(), resp.String())
|
|
ctx.Req.Less(resp.StatusCode(), 500,
|
|
"expected client error not server error, got %d: %s", resp.StatusCode(), resp.String())
|
|
})
|
|
}
|
|
|
|
// parseOidcError unmarshals an OIDC/OAuth error response body.
|
|
func parseOidcError(t *testing.T, body []byte) oidcErrorResponse {
|
|
t.Helper()
|
|
var errResp oidcErrorResponse
|
|
if err := json.Unmarshal(body, &errResp); err != nil {
|
|
t.Fatalf("failed to parse OIDC error response: %v\nbody: %s", err, string(body))
|
|
}
|
|
return errResp
|
|
}
|