Пример #1
0
// 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})
}
Пример #2
0
// 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
}
Пример #3
0
// 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
}
Пример #4
0
// 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
	}
}
Пример #5
0
// 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)})
	}
}
Пример #6
0
// 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
}
Пример #7
0
// 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)
}
Пример #8
0
// 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)
}
Пример #9
0
// 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)
}
Пример #10
0
// 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)
	}
}
Пример #11
0
// 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})
}
Пример #12
0
// 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)
}
Пример #13
0
// 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)
}
Пример #14
0
// 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})
}
Пример #15
0
// 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)
}
Пример #16
0
// 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)
}
Пример #17
0
// 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)
}
Пример #18
0
// 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)
}
Пример #19
0
// 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})
}
Пример #20
0
// 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
}
Пример #21
0
// 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)
}
Пример #22
0
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)
}
Пример #23
0
// 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)
}
Пример #24
0
// 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})
}
Пример #25
0
// 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)
}
Пример #26
0
// 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)
}
Пример #27
0
// 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})
}