mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-07 10:05:48 +08:00
Add users (#258)
For #248 Added a User DB similar to the Device DB. The user specification be added with the API/mdmctl commands and then referenced with a blueprint. If added to the blueprint, the user will be installed as an administrator during the AccountConfiguration step.
This commit is contained in:
@@ -21,6 +21,7 @@ type Blueprint struct {
|
||||
Name string `json:"name"`
|
||||
ApplicationURLs []string `json:"install_application_manifest_urls"`
|
||||
ProfileIdentifiers []string `json:"profile_ids"`
|
||||
UserUUID []string `json:"user_uuids"`
|
||||
ApplyAt []string `json:"apply_at"`
|
||||
}
|
||||
|
||||
@@ -37,6 +38,7 @@ func MarshalBlueprint(bp *Blueprint) ([]byte, error) {
|
||||
Name: bp.Name,
|
||||
ManifestUrls: bp.ApplicationURLs,
|
||||
ProfileIds: bp.ProfileIdentifiers,
|
||||
UserUuid: bp.UserUUID,
|
||||
ApplyAt: bp.ApplyAt,
|
||||
}
|
||||
return proto.Marshal(&protobp)
|
||||
@@ -52,5 +54,6 @@ func UnmarshalBlueprint(data []byte, bp *Blueprint) error {
|
||||
bp.ApplicationURLs = pb.GetManifestUrls()
|
||||
bp.ProfileIdentifiers = pb.GetProfileIds()
|
||||
bp.ApplyAt = pb.GetApplyAt()
|
||||
bp.UserUUID = pb.GetUserUuid()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/micromdm/micromdm/profile"
|
||||
"github.com/micromdm/micromdm/user"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -18,9 +19,14 @@ const (
|
||||
type DB struct {
|
||||
*bolt.DB
|
||||
profDB *profile.DB
|
||||
userDB *user.DB
|
||||
}
|
||||
|
||||
func NewDB(db *bolt.DB, pDB *profile.DB) (*DB, error) {
|
||||
func NewDB(
|
||||
db *bolt.DB,
|
||||
profileDB *profile.DB,
|
||||
userDB *user.DB,
|
||||
) (*DB, error) {
|
||||
err := db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte(blueprintIndexBucket))
|
||||
if err != nil {
|
||||
@@ -34,7 +40,8 @@ func NewDB(db *bolt.DB, pDB *profile.DB) (*DB, error) {
|
||||
}
|
||||
datastore := &DB{
|
||||
DB: db,
|
||||
profDB: pDB,
|
||||
profDB: profileDB,
|
||||
userDB: userDB,
|
||||
}
|
||||
return datastore, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Code generated by protoc-gen-go.
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: blueprint.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package blueprintproto is a generated protocol buffer package.
|
||||
@@ -34,6 +33,7 @@ type Blueprint struct {
|
||||
ManifestUrls []string `protobuf:"bytes,3,rep,name=manifest_urls,json=manifestUrls" json:"manifest_urls,omitempty"`
|
||||
ProfileIds []string `protobuf:"bytes,5,rep,name=profile_ids,json=profileIds" json:"profile_ids,omitempty"`
|
||||
ApplyAt []string `protobuf:"bytes,6,rep,name=apply_at,json=applyAt" json:"apply_at,omitempty"`
|
||||
UserUuid []string `protobuf:"bytes,7,rep,name=user_uuid,json=userUuid" json:"user_uuid,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Blueprint) Reset() { *m = Blueprint{} }
|
||||
@@ -76,6 +76,13 @@ func (m *Blueprint) GetApplyAt() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Blueprint) GetUserUuid() []string {
|
||||
if m != nil {
|
||||
return m.UserUuid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Blueprint)(nil), "blueprintproto.Blueprint")
|
||||
}
|
||||
@@ -83,17 +90,18 @@ func init() {
|
||||
func init() { proto.RegisterFile("blueprint.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 185 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x3c, 0xce, 0x3d, 0x8e, 0xc2, 0x30,
|
||||
0x10, 0x05, 0x60, 0x65, 0xf3, 0xb3, 0xc9, 0xec, 0x06, 0x90, 0x2b, 0x53, 0x11, 0x41, 0x93, 0x8a,
|
||||
0x86, 0x13, 0x40, 0x07, 0x65, 0x24, 0xea, 0xc8, 0x21, 0x0e, 0x1a, 0xc9, 0xb1, 0x2d, 0xff, 0x14,
|
||||
0x1c, 0x89, 0x5b, 0xa2, 0x18, 0x42, 0xf7, 0xde, 0x37, 0x53, 0x3c, 0x58, 0x76, 0xc2, 0x73, 0x6d,
|
||||
0x50, 0xba, 0xbd, 0x36, 0xca, 0x29, 0xb2, 0xf8, 0x42, 0xe8, 0xdb, 0x67, 0x04, 0xc5, 0x69, 0x26,
|
||||
0x42, 0x20, 0xf1, 0x1e, 0x7b, 0x1a, 0x55, 0x51, 0x5d, 0x34, 0x21, 0x4f, 0x26, 0xd9, 0xc8, 0xe9,
|
||||
0xcf, 0xdb, 0xa6, 0x4c, 0x76, 0x50, 0x8e, 0x4c, 0xe2, 0xc0, 0xad, 0x6b, 0xbd, 0x11, 0x96, 0xc6,
|
||||
0x55, 0x5c, 0x17, 0xcd, 0xff, 0x8c, 0x57, 0x23, 0x2c, 0xd9, 0xc0, 0x9f, 0x36, 0x6a, 0x40, 0xc1,
|
||||
0x5b, 0xec, 0x2d, 0x4d, 0xc3, 0x0b, 0x7c, 0xe8, 0xdc, 0x5b, 0xb2, 0x86, 0x9c, 0x69, 0x2d, 0x1e,
|
||||
0x2d, 0x73, 0x34, 0x0b, 0xd7, 0xdf, 0xd0, 0x8f, 0xee, 0x92, 0xe4, 0xc9, 0x2a, 0x6d, 0xca, 0x51,
|
||||
0x75, 0x28, 0xf8, 0x4d, 0xc9, 0x01, 0xef, 0xb6, 0xcb, 0xc2, 0xe4, 0xc3, 0x2b, 0x00, 0x00, 0xff,
|
||||
0xff, 0x1d, 0x05, 0x14, 0x9a, 0xd5, 0x00, 0x00, 0x00,
|
||||
// 202 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x3c, 0xcf, 0x3d, 0x4e, 0xc4, 0x30,
|
||||
0x10, 0x05, 0x60, 0x85, 0xcd, 0x6e, 0xe2, 0x81, 0x00, 0x72, 0x65, 0x44, 0x41, 0x04, 0x4d, 0x2a,
|
||||
0x1a, 0x4e, 0x00, 0x1d, 0x94, 0x91, 0x52, 0x5b, 0x0e, 0x76, 0xd0, 0x48, 0x8e, 0x6d, 0xf9, 0xa7,
|
||||
0xe0, 0x82, 0x9c, 0x0b, 0x65, 0x20, 0xdb, 0xbd, 0xf7, 0x3d, 0x17, 0x1e, 0xb8, 0x99, 0x6d, 0x31,
|
||||
0x21, 0xa2, 0xcb, 0xcf, 0x21, 0xfa, 0xec, 0xf9, 0xf5, 0x19, 0xa8, 0x3f, 0xfe, 0x54, 0xc0, 0xde,
|
||||
0x76, 0xe2, 0x1c, 0xea, 0x52, 0x50, 0x8b, 0xaa, 0xaf, 0x06, 0x36, 0x52, 0xde, 0xcc, 0xa9, 0xd5,
|
||||
0x88, 0x8b, 0x3f, 0xdb, 0x32, 0x7f, 0x82, 0x6e, 0x55, 0x0e, 0x17, 0x93, 0xb2, 0x2c, 0xd1, 0x26,
|
||||
0x71, 0xe8, 0x0f, 0x03, 0x1b, 0xaf, 0x76, 0x9c, 0xa2, 0x4d, 0xfc, 0x01, 0x2e, 0x43, 0xf4, 0x0b,
|
||||
0x5a, 0x23, 0x51, 0x27, 0x71, 0xa4, 0x27, 0xf0, 0x4f, 0xef, 0x3a, 0xf1, 0x3b, 0x68, 0x55, 0x08,
|
||||
0xf6, 0x5b, 0xaa, 0x2c, 0x4e, 0xb4, 0x36, 0xd4, 0x5f, 0x33, 0xbf, 0x07, 0x56, 0x92, 0x89, 0x92,
|
||||
0x7e, 0xd3, 0xd0, 0xd6, 0x6e, 0x30, 0x15, 0xd4, 0x1f, 0x75, 0x5b, 0xdf, 0x1e, 0xc7, 0x6e, 0xf5,
|
||||
0x33, 0x5a, 0xf3, 0xe9, 0xdd, 0x82, 0x5f, 0x69, 0x3e, 0xd1, 0x3d, 0x2f, 0xbf, 0x01, 0x00, 0x00,
|
||||
0xff, 0xff, 0xc6, 0x8c, 0x53, 0x51, 0xf2, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
@@ -11,4 +11,5 @@ message Blueprint {
|
||||
repeated string manifest_urls = 3;
|
||||
repeated string profile_ids = 5;
|
||||
repeated string apply_at = 6;
|
||||
repeated string user_uuid = 7;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,34 @@ import (
|
||||
)
|
||||
|
||||
func (db *DB) ApplyToDevice(ctx context.Context, svc command.Service, bp *Blueprint, udid string) error {
|
||||
|
||||
var requests []*mdm.CommandRequest
|
||||
for _, uuid := range bp.UserUUID {
|
||||
fmt.Println("Adding user to admin account")
|
||||
u, err := db.userDB.User(uuid)
|
||||
if err != nil {
|
||||
fmt.Printf("User UUID %s in Blueprint %s not added \n", bp.UserUUID, bp.Name)
|
||||
continue
|
||||
}
|
||||
requests = append(requests, &mdm.CommandRequest{
|
||||
UDID: udid,
|
||||
Command: mdm.Command{
|
||||
RequestType: "AccountConfiguration",
|
||||
AccountConfiguration: mdm.AccountConfiguration{
|
||||
SkipPrimarySetupAccountCreation: true,
|
||||
SetPrimarySetupAccountAsRegularUser: false,
|
||||
AutoSetupAdminAccounts: []mdm.AdminAccount{
|
||||
mdm.AdminAccount{
|
||||
ShortName: u.UserShortname,
|
||||
FullName: u.UserLongname,
|
||||
PasswordHash: u.PasswordHash,
|
||||
Hidden: u.Hidden,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for _, appURL := range bp.ApplicationURLs {
|
||||
requests = append(requests, &mdm.CommandRequest{
|
||||
UDID: udid,
|
||||
@@ -55,7 +81,7 @@ func (db *DB) ApplyToDevice(ctx context.Context, svc command.Service, bp *Bluepr
|
||||
for _, r := range requests {
|
||||
_, err := svc.NewCommand(ctx, r)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "create new command from blueprint")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -59,6 +59,8 @@ func (cmd *applyCommand) Run(args []string) error {
|
||||
run = cmd.applyProfile
|
||||
case "app":
|
||||
run = cmd.applyApp
|
||||
case "users":
|
||||
run = cmd.applyUser
|
||||
default:
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
@@ -74,6 +76,7 @@ Valid resource types:
|
||||
|
||||
* blueprints
|
||||
* profiles
|
||||
* users
|
||||
* dep-tokens
|
||||
* dep-profiles
|
||||
* app
|
||||
@@ -107,6 +110,7 @@ func (cmd *applyCommand) applyBlueprint(args []string) error {
|
||||
UUID: uuid.NewV4().String(),
|
||||
ApplicationURLs: []string{cmd.config.ServerURL + "repo/exampleAppManifest.plist"},
|
||||
ProfileIdentifiers: []string{"com.example.my.profile"},
|
||||
UserUUID: []string{"your-admin-account-uuid"},
|
||||
ApplyAt: []string{"Enroll"},
|
||||
}
|
||||
|
||||
|
||||
95
cmd/mdmctl/apply_user.go
Normal file
95
cmd/mdmctl/apply_user.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/groob/plist"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/micromdm/micromdm/crypto/password"
|
||||
"github.com/micromdm/micromdm/user"
|
||||
)
|
||||
|
||||
func (cmd *applyCommand) applyUser(args []string) error {
|
||||
flagset := flag.NewFlagSet("users", flag.ExitOnError)
|
||||
var (
|
||||
flUserManifest = flagset.String("f", "", "Path to user manifest")
|
||||
flTemplate = flagset.Bool("template", false, "Print a JSON example of a user manifest.")
|
||||
flPassword = flagset.String("password", "", "Password of the user. Only required when creating a new user.")
|
||||
)
|
||||
flagset.Usage = usageFor(flagset, "mdmctl apply users [flags]")
|
||||
if err := flagset.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *flTemplate {
|
||||
printUserTemplate()
|
||||
return nil
|
||||
}
|
||||
|
||||
manifestData, err := ioutil.ReadFile(*flUserManifest)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read user manifest file")
|
||||
}
|
||||
|
||||
var manifest user.User
|
||||
if err := json.Unmarshal(manifestData, &manifest); err != nil {
|
||||
return errors.Wrap(err, "unmarshal user manifest")
|
||||
}
|
||||
|
||||
if manifest.UUID == "" && *flPassword == "" {
|
||||
return errors.New("password argument must be provided when creating a user")
|
||||
}
|
||||
|
||||
if *flPassword != "" {
|
||||
salted, err := password.SaltedSHA512PBKDF2(*flPassword)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "salting plaintext password")
|
||||
}
|
||||
hashDict := struct {
|
||||
SaltedSHA512PBKDF2 password.SaltedSHA512PBKDF2Dictionary `plist:"SALTED-SHA512-PBKDF2"`
|
||||
}{
|
||||
SaltedSHA512PBKDF2: salted,
|
||||
}
|
||||
hashPlist, err := plist.Marshal(hashDict)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshal salted password to plist")
|
||||
}
|
||||
manifest.PasswordHash = hashPlist
|
||||
}
|
||||
|
||||
usr, err := cmd.applysvc.ApplyUser(context.TODO(), manifest)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "apply user with mdmctl")
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(*flUserManifest, os.O_RDWR, os.ModeAppend)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "open user manifest %s", *flUserManifest)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
enc := json.NewEncoder(f)
|
||||
enc.SetIndent("", " ")
|
||||
if err := enc.Encode(usr); err != nil {
|
||||
return errors.Wrap(err, "encode user manifest")
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func printUserTemplate() {
|
||||
jsn := `
|
||||
{
|
||||
"user_shortname": "",
|
||||
"user_longname": "",
|
||||
"hidden": false
|
||||
}
|
||||
`
|
||||
fmt.Println(jsn)
|
||||
}
|
||||
@@ -65,6 +65,8 @@ func (cmd *getCommand) Run(args []string) error {
|
||||
run = cmd.getBlueprints
|
||||
case "profiles":
|
||||
run = cmd.getProfiles
|
||||
case "users":
|
||||
run = cmd.getUsers
|
||||
case "apps":
|
||||
run = cmd.getApps
|
||||
default:
|
||||
@@ -87,6 +89,7 @@ Valid resource types:
|
||||
* dep-devices
|
||||
* dep-account
|
||||
* dep-profiles
|
||||
* users
|
||||
* profiles
|
||||
* apps
|
||||
|
||||
|
||||
47
cmd/mdmctl/get_users.go
Normal file
47
cmd/mdmctl/get_users.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/micromdm/micromdm/core/list"
|
||||
)
|
||||
|
||||
type usersTableOutput struct{ w *tabwriter.Writer }
|
||||
|
||||
func (out *usersTableOutput) BasicHeader() {
|
||||
fmt.Fprintf(out.w, "UUID\tUDID\tUserID\tUserShortName\tUserLongName\n")
|
||||
}
|
||||
|
||||
func (out *usersTableOutput) BasicFooter() {
|
||||
out.w.Flush()
|
||||
}
|
||||
|
||||
func (cmd *getCommand) getUsers(args []string) error {
|
||||
flagset := flag.NewFlagSet("users", flag.ExitOnError)
|
||||
flagset.Usage = usageFor(flagset, "mdmctl get users [flags]")
|
||||
if err := flagset.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stderr, 0, 4, 2, ' ', 0)
|
||||
out := &usersTableOutput{w}
|
||||
out.BasicHeader()
|
||||
defer out.BasicFooter()
|
||||
|
||||
users, err := cmd.list.ListUsers(context.TODO(), list.ListUsersOption{})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "list users")
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
fmt.Fprintf(out.w, "%s\t%s\t%s\t%v\t%s\n", u.UUID, u.UDID, u.UserID, u.UserShortname, u.UserLongname)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -69,12 +69,24 @@ 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{
|
||||
ApplyBlueprintEndpoint: applyBlueprintEndpoint,
|
||||
ApplyDEPTokensEndpoint: applyDEPTokensEndpoint,
|
||||
ApplyProfileEndpoint: applyProfileEndpoint,
|
||||
DefineDEPProfileEndpoint: defineDEPProfileEndpoint,
|
||||
AppUploadEndpoint: uploadAppEndpoint,
|
||||
ApplyUserEndpoint: applyUserEndpoint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/micromdm/dep"
|
||||
"github.com/micromdm/micromdm/blueprint"
|
||||
"github.com/micromdm/micromdm/profile"
|
||||
"github.com/micromdm/micromdm/user"
|
||||
)
|
||||
|
||||
type Endpoints struct {
|
||||
@@ -16,6 +17,19 @@ type Endpoints struct {
|
||||
ApplyProfileEndpoint 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 {
|
||||
@@ -110,6 +124,17 @@ 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)
|
||||
@@ -171,3 +196,14 @@ 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 }
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"net/textproto"
|
||||
|
||||
"github.com/fullsailor/pkcs7"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/micromdm/dep"
|
||||
"github.com/micromdm/micromdm/appstore"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
"github.com/micromdm/micromdm/deptoken"
|
||||
"github.com/micromdm/micromdm/profile"
|
||||
"github.com/micromdm/micromdm/pubsub"
|
||||
"github.com/micromdm/micromdm/user"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
@@ -27,6 +29,7 @@ type Service interface {
|
||||
ApplyDEPToken(ctx context.Context, P7MContent []byte) error
|
||||
ApplyProfile(ctx context.Context, p *profile.Profile) 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
|
||||
}
|
||||
|
||||
@@ -38,6 +41,20 @@ type ApplyService struct {
|
||||
Profiles *profile.DB
|
||||
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 {
|
||||
|
||||
@@ -19,6 +19,7 @@ type HTTPHandlers struct {
|
||||
ProfileHandler http.Handler
|
||||
DefineDEPProfileHandler http.Handler
|
||||
AppUploadHandler http.Handler
|
||||
ApplyUserhandler http.Handler
|
||||
}
|
||||
|
||||
func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) HTTPHandlers {
|
||||
@@ -53,6 +54,12 @@ func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptran
|
||||
encodeResponse,
|
||||
opts...,
|
||||
),
|
||||
ApplyUserhandler: httptransport.NewServer(
|
||||
endpoints.ApplyUserEndpoint,
|
||||
decodeUserRequest,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
),
|
||||
}
|
||||
return h
|
||||
}
|
||||
@@ -110,6 +117,15 @@ 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)
|
||||
@@ -236,3 +252,12 @@ 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
|
||||
}
|
||||
|
||||
@@ -101,6 +101,17 @@ 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,
|
||||
@@ -110,6 +121,7 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra
|
||||
GetDEPDeviceEndpoint: getDEPDeviceDetailsEndpoint,
|
||||
GetDEPProfileEndpoint: getDEPProfilesEndpoint,
|
||||
ListAppsEndpont: listAppsEndpoint,
|
||||
ListUserEndpoint: listUsersEndpoint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/micromdm/micromdm/blueprint"
|
||||
"github.com/micromdm/micromdm/deptoken"
|
||||
"github.com/micromdm/micromdm/profile"
|
||||
"github.com/micromdm/micromdm/user"
|
||||
)
|
||||
|
||||
type Endpoints struct {
|
||||
@@ -20,6 +21,16 @@ 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) {
|
||||
@@ -84,6 +95,17 @@ 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)
|
||||
@@ -178,6 +200,14 @@ 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"`
|
||||
|
||||
@@ -8,13 +8,15 @@ import (
|
||||
|
||||
"github.com/groob/plist"
|
||||
"github.com/micromdm/dep"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/micromdm/micromdm/appstore"
|
||||
"github.com/micromdm/micromdm/blueprint"
|
||||
"github.com/micromdm/micromdm/deptoken"
|
||||
"github.com/micromdm/micromdm/device"
|
||||
"github.com/micromdm/micromdm/profile"
|
||||
"github.com/micromdm/micromdm/pubsub"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/micromdm/micromdm/user"
|
||||
)
|
||||
|
||||
type ListDevicesOption struct {
|
||||
@@ -25,6 +27,14 @@ type ListDevicesOption struct {
|
||||
FilterUDID []string
|
||||
}
|
||||
|
||||
type ListUsersOption struct {
|
||||
Page int
|
||||
PerPage int
|
||||
|
||||
FilterUserID []string
|
||||
FilterUDID []string
|
||||
}
|
||||
|
||||
type GetBlueprintsOption struct {
|
||||
FilterName string
|
||||
}
|
||||
@@ -39,6 +49,7 @@ type ListAppsOption struct {
|
||||
|
||||
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)
|
||||
GetBlueprints(ctx context.Context, opt GetBlueprintsOption) ([]blueprint.Blueprint, error)
|
||||
GetProfiles(ctx context.Context, opt GetProfilesOption) ([]profile.Profile, error)
|
||||
@@ -55,6 +66,7 @@ type ListService struct {
|
||||
Profiles *profile.DB
|
||||
Tokens *deptoken.DB
|
||||
Apps appstore.AppStore
|
||||
Users *user.DB
|
||||
}
|
||||
|
||||
func (svc *ListService) ListApplications(ctx context.Context, opts ListAppsOption) ([]AppDTO, error) {
|
||||
@@ -126,6 +138,11 @@ 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 {
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
httptransport "github.com/go-kit/kit/transport/http"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type HTTPHandlers struct {
|
||||
@@ -20,6 +20,7 @@ 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 {
|
||||
@@ -69,6 +70,12 @@ func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptran
|
||||
encodeResponse,
|
||||
opts...,
|
||||
),
|
||||
ListUsersHander: httptransport.NewServer(
|
||||
endpoints.ListUserEndpoint,
|
||||
decodeListUsersRequest,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
),
|
||||
}
|
||||
return h
|
||||
}
|
||||
@@ -84,6 +91,11 @@ 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 decodeGetBlueprintsRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
var opts GetBlueprintsOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&opts); err != nil {
|
||||
@@ -250,3 +262,12 @@ 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")
|
||||
}
|
||||
|
||||
74
crypto/password/password.go
Normal file
74
crypto/password/password.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Package password provides utilities for creating and verifying macOS
|
||||
// passwords for MDM account configuration.
|
||||
package password
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
// macKeyLen is the length of a PBKDF2 salt.
|
||||
const macKeyLen = 128
|
||||
|
||||
// ErrNoMatch is returned by Verify if the password does not match.
|
||||
var ErrNoMatch = errors.New("password does not match")
|
||||
|
||||
// SaltedSha512PBKDF2Dictionary is a SHA512 PBKDF2 dictionary.
|
||||
type SaltedSHA512PBKDF2Dictionary struct {
|
||||
Iterations int `plist:"iterations"`
|
||||
Salt []byte `plist:"salt"`
|
||||
Entropy []byte `plist:"entropy"`
|
||||
}
|
||||
|
||||
// SaltedSHA512PBKDF2 creates a SALTED-SHA512-PBKDF2 dictionary
|
||||
// from a plaintext password. The hash function will use a 128 bit
|
||||
// salt.
|
||||
func SaltedSHA512PBKDF2(plaintext string) (SaltedSHA512PBKDF2Dictionary, error) {
|
||||
salt := make([]byte, 32)
|
||||
_, err := rand.Read(salt)
|
||||
if err != nil {
|
||||
return SaltedSHA512PBKDF2Dictionary{}, err
|
||||
}
|
||||
iterations, err := secureRandInt(20000, 40000)
|
||||
if err != nil {
|
||||
return SaltedSHA512PBKDF2Dictionary{}, err
|
||||
}
|
||||
return SaltedSHA512PBKDF2Dictionary{
|
||||
Iterations: iterations,
|
||||
Salt: salt,
|
||||
Entropy: pbkdf2.Key([]byte(plaintext),
|
||||
salt, iterations, macKeyLen, sha512.New),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Verify verifies a plaintext password against a existing SALTED-SHA512-PBKDF2
|
||||
// password dictionary.
|
||||
func Verify(plaintext string, h SaltedSHA512PBKDF2Dictionary) error {
|
||||
hashed := pbkdf2.Key([]byte(plaintext), h.Salt, h.Iterations, macKeyLen, sha512.New)
|
||||
if !bytes.Equal(h.Entropy, hashed) {
|
||||
return ErrNoMatch
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CCCalibratePBKDF uses a pseudorandom value returned within 100 milliseconds.
|
||||
// Use a random int from crypto/rand between 20,000 and 40,000 instead.
|
||||
func secureRandInt(min, max int64) (int, error) {
|
||||
var random int
|
||||
for {
|
||||
iter, err := rand.Int(rand.Reader, big.NewInt(max))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if iter.Int64() >= min {
|
||||
random = int(iter.Int64())
|
||||
break
|
||||
}
|
||||
}
|
||||
return random, nil
|
||||
}
|
||||
20
serve.go
20
serve.go
@@ -56,6 +56,7 @@ import (
|
||||
"github.com/micromdm/micromdm/pubsub/inmem"
|
||||
nanopush "github.com/micromdm/micromdm/push"
|
||||
"github.com/micromdm/micromdm/queue"
|
||||
"github.com/micromdm/micromdm/user"
|
||||
)
|
||||
|
||||
const homePage = `<!doctype html>
|
||||
@@ -154,6 +155,11 @@ func serve(args []string) error {
|
||||
stdlog.Fatal(err)
|
||||
}
|
||||
|
||||
userDB, err := user.NewDB(sm.db, sm.pubclient)
|
||||
if err != nil {
|
||||
stdlog.Fatal(err)
|
||||
}
|
||||
|
||||
sm.profileDB, err = profile.NewDB(sm.db)
|
||||
if err != nil {
|
||||
stdlog.Fatal(err)
|
||||
@@ -164,7 +170,7 @@ func serve(args []string) error {
|
||||
stdlog.Fatalf("enrollment service: %s", sm.err)
|
||||
}
|
||||
|
||||
bpDB, err := blueprint.NewDB(sm.db, sm.profileDB)
|
||||
bpDB, err := blueprint.NewDB(sm.db, sm.profileDB, userDB)
|
||||
if err != nil {
|
||||
stdlog.Fatal(err)
|
||||
}
|
||||
@@ -245,6 +251,7 @@ func serve(args []string) error {
|
||||
}
|
||||
tokenDB := &deptoken.DB{DB: sm.db, Publisher: sm.pubclient}
|
||||
appDB := &appstore.Repo{Path: *flRepoPath}
|
||||
|
||||
var listsvc list.Service
|
||||
{
|
||||
l := &list.ListService{
|
||||
@@ -254,6 +261,7 @@ func serve(args []string) error {
|
||||
Blueprints: bpDB,
|
||||
Profiles: sm.profileDB,
|
||||
Apps: appDB,
|
||||
Users: userDB,
|
||||
}
|
||||
listsvc = l
|
||||
|
||||
@@ -275,6 +283,7 @@ 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
|
||||
@@ -285,6 +294,7 @@ func serve(args []string) error {
|
||||
Tokens: tokenDB,
|
||||
Profiles: sm.profileDB,
|
||||
Apps: appDB,
|
||||
Users: userDB,
|
||||
}
|
||||
applysvc = l
|
||||
if err := l.WatchTokenUpdates(sm.pubclient); err != nil {
|
||||
@@ -312,12 +322,18 @@ func serve(args []string) error {
|
||||
appUploadEndpoint = apply.MakeUploadAppEndpiont(applysvc)
|
||||
}
|
||||
|
||||
var applyUserEndpoint endpoint.Endpoint
|
||||
{
|
||||
applyUserEndpoint = apply.MakeApplyUserEndpoint(applysvc)
|
||||
}
|
||||
|
||||
applyEndpoints := apply.Endpoints{
|
||||
ApplyBlueprintEndpoint: applyBlueprintEndpoint,
|
||||
ApplyDEPTokensEndpoint: apply.MakeApplyDEPTokensEndpoint(applysvc),
|
||||
ApplyProfileEndpoint: applyProfileEndpoint,
|
||||
DefineDEPProfileEndpoint: defineDEPProfileEndpoint,
|
||||
AppUploadEndpoint: appUploadEndpoint,
|
||||
ApplyUserEndpoint: applyUserEndpoint,
|
||||
}
|
||||
|
||||
applyAPIHandlers := apply.MakeHTTPHandlers(ctx, applyEndpoints, connectOpts...)
|
||||
@@ -361,6 +377,8 @@ 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")
|
||||
}
|
||||
|
||||
|
||||
148
user/db.go
Normal file
148
user/db.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/micromdm/micromdm/pubsub"
|
||||
)
|
||||
|
||||
const (
|
||||
UserBucket = "mdm.Users"
|
||||
|
||||
userIndexBucket = "mdm.UserIdx"
|
||||
)
|
||||
|
||||
type DB struct {
|
||||
*bolt.DB
|
||||
}
|
||||
|
||||
func NewDB(db *bolt.DB, pubsubSvc pubsub.PublishSubscriber) (*DB, error) {
|
||||
err := db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte(userIndexBucket))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.CreateBucketIfNotExists([]byte(UserBucket))
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "creating %s bucket", UserBucket)
|
||||
}
|
||||
|
||||
datastore := &DB{
|
||||
DB: db,
|
||||
}
|
||||
|
||||
return datastore, nil
|
||||
}
|
||||
|
||||
func (db *DB) List() ([]User, error) {
|
||||
var users []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 {
|
||||
return err
|
||||
}
|
||||
users = append(users, u)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return users, errors.Wrap(err, "list users")
|
||||
}
|
||||
|
||||
func (db *DB) Save(u *User) error {
|
||||
tx, err := db.DB.Begin(true)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "begin transaction")
|
||||
}
|
||||
bkt := tx.Bucket([]byte(UserBucket))
|
||||
if bkt == nil {
|
||||
return fmt.Errorf("bucket %q not found!", UserBucket)
|
||||
}
|
||||
userpb, err := MarshalUser(u)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshalling user")
|
||||
}
|
||||
|
||||
// store an array of indices to reference the UUID, which will be the
|
||||
// key used to store the actual user.
|
||||
indexes := []string{u.UDID, u.UserID}
|
||||
idxBucket := tx.Bucket([]byte(userIndexBucket))
|
||||
if idxBucket == nil {
|
||||
return fmt.Errorf("bucket %q not found!", userIndexBucket)
|
||||
}
|
||||
for _, idx := range indexes {
|
||||
if idx == "" {
|
||||
continue
|
||||
}
|
||||
key := []byte(idx)
|
||||
if err := idxBucket.Put(key, []byte(u.UUID)); err != nil {
|
||||
return errors.Wrap(err, "user userIdx in boltdb")
|
||||
}
|
||||
}
|
||||
|
||||
key := []byte(u.UUID)
|
||||
if err := bkt.Put(key, userpb); err != nil {
|
||||
return errors.Wrap(err, "store user in boltdb")
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (db *DB) User(uuid string) (*User, error) {
|
||||
var u 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)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get user by user id from bolt")
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
func (db *DB) UserByUserID(userID string) (*User, error) {
|
||||
var u User
|
||||
err := db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte(UserBucket))
|
||||
ib := tx.Bucket([]byte(userIndexBucket))
|
||||
idx := ib.Get([]byte(userID))
|
||||
if idx == nil {
|
||||
return ¬Found{"User", fmt.Sprintf("user id %s", userID)}
|
||||
}
|
||||
v := b.Get(idx)
|
||||
if idx == nil {
|
||||
return ¬Found{"User", fmt.Sprintf("uuid %s", string(idx))}
|
||||
}
|
||||
return UnmarshalUser(v, &u)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get user by user id from bolt")
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
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 {
|
||||
if _, ok := err.(*notFound); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
3
user/internal/userproto/user.go
Normal file
3
user/internal/userproto/user.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package userproto
|
||||
|
||||
//go:generate protoc --go_out=. user.proto
|
||||
123
user/internal/userproto/user.pb.go
Normal file
123
user/internal/userproto/user.pb.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: user.proto
|
||||
|
||||
/*
|
||||
Package userproto is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
user.proto
|
||||
|
||||
It has these top-level messages:
|
||||
User
|
||||
*/
|
||||
package userproto
|
||||
|
||||
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 User struct {
|
||||
Uuid string `protobuf:"bytes,1,opt,name=uuid" json:"uuid,omitempty"`
|
||||
Udid string `protobuf:"bytes,2,opt,name=udid" json:"udid,omitempty"`
|
||||
UserId string `protobuf:"bytes,3,opt,name=user_id,json=userId" json:"user_id,omitempty"`
|
||||
UserShortname string `protobuf:"bytes,4,opt,name=user_shortname,json=userShortname" json:"user_shortname,omitempty"`
|
||||
UserLongname string `protobuf:"bytes,5,opt,name=user_longname,json=userLongname" json:"user_longname,omitempty"`
|
||||
AuthToken string `protobuf:"bytes,6,opt,name=auth_token,json=authToken" json:"auth_token,omitempty"`
|
||||
PasswordHash []byte `protobuf:"bytes,7,opt,name=password_hash,json=passwordHash,proto3" json:"password_hash,omitempty"`
|
||||
Hidden bool `protobuf:"varint,8,opt,name=hidden" json:"hidden,omitempty"`
|
||||
}
|
||||
|
||||
func (m *User) Reset() { *m = User{} }
|
||||
func (m *User) String() string { return proto.CompactTextString(m) }
|
||||
func (*User) ProtoMessage() {}
|
||||
func (*User) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *User) GetUuid() string {
|
||||
if m != nil {
|
||||
return m.Uuid
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *User) GetUdid() string {
|
||||
if m != nil {
|
||||
return m.Udid
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *User) GetUserId() string {
|
||||
if m != nil {
|
||||
return m.UserId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *User) GetUserShortname() string {
|
||||
if m != nil {
|
||||
return m.UserShortname
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *User) GetUserLongname() string {
|
||||
if m != nil {
|
||||
return m.UserLongname
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *User) GetAuthToken() string {
|
||||
if m != nil {
|
||||
return m.AuthToken
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *User) GetPasswordHash() []byte {
|
||||
if m != nil {
|
||||
return m.PasswordHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *User) GetHidden() bool {
|
||||
if m != nil {
|
||||
return m.Hidden
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*User)(nil), "userproto.User")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("user.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 206 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x34, 0xcf, 0xbf, 0x4a, 0x04, 0x31,
|
||||
0x10, 0x06, 0x70, 0xa2, 0x6b, 0xee, 0x76, 0x58, 0x2d, 0xa6, 0xd0, 0x34, 0xc2, 0xa2, 0x08, 0x5b,
|
||||
0xd9, 0xf8, 0x12, 0x0a, 0x56, 0xab, 0xd6, 0x21, 0x32, 0xc1, 0x04, 0x35, 0x39, 0xf2, 0x07, 0x1f,
|
||||
0xdb, 0x57, 0x38, 0x66, 0xd8, 0xeb, 0xbe, 0xf9, 0xcd, 0x97, 0xc0, 0x00, 0xf4, 0xea, 0xcb, 0xe3,
|
||||
0xa1, 0xe4, 0x96, 0x71, 0xe4, 0x2c, 0xf1, 0xee, 0x5f, 0xc1, 0xf0, 0x51, 0x7d, 0x41, 0x84, 0xa1,
|
||||
0xf7, 0x48, 0x46, 0xcd, 0x6a, 0x19, 0x57, 0xc9, 0x62, 0x14, 0xc9, 0x9c, 0x6d, 0x46, 0x91, 0xf0,
|
||||
0x06, 0x76, 0xfc, 0xda, 0x46, 0x32, 0xe7, 0xc2, 0x9a, 0xc7, 0x17, 0xc2, 0x07, 0xb8, 0x92, 0x45,
|
||||
0x0d, 0xb9, 0xb4, 0xe4, 0x7e, 0xbd, 0x19, 0x64, 0x7f, 0xc9, 0xfa, 0x76, 0x42, 0xbc, 0x07, 0x01,
|
||||
0xfb, 0x93, 0xd3, 0x97, 0xb4, 0x2e, 0xa4, 0x35, 0x31, 0xbe, 0x6e, 0x86, 0xb7, 0x00, 0xae, 0xb7,
|
||||
0x60, 0x5b, 0xfe, 0xf6, 0xc9, 0x68, 0x69, 0x8c, 0x2c, 0xef, 0x0c, 0xfc, 0xc7, 0xc1, 0xd5, 0xfa,
|
||||
0x97, 0x0b, 0xd9, 0xe0, 0x6a, 0x30, 0xbb, 0x59, 0x2d, 0xd3, 0x3a, 0x9d, 0xf0, 0xd9, 0xd5, 0x80,
|
||||
0xd7, 0xa0, 0x43, 0x24, 0xf2, 0xc9, 0xec, 0x67, 0xb5, 0xec, 0xd7, 0x6d, 0xfa, 0xd4, 0x72, 0xf8,
|
||||
0xd3, 0x31, 0x00, 0x00, 0xff, 0xff, 0x35, 0xa8, 0xcb, 0x32, 0x11, 0x01, 0x00, 0x00,
|
||||
}
|
||||
16
user/internal/userproto/user.proto
Normal file
16
user/internal/userproto/user.proto
Normal file
@@ -0,0 +1,16 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package userproto;
|
||||
|
||||
|
||||
message User {
|
||||
string uuid = 1;
|
||||
string udid = 2;
|
||||
string user_id = 3;
|
||||
string user_shortname = 4;
|
||||
string user_longname = 5;
|
||||
string auth_token = 6;
|
||||
bytes password_hash = 7;
|
||||
bool hidden = 8;
|
||||
}
|
||||
|
||||
62
user/user.go
Normal file
62
user/user.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Package user provides utilites for managing users with MDM.
|
||||
package user
|
||||
|
||||
import (
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/pkg/errors"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
|
||||
"github.com/micromdm/micromdm/user/internal/userproto"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
UUID string `json:"uuid"`
|
||||
UDID string `json:"udid"`
|
||||
UserID string `json:"user_id"`
|
||||
UserShortname string `json:"user_shortname"`
|
||||
UserLongname string `json:"user_longname"`
|
||||
AuthToken string `json:"auth_token"`
|
||||
PasswordHash []byte `json:"password_hash"`
|
||||
Hidden bool `json:"hidden"`
|
||||
}
|
||||
|
||||
func NewFromRequest(u User) (*User, error) {
|
||||
newUser := User{
|
||||
UUID: uuid.NewV4().String(),
|
||||
UserShortname: u.UserShortname,
|
||||
UserLongname: u.UserLongname,
|
||||
PasswordHash: u.PasswordHash,
|
||||
Hidden: u.Hidden,
|
||||
}
|
||||
return &newUser, nil
|
||||
}
|
||||
|
||||
func MarshalUser(u *User) ([]byte, error) {
|
||||
pb := userproto.User{
|
||||
Uuid: u.UUID,
|
||||
Udid: u.UDID,
|
||||
UserId: u.UserID,
|
||||
UserShortname: u.UserShortname,
|
||||
UserLongname: u.UserLongname,
|
||||
AuthToken: u.AuthToken,
|
||||
PasswordHash: u.PasswordHash,
|
||||
Hidden: u.Hidden,
|
||||
}
|
||||
return proto.Marshal(&pb)
|
||||
}
|
||||
|
||||
func UnmarshalUser(data []byte, u *User) error {
|
||||
var pb userproto.User
|
||||
if err := proto.Unmarshal(data, &pb); err != nil {
|
||||
return errors.Wrap(err, "unmarshal proto to user")
|
||||
}
|
||||
u.UUID = pb.GetUuid()
|
||||
u.UDID = pb.GetUdid()
|
||||
u.UserID = pb.GetUserId()
|
||||
u.UserShortname = pb.GetUserShortname()
|
||||
u.UserLongname = pb.GetUserLongname()
|
||||
u.AuthToken = pb.GetAuthToken()
|
||||
u.PasswordHash = pb.GetPasswordHash()
|
||||
u.Hidden = pb.GetHidden()
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user