diff --git a/cmd/mdmctl/apply_user.go b/cmd/mdmctl/apply_user.go index ecd51abf..e67ea0e7 100644 --- a/cmd/mdmctl/apply_user.go +++ b/cmd/mdmctl/apply_user.go @@ -63,7 +63,7 @@ func (cmd *applyCommand) applyUser(args []string) error { manifest.PasswordHash = hashPlist } - usr, err := cmd.applysvc.ApplyUser(context.TODO(), manifest) + usr, err := cmd.usersvc.ApplyUser(context.TODO(), manifest) if err != nil { return errors.Wrap(err, "apply user with mdmctl") } diff --git a/cmd/mdmctl/get_users.go b/cmd/mdmctl/get_users.go index 1e56ccd0..14d61933 100644 --- a/cmd/mdmctl/get_users.go +++ b/cmd/mdmctl/get_users.go @@ -7,9 +7,8 @@ import ( "os" "text/tabwriter" + "github.com/micromdm/micromdm/platform/user" "github.com/pkg/errors" - - "github.com/micromdm/micromdm/platform/api/server/list" ) type usersTableOutput struct{ w *tabwriter.Writer } @@ -34,7 +33,7 @@ func (cmd *getCommand) getUsers(args []string) error { out.BasicHeader() defer out.BasicFooter() - users, err := cmd.list.ListUsers(context.TODO(), list.ListUsersOption{}) + users, err := cmd.usersvc.ListUsers(context.TODO(), user.ListUsersOption{}) if err != nil { return errors.Wrap(err, "list users") } diff --git a/cmd/mdmctl/setup.go b/cmd/mdmctl/setup.go index f02f749f..85caff4d 100644 --- a/cmd/mdmctl/setup.go +++ b/cmd/mdmctl/setup.go @@ -9,12 +9,14 @@ import ( "github.com/micromdm/micromdm/platform/blueprint" "github.com/micromdm/micromdm/platform/profile" "github.com/micromdm/micromdm/platform/remove" + "github.com/micromdm/micromdm/platform/user" ) type remoteServices struct { profilesvc profile.Service blueprintsvc blueprint.Service blocksvc remove.Service + usersvc user.Service applysvc apply.Service list list.Service } @@ -46,6 +48,13 @@ func setupClient(logger log.Logger) (*remoteServices, error) { return nil, err } + usersvc, err := user.NewHTTPClient( + cfg.ServerURL, cfg.APIToken, logger, + httptransport.SetClient(skipVerifyHTTPClient(cfg.SkipVerify))) + if err != nil { + return nil, err + } + applysvc, err := apply.NewClient( cfg.ServerURL, logger, cfg.APIToken, httptransport.SetClient(skipVerifyHTTPClient(cfg.SkipVerify))) @@ -64,6 +73,7 @@ func setupClient(logger log.Logger) (*remoteServices, error) { profilesvc: profilesvc, blueprintsvc: blueprintsvc, blocksvc: blocksvc, + usersvc: usersvc, applysvc: applysvc, list: listsvc, }, nil diff --git a/cmd/micromdm/serve.go b/cmd/micromdm/serve.go index 90766789..c243eff6 100644 --- a/cmd/micromdm/serve.go +++ b/cmd/micromdm/serve.go @@ -61,6 +61,7 @@ import ( block "github.com/micromdm/micromdm/platform/remove" blockbuiltin "github.com/micromdm/micromdm/platform/remove/builtin" "github.com/micromdm/micromdm/platform/user" + userbuiltin "github.com/micromdm/micromdm/platform/user/builtin" "github.com/micromdm/micromdm/workflow/webhook" ) @@ -172,7 +173,7 @@ func serve(args []string) error { stdlog.Fatal(err) } - userDB, err := user.NewDB(sm.db, sm.pubclient, log.With(logger, "component", "user db")) + userDB, err := userbuiltin.NewDB(sm.db, sm.pubclient, log.With(logger, "component", "user db")) if err != nil { stdlog.Fatal(err) } @@ -284,8 +285,15 @@ func serve(args []string) error { } blueprintEndpoints := blueprint.MakeServerEndpoints(blueprintsvc) - blockEndpoints := block.MakeServerEndpoints(removeService) + + var usersvc user.Service + { + usersvc = user.New(userDB) + } + + userEndpoints := user.MakeServerEndpoints(usersvc) + var listsvc list.Service { l := &list.ListService{ @@ -293,7 +301,6 @@ func serve(args []string) error { Devices: devDB, Tokens: tokenDB, Apps: appDB, - Users: userDB, } listsvc = l @@ -313,7 +320,6 @@ func serve(args []string) error { GetDEPProfileEndpoint: list.MakeGetDEPProfileEndpoint(listsvc), GetDEPDeviceEndpoint: list.MakeGetDEPDeviceDetailsEndpoint(listsvc), ListAppsEndpont: list.MakeListAppsEndpoint(listsvc), - ListUserEndpoint: list.MakeListUsersEndpoint(listsvc), } var applysvc apply.Service @@ -322,7 +328,6 @@ func serve(args []string) error { DEPClient: dc, Tokens: tokenDB, Apps: appDB, - Users: userDB, } applysvc = l if err := l.WatchTokenUpdates(sm.pubclient); err != nil { @@ -340,16 +345,10 @@ func serve(args []string) error { appUploadEndpoint = apply.MakeUploadAppEndpiont(applysvc) } - var applyUserEndpoint endpoint.Endpoint - { - applyUserEndpoint = apply.MakeApplyUserEndpoint(applysvc) - } - applyEndpoints := apply.Endpoints{ ApplyDEPTokensEndpoint: apply.MakeApplyDEPTokensEndpoint(applysvc), DefineDEPProfileEndpoint: defineDEPProfileEndpoint, AppUploadEndpoint: appUploadEndpoint, - ApplyUserEndpoint: applyUserEndpoint, } applyAPIHandlers := apply.MakeHTTPHandlers(ctx, applyEndpoints, connectOpts...) @@ -375,11 +374,13 @@ func serve(args []string) error { profilesHandler := profile.MakeHTTPHandler(profileEndpoints, logger) blueprintsHandler := blueprint.MakeHTTPHandler(blueprintEndpoints, logger) blockhandler := block.MakeHTTPHandler(blockEndpoints, logger) + userHandler := user.MakeHTTPHandler(userEndpoints, logger) // API commands. Only handled if the user provides an api key. if *flAPIKey != "" { r.Handle("/v1/profiles", apiAuthMiddleware(*flAPIKey, profilesHandler)) r.Handle("/v1/blueprints", apiAuthMiddleware(*flAPIKey, blueprintsHandler)) + r.Handle("/v1/users", apiAuthMiddleware(*flAPIKey, userHandler)) r.Handle("/v1/devices/{udid}/block", apiAuthMiddleware(*flAPIKey, blockhandler)) r.Handle("/v1/devices/{udid}/unblock", apiAuthMiddleware(*flAPIKey, blockhandler)) r.Handle("/push/{udid}", apiAuthMiddleware(*flAPIKey, pushHandlers.PushHandler)) @@ -393,8 +394,6 @@ func serve(args []string) error { r.Handle("/v1/dep/profiles", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.DefineDEPProfileHandler)).Methods("POST") r.Handle("/v1/apps", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.AppUploadHandler)).Methods("POST") r.Handle("/v1/apps", apiAuthMiddleware(*flAPIKey, listAPIHandlers.ListAppsHandler)).Methods("GET") - r.Handle("/v1/users", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.ApplyUserhandler)).Methods("PUT") - r.Handle("/v1/users", apiAuthMiddleware(*flAPIKey, listAPIHandlers.ListUsersHander)).Methods("GET") r.Handle("/v1/config/certificate", apiAuthMiddleware(*flAPIKey, configHandlers.SavePushCertificateHandler)).Methods("PUT") } diff --git a/platform/api/server/apply/client.go b/platform/api/server/apply/client.go index e4eb6762..8bb1b9de 100644 --- a/platform/api/server/apply/client.go +++ b/platform/api/server/apply/client.go @@ -49,22 +49,10 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra ).Endpoint() } - var applyUserEndpoint endpoint.Endpoint - { - applyUserEndpoint = httptransport.NewClient( - "PUT", - copyURL(u, "/v1/users"), - encodeRequestWithToken(token, EncodeHTTPGenericRequest), - DecodeApplyUserResponse, - opts..., - ).Endpoint() - } - return Endpoints{ ApplyDEPTokensEndpoint: applyDEPTokensEndpoint, DefineDEPProfileEndpoint: defineDEPProfileEndpoint, AppUploadEndpoint: uploadAppEndpoint, - ApplyUserEndpoint: applyUserEndpoint, }, nil } diff --git a/platform/api/server/apply/endpoint.go b/platform/api/server/apply/endpoint.go index 9e97fc45..11ba1766 100644 --- a/platform/api/server/apply/endpoint.go +++ b/platform/api/server/apply/endpoint.go @@ -6,27 +6,12 @@ import ( "github.com/go-kit/kit/endpoint" "github.com/micromdm/dep" - - "github.com/micromdm/micromdm/platform/user" ) type Endpoints struct { ApplyDEPTokensEndpoint endpoint.Endpoint DefineDEPProfileEndpoint endpoint.Endpoint AppUploadEndpoint endpoint.Endpoint - ApplyUserEndpoint endpoint.Endpoint -} - -func (e Endpoints) ApplyUser(ctx context.Context, u user.User) (*user.User, error) { - request := applyUserRequest{ - User: u, - } - resp, err := e.ApplyUserEndpoint(ctx, request) - if err != nil { - return nil, err - } - usr := resp.(applyUserResponse).User - return &usr, resp.(applyUserResponse).Err } func (e Endpoints) UploadApp(ctx context.Context, manifestName string, manifest io.Reader, pkgName string, pkg io.Reader) error { @@ -83,17 +68,6 @@ func MakeDefineDEPProfile(svc Service) endpoint.Endpoint { } } -func MakeApplyUserEndpoint(svc Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (response interface{}, err error) { - req := request.(applyUserRequest) - u, err := svc.ApplyUser(ctx, req.User) - return applyUserResponse{ - User: *u, - Err: err, - }, nil - } -} - func MakeUploadAppEndpiont(svc Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { req := request.(appUploadRequest) @@ -135,14 +109,3 @@ type depProfileResponse struct { } func (r *depProfileResponse) error() error { return r.Err } - -type applyUserRequest struct { - User user.User `json:"user"` -} - -type applyUserResponse struct { - Err error `json:"err"` - User user.User `json:"user"` -} - -func (r applyUserResponse) error() error { return r.Err } diff --git a/platform/api/server/apply/service.go b/platform/api/server/apply/service.go index 85b87cbb..81b7a1b9 100644 --- a/platform/api/server/apply/service.go +++ b/platform/api/server/apply/service.go @@ -14,18 +14,15 @@ import ( "github.com/fullsailor/pkcs7" "github.com/micromdm/dep" - "github.com/pkg/errors" "github.com/micromdm/micromdm/platform/appstore" "github.com/micromdm/micromdm/platform/deptoken" "github.com/micromdm/micromdm/platform/pubsub" - "github.com/micromdm/micromdm/platform/user" ) type Service interface { ApplyDEPToken(ctx context.Context, P7MContent []byte) error 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 } @@ -35,20 +32,6 @@ type ApplyService struct { Tokens *deptoken.DB Apps appstore.AppStore - Users *user.DB -} - -func (svc *ApplyService) ApplyUser(ctx context.Context, u user.User) (*user.User, error) { - toSave := &u - if u.UUID == "" { //newUser - usr, err := user.NewFromRequest(u) - if err != nil { - return nil, errors.Wrap(err, "create user from request") - } - toSave = usr - } - err := svc.Users.Save(toSave) - return toSave, errors.Wrap(err, "apply user") } func (svc *ApplyService) UploadApp(ctx context.Context, manifestName string, manifest io.Reader, pkgName string, pkg io.Reader) error { diff --git a/platform/api/server/apply/transport_http.go b/platform/api/server/apply/transport_http.go index f56ed6ae..61e0e105 100644 --- a/platform/api/server/apply/transport_http.go +++ b/platform/api/server/apply/transport_http.go @@ -17,7 +17,6 @@ type HTTPHandlers struct { DEPTokensHandler http.Handler DefineDEPProfileHandler http.Handler AppUploadHandler http.Handler - ApplyUserhandler http.Handler } func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) HTTPHandlers { @@ -40,12 +39,6 @@ func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptran encodeResponse, opts..., ), - ApplyUserhandler: httptransport.NewServer( - endpoints.ApplyUserEndpoint, - decodeUserRequest, - encodeResponse, - opts..., - ), } return h } @@ -87,15 +80,6 @@ func decodeAppUploadRequest(ctx context.Context, r *http.Request) (interface{}, }, nil } -func decodeUserRequest(ctx context.Context, r *http.Request) (interface{}, error) { - var req applyUserRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(err, "decode user request") - } - defer r.Body.Close() - return req, nil -} - func EncodeUploadAppRequest(_ context.Context, r *http.Request, request interface{}) error { req := request.(appUploadRequest) body := new(bytes.Buffer) @@ -204,12 +188,3 @@ func DecodeUploadAppResponse(_ context.Context, r *http.Response) (interface{}, err := json.NewDecoder(r.Body).Decode(&resp) return resp, err } - -func DecodeApplyUserResponse(_ context.Context, r *http.Response) (interface{}, error) { - if r.StatusCode != http.StatusOK { - return nil, errorDecoder(r) - } - var resp applyUserResponse - err := json.NewDecoder(r.Body).Decode(&resp) - return resp, err -} diff --git a/platform/api/server/list/client.go b/platform/api/server/list/client.go index 7d8b1d1c..96a0080e 100644 --- a/platform/api/server/list/client.go +++ b/platform/api/server/list/client.go @@ -81,17 +81,6 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra ).Endpoint() } - var listUsersEndpoint endpoint.Endpoint - { - listUsersEndpoint = httptransport.NewClient( - "GET", - copyURL(u, "/v1/users"), - encodeRequestWithToken(token, EncodeHTTPGenericRequest), - DecodeListUsersResponse, - opts..., - ).Endpoint() - } - return Endpoints{ ListDevicesEndpoint: listDevicesEndpoint, GetDEPTokensEndpoint: getDEPTokensEndpoint, @@ -99,7 +88,6 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra GetDEPDeviceEndpoint: getDEPDeviceDetailsEndpoint, GetDEPProfileEndpoint: getDEPProfilesEndpoint, ListAppsEndpont: listAppsEndpoint, - ListUserEndpoint: listUsersEndpoint, }, nil } diff --git a/platform/api/server/list/endpoint.go b/platform/api/server/list/endpoint.go index 3b496e18..b3d02dbe 100644 --- a/platform/api/server/list/endpoint.go +++ b/platform/api/server/list/endpoint.go @@ -8,7 +8,6 @@ import ( "github.com/micromdm/dep" "github.com/micromdm/micromdm/platform/deptoken" - "github.com/micromdm/micromdm/platform/user" ) type Endpoints struct { @@ -18,16 +17,6 @@ type Endpoints struct { GetDEPDeviceEndpoint endpoint.Endpoint GetDEPProfileEndpoint endpoint.Endpoint ListAppsEndpont endpoint.Endpoint - ListUserEndpoint endpoint.Endpoint -} - -func (e Endpoints) ListUsers(ctx context.Context, opts ListUsersOption) ([]user.User, error) { - request := userRequest{opts} - response, err := e.ListUserEndpoint(ctx, request.Opts) - if err != nil { - return nil, err - } - return response.(userResponse).Users, response.(userResponse).Err } func (e Endpoints) ListDevices(ctx context.Context, opts ListDevicesOption) ([]DeviceDTO, error) { @@ -74,17 +63,6 @@ func (e Endpoints) GetDEPDevice(ctx context.Context, serials []string) (*dep.Dev return response.(depDeviceDetailsResponse).DeviceDetailsResponse, response.(depDeviceDetailsResponse).Err } -func MakeListUsersEndpoint(svc Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (response interface{}, err error) { - req := request.(userRequest) - dto, err := svc.ListUsers(ctx, req.Opts) - return userResponse{ - Users: dto, - Err: err, - }, nil - } -} - func MakeListDevicesEndpoint(svc Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { req := request.(devicesRequest) @@ -157,14 +135,6 @@ type DeviceDTO struct { LastSeen time.Time `json:"last_seen"` } -type userRequest struct{ Opts ListUsersOption } -type userResponse struct { - Users []user.User `json:"users"` - Err error `json:"err,omitempty"` -} - -func (r userResponse) error() error { return r.Err } - type devicesRequest struct{ Opts ListDevicesOption } type devicesResponse struct { Devices []DeviceDTO `json:"devices"` diff --git a/platform/api/server/list/service.go b/platform/api/server/list/service.go index c42e9851..70e64743 100644 --- a/platform/api/server/list/service.go +++ b/platform/api/server/list/service.go @@ -14,7 +14,6 @@ import ( "github.com/micromdm/micromdm/platform/deptoken" "github.com/micromdm/micromdm/platform/device" "github.com/micromdm/micromdm/platform/pubsub" - "github.com/micromdm/micromdm/platform/user" ) type ListDevicesOption struct { @@ -25,21 +24,12 @@ type ListDevicesOption struct { FilterUDID []string } -type ListUsersOption struct { - Page int - PerPage int - - FilterUserID []string - FilterUDID []string -} - type ListAppsOption struct { FilterName []string `json:"filter_name"` } type Service interface { ListDevices(ctx context.Context, opt ListDevicesOption) ([]DeviceDTO, error) - ListUsers(ctx context.Context, opt ListUsersOption) ([]user.User, error) GetDEPTokens(ctx context.Context) ([]deptoken.DEPToken, []byte, error) ListApplications(ctx context.Context, opt ListAppsOption) ([]AppDTO, error) DEPService @@ -52,7 +42,6 @@ type ListService struct { Devices *device.DB Tokens *deptoken.DB Apps appstore.AppStore - Users *user.DB } func (svc *ListService) ListApplications(ctx context.Context, opts ListAppsOption) ([]AppDTO, error) { @@ -124,11 +113,6 @@ func (svc *ListService) ListDevices(ctx context.Context, opt ListDevicesOption) return dto, err } -func (svc *ListService) ListUsers(ctx context.Context, opts ListUsersOption) ([]user.User, error) { - u, err := svc.Users.List() - return u, errors.Wrap(err, "list users from api request") -} - func (svc *ListService) GetDEPTokens(ctx context.Context) ([]deptoken.DEPToken, []byte, error) { _, cert, err := svc.Tokens.DEPKeypair() if err != nil { diff --git a/platform/api/server/list/transport_http.go b/platform/api/server/list/transport_http.go index f30b2812..1828974c 100644 --- a/platform/api/server/list/transport_http.go +++ b/platform/api/server/list/transport_http.go @@ -18,7 +18,6 @@ type HTTPHandlers struct { GetDEPProfileHandler http.Handler GetDEPDeviceDetailsHandler http.Handler ListAppsHandler http.Handler - ListUsersHander http.Handler } func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) HTTPHandlers { @@ -58,12 +57,6 @@ func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptran encodeResponse, opts..., ), - ListUsersHander: httptransport.NewServer( - endpoints.ListUserEndpoint, - decodeListUsersRequest, - encodeResponse, - opts..., - ), } return h } @@ -79,11 +72,6 @@ func decodeListDevicesRequest(ctx context.Context, r *http.Request) (interface{} return req, nil } -func decodeListUsersRequest(ctx context.Context, r *http.Request) (interface{}, error) { - req := userRequest{} - return req, nil -} - func decodeDepAccountInfoRequest(ctx context.Context, r *http.Request) (interface{}, error) { return nil, nil } @@ -210,12 +198,3 @@ func DecodeListAppsResponse(_ context.Context, r *http.Response) (interface{}, e err := json.NewDecoder(r.Body).Decode(&resp) return resp, err } - -func DecodeListUsersResponse(_ context.Context, r *http.Response) (interface{}, error) { - if r.StatusCode != http.StatusOK { - return nil, errorDecoder(r) - } - var resp userResponse - err := json.NewDecoder(r.Body).Decode(&resp) - return resp, errors.Wrap(err, "decode user response") -} diff --git a/platform/blueprint/builtin/db.go b/platform/blueprint/builtin/db.go index 342e71d2..885bf0a2 100644 --- a/platform/blueprint/builtin/db.go +++ b/platform/blueprint/builtin/db.go @@ -20,13 +20,13 @@ const ( type DB struct { *bolt.DB profDB profile.Store - userDB *user.DB + userDB user.Store } func NewDB( db *bolt.DB, profileDB profile.Store, - userDB *user.DB, + userDB user.Store, ) (*DB, error) { err := db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists([]byte(blueprintIndexBucket)) diff --git a/platform/blueprint/server.go b/platform/blueprint/server.go index 563923c9..9be9eee4 100644 --- a/platform/blueprint/server.go +++ b/platform/blueprint/server.go @@ -1,11 +1,10 @@ package blueprint import ( - "net/http" - "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/log" httptransport "github.com/go-kit/kit/transport/http" + "github.com/gorilla/mux" "github.com/micromdm/micromdm/pkg/httputil" ) @@ -23,7 +22,7 @@ func MakeServerEndpoints(s Service) Endpoints { } } -func MakeHTTPHandler(e Endpoints, logger log.Logger) http.Handler { +func MakeHTTPHandler(e Endpoints, logger log.Logger) *mux.Router { r, options := httputil.NewRouter(logger) // PUT /v1/blueprints create or replace a blueprint on the server diff --git a/platform/profile/server.go b/platform/profile/server.go index 8e0e5615..31500572 100644 --- a/platform/profile/server.go +++ b/platform/profile/server.go @@ -1,11 +1,10 @@ package profile import ( - "net/http" - "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/log" httptransport "github.com/go-kit/kit/transport/http" + "github.com/gorilla/mux" "github.com/micromdm/micromdm/pkg/httputil" ) @@ -23,7 +22,7 @@ func MakeServerEndpoints(s Service) Endpoints { } } -func MakeHTTPHandler(e Endpoints, logger log.Logger) http.Handler { +func MakeHTTPHandler(e Endpoints, logger log.Logger) *mux.Router { r, options := httputil.NewRouter(logger) // GET /v1/profiles get a list of profiles managed by the server diff --git a/platform/remove/server.go b/platform/remove/server.go index 7d8f6c30..6a181234 100644 --- a/platform/remove/server.go +++ b/platform/remove/server.go @@ -1,11 +1,10 @@ package remove import ( - "net/http" - "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/log" httptransport "github.com/go-kit/kit/transport/http" + "github.com/gorilla/mux" "github.com/micromdm/micromdm/pkg/httputil" ) @@ -21,7 +20,7 @@ func MakeServerEndpoints(s Service) Endpoints { } } -func MakeHTTPHandler(e Endpoints, logger log.Logger) http.Handler { +func MakeHTTPHandler(e Endpoints, logger log.Logger) *mux.Router { r, options := httputil.NewRouter(logger) // POST /v1/devices/:udid/block force a device to unenroll next time it connects diff --git a/platform/user/apply_user.go b/platform/user/apply_user.go new file mode 100644 index 00000000..cf450173 --- /dev/null +++ b/platform/user/apply_user.go @@ -0,0 +1,69 @@ +package user + +import ( + "context" + "net/http" + + "github.com/go-kit/kit/endpoint" + "github.com/micromdm/micromdm/pkg/httputil" + "github.com/pkg/errors" +) + +func (svc *UserService) ApplyUser(ctx context.Context, u User) (*User, error) { + toSave := &u + if u.UUID == "" { //newUser + usr, err := NewFromRequest(u) + if err != nil { + return nil, errors.Wrap(err, "create user from request") + } + toSave = usr + } + err := svc.store.Save(toSave) + return toSave, errors.Wrap(err, "apply user") +} + +type applyUserRequest struct { + User User `json:"user"` +} + +type applyUserResponse struct { + User User `json:"user"` + Err error `json:"err,omitempty"` +} + +func (r applyUserResponse) Failed() error { return r.Err } + +func decodeApplyUserRequest(ctx context.Context, r *http.Request) (interface{}, error) { + var req applyUserRequest + err := httputil.DecodeJSONRequest(r, &req) + return req, err +} + +func decodeApplyUserResponse(_ context.Context, r *http.Response) (interface{}, error) { + var resp applyUserResponse + err := httputil.DecodeJSONResponse(r, &resp) + return resp, err +} + +func MakeApplyUserEndpoint(svc Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(applyUserRequest) + u, err := svc.ApplyUser(ctx, req.User) + return applyUserResponse{ + User: *u, + Err: err, + }, nil + } +} + +func (e Endpoints) ApplyUser(ctx context.Context, u User) (*User, error) { + request := applyUserRequest{ + User: u, + } + resp, err := e.ApplyUserEndpoint(ctx, request) + if err != nil { + return nil, err + } + usr := resp.(applyUserResponse).User + return &usr, resp.(applyUserResponse).Err +} diff --git a/platform/user/db.go b/platform/user/builtin/db.go similarity index 87% rename from platform/user/db.go rename to platform/user/builtin/db.go index 906461c0..96cfbdbd 100644 --- a/platform/user/db.go +++ b/platform/user/builtin/db.go @@ -1,4 +1,4 @@ -package user +package builtin import ( "context" @@ -12,6 +12,7 @@ import ( "github.com/micromdm/micromdm/mdm/checkin" "github.com/micromdm/micromdm/platform/pubsub" + "github.com/micromdm/micromdm/platform/user" ) const ( @@ -51,14 +52,14 @@ func NewDB(db *bolt.DB, pubsubSvc pubsub.PublishSubscriber, logger log.Logger) ( return datastore, nil } -func (db *DB) List() ([]User, error) { - var users []User +func (db *DB) List() ([]user.User, error) { + var users []user.User err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(UserBucket)) c := b.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { - var u User - if err := UnmarshalUser(v, &u); err != nil { + var u user.User + if err := user.UnmarshalUser(v, &u); err != nil { return err } users = append(users, u) @@ -68,7 +69,7 @@ func (db *DB) List() ([]User, error) { return users, errors.Wrap(err, "list users") } -func (db *DB) Save(u *User) error { +func (db *DB) Save(u *user.User) error { tx, err := db.DB.Begin(true) if err != nil { return errors.Wrap(err, "begin transaction") @@ -77,7 +78,7 @@ func (db *DB) Save(u *User) error { if bkt == nil { return fmt.Errorf("bucket %q not found!", UserBucket) } - userpb, err := MarshalUser(u) + userpb, err := user.MarshalUser(u) if err != nil { return errors.Wrap(err, "marshalling user") } @@ -106,15 +107,15 @@ func (db *DB) Save(u *User) error { return tx.Commit() } -func (db *DB) User(uuid string) (*User, error) { - var u User +func (db *DB) User(uuid string) (*user.User, error) { + var u user.User err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(UserBucket)) v := b.Get([]byte(uuid)) if v == nil { return ¬Found{"User", fmt.Sprintf("uuid %s", uuid)} } - return UnmarshalUser(v, &u) + return user.UnmarshalUser(v, &u) }) if err != nil { return nil, errors.Wrap(err, "get user by uuid from bolt") @@ -122,8 +123,8 @@ func (db *DB) User(uuid string) (*User, error) { return &u, nil } -func (db *DB) UserByUserID(userID string) (*User, error) { - var u User +func (db *DB) UserByUserID(userID string) (*user.User, error) { + var u user.User err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(UserBucket)) ib := tx.Bucket([]byte(userIndexBucket)) @@ -135,7 +136,7 @@ func (db *DB) UserByUserID(userID string) (*User, error) { if idx == nil { return ¬Found{"User", fmt.Sprintf("uuid %s", string(idx))} } - return UnmarshalUser(v, &u) + return user.UnmarshalUser(v, &u) }) if err != nil { return nil, errors.Wrap(err, "get user by user id from bolt") @@ -143,14 +144,14 @@ func (db *DB) UserByUserID(userID string) (*User, error) { return &u, nil } -func (db *DB) DeviceUsers(udid string) ([]User, error) { - var users []User +func (db *DB) DeviceUsers(udid string) ([]user.User, error) { + var users []user.User err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(UserBucket)) c := b.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { - var u User - if err := UnmarshalUser(v, &u); err != nil { + var u user.User + if err := user.UnmarshalUser(v, &u); err != nil { return errors.Wrap(err, "unmarshal user for DeviceUsers") } if u.UDID == udid { @@ -170,8 +171,8 @@ func (db *DB) DeleteDeviceUsers(udid string) error { b := tx.Bucket([]byte(UserBucket)) c := b.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { - var u User - if err := UnmarshalUser(v, &u); err != nil { + var u user.User + if err := user.UnmarshalUser(v, &u); err != nil { return errors.Wrap(err, "unmarshal user for DeviceUsers") } if u.UDID != udid { @@ -195,6 +196,10 @@ func (e *notFound) Error() string { return fmt.Sprintf("not found: %s %s", e.ResourceType, e.Message) } +func (e *notFound) NotFound() bool { + return true +} + func (db *DB) pollCheckin(pubsubSvc pubsub.PublishSubscriber) error { tokenUpdateEvents, err := pubsubSvc.Subscribe(context.TODO(), "users", checkin.TokenUpdateTopic) if err != nil { @@ -213,7 +218,7 @@ func (db *DB) pollCheckin(pubsubSvc pubsub.PublishSubscriber) error { if event.Command.UserID == "" { break // only interested in user commands } - newUser := new(User) + newUser := new(user.User) byGUID, err := db.UserByUserID(event.Command.UserID) if err != nil && !isNotFound(err) { level.Info(db.logger).Log("err", err, "msg", "get user from DB") diff --git a/platform/user/client.go b/platform/user/client.go new file mode 100644 index 00000000..020cdc34 --- /dev/null +++ b/platform/user/client.go @@ -0,0 +1,44 @@ +package user + +import ( + "net/url" + + "github.com/go-kit/kit/endpoint" + "github.com/go-kit/kit/log" + httptransport "github.com/go-kit/kit/transport/http" + "github.com/micromdm/micromdm/pkg/httputil" +) + +func NewHTTPClient(instance, token string, logger log.Logger, opts ...httptransport.ClientOption) (Service, error) { + u, err := url.Parse(instance) + if err != nil { + return nil, err + } + + var applyUserEndpoint endpoint.Endpoint + { + applyUserEndpoint = httptransport.NewClient( + "PUT", + httputil.CopyURL(u, "/v1/users"), + httputil.EncodeRequestWithToken(token, httptransport.EncodeJSONRequest), + decodeApplyUserResponse, + opts..., + ).Endpoint() + } + + var listUsersEndpoint endpoint.Endpoint + { + listUsersEndpoint = httptransport.NewClient( + "GET", + httputil.CopyURL(u, "/v1/users"), + httputil.EncodeRequestWithToken(token, httptransport.EncodeJSONRequest), + decodeListUsersResponse, + opts..., + ).Endpoint() + } + + return Endpoints{ + ApplyUserEndpoint: applyUserEndpoint, + ListUsersEndpoint: listUsersEndpoint, + }, nil +} diff --git a/platform/user/list_user.go b/platform/user/list_user.go new file mode 100644 index 00000000..6a9fe0f6 --- /dev/null +++ b/platform/user/list_user.go @@ -0,0 +1,59 @@ +package user + +import ( + "context" + "encoding/json" + "net/http" + + "github.com/go-kit/kit/endpoint" + "github.com/micromdm/micromdm/pkg/httputil" + "github.com/pkg/errors" +) + +func (svc *UserService) ListUsers(ctx context.Context, opts ListUsersOption) ([]User, error) { + u, err := svc.store.List() + return u, errors.Wrap(err, "list users from api request") +} + +type getUsersRequest struct{ Opts ListUsersOption } +type getUsersResponse struct { + Users []User `json:"users"` + Err error `json:"err,omitempty"` +} + +func (r getUsersResponse) Failed() error { return r.Err } + +func decodeListUsersRequest(ctx context.Context, r *http.Request) (interface{}, error) { + var opts ListUsersOption + if err := json.NewDecoder(r.Body).Decode(&opts); err != nil { + return nil, err + } + req := getUsersRequest{Opts: opts} + return req, nil +} + +func decodeListUsersResponse(_ context.Context, r *http.Response) (interface{}, error) { + var resp getUsersResponse + err := httputil.DecodeJSONResponse(r, &resp) + return resp, err +} + +func MakeListUsersEndpoint(svc Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(getUsersRequest) + users, err := svc.ListUsers(ctx, req.Opts) + return getUsersResponse{ + Users: users, + Err: err, + }, nil + } +} + +func (e Endpoints) ListUsers(ctx context.Context, opts ListUsersOption) ([]User, error) { + request := getUsersRequest{opts} + response, err := e.ListUsersEndpoint(ctx, request.Opts) + if err != nil { + return nil, err + } + return response.(getUsersResponse).Users, response.(getUsersResponse).Err +} diff --git a/platform/user/server.go b/platform/user/server.go new file mode 100644 index 00000000..6ce55a22 --- /dev/null +++ b/platform/user/server.go @@ -0,0 +1,45 @@ +package user + +import ( + "github.com/go-kit/kit/endpoint" + "github.com/go-kit/kit/log" + httptransport "github.com/go-kit/kit/transport/http" + "github.com/gorilla/mux" + + "github.com/micromdm/micromdm/pkg/httputil" +) + +type Endpoints struct { + ApplyUserEndpoint endpoint.Endpoint + ListUsersEndpoint endpoint.Endpoint +} + +func MakeServerEndpoints(s Service) Endpoints { + return Endpoints{ + ApplyUserEndpoint: MakeApplyUserEndpoint(s), + ListUsersEndpoint: MakeListUsersEndpoint(s), + } +} + +func MakeHTTPHandler(e Endpoints, logger log.Logger) *mux.Router { + r, options := httputil.NewRouter(logger) + + // PUT /v1/users create or replace an user + // GET /v1/users get a list of users managed by the server + + r.Methods("PUT").Path("/v1/users").Handler(httptransport.NewServer( + e.ApplyUserEndpoint, + decodeApplyUserRequest, + httputil.EncodeJSONResponse, + options..., + )) + + r.Methods("GET").Path("/v1/users").Handler(httptransport.NewServer( + e.ListUsersEndpoint, + decodeListUsersRequest, + httputil.EncodeJSONResponse, + options..., + )) + + return r +} diff --git a/platform/user/service.go b/platform/user/service.go new file mode 100644 index 00000000..cd4f4f7e --- /dev/null +++ b/platform/user/service.go @@ -0,0 +1,32 @@ +package user + +import ( + "context" +) + +type ListUsersOption struct { + Page int `json:"page"` + PerPage int `json:"per_page"` + + FilterUserID []string `json:"filter_user_id"` + FilterUDID []string `json:"filter_udid"` +} + +type Service interface { + ApplyUser(ctx context.Context, u User) (*User, error) + ListUsers(ctx context.Context, opt ListUsersOption) ([]User, error) +} + +type Store interface { + User(string) (*User, error) + Save(*User) error + List() ([]User, error) +} + +type UserService struct { + store Store +} + +func New(store Store) *UserService { + return &UserService{store: store} +}