From 17c4c934fd7f4a6a4ec8e74ed98d43a9fa7b8414 Mon Sep 17 00:00:00 2001 From: Andrew Martinez Date: Fri, 11 Oct 2024 15:10:40 -0400 Subject: [PATCH] fixes #2474 legacy auth queries missing values - adds OIDC property checks on auth queries - adds legacy property checks on auth queries --- .../routes/current_api_session_api_model.go | 3 + tests/auth_external_jwt_signer_test.go | 125 +++++++++++++++++- tests/auth_oidc_test.go | 52 ++++++-- 3 files changed, 163 insertions(+), 17 deletions(-) diff --git a/controller/internal/routes/current_api_session_api_model.go b/controller/internal/routes/current_api_session_api_model.go index 8dc542679..d4476d1f8 100644 --- a/controller/internal/routes/current_api_session_api_model.go +++ b/controller/internal/routes/current_api_session_api_model.go @@ -90,6 +90,9 @@ func MapApiSessionAuthQueriesToRestEntity(ae *env.AppEnv, rc *response.RequestCo MinLength: authQuery.MinLength, Provider: authQuery.Provider, TypeID: authQuery.TypeID, + ClientID: authQuery.ClientID, + Scopes: authQuery.Scopes, + ID: authQuery.ID, }) } } diff --git a/tests/auth_external_jwt_signer_test.go b/tests/auth_external_jwt_signer_test.go index a37cb257a..2a4694c08 100644 --- a/tests/auth_external_jwt_signer_test.go +++ b/tests/auth_external_jwt_signer_test.go @@ -183,13 +183,20 @@ func Test_Authenticate_External_Jwt(t *testing.T) { validJwtSignerCert, validJwtSignerPrivateKey := newSelfSignedCert("valid signer") validJwtSignerCertPem := nfpem.EncodeToString(validJwtSignerCert) + validJwtSignerAuthUrl := "https://valid.jwt.signer.url.example.com" + validJwtSignerClientId := "valid-client-id" + validJwtSignerScopes := []string{"valid-scope1", "valid-scope2"} + validJwtSigner := &rest_model.ExternalJWTSignerCreate{ - CertPem: &validJwtSignerCertPem, - Enabled: B(true), - Name: S("Test JWT Signer - Enabled"), - Kid: S(uuid.NewString()), - Issuer: S("the-very-best-iss"), - Audience: S("the-very-best-aud"), + CertPem: &validJwtSignerCertPem, + Enabled: B(true), + Name: S("Test JWT Signer - Enabled"), + Kid: S(uuid.NewString()), + Issuer: S("the-very-best-iss"), + Audience: S("the-very-best-aud"), + ClientID: &validJwtSignerClientId, + Scopes: validJwtSignerScopes, + ExternalAuthURL: &validJwtSignerAuthUrl, } createResponseEnv = &rest_model.CreateEnvelope{} @@ -198,6 +205,7 @@ func Test_Authenticate_External_Jwt(t *testing.T) { ctx.Req.NoError(err) ctx.Req.Equal(http.StatusCreated, resp.StatusCode()) + validSignerUsingInternalId := createResponseEnv.Data.ID signerIds = append(signerIds, createResponseEnv.Data.ID) //valid signer w/ external id @@ -921,4 +929,109 @@ func Test_Authenticate_External_Jwt(t *testing.T) { ctx.Req.NotNil(result.Data) ctx.Req.NotNil(result.Data.Token) }) + + t.Run("can authenticate with cert auth and secondary ext-jwt", func(t *testing.T) { + ctx.testContextChanged(t) + + //create auth policy, new identity w/ policy, create token for authentication + authPolicyCertExtJwt := &rest_model.AuthPolicyCreate{ + Name: ToPtr("createCertJwtAuthPolicy"), + Primary: &rest_model.AuthPolicyPrimary{ + Cert: &rest_model.AuthPolicyPrimaryCert{ + AllowExpiredCerts: ToPtr(true), + Allowed: ToPtr(true), + }, + ExtJWT: &rest_model.AuthPolicyPrimaryExtJWT{ + AllowedSigners: []string{}, + Allowed: ToPtr(false), + }, + Updb: &rest_model.AuthPolicyPrimaryUpdb{ + Allowed: ToPtr(false), + LockoutDurationMinutes: ToPtr(int64(0)), + MaxAttempts: ToPtr(int64(3)), + MinPasswordLength: ToPtr(int64(7)), + RequireMixedCase: ToPtr(true), + RequireNumberChar: ToPtr(true), + RequireSpecialChar: ToPtr(true), + }, + }, + Secondary: &rest_model.AuthPolicySecondary{ + RequireExtJWTSigner: ToPtr(validSignerUsingInternalId), + RequireTotp: ToPtr(false), + }, + } + + createCertJwtAuthPolicy := &rest_model.CreateEnvelope{} + resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetResult(createCertJwtAuthPolicy).SetBody(authPolicyCertExtJwt).Post("/auth-policies") + ctx.NoError(err) + ctx.Equal(http.StatusCreated, resp.StatusCode(), string(resp.Body())) + ctx.NotNil(createCertJwtAuthPolicy) + ctx.NotNil(createCertJwtAuthPolicy.Data) + ctx.NotEmpty(createCertJwtAuthPolicy.Data.ID) + + newId, certAuth := ctx.AdminManagementSession.requireCreateIdentityOttEnrollment(uuid.NewString(), false) + + identityPatch := &rest_model.IdentityPatch{ + AuthPolicyID: ToPtr(createCertJwtAuthPolicy.Data.ID), + } + + resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(identityPatch).Patch("/identities/" + newId) + ctx.NoError(err) + ctx.Equal(http.StatusOK, resp.StatusCode(), string(resp.Body())) + + jwtToken := jwt.New(jwt.SigningMethodES256) + jwtToken.Claims = jwt.RegisteredClaims{ + Audience: []string{*validJwtSigner.Audience}, + ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(2 * time.Hour)}, + ID: time.Now().String(), + IssuedAt: &jwt.NumericDate{Time: time.Now()}, + Issuer: *validJwtSigner.Issuer, + NotBefore: &jwt.NumericDate{Time: time.Now()}, + Subject: newId, + } + + jwtToken.Header["kid"] = *validJwtSigner.Kid + + jwtStrSigned, err := jwtToken.SignedString(validJwtSignerPrivateKey) + ctx.Req.NoError(err) + ctx.Req.NotEmpty(jwtStrSigned) + + t.Run("authenticating with cert and jwt yields 0 auth queries", func(t *testing.T) { + ctx.testContextChanged(t) + + result := &rest_model.CurrentAPISessionDetailEnvelope{} + + testClient, _, transport := ctx.NewClientComponents(EdgeClientApiPath) + + transport.TLSClientConfig.Certificates = certAuth.TLSCertificates() + + resp, err := testClient.NewRequest().SetResult(result).SetHeader("Authorization", "Bearer "+jwtStrSigned).Post("/authenticate?method=cert") + ctx.Req.NoError(err) + ctx.Req.Equal(http.StatusOK, resp.StatusCode(), string(resp.Body())) + ctx.Req.Empty(result.Data.AuthQueries) + }) + + t.Run("authenticating with cert and jwt yields 1 ext-jwt auth query", func(t *testing.T) { + ctx.testContextChanged(t) + + result := &rest_model.CurrentAPISessionDetailEnvelope{} + + testClient, _, transport := ctx.NewClientComponents(EdgeClientApiPath) + + transport.TLSClientConfig.Certificates = certAuth.TLSCertificates() + + resp, err := testClient.NewRequest().SetResult(result).Post("/authenticate?method=cert") + ctx.Req.NoError(err) + ctx.Req.Equal(http.StatusOK, resp.StatusCode(), string(resp.Body())) + ctx.Req.Len(result.Data.AuthQueries, 1) + + ctx.Req.Equal(rest_model.AuthQueryTypeEXTDashJWT, result.Data.AuthQueries[0].TypeID) + ctx.Req.Equal(validJwtSignerClientId, result.Data.AuthQueries[0].ClientID) + ctx.Req.Equal(validJwtSignerScopes[0], result.Data.AuthQueries[0].Scopes[0]) + ctx.Req.Equal(validJwtSignerScopes[1], result.Data.AuthQueries[0].Scopes[1]) + ctx.Req.Equal(validJwtSignerAuthUrl, result.Data.AuthQueries[0].HTTPURL) + ctx.Req.Equal(validSignerUsingInternalId, result.Data.AuthQueries[0].ID) + + }) + }) } diff --git a/tests/auth_oidc_test.go b/tests/auth_oidc_test.go index b9def6451..c01dd64e6 100644 --- a/tests/auth_oidc_test.go +++ b/tests/auth_oidc_test.go @@ -261,14 +261,21 @@ func Test_Authenticate_OIDC_Auth(t *testing.T) { ctx.testContextChanged(t) jwtSignerCert, _ := newSelfSignedCert("Test Jwt Signer Cert - Auth Policy") + clientId := "test-client-id-99" + scope1 := "test-scope-1-99" + scope2 := "test-scope-2-99" + extAuthUrl := "https://some.auth.url.example.com/auth" createExtJwtParam := external_jwt_signer.NewCreateExternalJWTSignerParams() createExtJwtParam.ExternalJWTSigner = &rest_model.ExternalJWTSignerCreate{ - CertPem: S(nfpem.EncodeToString(jwtSignerCert)), - Enabled: B(true), - Name: S("Test JWT Signer - Auth Policy"), - Kid: S(uuid.NewString()), - Issuer: S("test-issuer-99"), - Audience: S("test-audience-99"), + CertPem: S(nfpem.EncodeToString(jwtSignerCert)), + Enabled: B(true), + Name: S("Test JWT Signer - Auth Policy"), + Kid: S(uuid.NewString()), + Issuer: S("test-issuer-99"), + Audience: S("test-audience-99"), + ClientID: &clientId, + Scopes: []string{scope1, scope2}, + ExternalAuthURL: S(extAuthUrl), } extJwtCreateResp, err := managementClient.API.ExternalJWTSigner.CreateExternalJWTSigner(createExtJwtParam, nil) @@ -337,7 +344,7 @@ func Test_Authenticate_OIDC_Auth(t *testing.T) { ctx.Req.NoError(rest_util.WrapErr(err)) ctx.Req.NotNil(createIdentityUpdbAuthenticatorResp) - t.Run("can authenticate via UPDB", func(t *testing.T) { + t.Run("can authenticate via UPDB and see two auth queries", func(t *testing.T) { ctx.testContextChanged(t) identityClient := resty.NewWithClient(ctx.NewHttpClient(ctx.NewTransport())) identityClient.SetRedirectPolicy(resty.DomainCheckRedirectPolicy("127.0.0.1", "localhost")) @@ -356,14 +363,37 @@ func Test_Authenticate_OIDC_Auth(t *testing.T) { ctx.Req.NoError(err) ctx.Req.Equal(http.StatusOK, resp.StatusCode()) - parsedBody := map[string]any{ - "authQueries": []*rest_model.AuthQueryDetail{}, + type respBody struct { + AuthQueries []*rest_model.AuthQueryDetail `json:"authQueries"` } - err = json.Unmarshal(resp.Body(), &parsedBody) + parsedBody := &respBody{} + + err = json.Unmarshal(resp.Body(), parsedBody) ctx.Req.NoError(err) - ctx.Req.Len(parsedBody["authQueries"], 2) + ctx.Req.Len(parsedBody.AuthQueries, 2) + + extJwtIdx := -1 + totpIdx := -1 + + for i, authQuery := range parsedBody.AuthQueries { + if authQuery.TypeID == rest_model.AuthQueryTypeEXTDashJWT { + extJwtIdx = i + } else if authQuery.TypeID == rest_model.AuthQueryTypeTOTP { + totpIdx = i + } else { + ctx.Req.Failf("unexexpected auth quuery type id encountered: %s", string(authQuery.TypeID)) + } + } + + ctx.Req.True(extJwtIdx >= 0, "expected extJwtIdx to be set") + ctx.Req.True(totpIdx >= 0, "expected totpIdx to be set") + + ctx.Req.Equal(parsedBody.AuthQueries[extJwtIdx].ClientID, clientId) + ctx.Req.Equal(parsedBody.AuthQueries[extJwtIdx].Scopes[0], scope1) + ctx.Req.Equal(parsedBody.AuthQueries[extJwtIdx].Scopes[1], scope2) + ctx.Req.Equal(parsedBody.AuthQueries[extJwtIdx].HTTPURL, extAuthUrl) }) })