Files
ziti/controller/model/posture_check_model_mfa_test.go
T
2022-06-30 17:45:08 -04:00

1002 lines
35 KiB
Go

/*
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 model
import (
"github.com/stretchr/testify/require"
"testing"
"time"
)
const (
mfaTestPostureCheckId = "abc123"
mfaTestApiSessionId = "def456"
)
func TestPostureCheckModelMfa(t *testing.T) {
t.Run("evaluateAt", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
mfaCheck.TimeoutSeconds = 40
t.Run("passes at 3/4 timeout", func(t *testing.T) {
threeFourthsTimeout := 10
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt.Add(time.Duration(threeFourthsTimeout) * time.Second)
result := mfaCheck.evaluateAt(mfaTestApiSessionId, postureData, now)
req := require.New(t)
req.True(result)
})
t.Run("passes at 1/2 timeout", func(t *testing.T) {
halfTimeout := 20
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt.Add(time.Duration(halfTimeout) * time.Second)
result := mfaCheck.evaluateAt(mfaTestApiSessionId, postureData, now)
req := require.New(t)
req.True(result)
})
t.Run("passes at 1/4 timeout", func(t *testing.T) {
quarterTimeout := 30
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt.Add(time.Duration(quarterTimeout) * time.Second)
result := mfaCheck.evaluateAt(mfaTestApiSessionId, postureData, now)
req := require.New(t)
req.True(result)
})
})
t.Run("Evaluate", func(t *testing.T) {
t.Run("returns true for valid MFA check", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
result := mfaCheck.Evaluate(mfaTestApiSessionId, postureData)
req := require.New(t)
req.True(result)
})
t.Run("returns false if API Session data is nil", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
postureData.ApiSessions[mfaTestApiSessionId] = nil
result := mfaCheck.Evaluate(mfaTestApiSessionId, postureData)
req := require.New(t)
req.False(result)
})
t.Run("returns false if state is nil", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
postureData.ApiSessions[mfaTestApiSessionId].Mfa = nil
result := mfaCheck.Evaluate(mfaTestApiSessionId, postureData)
req := require.New(t)
req.False(result)
})
t.Run("returns true if endpoint state is nil (no wake or unlock events)", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
postureData.ApiSessions[mfaTestApiSessionId].EndpointState = nil
result := mfaCheck.Evaluate(mfaTestApiSessionId, postureData)
req := require.New(t)
req.True(result)
})
t.Run("returns false if MFA passedAt is nil", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = nil
result := mfaCheck.Evaluate(mfaTestApiSessionId, postureData)
req := require.New(t)
req.False(result)
})
t.Run("returns false if MFA timed out", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
postureData.ApiSessions[mfaTestApiSessionId].Mfa.TimedOut = true
result := mfaCheck.Evaluate(mfaTestApiSessionId, postureData)
req := require.New(t)
req.False(result)
})
t.Run("returns false if MFA timed out with legacy client and not ignoring legacy clients", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
postureData.ApiSessions[mfaTestApiSessionId].Mfa.TimedOut = true
postureData.ApiSessions[mfaTestApiSessionId].SdkInfo.Version = ""
result := mfaCheck.Evaluate(mfaTestApiSessionId, postureData)
req := require.New(t)
req.False(result)
})
t.Run("returns true if MFA timed out with legacy client and ignoring legacy clients", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
mfaCheck.IgnoreLegacyEndpoints = true
postureData.ApiSessions[mfaTestApiSessionId].Mfa.TimedOut = true
postureData.ApiSessions[mfaTestApiSessionId].SdkInfo.Version = ""
result := mfaCheck.Evaluate(mfaTestApiSessionId, postureData)
req := require.New(t)
req.False(result)
})
t.Run("returns true if woke reported after MFA and inside grace period", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
wokenAt := time.Now().UTC()
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = &wokenAt
result := mfaCheck.Evaluate(mfaTestApiSessionId, postureData)
req := require.New(t)
req.True(result)
})
t.Run("returns true if woke reported after MFA and just inside grace period", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
mfaCheck.TimeoutSeconds = PostureCheckNoTimeout
wokenAt := time.Now().Add(MfaPromptGracePeriod).Add(1 * time.Second).UTC() //4min 59 seconds ago
passedMfaAt := wokenAt.Add(-1 * time.Minute)
postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = &passedMfaAt
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = &wokenAt
result := mfaCheck.Evaluate(mfaTestApiSessionId, postureData)
req := require.New(t)
req.True(result)
})
t.Run("returns false if woke reported after MFA and outside grace period", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
//set passed mfa before woken, put woken 6 minutes in the past making now() outside of the 5min grace period
wokenAt := time.Now().Add(MfaPromptGracePeriod).UTC()
passedMfaAt := wokenAt.Add(-1 * time.Minute)
postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = &passedMfaAt
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = &wokenAt
result := mfaCheck.Evaluate(mfaTestApiSessionId, postureData)
req := require.New(t)
req.False(result)
})
t.Run("returns true if unlocked reported after MFA and inside grace period", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
unlockedAt := time.Now().UTC()
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = &unlockedAt
result := mfaCheck.Evaluate(mfaTestApiSessionId, postureData)
req := require.New(t)
req.True(result)
})
t.Run("returns true if woke reported after MFA and just inside grace period", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
mfaCheck.TimeoutSeconds = PostureCheckNoTimeout
wokenAt := time.Now().Add(MfaPromptGracePeriod).Add(1 * time.Second).UTC() //4min 59 seconds ago
passedMfaAt := wokenAt.Add(-1 * time.Minute)
postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = &passedMfaAt
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = &wokenAt
result := mfaCheck.Evaluate(mfaTestApiSessionId, postureData)
req := require.New(t)
req.True(result)
})
t.Run("returns false if unlocked reported after MFA and outside grace period", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
//set passed mfa before woken, put woken 6 minutes in the past making now() outside of the 5min grace period
unlockedAt := time.Now().Add(MfaPromptGracePeriod).UTC()
passedMfaAt := unlockedAt.Add(-1 * time.Minute)
postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = &passedMfaAt
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = &unlockedAt
result := mfaCheck.Evaluate(mfaTestApiSessionId, postureData)
req := require.New(t)
req.False(result)
})
})
t.Run("PassedOnWake", func(t *testing.T) {
t.Run("returns true with default case", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
result := mfaCheck.PassedOnWake(postureData.ApiSessions[mfaTestApiSessionId], time.Now().UTC())
req := require.New(t)
req.True(result)
})
t.Run("returns true when PromptOnWake is false", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
mfaCheck.PromptOnWake = false
result := mfaCheck.PassedOnWake(postureData.ApiSessions[mfaTestApiSessionId], time.Now().UTC())
req := require.New(t)
req.True(result)
})
t.Run("returns false when PromptOnWake is true and ApiSessionData is nil", func(t *testing.T) {
mfaCheck, _ := newMfaCheckAndPostureData()
result := mfaCheck.PassedOnWake(nil, time.Now().UTC())
req := require.New(t)
req.False(result)
})
t.Run("returns false when PromptOnWake is true and MFA state is nil", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
apiSessionData := postureData.ApiSessions[mfaTestApiSessionId]
apiSessionData.Mfa = nil
result := mfaCheck.PassedOnWake(apiSessionData, time.Now().UTC())
req := require.New(t)
req.False(result)
})
t.Run("returns false when PromptOnWake is true and MFA PassedAt is nil", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
apiSessionData := postureData.ApiSessions[mfaTestApiSessionId]
apiSessionData.Mfa.PassedMfaAt = nil
result := mfaCheck.PassedOnWake(apiSessionData, time.Now().UTC())
req := require.New(t)
req.False(result)
})
t.Run("returns true when PromptOnWake is true and endpoint state is nil", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
apiSessionData := postureData.ApiSessions[mfaTestApiSessionId]
apiSessionData.EndpointState = nil
result := mfaCheck.PassedOnWake(apiSessionData, time.Now().UTC())
req := require.New(t)
req.True(result)
})
t.Run("returns true when PromptOnWake is true and endpoint state WokenAt is nil", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
apiSessionData := postureData.ApiSessions[mfaTestApiSessionId]
apiSessionData.EndpointState.WokenAt = nil
result := mfaCheck.PassedOnWake(apiSessionData, time.Now().UTC())
req := require.New(t)
req.True(result)
})
})
t.Run("PassedOnUnlock", func(t *testing.T) {
t.Run("returns true with default case", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
result := mfaCheck.PassedOnUnlock(postureData.ApiSessions[mfaTestApiSessionId], time.Now().UTC())
req := require.New(t)
req.True(result)
})
t.Run("returns true when PromptOnUnlock is false", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
mfaCheck.PromptOnUnlock = false
result := mfaCheck.PassedOnUnlock(postureData.ApiSessions[mfaTestApiSessionId], time.Now().UTC())
req := require.New(t)
req.True(result)
})
t.Run("returns false when PromptOnUnlock is true and ApiSessionData is nil", func(t *testing.T) {
mfaCheck, _ := newMfaCheckAndPostureData()
result := mfaCheck.PassedOnUnlock(nil, time.Now().UTC())
req := require.New(t)
req.False(result)
})
t.Run("returns false when PromptOnUnlock is true and MFA state is nil", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
apiSessionData := postureData.ApiSessions[mfaTestApiSessionId]
apiSessionData.Mfa = nil
result := mfaCheck.PassedOnUnlock(apiSessionData, time.Now().UTC())
req := require.New(t)
req.False(result)
})
t.Run("returns false when PromptOnUnlock is true and MFA PassedAt is nil", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
apiSessionData := postureData.ApiSessions[mfaTestApiSessionId]
apiSessionData.Mfa.PassedMfaAt = nil
result := mfaCheck.PassedOnUnlock(apiSessionData, time.Now().UTC())
req := require.New(t)
req.False(result)
})
t.Run("returns true when PromptOnUnlock is true and endpoint state is nil", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
apiSessionData := postureData.ApiSessions[mfaTestApiSessionId]
apiSessionData.EndpointState = nil
result := mfaCheck.PassedOnUnlock(apiSessionData, time.Now().UTC())
req := require.New(t)
req.True(result)
})
t.Run("returns true when PromptOnUnlock is true and endpoint state UnlockedAt is nil", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
apiSessionData := postureData.ApiSessions[mfaTestApiSessionId]
apiSessionData.EndpointState.UnlockedAt = nil
result := mfaCheck.PassedOnUnlock(apiSessionData, time.Now().UTC())
req := require.New(t)
req.True(result)
})
})
t.Run("FailureValues", func(t *testing.T) {
t.Run("expected values for default case are correct", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
val := mfaCheck.FailureValues(mfaTestApiSessionId, postureData)
failureValues := val.(*PostureCheckFailureValuesMfa)
req.NotEmpty(failureValues)
req.True(failureValues.ExpectedValue.PassedMfa)
req.True(failureValues.ExpectedValue.PassedOnWake)
req.True(failureValues.ExpectedValue.PassedOnUnlock)
req.False(failureValues.ExpectedValue.TimedOutSeconds)
})
t.Run("actual values for default case are correct", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
val := mfaCheck.FailureValues(mfaTestApiSessionId, postureData)
failureValues := val.(*PostureCheckFailureValuesMfa)
req.NotEmpty(failureValues)
req.True(failureValues.ActualValue.PassedMfa)
req.True(failureValues.ActualValue.PassedOnWake)
req.True(failureValues.ActualValue.PassedOnUnlock)
req.False(failureValues.ActualValue.TimedOutSeconds)
})
t.Run("actual values for for invalid API Session are correct", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
val := mfaCheck.FailureValues("invalid_api_session_id", postureData)
failureValues := val.(*PostureCheckFailureValuesMfa)
req.NotEmpty(failureValues)
req.False(failureValues.ActualValue.PassedMfa)
req.False(failureValues.ActualValue.PassedOnWake)
req.False(failureValues.ActualValue.PassedOnUnlock)
req.True(failureValues.ActualValue.TimedOutSeconds)
})
})
t.Run("IsLegacyClient", func(t *testing.T) {
t.Run("returns false for ziti-sdk-c and 0.25.0", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
result := mfaCheck.IsLegacyClient(postureData.ApiSessions[mfaTestApiSessionId])
req := require.New(t)
req.False(result)
})
t.Run("returns true for ziti-sdk-c and 0.24.4", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
apiSessionData := postureData.ApiSessions[mfaTestApiSessionId]
apiSessionData.SdkInfo.Version = "0.24.4"
result := mfaCheck.IsLegacyClient(postureData.ApiSessions[mfaTestApiSessionId])
req := require.New(t)
req.True(result)
})
t.Run("returns true for ziti-sdk-c and 0.24.5", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
apiSessionData := postureData.ApiSessions[mfaTestApiSessionId]
apiSessionData.SdkInfo.Version = "0.24.5"
result := mfaCheck.IsLegacyClient(postureData.ApiSessions[mfaTestApiSessionId])
req := require.New(t)
req.True(result)
})
t.Run("returns true for missing type and 0.24.4", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
apiSessionData := postureData.ApiSessions[mfaTestApiSessionId]
apiSessionData.SdkInfo.Version = "0.24.4"
apiSessionData.SdkInfo.Type = ""
result := mfaCheck.IsLegacyClient(postureData.ApiSessions[mfaTestApiSessionId])
req := require.New(t)
req.True(result)
})
t.Run("returns false for ziti-sdk-c and 1.0.0", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
apiSessionData := postureData.ApiSessions[mfaTestApiSessionId]
apiSessionData.SdkInfo.Version = "1.0.0"
result := mfaCheck.IsLegacyClient(postureData.ApiSessions[mfaTestApiSessionId])
req := require.New(t)
req.False(result)
})
t.Run("returns true if sdk info is nil", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
apiSessionData := postureData.ApiSessions[mfaTestApiSessionId]
apiSessionData.SdkInfo = nil
result := mfaCheck.IsLegacyClient(postureData.ApiSessions[mfaTestApiSessionId])
req := require.New(t)
req.True(result)
})
t.Run("returns true if for ziti-sdk-c and an invalid version", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
apiSessionData := postureData.ApiSessions[mfaTestApiSessionId]
apiSessionData.SdkInfo.Version = "weeeeeee"
result := mfaCheck.IsLegacyClient(postureData.ApiSessions[mfaTestApiSessionId])
req := require.New(t)
req.True(result)
})
})
t.Run("GetTimeoutSeconds", func(t *testing.T) {
t.Run("returns the static timeout value", func(t *testing.T) {
mfaCheck, _ := newMfaCheckAndPostureData()
req := require.New(t)
req.Equal(mfaCheck.TimeoutSeconds, mfaCheck.GetTimeoutSeconds())
})
t.Run("returns no timeout for negative numbers", func(t *testing.T) {
mfaCheck, _ := newMfaCheckAndPostureData()
mfaCheck.TimeoutSeconds = -100
req := require.New(t)
req.Equal(PostureCheckNoTimeout, mfaCheck.GetTimeoutSeconds())
})
t.Run("returns no timeout for 0", func(t *testing.T) {
mfaCheck, _ := newMfaCheckAndPostureData()
mfaCheck.TimeoutSeconds = 0
req := require.New(t)
req.Equal(PostureCheckNoTimeout, mfaCheck.GetTimeoutSeconds())
})
})
t.Run("GetTimeoutRemainingSeconds", func(t *testing.T) {
t.Run("returns 0 if no ApiSessionData is present", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
postureData.ApiSessions = map[string]*ApiSessionPostureData{}
req := require.New(t)
req.Equal(int64(0), mfaCheck.GetTimeoutRemainingSeconds(mfaTestApiSessionId, postureData))
})
t.Run("returns 0 if no MFA data is present", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
postureData.ApiSessions[mfaTestApiSessionId].Mfa = nil
req := require.New(t)
req.Equal(int64(0), mfaCheck.GetTimeoutRemainingSeconds(mfaTestApiSessionId, postureData))
})
t.Run("returns 0 if no last MFA passedAt is nil", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = nil
req := require.New(t)
req.Equal(int64(0), mfaCheck.GetTimeoutRemainingSeconds(mfaTestApiSessionId, postureData))
})
t.Run("returns near timeout if much time hasn't passed", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
req := require.New(t)
//time based, if on a slow machine this might slide
req.LessOrEqual(mfaCheck.GetTimeoutRemainingSeconds(mfaTestApiSessionId, postureData), mfaCheck.TimeoutSeconds)
req.GreaterOrEqual(mfaCheck.GetTimeoutRemainingSeconds(mfaTestApiSessionId, postureData), mfaCheck.TimeoutSeconds-5)
})
t.Run("returns not timeout for legacy clients if ignore legacy is true", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
mfaCheck.IgnoreLegacyEndpoints = true
postureData.ApiSessions[mfaTestApiSessionId].SdkInfo.Version = "0.24.4"
req := require.New(t)
req.Equal(PostureCheckNoTimeout, mfaCheck.GetTimeoutRemainingSeconds(mfaTestApiSessionId, postureData))
})
t.Run("returns near timeout for legacy client if ignore legacy is false", func(t *testing.T) {
mfaCheck, postureData := newMfaCheckAndPostureData()
postureData.ApiSessions[mfaTestApiSessionId].SdkInfo.Version = "0.24.4"
req := require.New(t)
//time based, if on a slow machine this might slide
req.LessOrEqual(mfaCheck.GetTimeoutRemainingSeconds(mfaTestApiSessionId, postureData), mfaCheck.TimeoutSeconds)
req.GreaterOrEqual(mfaCheck.GetTimeoutRemainingSeconds(mfaTestApiSessionId, postureData), mfaCheck.TimeoutSeconds-5)
})
})
t.Run("calculateTimeout", func(t *testing.T) {
t.Run("eventAt == now, results in 5m", func(t *testing.T) {
req := require.New(t)
now, err := time.Parse(time.RFC3339, "2020-01-01T11:30:00Z")
req.NoError(err)
eventAt, err := time.Parse(time.RFC3339, "2020-01-01T11:30:00Z")
req.NoError(err)
result := calculateTimeout(now, eventAt, -1*MfaPromptGracePeriod)
req.Equal(int64(300), result)
})
t.Run("eventAt 3min ago, results in 2m", func(t *testing.T) {
req := require.New(t)
now, err := time.Parse(time.RFC3339, "2020-01-01T11:30:00Z")
req.NoError(err)
eventAt, err := time.Parse(time.RFC3339, "2020-01-01T11:27:00Z")
req.NoError(err)
result := calculateTimeout(now, eventAt, -1*MfaPromptGracePeriod)
req.Equal(int64(120), result)
})
t.Run("eventAt 5m before now, results in 0", func(t *testing.T) {
req := require.New(t)
now, err := time.Parse(time.RFC3339, "2020-01-01T11:30:00Z")
req.NoError(err)
eventAt, err := time.Parse(time.RFC3339, "2020-01-01T11:25:00Z")
req.NoError(err)
result := calculateTimeout(now, eventAt, -1*MfaPromptGracePeriod)
req.Equal(int64(0), result)
})
t.Run("eventAt 10m before now, results in 0", func(t *testing.T) {
req := require.New(t)
now, err := time.Parse(time.RFC3339, "2020-01-01T11:30:00Z")
req.NoError(err)
eventAt, err := time.Parse(time.RFC3339, "2020-01-01T11:20:00Z")
req.NoError(err)
result := calculateTimeout(now, eventAt, -1*MfaPromptGracePeriod)
req.Equal(int64(0), result)
})
})
t.Run("getTimeoutRemainingAtSeconds", func(t *testing.T) {
t.Run("with a timeout of 600s, passedAt 0s ago, wake = NIL, unlock = NIL will result in 600s timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
passedAt := *postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
mfaCheck.TimeoutSeconds = 600
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = nil
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = nil
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, passedAt)
req.Equal(int64(600), timeoutRemaining)
})
t.Run("with a timeout of 600s, passedAt 600s ago, wake = NIL, unlock = NIL will result in 0s timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
passedAt := now.Add(-600 * time.Second)
mfaCheck.TimeoutSeconds = 600
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = nil
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = nil
postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = &passedAt
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, *now)
req.Equal(int64(0), timeoutRemaining)
})
t.Run("with a timeout of 600s, passedAt 601s ago, wake = NIL, unlock = NIL will result in 0s timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
passedAt := now.Add(-601 * time.Second)
mfaCheck.TimeoutSeconds = 600
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = nil
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = nil
postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = &passedAt
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, *now)
req.Equal(int64(0), timeoutRemaining)
})
t.Run("with a timeout of 600s, passedAt 30s ago, wake = NIL, unlock = NIL will result in 570s timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
passedAt := now.Add(-30 * time.Second)
mfaCheck.TimeoutSeconds = 600
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = nil
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = nil
postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = &passedAt
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, *now)
req.Equal(int64(570), timeoutRemaining)
})
t.Run("with a timeout of 600s, passedAt 0s ago, wake 10s ago, unlock = NIL will result in 600s timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
wokenAt := now.Add(-10 * time.Second)
mfaCheck.TimeoutSeconds = 600
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = &wokenAt
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = nil
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, *now)
req.Equal(int64(600), timeoutRemaining)
})
t.Run("with a timeout of 600s, passedAt 0s ago, wake = nil, unlock 10s ago will result in 600s timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
unlockedAt := now.Add(-10 * time.Second)
mfaCheck.TimeoutSeconds = 600
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = nil
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = &unlockedAt
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, *now)
req.Equal(int64(600), timeoutRemaining)
})
t.Run("with a timeout of 600s, passedAt 0s ago, wake 10s ago, unlock 10s ago will result in 600s timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
wokenAt := now.Add(-10 * time.Second)
unlockedAt := now.Add(-10 * time.Second)
mfaCheck.TimeoutSeconds = 600
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = &wokenAt
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = &unlockedAt
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, *now)
req.Equal(int64(600), timeoutRemaining)
})
t.Run("with a timeout of 600s, passedAt 30s ago, wake 20s ago, unlock 10s ago will result in 280s grace timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
passedAt := now.Add(-30 * time.Second)
wokenAt := now.Add(-20 * time.Second)
unlockedAt := now.Add(-10 * time.Second)
mfaCheck.TimeoutSeconds = 600
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = &wokenAt
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = &unlockedAt
postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = &passedAt
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, *now)
req.Equal(int64(280), timeoutRemaining)
})
t.Run("with a timeout of 600s, passedAt 30s ago, wake 20s ago, unlock = NIL will result in 280s grace timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
passedAt := now.Add(-30 * time.Second)
wokenAt := now.Add(-20 * time.Second)
mfaCheck.TimeoutSeconds = 600
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = &wokenAt
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = nil
postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = &passedAt
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, *now)
req.Equal(int64(280), timeoutRemaining)
})
t.Run("with a timeout of 600s, passedAt 30s ago, wake 10s ago, unlock 20s ago will result in 280s grace timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
passedAt := now.Add(-30 * time.Second)
wokenAt := now.Add(-10 * time.Second)
unlockedAt := now.Add(-20 * time.Second)
mfaCheck.TimeoutSeconds = 600
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = &wokenAt
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = &unlockedAt
postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = &passedAt
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, *now)
req.Equal(int64(280), timeoutRemaining)
})
t.Run("with a timeout of 600s, passedAt 30s ago, wake = NIL, unlock 20s ago will result in 280s grace timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
passedAt := now.Add(-30 * time.Second)
unlockedAt := now.Add(-20 * time.Second)
mfaCheck.TimeoutSeconds = 600
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = nil
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = &unlockedAt
postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = &passedAt
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, *now)
req.Equal(int64(280), timeoutRemaining)
})
t.Run("with a timeout of 100s, passedAt 0s ago, wake 10s ago, unlock 10s ago will return a 100s timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
wokenAt := now.Add(-10 * time.Second)
unlockedAt := now.Add(-20 * time.Second)
mfaCheck.TimeoutSeconds = 100
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = &wokenAt
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = &unlockedAt
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, *now)
req.Equal(int64(100), timeoutRemaining)
})
t.Run("with a timeout of 100s, passedAt 0s ago, wake = NIL, unlock 10s ago will return a 100s timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
unlockedAt := now.Add(-20 * time.Second)
mfaCheck.TimeoutSeconds = 100
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = nil
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = &unlockedAt
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, *now)
req.Equal(int64(100), timeoutRemaining)
})
t.Run("with a timeout of 100s, passedAt 0s ago, wake 10s ago, unlock = NIL will return a 100s timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
wokenAt := now.Add(-10 * time.Second)
mfaCheck.TimeoutSeconds = 100
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = &wokenAt
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = nil
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, *now)
req.Equal(int64(100), timeoutRemaining)
})
t.Run("with a timeout of 100s, passedAt 30s ago, wake 10s ago, unlock 10s ago will ignore grace periods and result in 70s timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
passedAt := now.Add(-30 * time.Second)
wokenAt := now.Add(-10 * time.Second)
unlockedAt := now.Add(-10 * time.Second)
mfaCheck.TimeoutSeconds = 100
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = &wokenAt
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = &unlockedAt
postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = &passedAt
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, *now)
req.Equal(int64(70), timeoutRemaining)
})
t.Run("with a timeout of 100s, passedAt 30s ago, wake = NIL, unlock 10s ago will ignore grace periods and result in 70s timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
passedAt := now.Add(-30 * time.Second)
unlockedAt := now.Add(-10 * time.Second)
mfaCheck.TimeoutSeconds = 100
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = nil
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = &unlockedAt
postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = &passedAt
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, *now)
req.Equal(int64(70), timeoutRemaining)
})
t.Run("with a timeout of 100s, passedAt 30s ago, wake 10s ago, unlock = NIL will ignore grace periods and result in 70s timeout", func(t *testing.T) {
req := require.New(t)
mfaCheck, postureData := newMfaCheckAndPostureData()
now := postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt
passedAt := now.Add(-30 * time.Second)
wokenAt := now.Add(-10 * time.Second)
mfaCheck.TimeoutSeconds = 100
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.WokenAt = &wokenAt
postureData.ApiSessions[mfaTestApiSessionId].EndpointState.UnlockedAt = nil
postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = &passedAt
timeoutRemaining := mfaCheck.getTimeoutRemainingAtSeconds(mfaTestApiSessionId, postureData, *now)
req.Equal(int64(70), timeoutRemaining)
})
})
}
// newMfaCheckAndPostureData returns a MFA posture check and posture data that will
// pass evaluation. The posture check will evaluate promptOnWake and promptOnUnlock
// with events occurring 10 seconds in the past.
func newMfaCheckAndPostureData() (*PostureCheckMfa, *PostureData) {
passedMfaAt := time.Now().UTC()
wokenAt := passedMfaAt.Add(-10 * time.Second)
unlockedAt := passedMfaAt.Add(-10 * time.Second)
postureResponse := &PostureResponse{
PostureCheckId: mfaTestPostureCheckId,
TypeId: PostureCheckTypeMFA,
TimedOut: false,
LastUpdatedAt: passedMfaAt,
}
postureResponseMfa := &PostureResponseMfa{
PostureResponse: nil,
ApiSessionId: mfaTestApiSessionId,
PassedMfaAt: &passedMfaAt,
}
postureResponse.SubType = postureResponseMfa
postureResponseMfa.PostureResponse = postureResponse
validPostureData := &PostureData{
ApiSessions: map[string]*ApiSessionPostureData{
mfaTestApiSessionId: {
Mfa: postureResponseMfa,
EndpointState: &PostureResponseEndpointState{
ApiSessionId: mfaTestApiSessionId,
WokenAt: &wokenAt,
UnlockedAt: &unlockedAt,
},
SdkInfo: &SdkInfo{
Type: "ziti-sdk-c",
Version: "0.25.0",
},
},
},
}
mfaCheck := &PostureCheckMfa{
TimeoutSeconds: 60,
PromptOnWake: true,
PromptOnUnlock: true,
}
return mfaCheck, validPostureData
}