// Index action func Index(render render.Render, r doorbot.Repositories, administrator *doorbot.Administrator) { repo := r.AccountRepository() accounts, err := repo.All(r.DB()) if err != nil { render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } render.JSON(http.StatusOK, AccountsViewModel{Accounts: accounts}) }
// AuthenticateAdministrator wraps the authentication logic for administrators. func AuthenticateAdministrator(r doorbot.Repositories, token string) (*doorbot.Administrator, error) { var administrator *doorbot.Administrator ar := r.AdministratorRepository() aar := r.AdministratorAuthenticationRepository() authentication, err := aar.FindByProviderIDAndToken(r.DB(), ProviderAPIToken, token) if err != nil { log.WithFields(log.Fields{ "error": err, }).Error("Auth::Authorization::AuthenticationAdministrator database error") return administrator, errors.New("Not authorized") } if authentication == nil { log.WithFields(log.Fields{ "token": token, }).Warn("Doorbot::Authorization::AuthenticateAdministrator token not found") return administrator, errors.New("Not authorized") } administrator, err = ar.Find(r.DB(), authentication.AdministratorID) if err != nil { log.Println(err) return administrator, errors.New("Doorbot::AuthenticateAdministrator ") } return administrator, nil }
// 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 }
// Get return a specific account 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.AccountRepository() account, err := repo.Find(r.DB(), uint(id)) if err != nil { log.WithFields(log.Fields{ "account_id": id, "error": err, }).Error("Api::Accounts->Get database error.") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } if account == nil { render.JSON(http.StatusNotFound, doorbot.NewEntityNotFoundResponse([]string{})) return } // Switch the view model depending on who/what requests the information. switch session.Type { case auth.AuthorizationAdministrator: render.JSON(http.StatusOK, AccountViewModel{Account: account}) case auth.AuthorizationPerson: if session.Person.IsAccountManager() { render.JSON(http.StatusOK, AccountViewModel{Account: account}) return } // Display a reduced version of the account. public := PublicAccount{ ID: account.ID, Name: account.Name, Host: account.Host, } render.JSON(http.StatusOK, PublicAccountViewModel{Account: public}) default: render.Status(http.StatusForbidden) return } }
// 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)}) } }
// generateHost generates a host that isn't yet taken. func generateHost(r doorbot.Repositories, config *doorbot.DoorbotConfig) (string, error) { repo := r.AccountRepository() // Generate a random valid host name for i := 0; i < 10; i++ { tmp := strings.ToLower(security.RandomPassword(10)) exists, err := repo.FindByHost(r.DB(), tmp) if err != nil { log.WithFields(log.Fields{ "error": err, "host": tmp, }).Error("Api::Accounts->Register database error.") return "", err } if exists == nil { return tmp, nil } } return "", nil }
// 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) }
// 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) }
// Delete an account ( admin panel ) func Delete(render render.Render, r doorbot.Repositories, params martini.Params, administrator *doorbot.Administrator) { 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.AccountRepository() account, err := repo.Find(r.DB(), uint(id)) if err != nil { log.WithFields(log.Fields{ "error": err.Error(), "account_id": account.ID, "administrator_id": administrator.ID, }).Error("Api::Accounts->Delete database find error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } if account == nil { render.JSON(http.StatusNotFound, doorbot.NewEntityNotFoundResponse([]string{"The specified account does not exists."})) return } _, err = repo.Delete(r.DB(), account) if err != nil { log.WithFields(log.Fields{ "error": err.Error(), "administrator_id": administrator.ID, "account_id": account.ID, }).Error("Api::Accounts->Delete database delete error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } log.WithFields(log.Fields{ "administrator_id": administrator.ID, "account_id": account.ID, }).Info("Api::Accounts->Delete account deleted by administrator.") render.Status(http.StatusNoContent) }
// 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 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) }
// Post create a new account ( using the admin panel ) func Post(render render.Render, r doorbot.Repositories, vm AccountViewModel, administrator *doorbot.Administrator) { repo := r.AccountRepository() exists, err := repo.FindByHost(r.DB(), vm.Account.Host) if err != nil { log.WithFields(log.Fields{ "error": err, "host": vm.Account.Host, }).Error("Api::Accounts->Post database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } if exists != nil { log.WithFields(log.Fields{ "host": vm.Account.Host, }).Warn("Api::Accounts->Post Host already registered") render.JSON(http.StatusConflict, doorbot.NewConflictErrorResponse([]string{"The specified host is already registered."})) return } err = repo.Create(r.DB(), vm.Account) if err != nil { log.WithFields(log.Fields{ "error": err, "host": vm.Account.Host, }).Error("Api::Accounts->Post database error.") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } log.WithFields(log.Fields{ "administrator_id": administrator.ID, "account_id": vm.Account.ID, "host": vm.Account.Host, }).Info("Account created by administrator") 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}) }
// 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) }
// Put updates an account ( using the admin panel ) func Put(render render.Render, a *doorbot.Account, r doorbot.Repositories, vm AccountViewModel, session *auth.Authorization) { repo := r.AccountRepository() switch session.Type { case auth.AuthorizationAdministrator: // ok case auth.AuthorizationPerson: if !session.Person.IsAccountManager() { log.WithFields(log.Fields{ "person_id": session.Person.ID, "account_id": a.ID, }).Warn("Api::Accounts->Put unauthorized user attempted to modify account.") render.Status(http.StatusForbidden) return } } a.Name = vm.Account.Name a.BridgeHubEnabled = vm.Account.BridgeHubEnabled a.BridgeHubURL = vm.Account.BridgeHubURL a.BridgeHubToken = vm.Account.BridgeHubToken a.BridgeHipChatEnabled = vm.Account.BridgeHipChatEnabled a.BridgeHipChatToken = vm.Account.BridgeHipChatToken a.BridgeSlackEnabled = vm.Account.BridgeSlackEnabled a.BridgeSlackToken = vm.Account.BridgeSlackToken a.NotificationsEmailMessageTemplate = vm.Account.NotificationsEmailMessageTemplate a.NotificationsSMSMessageTemplate = vm.Account.NotificationsSMSMessageTemplate a.NotificationsEnabled = vm.Account.NotificationsEnabled a.NotificationsMailgunEnabled = vm.Account.NotificationsMailgunEnabled a.NotificationsPostmarkEnabled = vm.Account.NotificationsPostmarkEnabled a.NotificationsMailgunEnabled = vm.Account.NotificationsMailgunEnabled a.NotificationsNexmoEnabled = vm.Account.NotificationsNexmoEnabled a.NotificationsNexmoToken = vm.Account.NotificationsNexmoToken a.NotificationsSlackEnabled = vm.Account.NotificationsSlackEnabled a.NotificationsSlackToken = vm.Account.NotificationsSlackToken a.NotificationsTwilioEnabled = vm.Account.NotificationsTwilioEnabled _, err := repo.Update(r.DB(), a) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": a.ID, }).Error("Api::Accounts->Put database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } vm.Account = a 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}) }
// 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 }
// Password will authenticate a person using the provided email and password. // A token will be generated if the authentication succeeds. func Password(render render.Render, account *doorbot.Account, r doorbot.Repositories, vm PasswordRequest) { personRepo := r.PersonRepository() authRepo := r.AuthenticationRepository() // Find the person by email person, err := personRepo.FindByEmail(r.DB(), vm.Authentication.Email) if err != nil { log.WithFields(log.Fields{ "error": err, "email": vm.Authentication.Email, "account_id": account.ID, "step": "find-person-by-email", }).Error("Api::Auth->Password database error") render.JSON(http.StatusUnauthorized, doorbot.NewUnauthorizedErrorResponse([]string{"Invalid email or password"})) return } if person == nil { log.WithFields(log.Fields{ "account_id": account.ID, "email": vm.Authentication.Email, "step": "find-person-by-email", }).Info("Api::Auth->Password invalid email.") render.JSON(http.StatusUnauthorized, doorbot.NewUnauthorizedErrorResponse([]string{"Invalid email or password"})) return } //Fetch the password authentication record authentication, err := authRepo.FindByPersonIDAndProviderID(r.DB(), person.ID, auth.ProviderPassword) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": account.ID, "person_id": person.ID, "step": "find-person-authentication", }).Error("Api::Auth->Password database error") render.JSON(http.StatusUnauthorized, doorbot.NewUnauthorizedErrorResponse([]string{"Invalid email or password"})) return } if authentication == nil { log.WithFields(log.Fields{ "account_id": account.ID, "person_id": person.ID, "step": "find-person-authentication", }).Info("Api::Auth->Password no authentication") render.JSON(http.StatusUnauthorized, doorbot.NewUnauthorizedErrorResponse([]string{"Invalid email or password"})) return } //Compare the passwords err = security.PasswordCompare([]byte(authentication.Token), []byte(vm.Authentication.Password)) if err != nil { log.WithFields(log.Fields{ "error": err, "person_id": person.ID, "account_id": account.ID, "step": "compare-password", }).Info("Api::Auth->Password compare password error") render.JSON(http.StatusUnauthorized, doorbot.NewUnauthorizedErrorResponse([]string{"Invalid email or password"})) return } // Find the first active API token for this person. token, err := authRepo.FindByPersonIDAndProviderID(r.DB(), person.ID, auth.ProviderAPIToken) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": account.ID, "person_id": person.ID, "step": "find-authentication", }).Error("Api::Auth->Password database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } // No active token or the person has not yet signed in. Generate a new token. if token == nil { token = &doorbot.Authentication{ PersonID: person.ID, ProviderID: auth.ProviderAPIToken, Token: security.GenerateAPIToken(), } err = authRepo.Create(r.DB(), token) if err != nil { log.WithFields(log.Fields{ "error": err, "person_id": person.ID, "account_id": account.ID, "step": "save-authentication", }).Error("Api::Auth->Password database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } } log.WithFields(log.Fields{ "account_id": account.ID, "person_id": person.ID, }).Info("Api::Auth->Password user logged in") resp := APITokenResponse{} resp.Authentication.Token = fmt.Sprintf("%d.%s", person.ID, token.Token) resp.Person = person resp.Policy = getPolicyForPerson(person) render.JSON(http.StatusOK, resp) }
func Token(render render.Render, account *doorbot.Account, r doorbot.Repositories, vm TokenRequest) { var person *doorbot.Person var device *doorbot.Device var policy *security.Policy switch vm.Authentication.Type { case auth.AuthorizationPerson: authRepo := r.AuthenticationRepository() personRepo := r.PersonRepository() authentication, err := authRepo.FindByProviderIDAndToken(r.DB(), auth.ProviderAPIToken, vm.Authentication.Token) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": account.ID, "token": vm.Authentication.Token, "step": "find-token", }).Error("Api::Auth->Token database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } if authentication == nil { log.WithFields(log.Fields{ "account_id": account.ID, "token": vm.Authentication.Token, "step": "find-token", }).Info("Api::Auth->Token no token found.") render.JSON(http.StatusUnauthorized, doorbot.NewUnauthorizedErrorResponse([]string{"Invalid token"})) return } person, err = personRepo.Find(r.DB(), authentication.PersonID) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": account.ID, "token": vm.Authentication.Token, "step": "find-person", }).Error("Api::Auth->Token database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } if person == nil { log.WithFields(log.Fields{ "account_id": account.ID, "token": vm.Authentication.Token, "step": "find-device", }).Warning("Api::Auth->Token person not found.") render.JSON(http.StatusUnauthorized, doorbot.NewUnauthorizedErrorResponse([]string{"Invalid token"})) return } policy = getPolicyForPerson(person) break case auth.AuthorizationDevice: deviceRepo := r.DeviceRepository() device, err := deviceRepo.FindByToken(r.DB(), vm.Authentication.Token) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": account.ID, "token": vm.Authentication.Token, "step": "find-device", }).Error("Api::Auth->Token database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } if device == nil { log.WithFields(log.Fields{ "account_id": account.ID, "token": vm.Authentication.Token, "step": "find-device", }).Info("Api::Auth->Token no device found.") render.JSON(http.StatusUnauthorized, doorbot.NewUnauthorizedErrorResponse([]string{"Invalid token"})) return } } resp := APITokenResponse{} resp.Authentication.Token = vm.Authentication.Token resp.Person = person resp.Policy = policy resp.Device = device render.JSON(http.StatusOK, resp) }
// 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) }
// Register a new account ( used by the dashboard ) func Register(render render.Render, config *doorbot.DoorbotConfig, r doorbot.Repositories, n notifications.Notificator, data RegisterViewModel) { repo := r.AccountRepository() tx, err := r.Transaction() if err != nil { log.WithFields(log.Fields{ "error": err, "step": "transaction-create", }).Error("Api::Accounts->Register database error.") render.Status(http.StatusInternalServerError) return } var host string if len(data.Account.Host) == 0 { host, err = generateHost(r, config) } else { var account *doorbot.Account account, err = repo.FindByHost(r.DB(), data.Account.Host) if account != nil { tx.Rollback() render.Status(http.StatusConflict) return } host = data.Account.Host } if err != nil { tx.Rollback() render.Status(http.StatusInternalServerError) return } if len(host) == 0 { log.WithFields(log.Fields{ "host": host, "step": "host-generation", }).Error("Api::Accounts->Register Unable to set a hostname.") tx.Rollback() render.Status(http.StatusServiceUnavailable) return } // Create the account instance account := &doorbot.Account{ Name: data.Account.Name, ContactName: data.Account.ContactName, ContactEmail: data.Account.ContactEmail, //TODO append the doorbot production domain Host: host, IsEnabled: true, } err = repo.Create(tx, account) if err != nil { tx.Rollback() log.WithFields(log.Fields{ "error": err, "host": host, "step": "account-create", }).Error("Api::Accounts->Register database error.") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } // Update the repositories account scopes to the one we just created. r.SetAccountScope(account.ID) // We need to create a person with a password to let them log in on the dashboard. person := &doorbot.Person{ Name: data.Account.ContactName, Email: data.Account.ContactEmail, AccountType: doorbot.AccountOwner, } ar := r.PersonRepository() err = ar.Create(tx, person) if err != nil { log.WithFields(log.Fields{ "error": err, "host": host, "email": person.Email, "step": "person-create", }).Error("Api::Accounts->Register database error.") tx.Rollback() render.Status(http.StatusInternalServerError) return } // Generate a random password password := security.RandomPassword(8) hash, err := security.PasswordCrypt([]byte(password)) if err != nil { log.WithFields(log.Fields{ "error": err, "host": host, "email": person.Email, "step": "person-password", }).Error("Api::Accounts->Register password generation error.") tx.Rollback() render.Status(http.StatusInternalServerError) return } // Create a new authentication record for the user authr := r.AuthenticationRepository() authentication := &doorbot.Authentication{ PersonID: person.ID, ProviderID: auth.ProviderPassword, Token: string(hash), } err = authr.Create(tx, authentication) if err != nil { log.WithFields(log.Fields{ "error": err, "host": host, "email": person.Email, "step": "person-authentication", }).Error("Api::Accounts->Register database error.") tx.Rollback() render.Status(http.StatusInternalServerError) return } err = tx.Commit() if err != nil { log.WithFields(log.Fields{ "error": err, "host": host, "email": person.Email, "step": "transaction-commit", }).Error("Api::Accounts->Register database error.") tx.Rollback() render.Status(http.StatusInternalServerError) return } //TODO Send an email to the user. log.WithFields(log.Fields{ "account_id": account.ID, "account_host": account.Host, "person_id": person.ID, }).Info("Account created") n.AccountCreated(account, person, password) render.JSON(http.StatusCreated, AccountViewModel{Account: account}) }
// 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) }
// Delete a device func Delete(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->Delete 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 } _, err = repo.Delete(r.DB(), device) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "device_id": id, "step": "device-delete", }).Error("Api::Devices->Delete database error") render.Status(http.StatusInternalServerError) return } log.WithFields(log.Fields{ "account_id": r.AccountScope(), "device_id": id, }).Info("Api::Devices->Disabled device deleted") render.Status(http.StatusNoContent) }
// Notify someone their presence is needed at a given door. func Notify(render render.Render, account *doorbot.Account, r doorbot.Repositories, notificator notifications.Notificator, vm ViewModel) { notification := vm.Notification log.WithFields(log.Fields{ "account_id": account.ID, "person_id": notification.PersonID, "door_id": notification.DoorID, }).Info("Api::Notifications->Notify request") peopleRepo := r.PersonRepository() doorRepo := r.DoorRepository() person, err := peopleRepo.Find(r.DB(), notification.PersonID) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": account.ID, "person_id": notification.PersonID, "door_id": notification.DoorID, "step": "person-find", }).Error("Api::Notifications->Notify 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."})) log.WithFields(log.Fields{ "account_id": account.ID, "person_id": notification.PersonID, "door_id": notification.DoorID, }).Info("Api::Notifications->Notify person not found") return } if !person.IsVisible || !person.IsAvailable { log.WithFields(log.Fields{ "account_id": account.ID, "person_id": notification.PersonID, "door_id": notification.DoorID, "person_is_visible": person.IsVisible, "person_is_available": person.IsAvailable, }).Info("Api::Notifications->Notify person is not available/visible") //TODO Would there be a better status code? render.JSON(http.StatusForbidden, doorbot.NewForbiddenErrorResponse([]string{"The specified user is currently not available."})) return } //TODO Infer from the device token? door, err := doorRepo.Find(r.DB(), notification.DoorID) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": account.ID, "person_id": notification.PersonID, "door_id": notification.DoorID, "step": "door-find", }).Error("Api::Notifications->Notify 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."})) log.WithFields(log.Fields{ "account_id": account.ID, "person_id": person.ID, "door_id": notification.DoorID, }).Info("Api::Notifications->Notify door not found") return } notificator.KnockKnock(door, person) render.JSON(http.StatusAccepted, ViewModel{Notification: notification}) }