Files
ziti/tests/auth_policy_test.go
Andrew Martinez fb2034245d fixes openziti/ziti#3356 adds www-authenticate headers (#3561)
* fixes openziti/ziti#3356 adds www-authenticate headers

- www-authenticate headers are returned on 401s from API requests
- www-authenticate headers are returned during authentication to signal
  addtional JWT bearer tokens needed (secondary ext jwt)
- adds support for additional headers on API errors
- adds SecurityTokenCtx for centralized security header processing
  (legacy, jwt, etc.)
- adds SecurityCtx for centralized identity, auth policy, MFA handling
- refactors existing JWT authentication methods (oidc, legacy) to use
  centralized processing where possible
2026-02-24 10:01:36 -05:00

1517 lines
64 KiB
Go

//go:build apitests
/*
Copyright NetFoundry Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tests
import (
"net/http"
"testing"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/openziti/edge-api/rest_model"
nfpem "github.com/openziti/foundation/v2/pem"
"github.com/openziti/ziti/v2/controller/db"
)
func Test_AuthPolicies(t *testing.T) {
ctx := NewTestContext(t)
defer ctx.Teardown()
ctx.StartServer()
ctx.RequireAdminManagementApiLogin()
// Create a dedicated safe auth policy for the default admin so that modifying the
// default auth policy (e.g. adding secondary ext-JWT/TOTP requirements) during these
// tests does not lock the admin session out of subsequent management API calls.
safeAdminAuthPolicy := &rest_model.AuthPolicyCreate{
Name: ToPtr("Admin Safe Auth Policy"),
Primary: &rest_model.AuthPolicyPrimary{
Cert: &rest_model.AuthPolicyPrimaryCert{
AllowExpiredCerts: ToPtr(false),
Allowed: ToPtr(true),
},
ExtJWT: &rest_model.AuthPolicyPrimaryExtJWT{
Allowed: ToPtr(false),
AllowedSigners: []string{},
},
Updb: &rest_model.AuthPolicyPrimaryUpdb{
Allowed: ToPtr(true),
MaxAttempts: ToPtr[int64](0),
MinPasswordLength: ToPtr[int64](5),
LockoutDurationMinutes: ToPtr[int64](0),
RequireMixedCase: ToPtr(false),
RequireNumberChar: ToPtr(false),
RequireSpecialChar: ToPtr(false),
},
},
Secondary: &rest_model.AuthPolicySecondary{
RequireTotp: ToPtr(false),
},
}
safeAdminAuthPolicyCreated := &rest_model.CreateEnvelope{}
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().
SetBody(safeAdminAuthPolicy).
SetResult(safeAdminAuthPolicyCreated).
Post("/auth-policies")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 creating safe admin auth policy: %s", resp.Body())
ctx.Req.NotEmpty(safeAdminAuthPolicyCreated.Data.ID)
adminIdentityId := *ctx.AdminManagementSession.AuthResponse.IdentityID
adminIdentityPatch := &rest_model.IdentityPatch{
AuthPolicyID: &safeAdminAuthPolicyCreated.Data.ID,
}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().
SetBody(adminIdentityPatch).
Patch("/identities/" + adminIdentityId)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 patching admin identity to safe auth policy: %s", resp.Body())
t.Run("create with valid values returns 200 Ok", func(t *testing.T) {
ctx.testContextChanged(t)
jwtSignerCert, _ := newSelfSignedCert("Test Jwt Signer Cert - Auth Policy")
extJwtSigner := &rest_model.ExternalJWTSignerCreate{
CertPem: ToPtr(nfpem.EncodeToString(jwtSignerCert)),
Enabled: ToPtr(true),
Name: ToPtr("Test JWT Signer - Auth Policy"),
Kid: ToPtr(uuid.NewString()),
Issuer: ToPtr("test-issuer-99"),
Audience: ToPtr("test-audience-99"),
}
extJwtSignerCreated := &rest_model.CreateEnvelope{}
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(extJwtSigner).SetResult(extJwtSignerCreated).Post("/external-jwt-signers")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", extJwtSigner, resp.Body())
ctx.Req.NotEmpty(extJwtSignerCreated.Data.ID)
tag1Name := "originalTag1Name"
tag1Value := "originalTag1Value"
authPolicy := &rest_model.AuthPolicyCreate{
Name: ToPtr("Original Name 1"),
Primary: &rest_model.AuthPolicyPrimary{
Cert: &rest_model.AuthPolicyPrimaryCert{
AllowExpiredCerts: ToPtr(true),
Allowed: ToPtr(true),
},
ExtJWT: &rest_model.AuthPolicyPrimaryExtJWT{
Allowed: ToPtr(true),
AllowedSigners: []string{
extJwtSignerCreated.Data.ID,
},
},
Updb: &rest_model.AuthPolicyPrimaryUpdb{
Allowed: ToPtr(true),
MaxAttempts: ToPtr[int64](5),
MinPasswordLength: ToPtr[int64](5),
LockoutDurationMinutes: ToPtr[int64](0),
RequireMixedCase: ToPtr(true),
RequireNumberChar: ToPtr(true),
RequireSpecialChar: ToPtr(true),
},
},
Secondary: &rest_model.AuthPolicySecondary{
RequireExtJWTSigner: nil,
RequireTotp: ToPtr(false),
},
Tags: &rest_model.Tags{
SubTags: map[string]interface{}{
tag1Name: tag1Value,
},
},
}
authPolicyCreated := &rest_model.CreateEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicy).SetResult(authPolicyCreated).Post("/auth-policies")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", authPolicy, resp.Body())
ctx.Req.NotEmpty(authPolicyCreated.Data.ID)
t.Run("get returns 200 and same input values", func(t *testing.T) {
ctx.testContextChanged(t)
authPolicyEnvelope := &rest_model.DetailAuthPolicyEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetResult(authPolicyEnvelope).Get("/auth-policies/" + authPolicyCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for GET %s: %s", resp.Request.URL, resp.Body())
authPolicyDetail := authPolicyEnvelope.Data
ctx.Req.Equal(authPolicyCreated.Data.ID, *authPolicyDetail.ID)
ctx.Req.Equal(len(authPolicy.Tags.SubTags), len(authPolicyDetail.Tags.SubTags))
ctx.Req.Equal(authPolicy.Tags.SubTags[tag1Name], authPolicyDetail.Tags.SubTags[tag1Name])
ctx.Req.Equal(*authPolicy.Name, *authPolicyDetail.Name)
ctx.Req.Equal(len(authPolicy.Primary.ExtJWT.AllowedSigners), len(authPolicyDetail.Primary.ExtJWT.AllowedSigners))
ctx.Req.Equal(authPolicy.Primary.ExtJWT.AllowedSigners[0], authPolicyDetail.Primary.ExtJWT.AllowedSigners[0])
ctx.Req.Equal(*authPolicy.Primary.ExtJWT.Allowed, *authPolicyDetail.Primary.ExtJWT.Allowed)
ctx.Req.Equal(*authPolicy.Primary.Updb.Allowed, *authPolicyDetail.Primary.Updb.Allowed)
ctx.Req.Equal(*authPolicy.Primary.Updb.MaxAttempts, *authPolicyDetail.Primary.Updb.MaxAttempts)
ctx.Req.Equal(*authPolicy.Primary.Updb.MinPasswordLength, *authPolicyDetail.Primary.Updb.MinPasswordLength)
ctx.Req.Equal(*authPolicy.Primary.Updb.LockoutDurationMinutes, *authPolicyDetail.Primary.Updb.LockoutDurationMinutes)
ctx.Req.Equal(*authPolicy.Primary.Updb.RequireMixedCase, *authPolicyDetail.Primary.Updb.RequireMixedCase)
ctx.Req.Equal(*authPolicy.Primary.Updb.RequireNumberChar, *authPolicyDetail.Primary.Updb.RequireNumberChar)
ctx.Req.Equal(*authPolicy.Primary.Updb.RequireSpecialChar, *authPolicyDetail.Primary.Updb.RequireSpecialChar)
ctx.Req.Equal(*authPolicy.Primary.Cert.Allowed, *authPolicyDetail.Primary.Cert.Allowed)
ctx.Req.Equal(*authPolicy.Primary.Cert.AllowExpiredCerts, *authPolicyDetail.Primary.Cert.AllowExpiredCerts)
ctx.Req.Equal(*authPolicy.Secondary.RequireTotp, *authPolicyDetail.Secondary.RequireTotp)
ctx.Req.Equal(authPolicy.Secondary.RequireExtJWTSigner, authPolicyDetail.Secondary.RequireExtJWTSigner)
})
t.Run("update returns 200", func(t *testing.T) {
ctx.testContextChanged(t)
tag2Name := "updatedTag2Name"
tag2Value := "updatedTag2Value"
authPolicyUpdate := &rest_model.AuthPolicyUpdate{
AuthPolicyCreate: rest_model.AuthPolicyCreate{
Name: ToPtr("Updated Name 1"),
Primary: &rest_model.AuthPolicyPrimary{
Cert: &rest_model.AuthPolicyPrimaryCert{
AllowExpiredCerts: ToPtr(false),
Allowed: ToPtr(false),
},
ExtJWT: &rest_model.AuthPolicyPrimaryExtJWT{
Allowed: ToPtr(true),
AllowedSigners: []string{
extJwtSignerCreated.Data.ID,
},
},
Updb: &rest_model.AuthPolicyPrimaryUpdb{
Allowed: ToPtr(false),
MaxAttempts: ToPtr[int64](1),
MinPasswordLength: ToPtr[int64](12),
LockoutDurationMinutes: ToPtr[int64](1),
RequireMixedCase: ToPtr(false),
RequireNumberChar: ToPtr(false),
RequireSpecialChar: ToPtr(false),
},
},
Secondary: &rest_model.AuthPolicySecondary{
RequireExtJWTSigner: &extJwtSignerCreated.Data.ID,
RequireTotp: ToPtr(true),
},
Tags: &rest_model.Tags{
SubTags: map[string]interface{}{
tag1Name: tag1Value,
},
},
},
}
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicyUpdate).Put("/auth-policies/" + authPolicyCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for PUT %T: %s", extJwtSigner, resp.Body())
t.Run("get returns 200 and updated values", func(t *testing.T) {
ctx.testContextChanged(t)
authPolicyUpdatedEnvelope := &rest_model.DetailAuthPolicyEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetResult(authPolicyUpdatedEnvelope).Get("/auth-policies/" + authPolicyCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for GET %s: %s", resp.Request.URL, resp.Body())
authPolicyUpdatedDetail := authPolicyUpdatedEnvelope.Data
ctx.Req.Equal(len(authPolicyUpdate.Tags.SubTags), len(authPolicyUpdatedDetail.Tags.SubTags))
ctx.Req.Equal(authPolicyUpdate.Tags.SubTags[tag1Name], authPolicyUpdatedDetail.Tags.SubTags[tag1Name])
ctx.Req.Equal(authPolicyUpdate.Tags.SubTags[tag1Name], authPolicyUpdatedDetail.Tags.SubTags[tag1Name])
ctx.Req.Equal(authPolicyUpdate.Tags.SubTags[tag2Name], authPolicyUpdatedDetail.Tags.SubTags[tag2Value])
ctx.Req.Equal(*authPolicyUpdate.Name, *authPolicyUpdatedDetail.Name)
ctx.Req.Equal(len(authPolicyUpdate.Primary.ExtJWT.AllowedSigners), len(authPolicyUpdatedDetail.Primary.ExtJWT.AllowedSigners))
ctx.Req.Equal(*authPolicyUpdate.Primary.ExtJWT.Allowed, *authPolicyUpdatedDetail.Primary.ExtJWT.Allowed)
ctx.Req.Equal(*authPolicyUpdate.Primary.Updb.Allowed, *authPolicyUpdatedDetail.Primary.Updb.Allowed)
ctx.Req.Equal(*authPolicyUpdate.Primary.Updb.MaxAttempts, *authPolicyUpdatedDetail.Primary.Updb.MaxAttempts)
ctx.Req.Equal(*authPolicyUpdate.Primary.Updb.MinPasswordLength, *authPolicyUpdatedDetail.Primary.Updb.MinPasswordLength)
ctx.Req.Equal(*authPolicyUpdate.Primary.Updb.LockoutDurationMinutes, *authPolicyUpdatedDetail.Primary.Updb.LockoutDurationMinutes)
ctx.Req.Equal(*authPolicyUpdate.Primary.Updb.RequireMixedCase, *authPolicyUpdatedDetail.Primary.Updb.RequireMixedCase)
ctx.Req.Equal(*authPolicyUpdate.Primary.Updb.RequireNumberChar, *authPolicyUpdatedDetail.Primary.Updb.RequireNumberChar)
ctx.Req.Equal(*authPolicyUpdate.Primary.Updb.RequireSpecialChar, *authPolicyUpdatedDetail.Primary.Updb.RequireSpecialChar)
ctx.Req.Equal(*authPolicyUpdate.Primary.Cert.Allowed, *authPolicyUpdatedDetail.Primary.Cert.Allowed)
ctx.Req.Equal(*authPolicyUpdate.Primary.Cert.AllowExpiredCerts, *authPolicyUpdatedDetail.Primary.Cert.AllowExpiredCerts)
ctx.Req.Equal(*authPolicyUpdate.Secondary.RequireTotp, *authPolicyUpdatedDetail.Secondary.RequireTotp)
ctx.Req.Equal(*authPolicyUpdate.Secondary.RequireExtJWTSigner, *authPolicyUpdatedDetail.Secondary.RequireExtJWTSigner)
})
})
t.Run("delete returns 200", func(t *testing.T) {
ctx.testContextChanged(t)
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().Delete("/auth-policies/" + authPolicyCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for DELETE %s: %s", resp.Request.URL, resp.Body())
t.Run("after delete get returns 404", func(t *testing.T) {
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().Get("/auth-policies/" + authPolicyCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusNotFound, resp.StatusCode(), "expected 404 for GET %s: %s", resp.Request.URL, resp.Body())
})
})
})
t.Run("patching names returns 200 and updates only the proper field", func(t *testing.T) {
ctx.testContextChanged(t)
jwtSignerCert, _ := newSelfSignedCert("Test Jwt Signer Cert - Auth Policy Patch")
extJwtSigner := &rest_model.ExternalJWTSignerCreate{
CertPem: ToPtr(nfpem.EncodeToString(jwtSignerCert)),
Enabled: ToPtr(true),
Name: ToPtr("Test JWT Signer - Auth Policy Patch"),
Kid: ToPtr(uuid.NewString()),
Issuer: ToPtr("test-issuer-100"),
Audience: ToPtr("test-audience-100"),
}
extJwtSignerCreated := &rest_model.CreateEnvelope{}
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(extJwtSigner).SetResult(extJwtSignerCreated).Post("/external-jwt-signers")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", extJwtSigner, resp.Body())
ctx.Req.NotEmpty(extJwtSignerCreated.Data.ID)
tag1Name := "originalTag1Name"
tag1Value := "originalTag1Value"
authPolicy := &rest_model.AuthPolicyCreate{
Name: ToPtr("Original Name 1"),
Primary: &rest_model.AuthPolicyPrimary{
Cert: &rest_model.AuthPolicyPrimaryCert{
AllowExpiredCerts: ToPtr(true),
Allowed: ToPtr(true),
},
ExtJWT: &rest_model.AuthPolicyPrimaryExtJWT{
Allowed: ToPtr(true),
AllowedSigners: []string{
extJwtSignerCreated.Data.ID,
},
},
Updb: &rest_model.AuthPolicyPrimaryUpdb{
Allowed: ToPtr(true),
MaxAttempts: ToPtr[int64](5),
MinPasswordLength: ToPtr[int64](5),
LockoutDurationMinutes: ToPtr[int64](0),
RequireMixedCase: ToPtr(true),
RequireNumberChar: ToPtr(true),
RequireSpecialChar: ToPtr(true),
},
},
Secondary: &rest_model.AuthPolicySecondary{
RequireExtJWTSigner: nil,
RequireTotp: ToPtr(false),
},
Tags: &rest_model.Tags{
SubTags: map[string]interface{}{
tag1Name: tag1Value,
},
},
}
authPolicyCreated := &rest_model.CreateEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicy).SetResult(authPolicyCreated).Post("/auth-policies")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", authPolicy, resp.Body())
ctx.Req.NotEmpty(authPolicyCreated.Data.ID)
t.Run("name returns 200", func(t *testing.T) {
ctx.testContextChanged(t)
authPolicyPatch := &rest_model.AuthPolicyPatch{
Name: ToPtr("PatchedName"),
}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicyPatch).Patch("/auth-policies/" + authPolicyCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for PATCH %T: %s", authPolicy, resp.Body())
t.Run("get returns 200 and updated values", func(t *testing.T) {
ctx.testContextChanged(t)
authPolicyPatchEnvelope := &rest_model.DetailAuthPolicyEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetResult(authPolicyPatchEnvelope).Get("/auth-policies/" + authPolicyCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for GET %s: %s", resp.Request.URL, resp.Body())
authPolicyPatchedDetail := authPolicyPatchEnvelope.Data
ctx.Req.Equal(len(authPolicy.Tags.SubTags), len(authPolicyPatchedDetail.Tags.SubTags))
ctx.Req.Equal(authPolicy.Tags.SubTags[tag1Name], authPolicyPatchedDetail.Tags.SubTags[tag1Name])
ctx.Req.Equal(authPolicy.Tags.SubTags[tag1Name], authPolicyPatchedDetail.Tags.SubTags[tag1Name])
ctx.Req.Equal(*authPolicyPatch.Name, *authPolicyPatchedDetail.Name)
ctx.Req.Equal(len(authPolicy.Primary.ExtJWT.AllowedSigners), len(authPolicyPatchedDetail.Primary.ExtJWT.AllowedSigners))
ctx.Req.Equal(*authPolicy.Primary.ExtJWT.Allowed, *authPolicyPatchedDetail.Primary.ExtJWT.Allowed)
ctx.Req.Equal(*authPolicy.Primary.Updb.Allowed, *authPolicyPatchedDetail.Primary.Updb.Allowed)
ctx.Req.Equal(*authPolicy.Primary.Updb.MaxAttempts, *authPolicyPatchedDetail.Primary.Updb.MaxAttempts)
ctx.Req.Equal(*authPolicy.Primary.Updb.MinPasswordLength, *authPolicyPatchedDetail.Primary.Updb.MinPasswordLength)
ctx.Req.Equal(*authPolicy.Primary.Updb.LockoutDurationMinutes, *authPolicyPatchedDetail.Primary.Updb.LockoutDurationMinutes)
ctx.Req.Equal(*authPolicy.Primary.Updb.RequireMixedCase, *authPolicyPatchedDetail.Primary.Updb.RequireMixedCase)
ctx.Req.Equal(*authPolicy.Primary.Updb.RequireNumberChar, *authPolicyPatchedDetail.Primary.Updb.RequireNumberChar)
ctx.Req.Equal(*authPolicy.Primary.Updb.RequireSpecialChar, *authPolicyPatchedDetail.Primary.Updb.RequireSpecialChar)
ctx.Req.Equal(*authPolicy.Primary.Cert.Allowed, *authPolicyPatchedDetail.Primary.Cert.Allowed)
ctx.Req.Equal(*authPolicy.Primary.Cert.AllowExpiredCerts, *authPolicyPatchedDetail.Primary.Cert.AllowExpiredCerts)
ctx.Req.Equal(*authPolicy.Secondary.RequireTotp, *authPolicyPatchedDetail.Secondary.RequireTotp)
ctx.Req.Equal(authPolicy.Secondary.RequireExtJWTSigner, authPolicyPatchedDetail.Secondary.RequireExtJWTSigner)
})
})
})
t.Run("patching primary updb allowed returns 200 and updates only the proper field", func(t *testing.T) {
ctx.testContextChanged(t)
jwtSignerCert, _ := newSelfSignedCert("Test Jwt Signer Cert - Auth Policy Patch")
extJwtSigner := &rest_model.ExternalJWTSignerCreate{
CertPem: ToPtr(nfpem.EncodeToString(jwtSignerCert)),
Enabled: ToPtr(true),
Name: ToPtr("Test JWT Signer - Auth Policy Patch1"),
Kid: ToPtr(uuid.NewString()),
Issuer: ToPtr("test-issuer-101"),
Audience: ToPtr("test-audience-101"),
}
extJwtSignerCreated := &rest_model.CreateEnvelope{}
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(extJwtSigner).SetResult(extJwtSignerCreated).Post("/external-jwt-signers")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", extJwtSigner, resp.Body())
ctx.Req.NotEmpty(extJwtSignerCreated.Data.ID)
tag1Name := "originalTag1Name"
tag1Value := "originalTag1Value"
authPolicy := &rest_model.AuthPolicyCreate{
Name: ToPtr("Original Name 1 - Patch Updb Allowed"),
Primary: &rest_model.AuthPolicyPrimary{
Cert: &rest_model.AuthPolicyPrimaryCert{
AllowExpiredCerts: ToPtr(true),
Allowed: ToPtr(true),
},
ExtJWT: &rest_model.AuthPolicyPrimaryExtJWT{
Allowed: ToPtr(true),
AllowedSigners: []string{
extJwtSignerCreated.Data.ID,
},
},
Updb: &rest_model.AuthPolicyPrimaryUpdb{
Allowed: ToPtr(true),
MaxAttempts: ToPtr[int64](5),
MinPasswordLength: ToPtr[int64](5),
LockoutDurationMinutes: ToPtr[int64](0),
RequireMixedCase: ToPtr(true),
RequireNumberChar: ToPtr(true),
RequireSpecialChar: ToPtr(true),
},
},
Secondary: &rest_model.AuthPolicySecondary{
RequireExtJWTSigner: nil,
RequireTotp: ToPtr(false),
},
Tags: &rest_model.Tags{
SubTags: map[string]interface{}{
tag1Name: tag1Value,
},
},
}
authPolicyCreated := &rest_model.CreateEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicy).SetResult(authPolicyCreated).Post("/auth-policies")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for %T: %s", authPolicy, resp.Body())
ctx.Req.NotEmpty(authPolicyCreated.Data.ID)
t.Run("returns 200", func(t *testing.T) {
ctx.testContextChanged(t)
authPolicyPatch := &rest_model.AuthPolicyPatch{
Primary: &rest_model.AuthPolicyPrimaryPatch{
Cert: nil,
ExtJWT: nil,
Updb: &rest_model.AuthPolicyPrimaryUpdbPatch{
Allowed: ToPtr(false),
},
},
}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicyPatch).Patch("/auth-policies/" + authPolicyCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for PATCH %T: %s", authPolicy, resp.Body())
t.Run("get returns 200 and updated values", func(t *testing.T) {
ctx.testContextChanged(t)
authPolicyPatchEnvelope := &rest_model.DetailAuthPolicyEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetResult(authPolicyPatchEnvelope).Get("/auth-policies/" + authPolicyCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for GET %s: %s", resp.Request.URL, resp.Body())
authPolicyPatchedDetail := authPolicyPatchEnvelope.Data
ctx.Req.Equal(len(authPolicy.Tags.SubTags), len(authPolicyPatchedDetail.Tags.SubTags))
ctx.Req.Equal(authPolicy.Tags.SubTags[tag1Name], authPolicyPatchedDetail.Tags.SubTags[tag1Name])
ctx.Req.Equal(authPolicy.Tags.SubTags[tag1Name], authPolicyPatchedDetail.Tags.SubTags[tag1Name])
ctx.Req.Equal(*authPolicy.Name, *authPolicyPatchedDetail.Name)
ctx.Req.Equal(len(authPolicy.Primary.ExtJWT.AllowedSigners), len(authPolicyPatchedDetail.Primary.ExtJWT.AllowedSigners))
ctx.Req.Equal(*authPolicy.Primary.ExtJWT.Allowed, *authPolicyPatchedDetail.Primary.ExtJWT.Allowed)
ctx.Req.Equal(*authPolicyPatch.Primary.Updb.Allowed, *authPolicyPatchedDetail.Primary.Updb.Allowed)
ctx.Req.Equal(*authPolicy.Primary.Updb.MaxAttempts, *authPolicyPatchedDetail.Primary.Updb.MaxAttempts)
ctx.Req.Equal(*authPolicy.Primary.Updb.MinPasswordLength, *authPolicyPatchedDetail.Primary.Updb.MinPasswordLength)
ctx.Req.Equal(*authPolicy.Primary.Updb.LockoutDurationMinutes, *authPolicyPatchedDetail.Primary.Updb.LockoutDurationMinutes)
ctx.Req.Equal(*authPolicy.Primary.Updb.RequireMixedCase, *authPolicyPatchedDetail.Primary.Updb.RequireMixedCase)
ctx.Req.Equal(*authPolicy.Primary.Updb.RequireNumberChar, *authPolicyPatchedDetail.Primary.Updb.RequireNumberChar)
ctx.Req.Equal(*authPolicy.Primary.Updb.RequireSpecialChar, *authPolicyPatchedDetail.Primary.Updb.RequireSpecialChar)
ctx.Req.Equal(*authPolicy.Primary.Cert.Allowed, *authPolicyPatchedDetail.Primary.Cert.Allowed)
ctx.Req.Equal(*authPolicy.Primary.Cert.AllowExpiredCerts, *authPolicyPatchedDetail.Primary.Cert.AllowExpiredCerts)
ctx.Req.Equal(*authPolicy.Secondary.RequireTotp, *authPolicyPatchedDetail.Secondary.RequireTotp)
ctx.Req.Equal(authPolicy.Secondary.RequireExtJWTSigner, authPolicyPatchedDetail.Secondary.RequireExtJWTSigner)
})
})
})
t.Run("create with invalid primary external jwt signers returns 404", func(t *testing.T) {
ctx.testContextChanged(t)
tag1Name := "originalTag1Name"
tag1Value := "originalTag1Value"
authPolicy := &rest_model.AuthPolicyCreate{
Name: ToPtr("Original Name 1"),
Primary: &rest_model.AuthPolicyPrimary{
Cert: &rest_model.AuthPolicyPrimaryCert{
AllowExpiredCerts: ToPtr(true),
Allowed: ToPtr(true),
},
ExtJWT: &rest_model.AuthPolicyPrimaryExtJWT{
Allowed: ToPtr(true),
AllowedSigners: []string{
"badId",
},
},
Updb: &rest_model.AuthPolicyPrimaryUpdb{
Allowed: ToPtr(true),
MaxAttempts: ToPtr[int64](5),
MinPasswordLength: ToPtr[int64](5),
LockoutDurationMinutes: ToPtr[int64](0),
RequireMixedCase: ToPtr(true),
RequireNumberChar: ToPtr(true),
RequireSpecialChar: ToPtr(true),
},
},
Secondary: &rest_model.AuthPolicySecondary{
RequireExtJWTSigner: ToPtr(""),
RequireTotp: ToPtr(false),
},
Tags: &rest_model.Tags{
SubTags: map[string]interface{}{
tag1Name: tag1Value,
},
},
}
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicy).Post("/auth-policies")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusNotFound, resp.StatusCode(), "expected 404 for POST %T: %s", authPolicy, resp.Body())
})
t.Run("create with invalid secondary external jwt signers returns 404", func(t *testing.T) {
ctx.testContextChanged(t)
tag1Name := "originalTag1Name"
tag1Value := "originalTag1Value"
authPolicy := &rest_model.AuthPolicyCreate{
Name: ToPtr("Original Name 1"),
Primary: &rest_model.AuthPolicyPrimary{
Cert: &rest_model.AuthPolicyPrimaryCert{
AllowExpiredCerts: ToPtr(true),
Allowed: ToPtr(true),
},
ExtJWT: &rest_model.AuthPolicyPrimaryExtJWT{
Allowed: ToPtr(true),
AllowedSigners: []string{},
},
Updb: &rest_model.AuthPolicyPrimaryUpdb{
Allowed: ToPtr(true),
MaxAttempts: ToPtr[int64](5),
MinPasswordLength: ToPtr[int64](5),
LockoutDurationMinutes: ToPtr[int64](0),
RequireMixedCase: ToPtr(true),
RequireNumberChar: ToPtr(true),
RequireSpecialChar: ToPtr(true),
},
},
Secondary: &rest_model.AuthPolicySecondary{
RequireExtJWTSigner: ToPtr("badId"),
RequireTotp: ToPtr(false),
},
Tags: &rest_model.Tags{
SubTags: map[string]interface{}{
tag1Name: tag1Value,
},
},
}
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicy).Post("/auth-policies")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusNotFound, resp.StatusCode(), "expected 404 for POST %T: %s", authPolicy, resp.Body())
})
t.Run("create auth policy with external jwt signer references", func(t *testing.T) {
ctx.testContextChanged(t)
jwtSignerCert1, _ := newSelfSignedCert("Test Jwt Signer Cert - Auth Policy - Delete Scenarios 1")
extJwtSigner1 := &rest_model.ExternalJWTSignerCreate{
CertPem: ToPtr(nfpem.EncodeToString(jwtSignerCert1)),
Enabled: ToPtr(true),
Name: ToPtr("Test JWT Signer - Auth Policy - Delete Scenarios 1"),
Kid: ToPtr(uuid.NewString()),
Issuer: ToPtr("test-issuer-102"),
Audience: ToPtr("test-audience-102"),
}
extJwtSignerCreated1 := &rest_model.CreateEnvelope{}
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(extJwtSigner1).SetResult(extJwtSignerCreated1).Post("/external-jwt-signers")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", extJwtSigner1, resp.Body())
ctx.Req.NotEmpty(extJwtSignerCreated1.Data.ID)
jwtSignerCert2, _ := newSelfSignedCert("Test Jwt Signer Cert - Auth Policy - Delete Scenarios 2")
extJwtSigner2 := &rest_model.ExternalJWTSignerCreate{
CertPem: ToPtr(nfpem.EncodeToString(jwtSignerCert2)),
Enabled: ToPtr(true),
Name: ToPtr("Test JWT Signer - Auth Policy - Delete Scenarios 2"),
Kid: ToPtr(uuid.NewString()),
Issuer: ToPtr("test-issuer-200"),
Audience: ToPtr("test-audience-200"),
}
extJwtSignerCreated2 := &rest_model.CreateEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(extJwtSigner2).SetResult(extJwtSignerCreated2).Post("/external-jwt-signers")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", extJwtSigner2, resp.Body())
ctx.Req.NotEmpty(extJwtSignerCreated2.Data.ID)
tag1Name := "originalTag1Name"
tag1Value := "originalTag1Value"
authPolicy := &rest_model.AuthPolicyCreate{
Name: ToPtr("Original Name 1"),
Primary: &rest_model.AuthPolicyPrimary{
Cert: &rest_model.AuthPolicyPrimaryCert{
AllowExpiredCerts: ToPtr(true),
Allowed: ToPtr(true),
},
ExtJWT: &rest_model.AuthPolicyPrimaryExtJWT{
Allowed: ToPtr(true),
AllowedSigners: []string{
extJwtSignerCreated1.Data.ID,
},
},
Updb: &rest_model.AuthPolicyPrimaryUpdb{
Allowed: ToPtr(true),
MaxAttempts: ToPtr[int64](5),
MinPasswordLength: ToPtr[int64](5),
LockoutDurationMinutes: ToPtr[int64](0),
RequireMixedCase: ToPtr(true),
RequireNumberChar: ToPtr(true),
RequireSpecialChar: ToPtr(true),
},
},
Secondary: &rest_model.AuthPolicySecondary{
RequireExtJWTSigner: &extJwtSignerCreated2.Data.ID,
RequireTotp: ToPtr(false),
},
Tags: &rest_model.Tags{
SubTags: map[string]interface{}{
tag1Name: tag1Value,
},
},
}
authPolicyCreated := &rest_model.CreateEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicy).SetResult(authPolicyCreated).Post("/auth-policies")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", authPolicy, resp.Body())
ctx.Req.NotEmpty(authPolicyCreated.Data.ID)
t.Run("can not delete external jwt signer referenced by auth policy", func(t *testing.T) {
ctx.testContextChanged(t)
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().Delete("/external-jwt-signers/" + extJwtSignerCreated1.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusConflict, resp.StatusCode(), "expected 409 for DELETE %T[%s]: %s", extJwtSigner1, extJwtSignerCreated1.Data.ID, resp.Body())
t.Run("can remove reference via patch", func(t *testing.T) {
ctx.testContextChanged(t)
authPolicyPatch := &rest_model.AuthPolicyPatch{
Primary: &rest_model.AuthPolicyPrimaryPatch{
ExtJWT: &rest_model.AuthPolicyPrimaryExtJWTPatch{
AllowedSigners: []string{},
},
},
}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicyPatch).Patch("/auth-policies/" + authPolicyCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for PATCH %T: %s", authPolicy, resp.Body())
t.Run("delete ext jwt signer returns 200", func(t *testing.T) {
ctx.testContextChanged(t)
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().Delete("/external-jwt-signers/" + extJwtSignerCreated1.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for DELETE %T[%s]: %s", extJwtSigner1, extJwtSignerCreated1.Data.ID, resp.Body())
t.Run("get deleted ext jwt signer returns 404", func(t *testing.T) {
ctx.testContextChanged(t)
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().Get("/external-jwt-signers/" + extJwtSignerCreated1.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusNotFound, resp.StatusCode(), "expected 404 for GET %T[%s]: %s", extJwtSigner1, extJwtSignerCreated1.Data.ID, resp.Body())
})
})
})
t.Run("auth policy primary ext jwt signers is updated", func(t *testing.T) {
ctx.testContextChanged(t)
authPolicyEnvelope := &rest_model.DetailAuthPolicyEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetResult(authPolicyEnvelope).Get("/auth-policies/" + authPolicyCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for GET %s: %s", resp.Request.URL, resp.Body())
authPolicyDetail := authPolicyEnvelope.Data
ctx.Req.Equal(0, len(authPolicyDetail.Primary.ExtJWT.AllowedSigners))
})
})
t.Run("can not delete referenced secondary jwt signer", func(t *testing.T) {
ctx.testContextChanged(t)
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().Delete("/external-jwt-signers/" + extJwtSignerCreated2.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusConflict, resp.StatusCode(), "expected 409 on DELETE %T[%s]: %s", extJwtSigner2, extJwtSignerCreated2.Data.ID, resp.Body())
t.Run("can remove reference via patch", func(t *testing.T) {
ctx.testContextChanged(t)
authPolicyPatch := &rest_model.AuthPolicyPatch{
Secondary: &rest_model.AuthPolicySecondaryPatch{
RequireExtJWTSigner: ToPtr(""),
},
}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicyPatch).Patch("/auth-policies/" + authPolicyCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for PATCH %T: %s", authPolicy, resp.Body())
t.Run("delete secondary ext jwt signer returns 200", func(t *testing.T) {
ctx.testContextChanged(t)
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().Delete("/external-jwt-signers/" + extJwtSignerCreated2.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for POST %T[%s]: %s", extJwtSigner2, extJwtSignerCreated2.Data.ID, resp.Body())
t.Run("get deleted ext jwt signer returns 404", func(t *testing.T) {
ctx.testContextChanged(t)
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().Get("/external-jwt-signers/" + extJwtSignerCreated2.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusNotFound, resp.StatusCode(), "expected 404 for GET %T[%s]: %s", extJwtSigner2, extJwtSignerCreated2.Data.ID, resp.Body())
})
})
})
})
})
t.Run("can create an identity referencing auth policies", func(t *testing.T) {
ctx.testContextChanged(t)
tag1Name := "originalTag1Name"
tag1Value := "originalTag1Value"
authPolicy := &rest_model.AuthPolicyCreate{
Name: ToPtr("Original Name 1 - Identity Refs"),
Primary: &rest_model.AuthPolicyPrimary{
Cert: &rest_model.AuthPolicyPrimaryCert{
AllowExpiredCerts: ToPtr(true),
Allowed: ToPtr(true),
},
ExtJWT: &rest_model.AuthPolicyPrimaryExtJWT{
Allowed: ToPtr(true),
AllowedSigners: []string{},
},
Updb: &rest_model.AuthPolicyPrimaryUpdb{
Allowed: ToPtr(true),
MaxAttempts: ToPtr[int64](5),
MinPasswordLength: ToPtr[int64](5),
LockoutDurationMinutes: ToPtr[int64](0),
RequireMixedCase: ToPtr(true),
RequireNumberChar: ToPtr(true),
RequireSpecialChar: ToPtr(true),
},
},
Secondary: &rest_model.AuthPolicySecondary{
RequireExtJWTSigner: nil,
RequireTotp: ToPtr(false),
},
Tags: &rest_model.Tags{
SubTags: map[string]interface{}{
tag1Name: tag1Value,
},
},
}
authPolicyCreated := &rest_model.CreateEnvelope{}
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicy).SetResult(authPolicyCreated).Post("/auth-policies")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", authPolicy, resp.Body())
ctx.Req.NotEmpty(authPolicyCreated.Data.ID)
identityType := rest_model.IdentityTypeDevice
identity := &rest_model.IdentityCreate{
AuthPolicyID: &authPolicyCreated.Data.ID,
IsAdmin: ToPtr(false),
Name: ToPtr("test-identity-auth-policy-ref"),
Type: &identityType,
}
identityCreated := &rest_model.CreateEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(identity).SetResult(identityCreated).Post("/identities")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", authPolicy, resp.Body())
ctx.Req.NotEmpty(authPolicyCreated.Data.ID)
t.Run("identity has auth policy set", func(t *testing.T) {
ctx.testContextChanged(t)
getResponse := &rest_model.DetailIdentityEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetResult(getResponse).Get("/identities/" + identityCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode())
ctx.Req.Equal(*identity.AuthPolicyID, *getResponse.Data.AuthPolicyID)
})
t.Run("cannot delete auth policy assigned to identity", func(t *testing.T) {
ctx.testContextChanged(t)
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().Delete("/auth-policies/" + authPolicyCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusConflict, resp.StatusCode(), "expected 409 for DELETE %T: %s", authPolicy, resp.Body())
})
t.Run("can delete auth policy after identity assignment removed", func(t *testing.T) {
ctx.testContextChanged(t)
identityPatch := &rest_model.IdentityPatch{
AuthPolicyID: ToPtr(db.DefaultAuthPolicyId),
}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(identityPatch).Patch("/identities/" + identityCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for PATCH %T: %s", authPolicy, resp.Body())
t.Run("delete returns 200", func(t *testing.T) {
ctx.testContextChanged(t)
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().Delete("/auth-policies/" + authPolicyCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for DELETE %T: %s", authPolicy, resp.Body())
})
})
})
t.Run("cannot delete the default auth policy", func(t *testing.T) {
ctx.testContextChanged(t)
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().Delete("/auth-policies/" + db.DefaultAuthPolicyId)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusConflict, resp.StatusCode(), "expected 409 for DELETE: %s", resp.Body())
})
t.Run("can update the default auth policy", func(t *testing.T) {
ctx.testContextChanged(t)
jwtSignerCert, _ := newSelfSignedCert("Test Jwt Signer Cert - Update Default")
extJwtSigner := &rest_model.ExternalJWTSignerCreate{
CertPem: ToPtr(nfpem.EncodeToString(jwtSignerCert)),
Enabled: ToPtr(true),
Name: ToPtr("Test JWT Signer - Auth Policy - Update Default"),
Kid: ToPtr(uuid.NewString()),
Issuer: ToPtr("test-issuer-104"),
Audience: ToPtr("test-audience-104"),
}
extJwtSignerCreated := &rest_model.CreateEnvelope{}
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(extJwtSigner).SetResult(extJwtSignerCreated).Post("/external-jwt-signers")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", extJwtSigner, resp.Body())
ctx.Req.NotEmpty(extJwtSignerCreated.Data.ID)
tag1Name := "updatedTag1Name"
tag1Value := "updatedTag1Value"
authPolicyUpdate := &rest_model.AuthPolicyUpdate{
AuthPolicyCreate: rest_model.AuthPolicyCreate{
Name: ToPtr("Updated Name 1"),
Primary: &rest_model.AuthPolicyPrimary{
Cert: &rest_model.AuthPolicyPrimaryCert{
AllowExpiredCerts: ToPtr(false),
Allowed: ToPtr(false),
},
ExtJWT: &rest_model.AuthPolicyPrimaryExtJWT{
Allowed: ToPtr(true),
AllowedSigners: []string{
extJwtSignerCreated.Data.ID,
},
},
Updb: &rest_model.AuthPolicyPrimaryUpdb{
Allowed: ToPtr(false),
MaxAttempts: ToPtr[int64](1),
MinPasswordLength: ToPtr[int64](5),
LockoutDurationMinutes: ToPtr[int64](1),
RequireMixedCase: ToPtr(false),
RequireNumberChar: ToPtr(false),
RequireSpecialChar: ToPtr(false),
},
},
Secondary: &rest_model.AuthPolicySecondary{
RequireExtJWTSigner: &extJwtSignerCreated.Data.ID,
RequireTotp: ToPtr(true),
},
Tags: &rest_model.Tags{
SubTags: map[string]interface{}{
tag1Name: tag1Value,
},
},
},
}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicyUpdate).Put("/auth-policies/" + db.DefaultAuthPolicyId)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for PUT %T: %s", extJwtSigner, resp.Body())
t.Run("get returns 200 and updated values", func(t *testing.T) {
ctx.testContextChanged(t)
authPolicyUpdatedEnvelope := &rest_model.DetailAuthPolicyEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetResult(authPolicyUpdatedEnvelope).Get("/auth-policies/" + db.DefaultAuthPolicyId)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for GET %s: %s", resp.Request.URL, resp.Body())
authPolicyUpdatedDetail := authPolicyUpdatedEnvelope.Data
ctx.Req.Equal(len(authPolicyUpdate.Tags.SubTags), len(authPolicyUpdatedDetail.Tags.SubTags))
ctx.Req.Equal(authPolicyUpdate.Tags.SubTags[tag1Name], authPolicyUpdatedDetail.Tags.SubTags[tag1Name])
ctx.Req.Equal(authPolicyUpdate.Tags.SubTags[tag1Name], authPolicyUpdatedDetail.Tags.SubTags[tag1Name])
ctx.Req.Equal(*authPolicyUpdate.Name, *authPolicyUpdatedDetail.Name)
ctx.Req.Equal(len(authPolicyUpdate.Primary.ExtJWT.AllowedSigners), len(authPolicyUpdatedDetail.Primary.ExtJWT.AllowedSigners))
ctx.Req.Equal(*authPolicyUpdate.Primary.ExtJWT.Allowed, *authPolicyUpdatedDetail.Primary.ExtJWT.Allowed)
ctx.Req.Equal(*authPolicyUpdate.Primary.Updb.Allowed, *authPolicyUpdatedDetail.Primary.Updb.Allowed)
ctx.Req.Equal(*authPolicyUpdate.Primary.Updb.MaxAttempts, *authPolicyUpdatedDetail.Primary.Updb.MaxAttempts)
ctx.Req.Equal(*authPolicyUpdate.Primary.Updb.MinPasswordLength, *authPolicyUpdatedDetail.Primary.Updb.MinPasswordLength)
ctx.Req.Equal(*authPolicyUpdate.Primary.Updb.LockoutDurationMinutes, *authPolicyUpdatedDetail.Primary.Updb.LockoutDurationMinutes)
ctx.Req.Equal(*authPolicyUpdate.Primary.Updb.RequireMixedCase, *authPolicyUpdatedDetail.Primary.Updb.RequireMixedCase)
ctx.Req.Equal(*authPolicyUpdate.Primary.Updb.RequireNumberChar, *authPolicyUpdatedDetail.Primary.Updb.RequireNumberChar)
ctx.Req.Equal(*authPolicyUpdate.Primary.Updb.RequireSpecialChar, *authPolicyUpdatedDetail.Primary.Updb.RequireSpecialChar)
ctx.Req.Equal(*authPolicyUpdate.Primary.Cert.Allowed, *authPolicyUpdatedDetail.Primary.Cert.Allowed)
ctx.Req.Equal(*authPolicyUpdate.Primary.Cert.AllowExpiredCerts, *authPolicyUpdatedDetail.Primary.Cert.AllowExpiredCerts)
ctx.Req.Equal(*authPolicyUpdate.Secondary.RequireTotp, *authPolicyUpdatedDetail.Secondary.RequireTotp)
ctx.Req.Equal(*authPolicyUpdate.Secondary.RequireExtJWTSigner, *authPolicyUpdatedDetail.Secondary.RequireExtJWTSigner)
})
})
t.Run("can patch default policy returns 200 and updates only the proper field", func(t *testing.T) {
ctx.testContextChanged(t)
authPolicyPatch := &rest_model.AuthPolicyPatch{
Name: ToPtr("PatchedName On Default"),
}
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicyPatch).Patch("/auth-policies/" + db.DefaultAuthPolicyId)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for PATCH %T: %s", "default auth policy", resp.Body())
t.Run("get returns 200 and updated values", func(t *testing.T) {
ctx.testContextChanged(t)
authPolicyPatchEnvelope := &rest_model.DetailAuthPolicyEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetResult(authPolicyPatchEnvelope).Get("/auth-policies/" + db.DefaultAuthPolicyId)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for GET %s: %s", resp.Request.URL, resp.Body())
authPolicyPatchedDetail := authPolicyPatchEnvelope.Data
ctx.Req.Equal(*authPolicyPatch.Name, *authPolicyPatchedDetail.Name)
})
})
t.Run("create with below minimum values default with 200 Ok", func(t *testing.T) {
ctx.testContextChanged(t)
jwtSignerCert, _ := newSelfSignedCert("Test Jwt Signer Cert - Auth Policy 08")
extJwtSigner := &rest_model.ExternalJWTSignerCreate{
CertPem: ToPtr(nfpem.EncodeToString(jwtSignerCert)),
Enabled: ToPtr(true),
Name: ToPtr("Test JWT Signer - Auth Policy 08"),
Kid: ToPtr(uuid.NewString()),
Issuer: ToPtr("test-issuer-105"),
Audience: ToPtr("test-audience-105"),
}
extJwtSignerCreated := &rest_model.CreateEnvelope{}
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(extJwtSigner).SetResult(extJwtSignerCreated).Post("/external-jwt-signers")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", extJwtSigner, resp.Body())
ctx.Req.NotEmpty(extJwtSignerCreated.Data.ID)
tag1Name := "originalTag1Name"
tag1Value := "originalTag1Value"
authPolicy := &rest_model.AuthPolicyCreate{
Name: ToPtr("Original Name 6"),
Primary: &rest_model.AuthPolicyPrimary{
Cert: &rest_model.AuthPolicyPrimaryCert{
AllowExpiredCerts: ToPtr(true),
Allowed: ToPtr(true),
},
ExtJWT: &rest_model.AuthPolicyPrimaryExtJWT{
Allowed: ToPtr(true),
AllowedSigners: []string{
extJwtSignerCreated.Data.ID,
},
},
Updb: &rest_model.AuthPolicyPrimaryUpdb{
Allowed: ToPtr(true),
MaxAttempts: ToPtr[int64](-1),
MinPasswordLength: ToPtr[int64](-1),
LockoutDurationMinutes: ToPtr[int64](-1),
RequireMixedCase: ToPtr(true),
RequireNumberChar: ToPtr(true),
RequireSpecialChar: ToPtr(true),
},
},
Secondary: &rest_model.AuthPolicySecondary{
RequireExtJWTSigner: nil,
RequireTotp: ToPtr(false),
},
Tags: &rest_model.Tags{
SubTags: map[string]interface{}{
tag1Name: tag1Value,
},
},
}
authPolicyCreated := &rest_model.CreateEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicy).SetResult(authPolicyCreated).Post("/auth-policies")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", authPolicy, resp.Body())
ctx.Req.NotEmpty(authPolicyCreated.Data.ID)
t.Run("get returns 200 and same input values", func(t *testing.T) {
ctx.testContextChanged(t)
authPolicyEnvelope := &rest_model.DetailAuthPolicyEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetResult(authPolicyEnvelope).Get("/auth-policies/" + authPolicyCreated.Data.ID)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode(), "expected 200 for GET %s: %s", resp.Request.URL, resp.Body())
authPolicyDetail := authPolicyEnvelope.Data
ctx.Req.Equal(authPolicyCreated.Data.ID, *authPolicyDetail.ID)
ctx.Req.Equal(len(authPolicy.Tags.SubTags), len(authPolicyDetail.Tags.SubTags))
ctx.Req.Equal(authPolicy.Tags.SubTags[tag1Name], authPolicyDetail.Tags.SubTags[tag1Name])
ctx.Req.Equal(*authPolicy.Name, *authPolicyDetail.Name)
ctx.Req.Equal(len(authPolicy.Primary.ExtJWT.AllowedSigners), len(authPolicyDetail.Primary.ExtJWT.AllowedSigners))
ctx.Req.Equal(authPolicy.Primary.ExtJWT.AllowedSigners[0], authPolicyDetail.Primary.ExtJWT.AllowedSigners[0])
ctx.Req.Equal(*authPolicy.Primary.ExtJWT.Allowed, *authPolicyDetail.Primary.ExtJWT.Allowed)
ctx.Req.Equal(*authPolicy.Primary.Updb.Allowed, *authPolicyDetail.Primary.Updb.Allowed)
ctx.Req.Equal(db.UpdbUnlimitedAttemptsLimit, *authPolicyDetail.Primary.Updb.MaxAttempts)
ctx.Req.Equal(db.DefaultUpdbMinPasswordLength, *authPolicyDetail.Primary.Updb.MinPasswordLength)
ctx.Req.Equal(db.UpdbIndefiniteLockout, *authPolicyDetail.Primary.Updb.LockoutDurationMinutes)
ctx.Req.Equal(*authPolicy.Primary.Updb.RequireMixedCase, *authPolicyDetail.Primary.Updb.RequireMixedCase)
ctx.Req.Equal(*authPolicy.Primary.Updb.RequireNumberChar, *authPolicyDetail.Primary.Updb.RequireNumberChar)
ctx.Req.Equal(*authPolicy.Primary.Updb.RequireSpecialChar, *authPolicyDetail.Primary.Updb.RequireSpecialChar)
ctx.Req.Equal(*authPolicy.Primary.Cert.Allowed, *authPolicyDetail.Primary.Cert.Allowed)
ctx.Req.Equal(*authPolicy.Primary.Cert.AllowExpiredCerts, *authPolicyDetail.Primary.Cert.AllowExpiredCerts)
ctx.Req.Equal(*authPolicy.Secondary.RequireTotp, *authPolicyDetail.Secondary.RequireTotp)
ctx.Req.Equal(authPolicy.Secondary.RequireExtJWTSigner, authPolicyDetail.Secondary.RequireExtJWTSigner)
})
})
t.Run("disabled auth modules stop authentication", func(t *testing.T) {
ctx.testContextChanged(t)
authPolicy := &rest_model.AuthPolicyCreate{
Name: ToPtr("Original Name 1 - Stops Auth"),
Primary: &rest_model.AuthPolicyPrimary{
Cert: &rest_model.AuthPolicyPrimaryCert{
AllowExpiredCerts: ToPtr(true),
Allowed: ToPtr(false),
},
ExtJWT: &rest_model.AuthPolicyPrimaryExtJWT{
Allowed: ToPtr(false),
AllowedSigners: []string{},
},
Updb: &rest_model.AuthPolicyPrimaryUpdb{
Allowed: ToPtr(false),
MaxAttempts: ToPtr[int64](5),
MinPasswordLength: ToPtr[int64](5),
LockoutDurationMinutes: ToPtr[int64](0),
RequireMixedCase: ToPtr(true),
RequireNumberChar: ToPtr(true),
RequireSpecialChar: ToPtr(true),
},
},
Secondary: &rest_model.AuthPolicySecondary{
RequireExtJWTSigner: nil,
RequireTotp: ToPtr(false),
},
}
authPolicyCreated := &rest_model.CreateEnvelope{}
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicy).SetResult(authPolicyCreated).Post("/auth-policies")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", authPolicy, resp.Body())
ctx.Req.NotEmpty(authPolicyCreated.Data.ID)
identityType := rest_model.IdentityTypeDevice
t.Run("cert authentication fails", func(t *testing.T) {
ctx.testContextChanged(t)
identityOtt := &rest_model.IdentityCreate{
AuthPolicyID: &authPolicyCreated.Data.ID,
IsAdmin: ToPtr(false),
Name: ToPtr("test-identity-auth-policy-cert-not-allowed"),
Type: &identityType,
Enrollment: &rest_model.IdentityCreateEnrollment{
Ott: true,
},
}
identityOttCreated := &rest_model.CreateEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(identityOtt).SetResult(identityOttCreated).Post("/identities")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", identityOtt, resp.Body())
ctx.Req.NotEmpty(authPolicyCreated.Data.ID)
certAuthenticator := ctx.completeOttEnrollment(identityOttCreated.Data.ID)
apiSession, err := certAuthenticator.AuthenticateClientApi(ctx)
ctx.Req.Error(err)
ctx.Req.Nil(apiSession)
})
t.Run("updb authentication fails", func(t *testing.T) {
ctx.testContextChanged(t)
username := "test-identity-auth-policy-updb"
password := "123abcABC#%!#"
identityUpdb := &rest_model.IdentityCreate{
AuthPolicyID: &authPolicyCreated.Data.ID,
IsAdmin: ToPtr(false),
Name: ToPtr("test-identity-auth-policy-updb-not-allowed"),
Type: &identityType,
Enrollment: &rest_model.IdentityCreateEnrollment{
Updb: username,
},
}
identityUpdbCreated := &rest_model.CreateEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(identityUpdb).SetResult(identityUpdbCreated).Post("/identities")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", identityUpdb, resp.Body())
ctx.Req.NotEmpty(identityUpdbCreated.Data.ID)
ctx.completeUpdbEnrollment(identityUpdbCreated.Data.ID, password)
passwordAuthenticator := &updbAuthenticator{
Username: username,
Password: password,
}
apiSession, err := passwordAuthenticator.AuthenticateClientApi(ctx)
ctx.Req.Error(err)
ctx.Req.Nil(apiSession)
})
t.Run("ext jwt authentication fails", func(t *testing.T) {
ctx.testContextChanged(t)
jwtSignerCert, jwtSignerPrivate := newSelfSignedCert("Test Jwt Signer Cert - Auth Policy Ext JWT Not Allowed 01")
extJwtSigner := &rest_model.ExternalJWTSignerCreate{
CertPem: ToPtr(nfpem.EncodeToString(jwtSignerCert)),
Enabled: ToPtr(true),
Name: ToPtr("Test JWT Signer - Auth Policy - Auth Policy Ext JWT Not Allowed 01"),
Kid: ToPtr(uuid.NewString()),
Issuer: ToPtr("test-issuer-106"),
Audience: ToPtr("test-audience-106"),
}
extJwtSignerCreated := &rest_model.CreateEnvelope{}
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(extJwtSigner).SetResult(extJwtSignerCreated).Post("/external-jwt-signers")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", extJwtSigner, resp.Body())
ctx.Req.NotEmpty(extJwtSignerCreated.Data.ID)
identityExtJwt := &rest_model.IdentityCreate{
AuthPolicyID: &authPolicyCreated.Data.ID,
IsAdmin: ToPtr(false),
Name: ToPtr("test-identity-auth-policy-ext-jwt-not-allowed"),
Type: &identityType,
}
identityExtJwtCreated := &rest_model.CreateEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(identityExtJwt).SetResult(identityExtJwtCreated).Post("/identities")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", identityExtJwt, resp.Body())
ctx.Req.NotEmpty(identityExtJwtCreated.Data.ID)
jwtToken := jwt.New(jwt.SigningMethodES256)
jwtToken.Claims = jwt.RegisteredClaims{
Audience: []string{"ziti.controller"},
ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(2 * time.Hour)},
ID: time.Now().String(),
IssuedAt: &jwt.NumericDate{Time: time.Now()},
Issuer: "fake.issuer",
NotBefore: &jwt.NumericDate{Time: time.Now()},
Subject: identityExtJwtCreated.Data.ID,
}
jwtToken.Header["kid"] = *extJwtSigner.Kid
jwtStrSigned, err := jwtToken.SignedString(jwtSignerPrivate)
ctx.Req.NoError(err)
ctx.Req.NotEmpty(jwtStrSigned)
result := &rest_model.CurrentAPISessionDetailEnvelope{}
resp, err = ctx.newAnonymousClientApiRequest().SetResult(result).SetHeader("Authorization", "Bearer "+jwtStrSigned).Post("/authenticate?method=ext-jwt")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusUnauthorized, resp.StatusCode())
})
})
t.Run("auth module limits valid ext-jwt signers", func(t *testing.T) {
ctx.testContextChanged(t)
jwtSignerCertAllowed, jwtSignerPrivateAllowed := newSelfSignedCert("Test Jwt Signer Cert - Auth Policy Ext JWT Allowed 01")
extJwtSignerAllowed := &rest_model.ExternalJWTSignerCreate{
CertPem: ToPtr(nfpem.EncodeToString(jwtSignerCertAllowed)),
Enabled: ToPtr(true),
Name: ToPtr("Test JWT Signer - Auth Policy - Auth Policy Ext JWT Limited 01"),
Kid: ToPtr(uuid.NewString()),
Issuer: ToPtr("test-issuer-107"),
Audience: ToPtr("test-audience-107"),
}
extJwtSignerCreatedAllowed := &rest_model.CreateEnvelope{}
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(extJwtSignerAllowed).SetResult(extJwtSignerCreatedAllowed).Post("/external-jwt-signers")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", extJwtSignerAllowed, resp.Body())
ctx.Req.NotEmpty(extJwtSignerCreatedAllowed.Data.ID)
jwtSignerCertNotAllowed, jwtSignerPrivateNotAllowed := newSelfSignedCert("Test Jwt Signer Cert - Auth Policy Ext JWT Not Allowed 02")
extJwtSigner := &rest_model.ExternalJWTSignerCreate{
CertPem: ToPtr(nfpem.EncodeToString(jwtSignerCertNotAllowed)),
Enabled: ToPtr(true),
Name: ToPtr("Test JWT Signer - Auth Policy - Auth Policy Ext JWT Limited 02"),
Kid: ToPtr(uuid.NewString()),
Issuer: ToPtr("test-issuer-108"),
Audience: ToPtr("test-audience-108"),
}
extJwtSignerCreatedNotAllowed := &rest_model.CreateEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(extJwtSigner).SetResult(extJwtSignerCreatedNotAllowed).Post("/external-jwt-signers")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", extJwtSigner, resp.Body())
ctx.Req.NotEmpty(extJwtSignerCreatedNotAllowed.Data.ID)
authPolicy := &rest_model.AuthPolicyCreate{
Name: ToPtr("Original Name 1 - Limits ext-jwt signers"),
Primary: &rest_model.AuthPolicyPrimary{
Cert: &rest_model.AuthPolicyPrimaryCert{
AllowExpiredCerts: ToPtr(true),
Allowed: ToPtr(false),
},
ExtJWT: &rest_model.AuthPolicyPrimaryExtJWT{
Allowed: ToPtr(true),
AllowedSigners: []string{
extJwtSignerCreatedAllowed.Data.ID,
},
},
Updb: &rest_model.AuthPolicyPrimaryUpdb{
Allowed: ToPtr(false),
MaxAttempts: ToPtr[int64](5),
MinPasswordLength: ToPtr[int64](5),
LockoutDurationMinutes: ToPtr[int64](0),
RequireMixedCase: ToPtr(true),
RequireNumberChar: ToPtr(true),
RequireSpecialChar: ToPtr(true),
},
},
Secondary: &rest_model.AuthPolicySecondary{
RequireExtJWTSigner: nil,
RequireTotp: ToPtr(false),
},
}
authPolicyCreated := &rest_model.CreateEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicy).SetResult(authPolicyCreated).Post("/auth-policies")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", authPolicy, resp.Body())
ctx.Req.NotEmpty(authPolicyCreated.Data.ID)
identityType := rest_model.IdentityTypeDevice
identityExtJwt := &rest_model.IdentityCreate{
AuthPolicyID: &authPolicyCreated.Data.ID,
IsAdmin: ToPtr(false),
Name: ToPtr("test-identity-auth-policy-ext-jwt-not-allowed 02"),
Type: &identityType,
}
identityExtJwtCreated := &rest_model.CreateEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(identityExtJwt).SetResult(identityExtJwtCreated).Post("/identities")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", identityExtJwt, resp.Body())
ctx.Req.NotEmpty(identityExtJwtCreated.Data.ID)
t.Run("can authenticate with approved external jwt signer", func(t *testing.T) {
jwtToken := jwt.New(jwt.SigningMethodES256)
jwtToken.Claims = jwt.RegisteredClaims{
ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(2 * time.Hour)},
ID: time.Now().String(),
IssuedAt: &jwt.NumericDate{Time: time.Now()},
Issuer: "test-issuer-107",
Audience: []string{"test-audience-107"},
NotBefore: &jwt.NumericDate{Time: time.Now()},
Subject: identityExtJwtCreated.Data.ID,
}
jwtToken.Header["kid"] = *extJwtSignerAllowed.Kid
jwtStrSigned, err := jwtToken.SignedString(jwtSignerPrivateAllowed)
ctx.Req.NoError(err)
ctx.Req.NotEmpty(jwtStrSigned)
result := &rest_model.CurrentAPISessionDetailEnvelope{}
resp, err = ctx.newAnonymousClientApiRequest().SetResult(result).SetHeader("Authorization", "Bearer "+jwtStrSigned).Post("/authenticate?method=ext-jwt")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode())
})
t.Run("cannot authenticate with unapproved external jwt signer", func(t *testing.T) {
ctx.testContextChanged(t)
jwtToken := jwt.New(jwt.SigningMethodES256)
jwtToken.Claims = jwt.RegisteredClaims{
Audience: []string{"ziti.controller"},
ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(2 * time.Hour)},
ID: time.Now().String(),
IssuedAt: &jwt.NumericDate{Time: time.Now()},
Issuer: "fake.issuer",
NotBefore: &jwt.NumericDate{Time: time.Now()},
Subject: identityExtJwtCreated.Data.ID,
}
jwtToken.Header["kid"] = *extJwtSigner.Kid
jwtStrSigned, err := jwtToken.SignedString(jwtSignerPrivateNotAllowed)
ctx.Req.NoError(err)
ctx.Req.NotEmpty(jwtStrSigned)
result := &rest_model.CurrentAPISessionDetailEnvelope{}
resp, err = ctx.newAnonymousClientApiRequest().SetResult(result).SetHeader("Authorization", "Bearer "+jwtStrSigned).Post("/authenticate?method=ext-jwt")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusUnauthorized, resp.StatusCode())
})
})
t.Run("auth policy with secondary ext jwt signer", func(t *testing.T) {
ctx.testContextChanged(t)
jwtSignerCertAllowed, validJwtSignerPrivateKey := newSelfSignedCert("Test Jwt Signer Cert - Auth Policy Ext JWT FA 01")
extJwtSignerAllowed := &rest_model.ExternalJWTSignerCreate{
CertPem: ToPtr(nfpem.EncodeToString(jwtSignerCertAllowed)),
Enabled: ToPtr(true),
Name: ToPtr("Test JWT Signer - Auth Policy - Auth Policy Ext JWT FA 01"),
ExternalAuthURL: ToPtr("https://get.some.jwt.here"),
Kid: ToPtr(uuid.NewString()),
Issuer: ToPtr("test-issuer-109"),
Audience: ToPtr("test-audience-109"),
}
extJwtSignerCreatedAllowed := &rest_model.CreateEnvelope{}
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(extJwtSignerAllowed).SetResult(extJwtSignerCreatedAllowed).Post("/external-jwt-signers")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", extJwtSignerAllowed, resp.Body())
ctx.Req.NotEmpty(extJwtSignerCreatedAllowed.Data.ID)
authPolicy := &rest_model.AuthPolicyCreate{
Name: ToPtr("Original Name 1 - ext jwt FA 01"),
Primary: &rest_model.AuthPolicyPrimary{
Cert: &rest_model.AuthPolicyPrimaryCert{
AllowExpiredCerts: ToPtr(true),
Allowed: ToPtr(true),
},
ExtJWT: &rest_model.AuthPolicyPrimaryExtJWT{
Allowed: ToPtr(true),
AllowedSigners: []string{},
},
Updb: &rest_model.AuthPolicyPrimaryUpdb{
Allowed: ToPtr(false),
MaxAttempts: ToPtr[int64](5),
MinPasswordLength: ToPtr[int64](5),
LockoutDurationMinutes: ToPtr[int64](0),
RequireMixedCase: ToPtr(true),
RequireNumberChar: ToPtr(true),
RequireSpecialChar: ToPtr(true),
},
},
Secondary: &rest_model.AuthPolicySecondary{
RequireExtJWTSigner: &extJwtSignerCreatedAllowed.Data.ID,
RequireTotp: ToPtr(false),
},
}
authPolicyCreated := &rest_model.CreateEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(authPolicy).SetResult(authPolicyCreated).Post("/auth-policies")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", authPolicy, resp.Body())
ctx.Req.NotEmpty(authPolicyCreated.Data.ID)
identityType := rest_model.IdentityTypeDevice
identityExtJwt := &rest_model.IdentityCreate{
AuthPolicyID: &authPolicyCreated.Data.ID,
IsAdmin: ToPtr(false),
Name: ToPtr("test-identity-auth-policy-ext-jwt-FA 01"),
Type: &identityType,
Enrollment: &rest_model.IdentityCreateEnrollment{
Ott: true,
},
}
identityExtJwtCreated := &rest_model.CreateEnvelope{}
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(identityExtJwt).SetResult(identityExtJwtCreated).Post("/identities")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusCreated, resp.StatusCode(), "expected 201 for POST %T: %s", identityExtJwt, resp.Body())
ctx.Req.NotEmpty(identityExtJwtCreated.Data.ID)
certAuthenticator := ctx.completeOttEnrollment(identityExtJwtCreated.Data.ID)
ctx.Req.NotNil(certAuthenticator)
t.Run("authenticating returns jwt auth query", func(t *testing.T) {
ctx.testContextChanged(t)
apiSession, err := certAuthenticator.AuthenticateClientApi(ctx)
ctx.Req.NoError(err)
ctx.Req.NotNil(apiSession)
currentApiSessionEnv := &rest_model.CurrentAPISessionDetailEnvelope{}
resp, err = apiSession.newAuthenticatedRequest().SetResult(currentApiSessionEnv).Get("current-api-session")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode())
ctx.Req.NotNil(currentApiSessionEnv.Data)
ctx.Req.NotEmpty(currentApiSessionEnv.Data.AuthQueries)
//API Session from auth ahs the auth query
ctx.Req.Equal(rest_model.AuthQueryTypeEXTDashJWT, apiSession.AuthResponse.AuthQueries[0].TypeID)
ctx.Req.Equal(*extJwtSignerAllowed.ExternalAuthURL, apiSession.AuthResponse.AuthQueries[0].HTTPURL)
//API Session from get current has auth query
ctx.Req.Equal(rest_model.AuthQueryTypeEXTDashJWT, currentApiSessionEnv.Data.AuthQueries[0].TypeID)
ctx.Req.Equal(*extJwtSignerAllowed.ExternalAuthURL, currentApiSessionEnv.Data.AuthQueries[0].HTTPURL)
t.Run("without bearer token partially authenticated", func(t *testing.T) {
ctx.testContextChanged(t)
resp, err := apiSession.newAuthenticatedRequest().Get("services")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusUnauthorized, resp.StatusCode())
})
t.Run("with bearer token full access is granted", func(t *testing.T) {
ctx.testContextChanged(t)
jwtToken := jwt.New(jwt.SigningMethodES256)
jwtToken.Claims = jwt.RegisteredClaims{
ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(2 * time.Hour)},
ID: time.Now().String(),
IssuedAt: &jwt.NumericDate{Time: time.Now()},
Issuer: "test-issuer-109",
Audience: []string{"test-audience-109"},
NotBefore: &jwt.NumericDate{Time: time.Now()},
Subject: identityExtJwtCreated.Data.ID,
}
jwtToken.Header["kid"] = *extJwtSignerAllowed.Kid
jwtStrSigned, err := jwtToken.SignedString(validJwtSignerPrivateKey)
ctx.Req.NoError(err)
ctx.Req.NotEmpty(jwtStrSigned)
resp, err := apiSession.newAuthenticatedRequest().SetHeader("Authorization", "Bearer "+jwtStrSigned).Get("services")
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusOK, resp.StatusCode())
})
})
})
}