mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
444 lines
18 KiB
Go
444 lines
18 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/authz"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/mdm"
|
|
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
|
|
"github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig"
|
|
nanomdm_mdm "github.com/fleetdm/fleet/v4/server/mdm/nanomdm/mdm"
|
|
nanomdm_push "github.com/fleetdm/fleet/v4/server/mdm/nanomdm/push"
|
|
nanomdm_pushsvc "github.com/fleetdm/fleet/v4/server/mdm/nanomdm/push/service"
|
|
"github.com/fleetdm/fleet/v4/server/mock"
|
|
mdmmock "github.com/fleetdm/fleet/v4/server/mock/mdm"
|
|
mocksvc "github.com/fleetdm/fleet/v4/server/mock/service"
|
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
|
svcmock "github.com/fleetdm/fleet/v4/server/service/mock"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/test"
|
|
"github.com/jmoiron/sqlx"
|
|
"github.com/micromdm/nanolib/log/stdlogfmt"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"howett.net/plist"
|
|
)
|
|
|
|
func setup(t *testing.T) (*mock.Store, *Service) {
|
|
ds := new(mock.Store)
|
|
|
|
ds.GetAllMDMConfigAssetsByNameFunc = func(ctx context.Context, assetNames []fleet.MDMAssetName,
|
|
_ sqlx.QueryerContext,
|
|
) (map[fleet.MDMAssetName]fleet.MDMConfigAsset, error) {
|
|
return map[fleet.MDMAssetName]fleet.MDMConfigAsset{
|
|
fleet.MDMAssetCACert: {Value: []byte(testCert)},
|
|
fleet.MDMAssetCAKey: {Value: []byte(testKey)},
|
|
fleet.MDMAssetAPNSKey: {Value: []byte(testKey)},
|
|
fleet.MDMAssetAPNSCert: {Value: []byte(testCert)},
|
|
}, nil
|
|
}
|
|
|
|
svc := &Service{
|
|
ds: ds,
|
|
}
|
|
return ds, svc
|
|
}
|
|
|
|
func TestMDMAppleEnableFileVaultAndEscrow(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
getPayloadWithType := func(mc mobileconfig.Mobileconfig, payloadType string) map[string]interface{} {
|
|
var payload struct {
|
|
PayloadContent []map[string]interface{}
|
|
}
|
|
_, err := plist.Unmarshal(mc, &payload)
|
|
require.NoError(t, err)
|
|
|
|
for _, p := range payload.PayloadContent {
|
|
if p["PayloadType"] == payloadType {
|
|
return p
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
t.Run("fails if SCEP is not configured", func(t *testing.T) {
|
|
ds := new(mock.Store)
|
|
svc := &Service{ds: ds}
|
|
ds.GetAllMDMConfigAssetsByNameFunc = func(ctx context.Context, assetNames []fleet.MDMAssetName,
|
|
_ sqlx.QueryerContext,
|
|
) (map[fleet.MDMAssetName]fleet.MDMConfigAsset, error) {
|
|
return nil, nil
|
|
}
|
|
err := svc.MDMAppleEnableFileVaultAndEscrow(ctx, nil)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("fails if the profile can't be saved in the db", func(t *testing.T) {
|
|
ds, svc := setup(t)
|
|
testErr := errors.New("test")
|
|
ds.NewMDMAppleConfigProfileFunc = func(ctx context.Context, p fleet.MDMAppleConfigProfile, vars []fleet.FleetVarName) (*fleet.MDMAppleConfigProfile, error) {
|
|
return nil, testErr
|
|
}
|
|
err := svc.MDMAppleEnableFileVaultAndEscrow(ctx, nil)
|
|
require.ErrorIs(t, err, testErr)
|
|
require.True(t, ds.NewMDMAppleConfigProfileFuncInvoked)
|
|
})
|
|
|
|
t.Run("happy path", func(t *testing.T) {
|
|
var teamID uint = 4
|
|
ds, svc := setup(t)
|
|
ds.NewMDMAppleConfigProfileFunc = func(ctx context.Context, p fleet.MDMAppleConfigProfile, vars []fleet.FleetVarName) (*fleet.MDMAppleConfigProfile, error) {
|
|
require.Equal(t, &teamID, p.TeamID)
|
|
require.Equal(t, p.Identifier, mobileconfig.FleetFileVaultPayloadIdentifier)
|
|
require.Equal(t, p.Name, mdm.FleetFileVaultProfileName)
|
|
require.Contains(t, string(p.Mobileconfig), `MIID6DCCAdACFGX99Sw4aF2qKGLucoIWQRAXHrs1MA0GCSqGSIb3DQEBCwUAMDUxEzARBgNVBAoMClJlZGlzIFRlc3QxHjAcBgNVBAMMFUNlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0yMTEwMTkxNzM0MzlaFw0yMjEwMTkxNzM0MzlaMCwxEzARBgNVBAoMClJlZGlzIFRlc3QxFTATBgNVBAMMDEdlbmVyaWMtY2VydDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKSHcH8EjSvp3Nm4IHAFxG9DZm8+0h1BwU0OX0VHcJ+Cf+f6h0XYMcMo9LFEpnUJRRMjKrM4mkI75NIIufNBN+GrtqqTPTid8wfOGu/Ufa5EEU1hb2j7AiMlpM6i0+ZysXSNo+Vc/cNZT0PXfyOtJnYm6p9WZM84ID1t2ea0bLwC12cTKv5oybVGtJHh76TRxAR3FeQ9+SY30vUAxYm6oWyYho8rRdKtUSe11pXj6OhxxfTZnsSWn4lo0uBpXai63XtieTVpz74htSNC1bunIGv7//m5F60sH5MrF5JSkPxfCfgqski84ICDSRNlvpT+eMPiygAAJ8zY8wYUXRYFYTUCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAAAw+6Uz2bAcXgQ7fQfdOm+T6FLRBcr8PD4ajOvSu/T+HhVVjE26Qt2IBwFEYve2FvDxrBCF8aQYZcyQqnP8bdKebnWAaqL8BbTwLWW+fDuZLO2b4QHjAEdEKKdZC5/FRpQrkerf5CCPTHE+5M17OZg41wdVYnCEwJOkP5pUAVsmwtrSwVeIquy20TZO0qbscDQETf7NIJgW0IXg82wBe53Rv4/wL3Ybq13XVRGYiJrwpaNTfUNgsDWqgwlQ5L2GOLDgg8S2NoF9mWVgCGSp3a2eHW+EmBRQ1OP6EYQtIhKdGLrSndAOMJ2ER1pgHWUFKkWQaZ9i37Dx2j7P5c4/XNeVozcRQcLwKwN+n8k+bwIYcTX0HMOVFYm+WiFi/gjI860Tx853Sc0nkpOXmBCeHSXigGUscgjBYbmJz4iExXuwgawLXKLDKs0yyhLDnKEjmx/Vhz03JpsVFJ84kSWkTZkYsXiG306TxuJCX9zAt1z+6ClieTTGiFY+D8DfkC4H82rlPEtImpZ6rInsMUlAykImpd58e4PMSa+w/wSHXDvwFP7py1Gvz3XvcbGLmpBXblxTUpToqC7zSQJhHOMBBt6XnhcRwd6G9Vj/mQM3FvJIrxtKk8O7FwMJloGivS85OEzCIur5A+bObXbM2pcI8y4ueHE4NtElRBwn859AdB2k=`)
|
|
|
|
testPayload := getPayloadWithType(p.Mobileconfig, "com.apple.MCX.FileVault2")
|
|
require.NotNil(t, testPayload)
|
|
require.Equal(t, true, testPayload["Defer"])
|
|
require.EqualValues(t, 0, testPayload["DeferForceAtUserLoginMaxBypassAttempts"])
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
err := svc.MDMAppleEnableFileVaultAndEscrow(ctx, ptr.Uint(teamID))
|
|
require.NoError(t, err)
|
|
require.True(t, ds.NewMDMAppleConfigProfileFuncInvoked)
|
|
})
|
|
}
|
|
|
|
func TestMDMAppleDisableFileVaultAndEscrow(t *testing.T) {
|
|
var wantTeamID uint
|
|
ds, svc := setup(t)
|
|
ds.DeleteMDMAppleConfigProfileByTeamAndIdentifierFunc = func(ctx context.Context, teamID *uint, profileIdentifier string) error {
|
|
require.NotNil(t, teamID)
|
|
require.Equal(t, wantTeamID, *teamID)
|
|
require.Equal(t, mobileconfig.FleetFileVaultPayloadIdentifier, profileIdentifier)
|
|
return nil
|
|
}
|
|
|
|
err := svc.MDMAppleDisableFileVaultAndEscrow(context.Background(), ptr.Uint(wantTeamID))
|
|
require.NoError(t, err)
|
|
require.True(t, ds.DeleteMDMAppleConfigProfileByTeamAndIdentifierFuncInvoked)
|
|
}
|
|
|
|
var (
|
|
testCert = `-----BEGIN CERTIFICATE-----
|
|
MIID6DCCAdACFGX99Sw4aF2qKGLucoIWQRAXHrs1MA0GCSqGSIb3DQEBCwUAMDUx
|
|
EzARBgNVBAoMClJlZGlzIFRlc3QxHjAcBgNVBAMMFUNlcnRpZmljYXRlIEF1dGhv
|
|
cml0eTAeFw0yMTEwMTkxNzM0MzlaFw0yMjEwMTkxNzM0MzlaMCwxEzARBgNVBAoM
|
|
ClJlZGlzIFRlc3QxFTATBgNVBAMMDEdlbmVyaWMtY2VydDCCASIwDQYJKoZIhvcN
|
|
AQEBBQADggEPADCCAQoCggEBAKSHcH8EjSvp3Nm4IHAFxG9DZm8+0h1BwU0OX0VH
|
|
cJ+Cf+f6h0XYMcMo9LFEpnUJRRMjKrM4mkI75NIIufNBN+GrtqqTPTid8wfOGu/U
|
|
fa5EEU1hb2j7AiMlpM6i0+ZysXSNo+Vc/cNZT0PXfyOtJnYm6p9WZM84ID1t2ea0
|
|
bLwC12cTKv5oybVGtJHh76TRxAR3FeQ9+SY30vUAxYm6oWyYho8rRdKtUSe11pXj
|
|
6OhxxfTZnsSWn4lo0uBpXai63XtieTVpz74htSNC1bunIGv7//m5F60sH5MrF5JS
|
|
kPxfCfgqski84ICDSRNlvpT+eMPiygAAJ8zY8wYUXRYFYTUCAwEAATANBgkqhkiG
|
|
9w0BAQsFAAOCAgEAAAw+6Uz2bAcXgQ7fQfdOm+T6FLRBcr8PD4ajOvSu/T+HhVVj
|
|
E26Qt2IBwFEYve2FvDxrBCF8aQYZcyQqnP8bdKebnWAaqL8BbTwLWW+fDuZLO2b4
|
|
QHjAEdEKKdZC5/FRpQrkerf5CCPTHE+5M17OZg41wdVYnCEwJOkP5pUAVsmwtrSw
|
|
VeIquy20TZO0qbscDQETf7NIJgW0IXg82wBe53Rv4/wL3Ybq13XVRGYiJrwpaNTf
|
|
UNgsDWqgwlQ5L2GOLDgg8S2NoF9mWVgCGSp3a2eHW+EmBRQ1OP6EYQtIhKdGLrSn
|
|
dAOMJ2ER1pgHWUFKkWQaZ9i37Dx2j7P5c4/XNeVozcRQcLwKwN+n8k+bwIYcTX0H
|
|
MOVFYm+WiFi/gjI860Tx853Sc0nkpOXmBCeHSXigGUscgjBYbmJz4iExXuwgawLX
|
|
KLDKs0yyhLDnKEjmx/Vhz03JpsVFJ84kSWkTZkYsXiG306TxuJCX9zAt1z+6Clie
|
|
TTGiFY+D8DfkC4H82rlPEtImpZ6rInsMUlAykImpd58e4PMSa+w/wSHXDvwFP7py
|
|
1Gvz3XvcbGLmpBXblxTUpToqC7zSQJhHOMBBt6XnhcRwd6G9Vj/mQM3FvJIrxtKk
|
|
8O7FwMJloGivS85OEzCIur5A+bObXbM2pcI8y4ueHE4NtElRBwn859AdB2k=
|
|
-----END CERTIFICATE-----`
|
|
|
|
testKey = testingKey(`-----BEGIN RSA TESTING KEY-----
|
|
MIIEogIBAAKCAQEApIdwfwSNK+nc2bggcAXEb0Nmbz7SHUHBTQ5fRUdwn4J/5/qH
|
|
Rdgxwyj0sUSmdQlFEyMqsziaQjvk0gi580E34au2qpM9OJ3zB84a79R9rkQRTWFv
|
|
aPsCIyWkzqLT5nKxdI2j5Vz9w1lPQ9d/I60mdibqn1ZkzzggPW3Z5rRsvALXZxMq
|
|
/mjJtUa0keHvpNHEBHcV5D35JjfS9QDFibqhbJiGjytF0q1RJ7XWlePo6HHF9Nme
|
|
xJafiWjS4GldqLrde2J5NWnPviG1I0LVu6cga/v/+bkXrSwfkysXklKQ/F8J+Cqy
|
|
SLzggINJE2W+lP54w+LKAAAnzNjzBhRdFgVhNQIDAQABAoIBAAtUbFHC3XnVq+iu
|
|
PkWYkBNdX9NvTwbGvWnyAGuD5OSHFwnBfck4fwzCaD9Ay/mpPsF3nXwj/LNs7m/s
|
|
O+ndZty6d2S9qOyaK98wuTgkuNbkRxC+Ee73wgjrkbLNEax/32p4Sn4D7lGid8vj
|
|
LhUl2k0ult+MEnsWkVnJk8TITeiQaT2AHhMr3HKdaI86hJJfam3wEBiLBglnnKqA
|
|
TInMqHoudnFOn/C8iVCFuHCE0oo1dMalbc4rlZuRBqezVhbSMWPLypMVXQb7eixM
|
|
ScJ3m8+DooGDSIe+EW/afhN2VnFbrhQC9/DlxGfwTwsUseWv7pgp53ufyyAzzydn
|
|
2plW/4ECgYEA1Va5RzSUDxr75JX003YZiBcYrG268vosiNYWRhE7frvn5EorZBRW
|
|
t4R70Y2gcXA10aPHzpbq40t6voWtpkfynU3fyRzbBmwfiWLEgckrYMwtcNz8nhG2
|
|
ETAg4LXO9CufbwuDa66h76TpkBzQVNc5TSbBUr/apLDWjKPMz6qW7VUCgYEAxW4K
|
|
Yqp3NgJkC5DhuD098jir9AH96hGhUryOi2CasCvmbjWCgWdolD7SRZJfxOXFOtHv
|
|
7Dkp9glA1Cg/nSmEHKslaTJfBIWK+5rqVD6k6kZE/+4QQWQtUxXXVgGINnGrnPvo
|
|
6MlRJxqGUtYJ0GRTFJP4Py0gwuzf5BMIwe+fpGECgYAOhLRfMCjTTlbOG5ZpvaPH
|
|
Kys2sNEEMBpPxaIGaq3N1iPV2WZSjT/JhW6XuDevAJ/pAGhcmtCpXz2fMaG7qzHL
|
|
mr0cBqaxLTKIOvx8iKA3Gi4NfDyE1Ve6m7fhEv5eh4l2GSZ8cYn7sRFkCVH0NCFm
|
|
KrkFVKEgjBhNwefySf2zcQKBgHDVPgw7nlv4q9LMX6RbI98eMnAG/2XZ45gUeWcA
|
|
tAeBX3WXEVoBjoxDBwuJ5z/xjXHbb8JSvT+G9E0MH6cjhgSYb44aoqFD7TV0yP2S
|
|
u8/Ej0SxewrURO8aKXJW99Edz9WtRuRbwgyWJTSMbRlzbOPy2UrJ8NJWbHK9yiCE
|
|
YXmhAoGAA3QUiCCl11c1C4VsF68Fa2i7qwnty3fvFidZpW3ds0tzZdIvkpRLp5+u
|
|
XAJ5+zStdEGdnu0iXALQlY7ektawXguT/zYKg3nfS9RMGW6CxZotn4bqfQwDuttf
|
|
b1xn1jGQd/o0xFf9ojpDNy6vNojidQGHh6E3h0GYvxbnQmVNq5U=
|
|
-----END RSA TESTING KEY-----`)
|
|
)
|
|
|
|
// prevent static analysis tools from raising issues due to detection of
|
|
// private key in code.
|
|
func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
|
|
|
|
func TestCountABMTokensAuth(t *testing.T) {
|
|
t.Parallel()
|
|
ds := new(mock.Store)
|
|
ctx := context.Background()
|
|
authorizer, err := authz.NewAuthorizer()
|
|
require.NoError(t, err)
|
|
svc := Service{ds: ds, authz: authorizer}
|
|
|
|
ds.GetABMTokenCountFunc = func(ctx context.Context) (int, error) {
|
|
return 5, nil
|
|
}
|
|
|
|
t.Run("CountABMTokens", func(t *testing.T) {
|
|
cases := []struct {
|
|
desc string
|
|
user *fleet.User
|
|
shoudFailWithAuth bool
|
|
}{
|
|
{"no role", test.UserNoRoles, true},
|
|
{"gitops can read", test.UserGitOps, false},
|
|
{"maintainer can read", test.UserMaintainer, false},
|
|
{"observer can read", test.UserObserver, false},
|
|
{"observer+ can read", test.UserObserverPlus, false},
|
|
{"admin can read", test.UserAdmin, false},
|
|
{"tm1 gitops can read", test.UserTeamGitOpsTeam1, false},
|
|
{"tm1 maintainer can read", test.UserTeamMaintainerTeam1, false},
|
|
{"tm1 observer can read", test.UserTeamObserverTeam1, false},
|
|
{"tm1 observer+ can read", test.UserTeamObserverPlusTeam1, false},
|
|
{"tm1 admin can read", test.UserTeamAdminTeam1, false},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.desc, func(t *testing.T) {
|
|
ctx = test.UserContext(ctx, c.user)
|
|
count, err := svc.CountABMTokens(ctx)
|
|
checkAuthErr(t, c.shoudFailWithAuth, err)
|
|
if !c.shoudFailWithAuth {
|
|
assert.EqualValues(t, 5, count)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestClearPasscode(t *testing.T) {
|
|
t.Parallel()
|
|
ds := new(mock.Store)
|
|
authorizer, err := authz.NewAuthorizer()
|
|
require.NoError(t, err)
|
|
|
|
// Set up the real commander with mocked storage and pusher.
|
|
mdmStorage := &mdmmock.MDMAppleStore{}
|
|
pushProvider := &svcmock.APNSPushProvider{}
|
|
pushProvider.PushFunc = func(_ context.Context, pushes []*nanomdm_mdm.Push) (map[string]*nanomdm_push.Response, error) {
|
|
res := make(map[string]*nanomdm_push.Response, len(pushes))
|
|
for _, p := range pushes {
|
|
res[p.Token.String()] = &nanomdm_push.Response{Id: "ok"}
|
|
}
|
|
return res, nil
|
|
}
|
|
pushFactory := &svcmock.APNSPushProviderFactory{}
|
|
pushFactory.NewPushProviderFunc = func(*tls.Certificate) (nanomdm_push.PushProvider, error) {
|
|
return pushProvider, nil
|
|
}
|
|
pusher := nanomdm_pushsvc.New(mdmStorage, mdmStorage, pushFactory, stdlogfmt.New())
|
|
commander := apple_mdm.NewMDMAppleCommander(mdmStorage, pusher)
|
|
svc := Service{ds: ds, authz: authorizer, mdmAppleCommander: commander, Service: &mocksvc.Service{
|
|
NewActivityFunc: func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
|
|
return nil
|
|
},
|
|
}}
|
|
|
|
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
|
return &fleet.AppConfig{MDM: fleet.MDM{EnabledAndConfigured: true}}, nil
|
|
}
|
|
|
|
// Common mdmStorage mocks for enqueue + push.
|
|
mdmStorage.EnqueueCommandFunc = func(ctx context.Context, id []string, cmd *nanomdm_mdm.CommandWithSubtype) (map[string]error, error) {
|
|
return nil, nil
|
|
}
|
|
mdmStorage.RetrievePushInfoFunc = func(ctx context.Context, targets []string) (map[string]*nanomdm_mdm.Push, error) {
|
|
pushes := make(map[string]*nanomdm_mdm.Push, len(targets))
|
|
for _, uuid := range targets {
|
|
pushes[uuid] = &nanomdm_mdm.Push{
|
|
PushMagic: "magic" + uuid,
|
|
Token: []byte("token" + uuid),
|
|
Topic: "topic" + uuid,
|
|
}
|
|
}
|
|
return pushes, nil
|
|
}
|
|
mdmStorage.RetrievePushCertFunc = func(ctx context.Context, topic string) (*tls.Certificate, string, error) {
|
|
cert, err := tls.LoadX509KeyPair("../../../server/service/testdata/server.pem", "../../../server/service/testdata/server.key")
|
|
return &cert, "", err
|
|
}
|
|
mdmStorage.IsPushCertStaleFunc = func(ctx context.Context, topic string, staleToken string) (bool, error) {
|
|
return false, nil
|
|
}
|
|
|
|
t.Run("authorization", func(t *testing.T) {
|
|
ds.HostLiteFunc = func(ctx context.Context, hostID uint) (*fleet.Host, error) {
|
|
return &fleet.Host{ID: hostID, Platform: "ipados"}, nil
|
|
}
|
|
ds.GetHostMDMFunc = func(ctx context.Context, hostID uint) (*fleet.HostMDM, error) {
|
|
return &fleet.HostMDM{}, nil
|
|
}
|
|
ds.GetNanoMDMEnrollmentDetailsFunc = func(ctx context.Context, hostUUID string) (*fleet.NanoMDMEnrollmentDetails, error) {
|
|
return &fleet.NanoMDMEnrollmentDetails{UnlockToken: new("fake-token")}, nil
|
|
}
|
|
|
|
cases := []struct {
|
|
desc string
|
|
user *fleet.User
|
|
shoudFailWithAuth bool
|
|
}{
|
|
{"no role", test.UserNoRoles, true},
|
|
{"observer", test.UserObserver, true},
|
|
{"observer+", test.UserObserverPlus, true},
|
|
{"technician", test.UserTechnician, true},
|
|
{"gitops", test.UserGitOps, true},
|
|
{"maintainer", test.UserMaintainer, false},
|
|
{"admin", test.UserAdmin, false},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.desc, func(t *testing.T) {
|
|
ctx := test.UserContext(t.Context(), c.user)
|
|
_, err := svc.ClearPasscode(ctx, 1)
|
|
checkAuthErr(t, c.shoudFailWithAuth, err)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("happy path ipados", func(t *testing.T) {
|
|
ds.HostLiteFunc = func(ctx context.Context, hostID uint) (*fleet.Host, error) {
|
|
return &fleet.Host{ID: hostID, UUID: "host-uuid-1", Platform: "ipados"}, nil
|
|
}
|
|
ds.GetHostMDMFunc = func(ctx context.Context, hostID uint) (*fleet.HostMDM, error) {
|
|
return &fleet.HostMDM{}, nil
|
|
}
|
|
|
|
ctx := test.UserContext(t.Context(), test.UserAdmin)
|
|
_, err := svc.ClearPasscode(ctx, 1)
|
|
require.NoError(t, err)
|
|
require.True(t, mdmStorage.EnqueueCommandFuncInvoked)
|
|
mdmStorage.EnqueueCommandFuncInvoked = false
|
|
})
|
|
|
|
t.Run("happy path ios", func(t *testing.T) {
|
|
ds.HostLiteFunc = func(ctx context.Context, hostID uint) (*fleet.Host, error) {
|
|
return &fleet.Host{ID: hostID, UUID: "host-uuid-2", Platform: "ios"}, nil
|
|
}
|
|
ds.GetHostMDMFunc = func(ctx context.Context, hostID uint) (*fleet.HostMDM, error) {
|
|
return &fleet.HostMDM{}, nil
|
|
}
|
|
|
|
ctx := test.UserContext(t.Context(), test.UserAdmin)
|
|
_, err := svc.ClearPasscode(ctx, 1)
|
|
require.NoError(t, err)
|
|
require.True(t, mdmStorage.EnqueueCommandFuncInvoked)
|
|
mdmStorage.EnqueueCommandFuncInvoked = false
|
|
})
|
|
|
|
t.Run("non-apple platform", func(t *testing.T) {
|
|
ds.HostLiteFunc = func(ctx context.Context, hostID uint) (*fleet.Host, error) {
|
|
return &fleet.Host{ID: hostID, Platform: "windows"}, nil
|
|
}
|
|
|
|
ctx := test.UserContext(t.Context(), test.UserAdmin)
|
|
_, err := svc.ClearPasscode(ctx, 1)
|
|
require.Error(t, err)
|
|
var badReq *fleet.BadRequestError
|
|
require.ErrorAs(t, err, &badReq)
|
|
assert.Contains(t, badReq.Message, "only supported on Apple mobile platforms")
|
|
})
|
|
|
|
t.Run("macOS not supported", func(t *testing.T) {
|
|
ds.HostLiteFunc = func(ctx context.Context, hostID uint) (*fleet.Host, error) {
|
|
return &fleet.Host{ID: hostID, Platform: "darwin"}, nil
|
|
}
|
|
|
|
ctx := test.UserContext(t.Context(), test.UserAdmin)
|
|
_, err := svc.ClearPasscode(ctx, 1)
|
|
require.Error(t, err)
|
|
var badReq *fleet.BadRequestError
|
|
require.ErrorAs(t, err, &badReq)
|
|
assert.Contains(t, badReq.Message, "ClearPasscode command is only available for iOS and iPadOS. Unable to issue ClearPasscode command.")
|
|
})
|
|
|
|
t.Run("MDM not enabled", func(t *testing.T) {
|
|
ds.HostLiteFunc = func(ctx context.Context, hostID uint) (*fleet.Host, error) {
|
|
return &fleet.Host{ID: hostID, Platform: "ipados"}, nil
|
|
}
|
|
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
|
return &fleet.AppConfig{MDM: fleet.MDM{EnabledAndConfigured: false}}, nil
|
|
}
|
|
|
|
ctx := test.UserContext(t.Context(), test.UserAdmin)
|
|
_, err := svc.ClearPasscode(ctx, 1)
|
|
require.Error(t, err)
|
|
var badReq *fleet.BadRequestError
|
|
require.ErrorAs(t, err, &badReq)
|
|
assert.Contains(t, badReq.Message, "Apple MDM must be turned on to use Clear passcode.")
|
|
|
|
// Restore for subsequent tests.
|
|
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
|
return &fleet.AppConfig{MDM: fleet.MDM{EnabledAndConfigured: true}}, nil
|
|
}
|
|
})
|
|
|
|
t.Run("personal enrollment", func(t *testing.T) {
|
|
ds.HostLiteFunc = func(ctx context.Context, hostID uint) (*fleet.Host, error) {
|
|
return &fleet.Host{ID: hostID, Platform: "ipados"}, nil
|
|
}
|
|
ds.GetHostMDMFunc = func(ctx context.Context, hostID uint) (*fleet.HostMDM, error) {
|
|
return &fleet.HostMDM{IsPersonalEnrollment: true}, nil
|
|
}
|
|
|
|
ctx := test.UserContext(t.Context(), test.UserAdmin)
|
|
_, err := svc.ClearPasscode(ctx, 1)
|
|
require.Error(t, err)
|
|
var badReq *fleet.BadRequestError
|
|
require.ErrorAs(t, err, &badReq)
|
|
assert.Contains(t, badReq.Message, "Unlock token is not available")
|
|
})
|
|
|
|
t.Run("enqueue command error", func(t *testing.T) {
|
|
ds.HostLiteFunc = func(ctx context.Context, hostID uint) (*fleet.Host, error) {
|
|
return &fleet.Host{ID: hostID, UUID: "host-uuid-3", Platform: "ipados"}, nil
|
|
}
|
|
ds.GetHostMDMFunc = func(ctx context.Context, hostID uint) (*fleet.HostMDM, error) {
|
|
return &fleet.HostMDM{}, nil
|
|
}
|
|
mdmStorage.EnqueueCommandFunc = func(ctx context.Context, id []string, cmd *nanomdm_mdm.CommandWithSubtype) (map[string]error, error) {
|
|
return nil, errors.New("enqueue failed")
|
|
}
|
|
|
|
ctx := test.UserContext(t.Context(), test.UserAdmin)
|
|
_, err := svc.ClearPasscode(ctx, 1)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "enqueue failed")
|
|
|
|
// Restore for subsequent tests.
|
|
mdmStorage.EnqueueCommandFunc = func(ctx context.Context, id []string, cmd *nanomdm_mdm.CommandWithSubtype) (map[string]error, error) {
|
|
return nil, nil
|
|
}
|
|
})
|
|
|
|
t.Run("host not found", func(t *testing.T) {
|
|
ds.HostLiteFunc = func(ctx context.Context, hostID uint) (*fleet.Host, error) {
|
|
return nil, ¬FoundError{}
|
|
}
|
|
|
|
ctx := test.UserContext(t.Context(), test.UserAdmin)
|
|
_, err := svc.ClearPasscode(ctx, 999)
|
|
require.Error(t, err)
|
|
})
|
|
}
|