mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-06 09:16:24 +08:00
add checkout middleware (#342)
Implement a connect service middleware which allows an operator to forcefully un-enroll a device. Documentation: https://github.com/micromdm/micromdm/wiki/Terminating-a-Management-Relationship-with-a-device-block
This commit is contained in:
@@ -6,6 +6,8 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -60,6 +62,8 @@ func (cmd *applyCommand) Run(args []string) error {
|
||||
run = cmd.applyProfile
|
||||
case "app":
|
||||
run = cmd.applyApp
|
||||
case "block":
|
||||
run = cmd.applyBlock
|
||||
case "users":
|
||||
run = cmd.applyUser
|
||||
default:
|
||||
@@ -81,6 +85,7 @@ Valid resource types:
|
||||
* dep-tokens
|
||||
* dep-profiles
|
||||
* app
|
||||
* block
|
||||
|
||||
Examples:
|
||||
# Apply a Blueprint.
|
||||
@@ -189,6 +194,40 @@ func (cmd *applyCommand) applyDEPTokens(args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *applyCommand) applyBlock(args []string) error {
|
||||
flagset := flag.NewFlagSet("block", flag.ExitOnError)
|
||||
var (
|
||||
flUDID = flagset.String("udid", "", "UDID of a device to block.")
|
||||
)
|
||||
flagset.Usage = usageFor(flagset, "mdmctl apply block [flags]")
|
||||
if err := flagset.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
if *flUDID == "" {
|
||||
flagset.Usage()
|
||||
return errors.New("bad input: must provide a device UDID to block.")
|
||||
}
|
||||
if err := cmd.applysvc.BlockDevice(context.Background(), *flUDID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// trigger a push
|
||||
u, err := url.Parse(cmd.config.ServerURL)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil
|
||||
}
|
||||
u.Path = "/push/" + url.QueryEscape(*flUDID)
|
||||
req, err := http.NewRequest("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil
|
||||
}
|
||||
req.SetBasicAuth("micromdm", cmd.config.APIToken)
|
||||
http.DefaultClient.Do(req)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *applyCommand) applyProfile(args []string) error {
|
||||
flagset := flag.NewFlagSet("profiles", flag.ExitOnError)
|
||||
var (
|
||||
|
||||
@@ -48,6 +48,8 @@ func (cmd *removeCommand) Run(args []string) error {
|
||||
run = cmd.removeBlueprints
|
||||
case "profiles":
|
||||
run = cmd.removeProfiles
|
||||
case "block":
|
||||
run = cmd.removeBlock
|
||||
default:
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
@@ -63,7 +65,9 @@ Display one or many resources.
|
||||
Valid resource types:
|
||||
|
||||
* blueprints
|
||||
* profiles`
|
||||
* profiles
|
||||
* block
|
||||
`
|
||||
|
||||
fmt.Println(getUsage)
|
||||
return nil
|
||||
|
||||
34
cmd/mdmctl/remove_block.go
Normal file
34
cmd/mdmctl/remove_block.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (cmd *removeCommand) removeBlock(args []string) error {
|
||||
flagset := flag.NewFlagSet("unblock", flag.ExitOnError)
|
||||
var (
|
||||
flUDID = flagset.String("udid", "", "UDID of device to unblock")
|
||||
)
|
||||
flagset.Usage = usageFor(flagset, "mdmctl remove block [flags]")
|
||||
if err := flagset.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *flUDID == "" {
|
||||
flagset.Usage()
|
||||
return errors.New("bad input: must provide a device UDID to unblock.")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
if err := cmd.remove.UnblockDevice(ctx, *flUDID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("success")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -57,6 +57,7 @@ import (
|
||||
"github.com/micromdm/micromdm/platform/pubsub"
|
||||
"github.com/micromdm/micromdm/platform/pubsub/inmem"
|
||||
"github.com/micromdm/micromdm/platform/queue"
|
||||
block "github.com/micromdm/micromdm/platform/remove"
|
||||
"github.com/micromdm/micromdm/platform/user"
|
||||
"github.com/micromdm/micromdm/workflow/webhook"
|
||||
)
|
||||
@@ -142,8 +143,10 @@ func serve(args []string) error {
|
||||
// no less secure and prevents a useless dialog from showing.
|
||||
SCEPChallenge: "micromdm",
|
||||
}
|
||||
|
||||
sm.setupPubSub()
|
||||
sm.setupBolt()
|
||||
sm.setupRemoveService()
|
||||
sm.setupConfigStore()
|
||||
sm.loadPushCerts()
|
||||
sm.setupSCEP(logger)
|
||||
@@ -157,6 +160,11 @@ func serve(args []string) error {
|
||||
stdlog.Fatal(sm.err)
|
||||
}
|
||||
|
||||
removeService, err := block.NewService(sm.removeDB)
|
||||
if err != nil {
|
||||
stdlog.Fatal(err)
|
||||
}
|
||||
|
||||
devDB, err := device.NewDB(sm.db, sm.pubclient)
|
||||
if err != nil {
|
||||
stdlog.Fatal(err)
|
||||
@@ -298,12 +306,13 @@ func serve(args []string) error {
|
||||
var applysvc apply.Service
|
||||
{
|
||||
l := &apply.ApplyService{
|
||||
DEPClient: dc,
|
||||
Blueprints: bpDB,
|
||||
Tokens: tokenDB,
|
||||
Profiles: sm.profileDB,
|
||||
Apps: appDB,
|
||||
Users: userDB,
|
||||
DEPClient: dc,
|
||||
Blueprints: bpDB,
|
||||
Tokens: tokenDB,
|
||||
Profiles: sm.profileDB,
|
||||
Apps: appDB,
|
||||
Users: userDB,
|
||||
RemoveService: removeService,
|
||||
}
|
||||
applysvc = l
|
||||
if err := l.WatchTokenUpdates(sm.pubclient); err != nil {
|
||||
@@ -343,13 +352,14 @@ func serve(args []string) error {
|
||||
DefineDEPProfileEndpoint: defineDEPProfileEndpoint,
|
||||
AppUploadEndpoint: appUploadEndpoint,
|
||||
ApplyUserEndpoint: applyUserEndpoint,
|
||||
BlockDeviceEndpoint: apply.MakeBlockDeviceEndpoint(applysvc),
|
||||
}
|
||||
|
||||
applyAPIHandlers := apply.MakeHTTPHandlers(ctx, applyEndpoints, connectOpts...)
|
||||
|
||||
listAPIHandlers := list.MakeHTTPHandlers(ctx, listEndpoints, connectOpts...)
|
||||
|
||||
rmsvc := &remove.RemoveService{Blueprints: bpDB, Profiles: sm.profileDB}
|
||||
rmsvc := &remove.RemoveService{Blueprints: bpDB, Profiles: sm.profileDB, RemoveService: removeService}
|
||||
removeAPIHandlers := remove.MakeHTTPHandlers(ctx, remove.MakeEndpoints(rmsvc), connectOpts...)
|
||||
|
||||
connectHandlers := connect.MakeHTTPHandlers(ctx, connectEndpoints, connectOpts...)
|
||||
@@ -373,6 +383,8 @@ func serve(args []string) error {
|
||||
r.Handle("/push/{udid}", apiAuthMiddleware(*flAPIKey, pushHandlers.PushHandler))
|
||||
r.Handle("/v1/commands", apiAuthMiddleware(*flAPIKey, commandHandlers.NewCommandHandler)).Methods("POST")
|
||||
r.Handle("/v1/devices", apiAuthMiddleware(*flAPIKey, listAPIHandlers.ListDevicesHandler)).Methods("GET")
|
||||
r.Handle("/v1/devices/{udid}/block", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.BlockDeviceHandler)).Methods("POST")
|
||||
r.Handle("/v1/devices/{udid}/unblock", apiAuthMiddleware(*flAPIKey, removeAPIHandlers.UnblockDeviceHandler)).Methods("POST")
|
||||
r.Handle("/v1/dep-tokens", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetDEPTokensHandler)).Methods("GET")
|
||||
r.Handle("/v1/dep-tokens", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.DEPTokensHandler)).Methods("PUT")
|
||||
r.Handle("/v1/blueprints", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetBlueprintsHandler)).Methods("GET")
|
||||
@@ -486,6 +498,7 @@ type server struct {
|
||||
scepDepot *boltdepot.Depot
|
||||
profileDB *profile.DB
|
||||
configDB *config.DB
|
||||
removeDB *block.DB
|
||||
CommandWebhookURL string
|
||||
|
||||
// TODO: refactor enroll service and remove the need to reference
|
||||
@@ -496,7 +509,7 @@ type server struct {
|
||||
PushService *push.Service // bufford push
|
||||
pushService apns.Service
|
||||
checkinService checkin.Service
|
||||
connectService connect.ConnectService
|
||||
connectService connect.Service
|
||||
enrollService enroll.Service
|
||||
scepService scep.Service
|
||||
commandService command.Service
|
||||
@@ -550,6 +563,18 @@ func (c *server) startWebhooks() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *server) setupRemoveService() {
|
||||
if c.err != nil {
|
||||
return
|
||||
}
|
||||
removeDB, err := block.NewDB(c.db)
|
||||
if err != nil {
|
||||
c.err = err
|
||||
return
|
||||
}
|
||||
c.removeDB = removeDB
|
||||
}
|
||||
|
||||
func (c *server) setupCommandQueue(logger log.Logger) {
|
||||
if c.err != nil {
|
||||
return
|
||||
@@ -560,18 +585,16 @@ func (c *server) setupCommandQueue(logger log.Logger) {
|
||||
return
|
||||
}
|
||||
|
||||
var connectService connect.ConnectService
|
||||
var connectService connect.Service
|
||||
{
|
||||
svc, err := connect.New(q, c.pubclient)
|
||||
if err != nil {
|
||||
c.err = err
|
||||
return
|
||||
}
|
||||
svc = connect.NewLoggingService(
|
||||
svc,
|
||||
log.With(level.Info(logger), "component", "connect"),
|
||||
)
|
||||
connectService = svc
|
||||
connectService = connect.LoggingMiddleware(log.With(level.Info(logger), "component", "connect"))(svc)
|
||||
connectService = block.RemoveMiddleware(c.removeDB)(connectService)
|
||||
}
|
||||
c.connectService = connectService
|
||||
}
|
||||
|
||||
@@ -12,16 +12,16 @@ import (
|
||||
|
||||
const ConnectTopic = "mdm.Connect"
|
||||
|
||||
// The ConnectService accepts responses sent to an MDM server by an enrolled
|
||||
// The Service accepts responses sent to an MDM server by an enrolled
|
||||
// device.
|
||||
type ConnectService interface {
|
||||
type Service interface {
|
||||
|
||||
// Acknowledge acknowledges a response sent by a device and returns
|
||||
// the next payload if one is available.
|
||||
Acknowledge(ctx context.Context, req MDMConnectRequest) (payload []byte, err error)
|
||||
}
|
||||
|
||||
type connectSvc struct {
|
||||
type ConnectService struct {
|
||||
queue Queue
|
||||
pub pubsub.Publisher
|
||||
}
|
||||
@@ -30,14 +30,14 @@ type Queue interface {
|
||||
Next(context.Context, mdm.Response) (*queue.Command, error)
|
||||
}
|
||||
|
||||
func New(queue Queue, pub pubsub.Publisher) (ConnectService, error) {
|
||||
return &connectSvc{
|
||||
func New(queue Queue, pub pubsub.Publisher) (*ConnectService, error) {
|
||||
return &ConnectService{
|
||||
queue: queue,
|
||||
pub: pub,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *connectSvc) Acknowledge(ctx context.Context, req MDMConnectRequest) (payload []byte, err error) {
|
||||
func (svc *ConnectService) Acknowledge(ctx context.Context, req MDMConnectRequest) (payload []byte, err error) {
|
||||
event := NewEvent(req)
|
||||
msg, err := MarshalEvent(event)
|
||||
if err != nil {
|
||||
|
||||
@@ -23,7 +23,7 @@ type Endpoints struct {
|
||||
ConnectEndpoint endpoint.Endpoint
|
||||
}
|
||||
|
||||
func MakeConnectEndpoint(svc ConnectService) endpoint.Endpoint {
|
||||
func MakeConnectEndpoint(svc Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(MDMConnectRequest)
|
||||
payload, err := svc.Acknowledge(ctx, req)
|
||||
|
||||
@@ -7,16 +7,20 @@ import (
|
||||
"github.com/go-kit/kit/log"
|
||||
)
|
||||
|
||||
type loggingMiddleware struct {
|
||||
logger log.Logger
|
||||
next ConnectService
|
||||
type Middleware func(Service) Service
|
||||
|
||||
func LoggingMiddleware(logger log.Logger) Middleware {
|
||||
return func(next Service) Service {
|
||||
return &loggingMiddleware{
|
||||
next: next,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewLoggingService(svc ConnectService, logger log.Logger) loggingMiddleware {
|
||||
return loggingMiddleware{
|
||||
next: svc,
|
||||
logger: logger,
|
||||
}
|
||||
type loggingMiddleware struct {
|
||||
next Service
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
func (mw loggingMiddleware) Acknowledge(ctx context.Context, req MDMConnectRequest) (payload []byte, err error) {
|
||||
@@ -2,8 +2,8 @@ package connect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
httptransport "github.com/go-kit/kit/transport/http"
|
||||
@@ -68,6 +68,17 @@ func encodeResponse(ctx context.Context, w http.ResponseWriter, response interfa
|
||||
// The EncodeError should be passed to the Go-Kit httptransport as the
|
||||
// ServerErrorEncoder to encode error responses.
|
||||
func EncodeError(ctx context.Context, err error, w http.ResponseWriter) {
|
||||
fmt.Printf("connect error: %s\n", err)
|
||||
type checkoutErr interface {
|
||||
error
|
||||
Checkout() bool
|
||||
}
|
||||
if e, ok := err.(checkoutErr); ok {
|
||||
if e.Checkout() {
|
||||
log.Printf("connect: forced checkout error: %s\n", err)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
log.Printf("connect error: %s\n", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
@@ -80,6 +80,17 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra
|
||||
).Endpoint()
|
||||
}
|
||||
|
||||
var blockDeviceEndpoint endpoint.Endpoint
|
||||
{
|
||||
blockDeviceEndpoint = httptransport.NewClient(
|
||||
"POST",
|
||||
copyURL(u, ""), // empty path, modified by the encodeRequest func
|
||||
encodeRequestWithToken(token, encodeBlockDeviceRequest),
|
||||
DecodeBlockDeviceResponse,
|
||||
opts...,
|
||||
).Endpoint()
|
||||
}
|
||||
|
||||
return Endpoints{
|
||||
ApplyBlueprintEndpoint: applyBlueprintEndpoint,
|
||||
ApplyDEPTokensEndpoint: applyDEPTokensEndpoint,
|
||||
@@ -87,6 +98,7 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra
|
||||
DefineDEPProfileEndpoint: defineDEPProfileEndpoint,
|
||||
AppUploadEndpoint: uploadAppEndpoint,
|
||||
ApplyUserEndpoint: applyUserEndpoint,
|
||||
BlockDeviceEndpoint: blockDeviceEndpoint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ type Endpoints struct {
|
||||
DefineDEPProfileEndpoint endpoint.Endpoint
|
||||
AppUploadEndpoint endpoint.Endpoint
|
||||
ApplyUserEndpoint endpoint.Endpoint
|
||||
BlockDeviceEndpoint endpoint.Endpoint
|
||||
}
|
||||
|
||||
func (e Endpoints) ApplyUser(ctx context.Context, u user.User) (*user.User, error) {
|
||||
@@ -33,6 +34,17 @@ func (e Endpoints) ApplyUser(ctx context.Context, u user.User) (*user.User, erro
|
||||
return &usr, resp.(applyUserResponse).Err
|
||||
}
|
||||
|
||||
func (e Endpoints) BlockDevice(ctx context.Context, udid string) error {
|
||||
request := blockDeviceRequest{
|
||||
UDID: udid,
|
||||
}
|
||||
resp, err := e.BlockDeviceEndpoint(ctx, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return resp.(blockDeviceResponse).Err
|
||||
}
|
||||
|
||||
func (e Endpoints) UploadApp(ctx context.Context, manifestName string, manifest io.Reader, pkgName string, pkg io.Reader) error {
|
||||
request := appUploadRequest{
|
||||
ManifestName: manifestName,
|
||||
@@ -146,6 +158,16 @@ func MakeUploadAppEndpiont(svc Service) endpoint.Endpoint {
|
||||
}
|
||||
}
|
||||
|
||||
func MakeBlockDeviceEndpoint(svc Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
|
||||
req := request.(blockDeviceRequest)
|
||||
err = svc.BlockDevice(ctx, req.UDID)
|
||||
return &blockDeviceResponse{
|
||||
Err: err,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type appUploadRequest struct {
|
||||
ManifestName string
|
||||
ManifestFile io.Reader
|
||||
@@ -208,3 +230,13 @@ type applyUserResponse struct {
|
||||
}
|
||||
|
||||
func (r applyUserResponse) error() error { return r.Err }
|
||||
|
||||
type blockDeviceRequest struct {
|
||||
UDID string
|
||||
}
|
||||
|
||||
type blockDeviceResponse struct {
|
||||
Err error `json:"err,omitempty"`
|
||||
}
|
||||
|
||||
func (r blockDeviceResponse) error() error { return r.Err }
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/micromdm/micromdm/platform/deptoken"
|
||||
"github.com/micromdm/micromdm/platform/profile"
|
||||
"github.com/micromdm/micromdm/platform/pubsub"
|
||||
"github.com/micromdm/micromdm/platform/remove"
|
||||
"github.com/micromdm/micromdm/platform/user"
|
||||
)
|
||||
|
||||
@@ -31,6 +32,7 @@ type Service interface {
|
||||
UploadApp(ctx context.Context, manifestName string, manifest io.Reader, pkgName string, pkg io.Reader) error
|
||||
ApplyUser(ctx context.Context, u user.User) (*user.User, error)
|
||||
DEPService
|
||||
BlockDevice(ctx context.Context, udid string) error
|
||||
}
|
||||
|
||||
type ApplyService struct {
|
||||
@@ -42,6 +44,7 @@ type ApplyService struct {
|
||||
Tokens *deptoken.DB
|
||||
Apps appstore.AppStore
|
||||
Users *user.DB
|
||||
*remove.RemoveService
|
||||
}
|
||||
|
||||
func (svc *ApplyService) ApplyUser(ctx context.Context, u user.User) (*user.User, error) {
|
||||
|
||||
@@ -8,8 +8,10 @@ import (
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
httptransport "github.com/go-kit/kit/transport/http"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -20,6 +22,7 @@ type HTTPHandlers struct {
|
||||
DefineDEPProfileHandler http.Handler
|
||||
AppUploadHandler http.Handler
|
||||
ApplyUserhandler http.Handler
|
||||
BlockDeviceHandler http.Handler
|
||||
}
|
||||
|
||||
func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) HTTPHandlers {
|
||||
@@ -60,10 +63,28 @@ func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptran
|
||||
encodeResponse,
|
||||
opts...,
|
||||
),
|
||||
BlockDeviceHandler: httptransport.NewServer(
|
||||
endpoints.BlockDeviceEndpoint,
|
||||
decodeBlockDeviceRequest,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
),
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func decodeBlockDeviceRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
var errBadRoute = errors.New("bad route")
|
||||
var req blockDeviceRequest
|
||||
vars := mux.Vars(r)
|
||||
udid, ok := vars["udid"]
|
||||
if !ok {
|
||||
return 0, errBadRoute
|
||||
}
|
||||
req.UDID = udid
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeDEPTokensRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
var req depTokensRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -208,6 +229,13 @@ func EncodeHTTPGenericRequest(_ context.Context, r *http.Request, request interf
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeBlockDeviceRequest(_ context.Context, r *http.Request, request interface{}) error {
|
||||
req := request.(blockDeviceRequest)
|
||||
udid := url.QueryEscape(req.UDID)
|
||||
r.Method, r.URL.Path = "POST", "/v1/devices/"+udid+"/block"
|
||||
return nil
|
||||
}
|
||||
|
||||
func DecodeBlueprintResponse(_ context.Context, r *http.Response) (interface{}, error) {
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return nil, errorDecoder(r)
|
||||
@@ -261,3 +289,12 @@ func DecodeApplyUserResponse(_ context.Context, r *http.Response) (interface{},
|
||||
err := json.NewDecoder(r.Body).Decode(&resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func DecodeBlockDeviceResponse(_ context.Context, r *http.Response) (interface{}, error) {
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return nil, errorDecoder(r)
|
||||
}
|
||||
var resp blockDeviceResponse
|
||||
err := json.NewDecoder(r.Body).Decode(&resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
@@ -37,9 +37,21 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra
|
||||
).Endpoint()
|
||||
}
|
||||
|
||||
var unblockDeviceEndpoint endpoint.Endpoint
|
||||
{
|
||||
unblockDeviceEndpoint = httptransport.NewClient(
|
||||
"POST",
|
||||
copyURL(u, ""), //modified by encodeRequestFunc
|
||||
encodeRequestWithToken(token, encodeUnblockDeviceRequest),
|
||||
DecodeUnblockDeviceResponse,
|
||||
opts...,
|
||||
).Endpoint()
|
||||
}
|
||||
|
||||
return Endpoints{
|
||||
RemoveBlueprintsEndpoint: removeBlueprintsEndpoint,
|
||||
RemoveProfilesEndpoint: removeProfilesEndpoint,
|
||||
UnblockDeviceEndpoint: unblockDeviceEndpoint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -9,16 +9,27 @@ import (
|
||||
type Endpoints struct {
|
||||
RemoveBlueprintsEndpoint endpoint.Endpoint
|
||||
RemoveProfilesEndpoint endpoint.Endpoint
|
||||
UnblockDeviceEndpoint endpoint.Endpoint
|
||||
}
|
||||
|
||||
func MakeEndpoints(svc Service) Endpoints {
|
||||
e := Endpoints{
|
||||
RemoveBlueprintsEndpoint: MakeRemoveBlueprintsEndpoint(svc),
|
||||
RemoveProfilesEndpoint: MakeRemoveProfilesEndpoint(svc),
|
||||
UnblockDeviceEndpoint: MakeUnblockDeviceEndpoint(svc),
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (e Endpoints) UnblockDevice(ctx context.Context, udid string) error {
|
||||
request := unblockDeviceRequest{UDID: udid}
|
||||
resp, err := e.UnblockDeviceEndpoint(ctx, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return resp.(unblockDeviceResponse).Err
|
||||
}
|
||||
|
||||
func (e Endpoints) RemoveBlueprints(ctx context.Context, names []string) error {
|
||||
request := blueprintRequest{Names: names}
|
||||
resp, err := e.RemoveBlueprintsEndpoint(ctx, request)
|
||||
@@ -57,6 +68,26 @@ func MakeRemoveProfilesEndpoint(svc Service) endpoint.Endpoint {
|
||||
}
|
||||
}
|
||||
|
||||
func MakeUnblockDeviceEndpoint(svc Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
|
||||
req := request.(unblockDeviceRequest)
|
||||
err = svc.UnblockDevice(ctx, req.UDID)
|
||||
return unblockDeviceResponse{
|
||||
Err: err,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type unblockDeviceRequest struct {
|
||||
UDID string
|
||||
}
|
||||
|
||||
type unblockDeviceResponse struct {
|
||||
Err error `json:"err,omitempty"`
|
||||
}
|
||||
|
||||
func (r unblockDeviceResponse) error() error { return r.Err }
|
||||
|
||||
type blueprintRequest struct {
|
||||
Names []string `json:"names"`
|
||||
}
|
||||
|
||||
@@ -5,16 +5,19 @@ import (
|
||||
|
||||
"github.com/micromdm/micromdm/platform/blueprint"
|
||||
"github.com/micromdm/micromdm/platform/profile"
|
||||
"github.com/micromdm/micromdm/platform/remove"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
RemoveBlueprints(ctx context.Context, names []string) error
|
||||
RemoveProfiles(ctx context.Context, ids []string) error
|
||||
UnblockDevice(ctx context.Context, udid string) error
|
||||
}
|
||||
|
||||
type RemoveService struct {
|
||||
Blueprints *blueprint.DB
|
||||
Profiles *profile.DB
|
||||
*remove.RemoveService
|
||||
}
|
||||
|
||||
func (svc *RemoveService) RemoveBlueprints(ctx context.Context, names []string) error {
|
||||
|
||||
@@ -7,13 +7,16 @@ import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
httptransport "github.com/go-kit/kit/transport/http"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type HTTPHandlers struct {
|
||||
BlueprintHandler http.Handler
|
||||
ProfileHandler http.Handler
|
||||
BlueprintHandler http.Handler
|
||||
ProfileHandler http.Handler
|
||||
UnblockDeviceHandler http.Handler
|
||||
}
|
||||
|
||||
func MakeHTTPHandlers(ctx context.Context, endpoint Endpoints, opts ...httptransport.ServerOption) HTTPHandlers {
|
||||
@@ -30,6 +33,12 @@ func MakeHTTPHandlers(ctx context.Context, endpoint Endpoints, opts ...httptrans
|
||||
encodeResponse,
|
||||
opts...,
|
||||
),
|
||||
UnblockDeviceHandler: httptransport.NewServer(
|
||||
endpoint.UnblockDeviceEndpoint,
|
||||
decodeUnblockDeviceRequest,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
),
|
||||
}
|
||||
return h
|
||||
}
|
||||
@@ -50,6 +59,18 @@ func decodeProfileRequest(ctx context.Context, r *http.Request) (interface{}, er
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeUnblockDeviceRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
var errBadRoute = errors.New("bad route")
|
||||
var req unblockDeviceRequest
|
||||
vars := mux.Vars(r)
|
||||
udid, ok := vars["udid"]
|
||||
if !ok {
|
||||
return 0, errBadRoute
|
||||
}
|
||||
req.UDID = udid
|
||||
return req, nil
|
||||
}
|
||||
|
||||
type errorWrapper struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
@@ -95,6 +116,13 @@ func EncodeHTTPGenericRequest(_ context.Context, r *http.Request, request interf
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeUnblockDeviceRequest(_ context.Context, r *http.Request, request interface{}) error {
|
||||
req := request.(unblockDeviceRequest)
|
||||
udid := url.QueryEscape(req.UDID)
|
||||
r.Method, r.URL.Path = "POST", "/v1/devices/"+udid+"/unblock"
|
||||
return nil
|
||||
}
|
||||
|
||||
func DecodeBlueprintResponse(_ context.Context, r *http.Response) (interface{}, error) {
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return nil, errorDecoder(r)
|
||||
@@ -112,3 +140,12 @@ func DecodeProfileResponse(_ context.Context, r *http.Response) (interface{}, er
|
||||
err := json.NewDecoder(r.Body).Decode(&resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func DecodeUnblockDeviceResponse(_ context.Context, r *http.Response) (interface{}, error) {
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return nil, errorDecoder(r)
|
||||
}
|
||||
var resp unblockDeviceResponse
|
||||
err := json.NewDecoder(r.Body).Decode(&resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
90
platform/remove/db.go
Normal file
90
platform/remove/db.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package remove
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const RemoveBucket = "mdm.RemoveDevice"
|
||||
|
||||
type DB struct {
|
||||
*bolt.DB
|
||||
}
|
||||
|
||||
func NewDB(db *bolt.DB) (*DB, error) {
|
||||
err := db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte(RemoveBucket))
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "creating %s bucket", RemoveBucket)
|
||||
}
|
||||
datastore := &DB{
|
||||
DB: db,
|
||||
}
|
||||
return datastore, nil
|
||||
}
|
||||
|
||||
func (db *DB) DeviceByUDID(udid string) (*Device, error) {
|
||||
var dev Device
|
||||
err := db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte(RemoveBucket))
|
||||
v := b.Get([]byte(udid))
|
||||
if v == nil {
|
||||
return ¬Found{"Device", fmt.Sprintf("udid %s", udid)}
|
||||
}
|
||||
return UnmarshalDevice(v, &dev)
|
||||
})
|
||||
return &dev, errors.Wrap(err, "remove: get device by udid")
|
||||
}
|
||||
|
||||
func (db *DB) Save(dev *Device) error {
|
||||
tx, err := db.DB.Begin(true)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "begin transaction")
|
||||
}
|
||||
bkt := tx.Bucket([]byte(RemoveBucket))
|
||||
if bkt == nil {
|
||||
return fmt.Errorf("bucket %q not found!", RemoveBucket)
|
||||
}
|
||||
pb, err := MarshalDevice(dev)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshalling Device")
|
||||
}
|
||||
key := []byte(dev.UDID)
|
||||
if err := bkt.Put(key, pb); err != nil {
|
||||
return errors.Wrap(err, "put device to boltdb")
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (db *DB) Delete(udid string) error {
|
||||
err := db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte(RemoveBucket))
|
||||
v := b.Get([]byte(udid))
|
||||
if v == nil {
|
||||
return ¬Found{"Device", fmt.Sprintf("udid %s", udid)}
|
||||
}
|
||||
return b.Delete([]byte(udid))
|
||||
})
|
||||
return errors.Wrapf(err, "delete device with udid %s", udid)
|
||||
}
|
||||
|
||||
type notFound struct {
|
||||
ResourceType string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *notFound) Error() string {
|
||||
return fmt.Sprintf("not found: %s %s", e.ResourceType, e.Message)
|
||||
}
|
||||
|
||||
func isNotFound(err error) bool {
|
||||
cause := errors.Cause(err)
|
||||
if _, ok := cause.(*notFound); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
3
platform/remove/internal/removeproto/remove.go
Normal file
3
platform/remove/internal/removeproto/remove.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package removeproto
|
||||
|
||||
//go:generate protoc --go_out=. remove.proto
|
||||
59
platform/remove/internal/removeproto/remove.pb.go
Normal file
59
platform/remove/internal/removeproto/remove.pb.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: remove.proto
|
||||
|
||||
/*
|
||||
Package removeproto is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
remove.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Device
|
||||
*/
|
||||
package removeproto
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type Device struct {
|
||||
Udid string `protobuf:"bytes,1,opt,name=udid" json:"udid,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Device) Reset() { *m = Device{} }
|
||||
func (m *Device) String() string { return proto.CompactTextString(m) }
|
||||
func (*Device) ProtoMessage() {}
|
||||
func (*Device) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *Device) GetUdid() string {
|
||||
if m != nil {
|
||||
return m.Udid
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Device)(nil), "removeproto.Device")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("remove.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 75 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0x4a, 0xcd, 0xcd,
|
||||
0x2f, 0x4b, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x86, 0xf0, 0xc0, 0x1c, 0x25, 0x19,
|
||||
0x2e, 0x36, 0x97, 0xd4, 0xb2, 0xcc, 0xe4, 0x54, 0x21, 0x21, 0x2e, 0x96, 0xd2, 0x94, 0xcc, 0x14,
|
||||
0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x30, 0x3b, 0x89, 0x0d, 0xac, 0xc8, 0x18, 0x10, 0x00,
|
||||
0x00, 0xff, 0xff, 0xf6, 0xb9, 0x87, 0xa4, 0x41, 0x00, 0x00, 0x00,
|
||||
}
|
||||
7
platform/remove/internal/removeproto/remove.proto
Normal file
7
platform/remove/internal/removeproto/remove.proto
Normal file
@@ -0,0 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package removeproto;
|
||||
|
||||
message Device {
|
||||
string udid = 1;
|
||||
}
|
||||
90
platform/remove/remove.go
Normal file
90
platform/remove/remove.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package remove
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/micromdm/micromdm/mdm/connect"
|
||||
"github.com/micromdm/micromdm/platform/remove/internal/removeproto"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
BlockDevice(ctx context.Context, udid string) error
|
||||
UnblockDevice(ctx context.Context, udid string) error
|
||||
}
|
||||
|
||||
type RemoveService struct {
|
||||
db *DB
|
||||
}
|
||||
|
||||
func NewService(db *DB) (*RemoveService, error) {
|
||||
return &RemoveService{db: db}, nil
|
||||
}
|
||||
|
||||
func (svc *RemoveService) BlockDevice(ctx context.Context, udid string) error {
|
||||
return svc.db.Save(&Device{UDID: udid})
|
||||
}
|
||||
|
||||
func (svc *RemoveService) UnblockDevice(ctx context.Context, udid string) error {
|
||||
return svc.db.Delete(udid)
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
UDID string `json:"udid"`
|
||||
}
|
||||
|
||||
func MarshalDevice(dev *Device) ([]byte, error) {
|
||||
protodev := removeproto.Device{
|
||||
Udid: dev.UDID,
|
||||
}
|
||||
return proto.Marshal(&protodev)
|
||||
}
|
||||
|
||||
func UnmarshalDevice(data []byte, dev *Device) error {
|
||||
var pb removeproto.Device
|
||||
if err := proto.Unmarshal(data, &pb); err != nil {
|
||||
return errors.Wrap(err, "remove: unmarshal proto to device")
|
||||
}
|
||||
dev.UDID = pb.GetUdid()
|
||||
return nil
|
||||
}
|
||||
|
||||
func RemoveMiddleware(db *DB) connect.Middleware {
|
||||
return func(next connect.Service) connect.Service {
|
||||
return &removeMiddleware{
|
||||
db: db,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type removeMiddleware struct {
|
||||
db *DB
|
||||
next connect.Service
|
||||
}
|
||||
|
||||
func (mw removeMiddleware) Acknowledge(ctx context.Context, req connect.MDMConnectRequest) ([]byte, error) {
|
||||
udid := req.MDMResponse.UDID
|
||||
_, err := mw.db.DeviceByUDID(udid)
|
||||
if err != nil {
|
||||
if !isNotFound(err) {
|
||||
return nil, errors.Wrapf(err, "remove: get device by udid %s", udid)
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
return nil, checkoutErr{}
|
||||
}
|
||||
return mw.next.Acknowledge(ctx, req)
|
||||
}
|
||||
|
||||
type checkoutErr struct{}
|
||||
|
||||
func (checkoutErr) Error() string {
|
||||
return "checkout forced by device block"
|
||||
}
|
||||
|
||||
func (checkoutErr) Checkout() bool {
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user