diff --git a/changes/19545-unlock-pin b/changes/19545-unlock-pin index ee0f715202..6b8eff49c9 100644 --- a/changes/19545-unlock-pin +++ b/changes/19545-unlock-pin @@ -1,2 +1,2 @@ -* /api/latest/fleet/hosts/:id/lock returns `unlock_pin` for Apple hosts +* /api/latest/fleet/hosts/:id/lock returns `unlock_pin` for Apple hosts when query parameter `view_pin=true` is set * UI no longer uses unlock pending state for Apple hosts diff --git a/ee/server/service/hosts.go b/ee/server/service/hosts.go index 70e249d908..1e5fb95603 100644 --- a/ee/server/service/hosts.go +++ b/ee/server/service/hosts.go @@ -38,7 +38,7 @@ func (svc *Service) OSVersion(ctx context.Context, osID uint, teamID *uint, incl return svc.Service.OSVersion(ctx, osID, teamID, true) } -func (svc *Service) LockHost(ctx context.Context, hostID uint) (unlockPIN string, err error) { +func (svc *Service) LockHost(ctx context.Context, hostID uint, viewPIN bool) (unlockPIN string, err error) { // First ensure the user has access to list hosts, then check the specific // host once team_id is loaded. if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil { @@ -152,7 +152,7 @@ func (svc *Service) LockHost(ctx context.Context, hostID uint) (unlockPIN string } // all good, go ahead with queuing the lock request. - return svc.enqueueLockHostRequest(ctx, host, lockWipe) + return svc.enqueueLockHostRequest(ctx, host, lockWipe, viewPIN) } func (svc *Service) UnlockHost(ctx context.Context, hostID uint) (string, error) { @@ -345,7 +345,7 @@ func (svc *Service) WipeHost(ctx context.Context, hostID uint) error { return svc.enqueueWipeHostRequest(ctx, host, lockWipe) } -func (svc *Service) enqueueLockHostRequest(ctx context.Context, host *fleet.Host, lockStatus *fleet.HostLockWipeStatus) ( +func (svc *Service) enqueueLockHostRequest(ctx context.Context, host *fleet.Host, lockStatus *fleet.HostLockWipeStatus, viewPIN bool) ( unlockPIN string, err error, ) { vc, ok := viewer.FromContext(ctx) @@ -365,6 +365,7 @@ func (svc *Service) enqueueLockHostRequest(ctx context.Context, host *fleet.Host fleet.ActivityTypeLockedHost{ HostID: host.ID, HostDisplayName: host.DisplayName(), + ViewPIN: viewPIN, }, ); err != nil { return "", ctxerr.Wrap(ctx, err, "create activity for darwin lock host request") diff --git a/server/fleet/activities.go b/server/fleet/activities.go index c3d2f3c0f0..ba2384ce84 100644 --- a/server/fleet/activities.go +++ b/server/fleet/activities.go @@ -1267,6 +1267,7 @@ func (a ActivityTypeEditedWindowsProfile) Documentation() (activity, details, de type ActivityTypeLockedHost struct { HostID uint `json:"host_id"` HostDisplayName string `json:"host_display_name"` + ViewPIN bool `json:"view_pin"` } func (a ActivityTypeLockedHost) ActivityName() string { diff --git a/server/fleet/service.go b/server/fleet/service.go index b493b1bff0..73c0c3ad85 100644 --- a/server/fleet/service.go +++ b/server/fleet/service.go @@ -1042,7 +1042,7 @@ type Service interface { BatchSetScripts(ctx context.Context, maybeTmID *uint, maybeTmName *string, payloads []ScriptPayload, dryRun bool) error // Script-based methods (at least for some platforms, MDM-based for others) - LockHost(ctx context.Context, hostID uint) (unlockPIN string, err error) + LockHost(ctx context.Context, hostID uint, viewPIN bool) (unlockPIN string, err error) UnlockHost(ctx context.Context, hostID uint) (unlockPIN string, err error) WipeHost(ctx context.Context, hostID uint) error diff --git a/server/service/handler.go b/server/service/handler.go index 07b1b72010..2d7ad4ded3 100644 --- a/server/service/handler.go +++ b/server/service/handler.go @@ -598,6 +598,8 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC mdmAppleMW.PATCH("/api/_version_/fleet/mdm/hosts/{id:[0-9]+}/unenroll", mdmAppleCommandRemoveEnrollmentProfileEndpoint, mdmAppleCommandRemoveEnrollmentProfileRequest{}) mdmAppleMW.DELETE("/api/_version_/fleet/hosts/{id:[0-9]+}/mdm", mdmAppleCommandRemoveEnrollmentProfileEndpoint, mdmAppleCommandRemoveEnrollmentProfileRequest{}) + // Deprecated: POST /mdm/hosts/:id/lock is now deprecated, replaced by + // POST /hosts/:id/lock. mdmAppleMW.POST("/api/_version_/fleet/mdm/hosts/{id:[0-9]+}/lock", deviceLockEndpoint, deviceLockRequest{}) mdmAppleMW.POST("/api/_version_/fleet/mdm/hosts/{id:[0-9]+}/wipe", deviceWipeEndpoint, deviceWipeRequest{}) diff --git a/server/service/hosts_test.go b/server/service/hosts_test.go index 6be6d51ce6..f7278c7775 100644 --- a/server/service/hosts_test.go +++ b/server/service/hosts_test.go @@ -1651,9 +1651,9 @@ func TestLockUnlockWipeHostAuth(t *testing.T) { } ctx := viewer.NewContext(ctx, viewer.Viewer{User: tt.user}) - _, err := svc.LockHost(ctx, globalHostID) + _, err := svc.LockHost(ctx, globalHostID, false) checkAuthErr(t, tt.shouldFailGlobalWrite, err) - _, err = svc.LockHost(ctx, teamHostID) + _, err = svc.LockHost(ctx, teamHostID, false) checkAuthErr(t, tt.shouldFailTeamWrite, err) // Pretend we locked the host diff --git a/server/service/integration_mdm_lifecycle_test.go b/server/service/integration_mdm_lifecycle_test.go index 72bcb19bf0..75fad197f0 100644 --- a/server/service/integration_mdm_lifecycle_test.go +++ b/server/service/integration_mdm_lifecycle_test.go @@ -5,7 +5,6 @@ import ( "encoding/json" "encoding/xml" "fmt" - "github.com/stretchr/testify/assert" "net/http" "os" "path/filepath" @@ -74,15 +73,12 @@ func (s *integrationMDMTestSuite) TestTurnOnLifecycleEventsApple() { { "locked host turns on MDM", func(t *testing.T, host *fleet.Host, device *mdmtest.TestAppleMDMClient) { - var resp lockHostResponse - s.DoJSON( + s.Do( "POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/lock", host.ID), nil, - http.StatusOK, - &resp, + http.StatusNoContent, ) - assert.Len(t, resp.UnlockPIN, 6) cmd, err := device.Idle() require.NoError(t, err) diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index c25604d5e3..1e310addaf 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -8024,7 +8024,7 @@ func (s *integrationMDMTestSuite) TestLockUnlockWipeMacOS() { // lock the host var lockResp lockHostResponse - s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/lock", host.ID), nil, http.StatusOK, &lockResp) + s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/lock", host.ID), nil, http.StatusOK, &lockResp, "view_pin", "true") assert.Len(t, lockResp.UnlockPIN, 6) // refresh the host's status, it is now pending lock @@ -8155,6 +8155,10 @@ func (s *integrationMDMTestSuite) TestLockUnlockWipeMacOS() { require.Equal(t, "unlocked", *getHostResp.Host.MDM.DeviceStatus) require.NotNil(t, getHostResp.Host.MDM.PendingAction) require.Equal(t, "", *getHostResp.Host.MDM.PendingAction) + + // lock the host without viewing the PIN + s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/lock", host.ID), nil, http.StatusNoContent) + } func (s *integrationMDMTestSuite) TestZCustomConfigurationWebURL() { diff --git a/server/service/scripts.go b/server/service/scripts.go index b9d8fec23a..fb847352b4 100644 --- a/server/service/scripts.go +++ b/server/service/scripts.go @@ -911,7 +911,8 @@ func (svc *Service) authorizeScriptByID(ctx context.Context, scriptID uint, auth //////////////////////////////////////////////////////////////////////////////// type lockHostRequest struct { - HostID uint `url:"id"` + HostID uint `url:"id"` + ViewPin bool `query:"view_pin,optional"` } type lockHostResponse struct { @@ -930,17 +931,17 @@ func (r lockHostResponse) error() error { return r.Err } func lockHostEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) { req := request.(*lockHostRequest) - unlockPIN, err := svc.LockHost(ctx, req.HostID) + unlockPIN, err := svc.LockHost(ctx, req.HostID, req.ViewPin) if err != nil { return lockHostResponse{Err: err}, nil } - if unlockPIN != "" { + if req.ViewPin && unlockPIN != "" { return lockHostResponse{UnlockPIN: unlockPIN, StatusCode: http.StatusOK}, nil } return lockHostResponse{}, nil } -func (svc *Service) LockHost(ctx context.Context, hostID uint) (string, error) { +func (svc *Service) LockHost(ctx context.Context, _ uint, _ bool) (string, error) { // skipauth: No authorization check needed due to implementation returning // only license error. svc.authz.SkipAuthorization(ctx)