// AuthenticateDevice wraps the authentication logic for devices func AuthenticateDevice(r doorbot.Repositories, token string) (*doorbot.Device, error) { dr := r.DeviceRepository() device, err := dr.FindByToken(r.DB(), token) if err != nil { log.WithFields(log.Fields{ "error:": err, "device_token": token, "step": "device-get-by-token", }).Error("Api::Handlers->SecuredRouteHandler database error") return nil, err } if device == nil { log.WithFields(log.Fields{ "account_id": r.AccountScope(), "device_id": device.ID, }).Warn("Api::Handlers->SecuredRouteHandler device not found") return nil, nil } if device.IsEnabled == false { log.WithFields(log.Fields{ "account_id": r.AccountScope(), "device_id": device.ID, }).Warn("Api::Handlers->SecuredRouteHandler device disabled.") return nil, nil } return device, nil }
// Index returns people func Index(render render.Render, r doorbot.Repositories, session *auth.Authorization) { repo := r.PersonRepository() people, err := repo.All(r.DB()) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), }).Error("Api::People->Index database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } switch session.Type { case auth.AuthorizationAdministrator: render.JSON(http.StatusOK, PeopleViewModel{People: people}) case auth.AuthorizationDevice: render.JSON(http.StatusOK, PublicPeopleViewModel{People: newPublicPeople(people)}) case auth.AuthorizationPerson: if session.Person.IsAccountManager() { render.JSON(http.StatusOK, PeopleViewModel{People: people}) return } render.JSON(http.StatusOK, PublicPeopleViewModel{People: newPublicPeople(people)}) } }
// Get a specific person func Get(render render.Render, r doorbot.Repositories, params martini.Params, session *auth.Authorization) { id, err := strconv.ParseUint(params["id"], 10, 32) if err != nil { render.JSON(http.StatusBadRequest, doorbot.NewBadRequestErrorResponse([]string{"The id must be an unsigned integer"})) return } repo := r.PersonRepository() person, err := repo.Find(r.DB(), uint(id)) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "person_id": id, }).Error("Api::People->Get database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } if person == nil { err := doorbot.NewEntityNotFoundResponse([]string{"The specified person does not exists"}) render.JSON(http.StatusNotFound, err) return } switch session.Type { case auth.AuthorizationAdministrator: render.JSON(http.StatusOK, PersonViewModel{Person: person}) case auth.AuthorizationDevice: render.JSON(http.StatusOK, PublicPersonViewModel{Person: newPublicPerson(person)}) case auth.AuthorizationPerson: // Display detailed info if the requesting user is an account manager or it is the same person if session.Person.IsAccountManager() || session.Person.ID == person.ID { render.JSON(http.StatusOK, PersonViewModel{Person: person}) return } render.JSON(http.StatusOK, PublicPersonViewModel{Person: newPublicPerson(person)}) default: render.Status(http.StatusForbidden) } }
// Index return a list of doors func Index(render render.Render, r doorbot.Repositories) { repo := r.DoorRepository() doors, err := repo.All(r.DB()) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), }).Error("Api::Doors->Index database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } render.JSON(http.StatusOK, DoorsViewModel{Doors: doors}) }
// Post creates a new person func Post(render render.Render, r doorbot.Repositories, vm PersonViewModel) { repo := r.PersonRepository() err := repo.Create(r.DB(), vm.Person) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), }).Error("Api::People->Post database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } log.WithFields(log.Fields{ "account_id": r.AccountScope(), "person_id": vm.Person.ID, }).Info("Api::People->Post person added.") render.JSON(http.StatusCreated, vm) }
// Post creates a door func Post(render render.Render, r doorbot.Repositories, vm DoorViewModel) { repo := r.DoorRepository() err := repo.Create(r.DB(), vm.Door) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), }).Error("Api::Doors->Post database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } log.WithFields(log.Fields{ "account_id": r.AccountScope(), "door_id": vm.Door.ID, }).Error("Api::Doors->Post door created") render.JSON(http.StatusCreated, vm) }
// Get return a specific door func Get(render render.Render, r doorbot.Repositories, params martini.Params) { id, err := strconv.ParseUint(params["id"], 10, 32) if err != nil { render.JSON(http.StatusBadRequest, doorbot.NewBadRequestErrorResponse([]string{"The id must be an unsigned integer"})) return } repo := r.DoorRepository() door, err := repo.Find(r.DB(), uint(id)) if door == nil || err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "door_id": id, }).Error("Api::Doors->Get database error") render.JSON(http.StatusNotFound, doorbot.NewEntityNotFoundResponse([]string{"The specified door does not exists"})) return } render.JSON(http.StatusOK, DoorViewModel{Door: door}) }
// Post creates a new device func Post(render render.Render, r doorbot.Repositories, vm DeviceViewModel) { repo := r.DeviceRepository() vm.Device.Token = security.GenerateAPIToken() err := repo.Create(r.DB(), vm.Device) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), }).Error("Api::Devices->Post database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } log.WithFields(log.Fields{ "account_id": r.AccountScope(), "device_id": vm.Device.ID, }).Info("Api::Devices->Put device created") render.JSON(http.StatusCreated, vm) }
// Put updates a device func Put(render render.Render, r doorbot.Repositories, params martini.Params, vm DeviceViewModel) { id, err := strconv.ParseUint(params["id"], 10, 32) if err != nil { render.JSON(http.StatusBadRequest, doorbot.NewBadRequestErrorResponse([]string{"The id must be an unsigned integer"})) return } repo := r.DeviceRepository() device, err := repo.Find(r.DB(), uint(id)) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "device_id": id, "step": "device-find", }).Error("Api::Devices->Put database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } if device == nil { render.JSON(http.StatusNotFound, doorbot.NewEntityNotFoundResponse([]string{"The specified device does not exists"})) return } device.Name = vm.Device.Name device.DeviceID = vm.Device.DeviceID device.Make = vm.Device.Make device.Description = vm.Device.Description device.IsEnabled = vm.Device.IsEnabled _, err = repo.Update(r.DB(), device) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "device_id": id, "step": "device-update", }).Error("Api::Devices->Put database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } log.WithFields(log.Fields{ "account_id": r.AccountScope(), "device_id": id, }).Info("Api::Devices->Put device updated") vm.Device = device render.JSON(http.StatusOK, vm) }
// Put updates a door func Put(render render.Render, r doorbot.Repositories, params martini.Params, vm DoorViewModel) { id, err := strconv.ParseUint(params["id"], 10, 32) if err != nil { render.JSON(http.StatusBadRequest, doorbot.NewBadRequestErrorResponse([]string{"The id must be an unsigned integer"})) return } repo := r.DoorRepository() door, err := repo.Find(r.DB(), uint(id)) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "door_id": id, "step": "door-find", }).Error("Api::Doors->Put database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } if door == nil { render.JSON(http.StatusNotFound, doorbot.NewEntityNotFoundResponse([]string{"The specified door does not exists"})) return } door.Name = vm.Door.Name _, err = repo.Update(r.DB(), door) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "door_id": id, "step": "door-update", }).Error("Api::Doors->Put database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } log.WithFields(log.Fields{ "account_id": r.AccountScope(), "door_id": vm.Door.ID, }).Error("Api::Doors->Post door updated") render.JSON(http.StatusOK, DoorViewModel{Door: door}) }
// Register creates a new device func Register(render render.Render, r doorbot.Repositories, vm DeviceViewModel) { repo := r.DeviceRepository() device, err := repo.FindByToken(r.DB(), vm.Device.Token) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "device_token": vm.Device.Token, "step": "device-find", }).Error("Api::Devices->Register database error") render.Status(http.StatusInternalServerError) return } if device == nil { render.Status(http.StatusNotFound) return } device.Make = vm.Device.Make device.DeviceID = vm.Device.DeviceID device.IsEnabled = true _, err = repo.Update(r.DB(), device) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "device_id": device.ID, "step": "device-update", }).Error("Api::Devices->Register database error") render.Status(http.StatusInternalServerError) return } log.WithFields(log.Fields{ "account_id": r.AccountScope(), "device_id": device.ID, }).Info("Api::Devices->Put device registered") render.JSON(http.StatusOK, device) }
// Disable a device func Disable(render render.Render, r doorbot.Repositories, params martini.Params) { id, err := strconv.ParseUint(params["id"], 10, 32) if err != nil { render.JSON(http.StatusBadRequest, doorbot.NewBadRequestErrorResponse([]string{"the id must be an unsigned integer"})) return } repo := r.DeviceRepository() device, err := repo.Find(r.DB(), uint(id)) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "device_id": id, "step": "device-find", }).Error("Api::Devices->Disable database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } if device == nil { render.JSON(http.StatusNotFound, doorbot.NewEntityNotFoundResponse([]string{"The specified deevice does not exists."})) return } _, err = repo.Enable(r.DB(), device, false) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "device_id": id, "step": "device-disable", }).Error("Api::Devices->Disable database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } log.WithFields(log.Fields{ "account_id": r.AccountScope(), "device_id": id, }).Info("Api::Devices->Disabled device disabled") render.Status(http.StatusNoContent) }
// Sync doorbot users with a foreign data source using a bridge. func Sync(render render.Render, r doorbot.Repositories, b bridges.Bridges, a *doorbot.Account, session *auth.Authorization) { var bUsers []*doorbot.BridgeUser var registered []*doorbot.BridgeUser var err error personRepo := r.PersonRepository() bUserRepo := r.BridgeUserRepository() var bridgesToSync = []uint{bridges.BridgeHub, bridges.BridgeHipChat} for _, bridgeId := range bridgesToSync { f := func() bool { users, err := b.GetUsers(bridgeId) for _, u := range users { log.WithFields(log.Fields{"user": *u}).Info("User") } if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": a.ID, "bridge_id": bridgeId, }).Error("Api::People->Sync bridge error") return false } existing, err := bUserRepo.FindByBridgeID(r.DB(), bridgeId) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "step": "bridge-user-find-by-bridge-id", "bridge_id": bridgeId, }).Error("Api::People->Sync database error") return false } registered = append(registered, existing...) bUsers = append(bUsers, users...) return true } f() } tx, err := r.Transaction() if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "step": "transaction-create", }).Error("Api::People->Sync database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } var buser *doorbot.BridgeUser for _, u := range bUsers { log.WithFields(log.Fields{ "account_id": r.AccountScope(), "bridge_user_id": u.UserID, "bridge_user_email": u.Email, "bridge_user_name": u.Name, }).Debug("Api::People->Sync bridge user") buser = findRegistered(registered, u.UserID) if buser != nil { log.WithFields(log.Fields{ "account_id": r.AccountScope(), "bridge_user_id": buser.UserID, "person_id": buser.PersonID, }).Debug("Api::People->Sync registered user found") person, err := personRepo.Find(tx, buser.PersonID) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "bridge_user_id": buser.UserID, "person_id": buser.PersonID, "step": "person-find-from-bridge-id", }).Error("Api::People->Sync database error") break } person.Name = u.Name person.Email = u.Email person.PhoneNumber = u.PhoneNumber _, err = personRepo.Update(tx, person) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "bridge_user_id": buser.UserID, "person_id": buser.PersonID, "step": "person-update-from-bridge-data", }).Error("Api::People->Sync database error") break } log.WithFields(log.Fields{ "account_id": r.AccountScope(), "bridge_user_id": buser.UserID, "person_id": buser.PersonID, }).Info("Api::People->Sync person updated from bridge data") continue } log.WithFields(log.Fields{ "account_id": r.AccountScope(), "bridge_user_id": u.UserID, "bridge_user_email": u.Email, "bridge_user_name": u.Name, }).Info("Api::People->Sync new bridge user") // User does not exists. Create them args := doorbot.PersonArguments{ Name: u.Name, Email: u.Email, } person := doorbot.NewPerson(args) err = personRepo.Create(tx, person) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "bridge_user_id": buser.UserID, "step": "person-create-from-bridge-data", }).Error("Api::People->Sync database error") break } u.PersonID = person.ID err = bUserRepo.Create(tx, u) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "bridge_user_id": buser.UserID, "step": "bridge-user-create", }).Error("Api::People->Sync database error") break } continue } if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), }).Error("Api::People->Sync error") tx.Rollback() render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } err = tx.Commit() if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "step": "transaction-commit", }).Error("Api::People->Sync database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } log.WithFields(log.Fields{ "account_id": r.AccountScope(), }).Info("Api::People->Sync bridge sync completed.") render.Status(http.StatusNoContent) }
// Delete a person func Delete(render render.Render, r doorbot.Repositories, params martini.Params, a *doorbot.Account, session *auth.Authorization) { id, err := strconv.ParseUint(params["id"], 10, 32) if err != nil { render.JSON(http.StatusBadRequest, doorbot.NewBadRequestErrorResponse([]string{"The id must be an unsigned integer"})) return } var logFields log.Fields var logMessage string switch session.Type { case auth.AuthorizationAdministrator: logFields = log.Fields{ "account_id": r.AccountScope(), "person_id": id, "admnistrator_id": session.Administrator.ID, } logMessage = "Api::People->Delete user deleted by administrator" case auth.AuthorizationPerson: if !session.Person.IsAccountManager() { log.WithFields(log.Fields{ "account_id": r.AccountScope(), "person_id": id, "request_person_id": session.Person.ID, }).Warn("Api::People->Delete forbidden") render.Status(http.StatusForbidden) return } logFields = log.Fields{ "account_id": r.AccountScope(), "person_id": id, "modified_by_id": session.Person.ID, } logMessage = "Api::People->Put user deleted by user" default: log.WithFields(log.Fields{ "account_id": r.AccountScope(), "person_id": id, "request_person_id": session.Person.ID, }).Warn("Api::People->Delete forbidden") render.Status(http.StatusForbidden) return } repo := r.PersonRepository() person, err := repo.Find(r.DB(), uint(id)) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "person_id": person.ID, "step": "person-find", }).Error("Api::People->Delete database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } if person == nil { render.JSON(http.StatusNotFound, doorbot.NewEntityNotFoundResponse([]string{"The specified person does not exists"})) return } _, err = repo.Delete(r.DB(), person) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "person_id": person.ID, "step": "person-delete", }).Error("Api::People->Delete database error") render.Status(http.StatusInternalServerError) return } log.WithFields(logFields).Info(logMessage) render.Status(http.StatusNoContent) }
// Put updates a person func Put(render render.Render, r doorbot.Repositories, params martini.Params, vm PersonViewModel, session *auth.Authorization) { id, err := strconv.ParseUint(params["id"], 10, 32) if err != nil { render.JSON(http.StatusBadRequest, doorbot.NewBadRequestErrorResponse([]string{"The id must be an unsigned integer"})) return } var logFields log.Fields var logMessage string canUpdateAccountType := false switch session.Type { case auth.AuthorizationAdministrator: logFields = log.Fields{ "account_id": r.AccountScope(), "person_id": id, "admnistrator_id": session.Administrator.ID, } logMessage = "Api::People->Put user updated by administrator" canUpdateAccountType = true case auth.AuthorizationPerson: if uint(id) != session.Person.ID { if session.Person.IsAccountManager() { canUpdateAccountType = true } else { log.WithFields(log.Fields{ "account_id": r.AccountScope(), "person_id": id, "request_person_id": session.Person.ID, }).Warn("Api::People->Delete forbidden") render.Status(http.StatusForbidden) return } } logFields = log.Fields{ "account_id": r.AccountScope(), "person_id": id, "request_person_id": session.Person.ID, } logMessage = "Api::People->Put user updated by user" default: log.WithFields(log.Fields{ "account_id": r.AccountScope(), "person_id": id, "request_person_id": session.Person.ID, }).Warn("Api::People->Put forbidden") render.Status(http.StatusForbidden) return } repo := r.PersonRepository() person, err := repo.Find(r.DB(), uint(id)) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "person_id": id, "step": "person-find", }).Error("Api::People->Put database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } if person == nil { render.JSON(http.StatusNotFound, doorbot.NewEntityNotFoundResponse([]string{"The specified person does not exists"})) return } person.Name = vm.Person.Name person.Email = vm.Person.Email person.PhoneNumber = vm.Person.PhoneNumber person.Title = vm.Person.Title person.IsVisible = vm.Person.IsVisible person.IsAvailable = vm.Person.IsAvailable person.NotificationsEnabled = vm.Person.NotificationsEnabled person.NotificationsAppEnabled = vm.Person.NotificationsAppEnabled person.NotificationsChatEnabled = vm.Person.NotificationsChatEnabled person.NotificationsEmailEnabled = vm.Person.NotificationsEmailEnabled person.NotificationsSMSEnabled = vm.Person.NotificationsSMSEnabled if canUpdateAccountType { person.AccountType = vm.Person.AccountType } _, err = repo.Update(r.DB(), person) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "person_id": person.ID, "request_person_id": session.Person.ID, "step": "person-update", }).Error("Api::People->Put database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } vm.Person = person log.WithFields(logFields).Info(logMessage) render.JSON(http.StatusOK, vm) }
// AuthenticatePerson wraps the authentication logic for people func AuthenticatePerson(r doorbot.Repositories, token string) (*doorbot.Person, error) { ar := r.AuthenticationRepository() pr := r.PersonRepository() parts := strings.Split(token, ".") if len(parts) != 2 { log.WithFields(log.Fields{ "account_id": r.AccountScope(), "token": token, "parts_count": len(parts), }).Warn("Auth->AuthenticatePerson Invalid person token") return nil, nil } id, err := strconv.ParseUint(parts[0], 10, 32) if err != nil { return nil, nil } token = parts[1] authentication, err := ar.FindByProviderIDAndPersonIDAndToken(r.DB(), ProviderAPIToken, uint(id), token) if err != nil { log.WithFields(log.Fields{ "error": err, "step": "authentication-get-by-person-and-token", "person_id": id, "account_id": r.AccountScope(), "token": token, }).Error("Auth->AuthenticatePerson database error") return nil, err } if authentication == nil { log.WithFields(log.Fields{ "token": token, "person_id": id, "account_id": r.AccountScope(), }).Info("Auth->AuthenticatePerson authentication not found") return nil, nil } person, err := pr.Find(r.DB(), authentication.PersonID) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "person_id": id, "authentication_id": authentication.ID, "step": "person-find", }).Error("Auth->AuthenticatePerson database error") return nil, err } if person == nil { log.WithFields(log.Fields{ "token": token, "person_id": id, "account_id": r.AccountScope(), "authentication_id": authentication.ID, }).Error("Auth->AuthenticatePerson person not found") return nil, nil } return person, err }