photoprism/internal/entity/person.go
Michael Mayer 33888fd231 Backend: Add credentials and extend person entity #98 #144
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
2020-09-06 14:18:40 +02:00

347 lines
13 KiB
Go

package entity
import (
"fmt"
"time"
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/txt"
)
type People []Person
// Person represents a real person that can also be a user if a password is set.
type Person struct {
ID int `gorm:"primary_key" json:"ID" yaml:"-"`
PersonUID string `gorm:"type:varbinary(42);unique_index;" json:"UID" yaml:"UID"`
ParentUID string `gorm:"type:varbinary(42);" json:"ParentUID" yaml:"ParentUID,omitempty"`
UserUUID string `gorm:"type:varbinary(42);index;" json:"UserUUID" yaml:"UserUUID,omitempty"`
UserName string `gorm:"size:64;" json:"UserName" yaml:"UserName,omitempty"`
UserLocale string `gorm:"size:64;" json:"UserLocale" yaml:"UserLocale,omitempty"`
TimeZone string `gorm:"size:255;" json:"TimeZone" yaml:"TimeZone,omitempty"`
PrimaryEmail string `gorm:"size:255;index;" json:"PrimaryEmail" yaml:"PrimaryEmail,omitempty"`
BackupEmail string `gorm:"size:255;" json:"BackupEmail" yaml:"BackupEmail,omitempty"`
DisplayName string `gorm:"size:255;" json:"DisplayName" yaml:"DisplayName,omitempty"`
DisplayLocation string `gorm:"size:255;" json:"DisplayLocation" yaml:"DisplayLocation,omitempty"`
DisplayBio string `gorm:"type:text;" json:"DisplayBio" yaml:"DisplayBio,omitempty"`
NamePrefix string `gorm:"size:64;" json:"NamePrefix" yaml:"NamePrefix,omitempty"`
GivenName string `gorm:"size:128;" json:"GivenName" yaml:"GivenName,omitempty"`
FamilyName string `gorm:"size:128;" json:"FamilyName" yaml:"FamilyName,omitempty"`
NameSuffix string `gorm:"size:64;" json:"NameSuffix" yaml:"NameSuffix,omitempty"`
AvatarUID string `gorm:"type:varbinary(42);" json:"AvatarUID" yaml:"AvatarUID,omitempty"`
AvatarURL string `gorm:"size:255;" json:"AvatarURL" yaml:"AvatarURL,omitempty"`
FeedURL string `gorm:"size:255;" json:"FeedURL" yaml:"FeedURL,omitempty"`
FeedType string `gorm:"size:64" json:"FeedType" yaml:"FeedType,omitempty"`
FeedFollow bool `json:"FeedFollow" yaml:"FeedFollow,omitempty"`
BlogURL string `gorm:"size:255;" json:"BlogURL" yaml:"BlogURL,omitempty"`
BlogType string `gorm:"size:64;" json:"BlogType" yaml:"BlogType,omitempty"`
BlogFollow bool `json:"BlogFollow" yaml:"BlogFollow,omitempty"`
CompanyURL string `gorm:"size:255;" json:"CompanyURL" yaml:"CompanyURL,omitempty"`
CompanyName string `gorm:"size:128;" json:"CompanyName" yaml:"CompanyName,omitempty"`
CompanyPhone string `gorm:"size:255;" json:"CompanyPhone" yaml:"CompanyPhone,omitempty"`
PrimaryPhone string `gorm:"size:255;" json:"PrimaryPhone" yaml:"PrimaryPhone,omitempty"`
DepartmentName string `gorm:"size:255;" json:"DepartmentName" yaml:"DepartmentName,omitempty"`
JobTitle string `gorm:"size:255;" json:"JobTitle" yaml:"JobTitle,omitempty"`
AddressLat float32 `gorm:"type:FLOAT;index;" json:"AddressLat" yaml:"AddressLat,omitempty"`
AddressLng float32 `gorm:"type:FLOAT;index;" json:"AddressLng" yaml:"AddressLng,omitempty"`
AddressLine1 string `gorm:"size:255;" json:"AddressLine1" yaml:"AddressLine1,omitempty"`
AddressLine2 string `gorm:"size:255;" json:"AddressLine2" yaml:"AddressLine2,omitempty"`
AddressZip string `gorm:"size:255;" json:"AddressZip" yaml:"AddressZip,omitempty"`
AddressCity string `gorm:"size:255;" json:"AddressCity" yaml:"AddressCity,omitempty"`
AddressState string `gorm:"size:255;" json:"AddressState" yaml:"AddressState,omitempty"`
AddressCountry string `gorm:"type:varbinary(2);default:'zz'" json:"AddressCountry" yaml:"AddressCountry,omitempty"`
BirthYear int `json:"BirthYear" yaml:"BirthYear,omitempty"`
BirthMonth int `json:"BirthMonth" yaml:"BirthMonth,omitempty"`
BirthDay int `json:"BirthDay" yaml:"BirthDay,omitempty"`
TermsAccepted bool `json:"TermsAccepted" yaml:"TermsAccepted,omitempty"`
IsActive bool `json:"IsActive" yaml:"IsActive,omitempty"`
IsConfirmed bool `json:"IsConfirmed" yaml:"IsConfirmed,omitempty"`
IsPro bool `json:"IsPro" yaml:"IsPro,omitempty"`
IsSponsor bool `json:"IsSponsor" yaml:"IsSponsor,omitempty"`
IsContributor bool `json:"IsContributor" yaml:"IsContributor,omitempty"`
IsArtist bool `json:"IsArtist" yaml:"IsArtist,omitempty"`
IsSubject bool `json:"IsSubject" yaml:"IsSubject,omitempty"`
RoleAdmin bool `json:"RoleAdmin" yaml:"RoleAdmin,omitempty"`
RoleGuest bool `json:"RoleGuest" yaml:"RoleGuest,omitempty"`
RoleChild bool `json:"RoleChild" yaml:"RoleChild,omitempty"`
RoleFamily bool `json:"RoleFamily" yaml:"RoleFamily,omitempty"`
RoleFriend bool `json:"RoleFriend" yaml:"RoleFriend,omitempty"`
CanEdit bool `json:"CanEdit" yaml:"CanEdit,omitempty"`
CanDelete bool `json:"CanDelete" yaml:"CanDelete,omitempty"`
CanIndex bool `json:"CanIndex" yaml:"CanIndex,omitempty"`
CanShare bool `json:"CanShare" yaml:"CanShare,omitempty"`
CanComment bool `json:"CanComment" yaml:"CanComment,omitempty"`
CanUpload bool `json:"CanUpload" yaml:"CanUpload,omitempty"`
CanDownload bool `json:"CanDownload" yaml:"CanDownload,omitempty"`
HideLabels bool `json:"HideLabels" yaml:"HideLabels,omitempty"`
HidePlaces bool `json:"HidePlaces" yaml:"HidePlaces,omitempty"`
HidePeople bool `json:"HidePeople" yaml:"HidePeople,omitempty"`
HidePrivate bool `json:"HidePrivate" yaml:"HidePrivate,omitempty"`
HideLibrary bool `json:"HideLibrary" yaml:"HideLibrary,omitempty"`
HideSettings bool `json:"HideSettings" yaml:"HideSettings,omitempty"`
WebDAV bool `gorm:"column:webdav" json:"WebDAV" yaml:"WebDAV,omitempty"`
StoragePath string `gorm:"column:storage_path;size:255;" json:"StoragePath" yaml:"StoragePath,omitempty"`
ApiToken string `gorm:"column:api_token;size:255;" json:"ApiToken" yaml:"ApiToken,omitempty"`
ApiSecret string `gorm:"column:api_secret;size:255;" json:"-" yaml:"-"`
AmazonID string `gorm:"column:amazon_id;size:255;" json:"AmazonID" yaml:"AmazonID,omitempty"`
AppleID string `gorm:"column:apple_id;size:255;" json:"AppleID" yaml:"AppleID,omitempty"`
EyeEmID string `gorm:"column:eyeem_id;size:255;" json:"EyeEmID" yaml:"EyeEmID,omitempty"`
FacebookID string `gorm:"column:facebook_id;size:255;" json:"FacebookID" yaml:"FacebookID,omitempty"`
FlickrID string `gorm:"column:flickr_id;size:255;" json:"FlickrID" yaml:"FlickrID,omitempty"`
GitHubID string `gorm:"column:github_id;size:255;" json:"GitHubID" yaml:"GitHubID,omitempty"`
GitLabID string `gorm:"column:gitlab_id;size:255;" json:"GitLabID" yaml:"GitLabID,omitempty"`
GoogleID string `gorm:"column:google_id;size:255;" json:"GoogleID" yaml:"GoogleID,omitempty"`
InstagramID string `gorm:"column:instagram_id;size:255;" json:"InstagramID" yaml:"InstagramID,omitempty"`
LinkedinID string `gorm:"column:linkedin_id;size:255;" json:"LinkedinID" yaml:"LinkedinID,omitempty"`
MastodonID string `gorm:"column:mastodon_id;size:255;" json:"MastodonID" yaml:"MastodonID,omitempty"`
NextcloudID string `gorm:"column:nextcloud_id;size:255;" json:"NextcloudID" yaml:"NextcloudID,omitempty"`
TelegramID string `gorm:"column:telegram_id;size:255;" json:"TelegramID" yaml:"TelegramID,omitempty"`
TwitterID string `gorm:"column:twitter_id;size:255;" json:"TwitterID" yaml:"TwitterID,omitempty"`
WhatsAppID string `gorm:"column:whatsapp_id;size:255;" json:"WhatsAppID" yaml:"WhatsAppID,omitempty"`
YouTubeID string `gorm:"column:youtube_id;size:255;" json:"YouTubeID" yaml:"YouTubeID,omitempty"`
UserNotes string `gorm:"type:text;" json:"UserNotes" yaml:"UserNotes,omitempty"`
LoginAttempts int `json:"-" yaml:"-,omitempty"`
LoginAt *time.Time `json:"-" yaml:"-"`
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
DeletedAt *time.Time `sql:"index" json:"DeletedAt,omitempty" yaml:"-"`
}
// TableName the database table name.
func (Person) TableName() string {
return "people"
}
// Default admin user.
var Admin = Person{
ID: 1,
UserName: "admin",
DisplayName: "Admin",
RoleAdmin: true,
IsActive: true,
IsConfirmed: true,
}
// Anonymous, public user without own account.
var UnknownPerson = Person{
ID: -1,
PersonUID: "u000000000000001",
UserName: "",
DisplayName: "Anonymous",
RoleAdmin: false,
RoleGuest: false,
IsActive: false,
IsConfirmed: false,
}
// Guest user without own account for link sharing.
var Guest = Person{
ID: -2,
PersonUID: "u000000000000002",
UserName: "",
DisplayName: "Guest",
RoleAdmin: false,
RoleGuest: true,
IsActive: false,
IsConfirmed: false,
}
// CreateDefaultUsers initializes the database with default user accounts.
func CreateDefaultUsers() {
if user := FirstOrCreatePerson(&Admin); user != nil {
Admin = *user
}
if user := FirstOrCreatePerson(&UnknownPerson); user != nil {
UnknownPerson = *user
}
if user := FirstOrCreatePerson(&Guest); user != nil {
Guest = *user
}
}
// Create inserts a new row to the database.
func (m *Person) Create() error {
return Db().Create(m).Error
}
// Saves the new row to the database.
func (m *Person) Save() error {
return Db().Save(m).Error
}
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
func (m *Person) BeforeCreate(scope *gorm.Scope) error {
if rnd.IsUID(m.PersonUID, 'u') {
return nil
}
return scope.SetColumn("PersonUID", rnd.PPID('u'))
}
// FirstOrCreatePerson returns an existing row, inserts a new row or nil in case of errors.
func FirstOrCreatePerson(m *Person) *Person {
result := Person{}
if err := Db().Where("id = ? OR person_uid = ?", m.ID, m.PersonUID).First(&result).Error; err == nil {
return &result
} else if err := m.Create(); err != nil {
log.Debugf("person: %s", err)
return nil
}
return m
}
// FindPersonByUserName returns an existing user or nil if not found.
func FindPersonByUserName(userName string) *Person {
if userName == "" {
return nil
}
result := Person{}
if err := Db().Where("user_name = ?", userName).First(&result).Error; err == nil {
return &result
} else {
log.Debugf("user %s not found", txt.Quote(userName))
return nil
}
}
// FindPersonByUID returns an existing user or nil if not found.
func FindPersonByUID(uid string) *Person {
if uid == "" {
return nil
}
result := Person{}
if err := Db().Where("person_uid = ?", uid).First(&result).Error; err == nil {
return &result
} else {
log.Debugf("user %s not found", txt.Quote(uid))
return nil
}
}
// String returns an identifier that can be used in logs.
func (m *Person) String() string {
if m.UserName != "" {
return m.UserName
}
if m.DisplayName != "" {
return m.DisplayName
}
return m.PersonUID
}
// User returns true if the person has a user name.
func (m *Person) Registered() bool {
return m.UserName != "" && rnd.IsPPID(m.PersonUID, 'u')
}
// Admin returns true if the person is an admin with user name.
func (m *Person) Admin() bool {
return m.Registered() && m.RoleAdmin
}
// Anonymous returns true if the person is unknown.
func (m *Person) Anonymous() bool {
return !rnd.IsPPID(m.PersonUID, 'u') || m.ID == UnknownPerson.ID || m.PersonUID == UnknownPerson.PersonUID
}
// Guest returns true if the person is a guest.
func (m *Person) Guest() bool {
return m.RoleGuest
}
// SetPassword sets a new password stored as hash.
func (m *Person) SetPassword(password string) error {
if !m.Registered() {
return fmt.Errorf("only registered users can change their password")
}
if len(password) < 6 {
return fmt.Errorf("new password for %s must be at least 6 characters", txt.Quote(m.UserName))
}
pw := NewPassword(m.PersonUID, password)
return pw.Save()
}
// InitPassword sets the initial user password stored as hash.
func (m *Person) InitPassword(password string) {
if !m.Registered() {
log.Warn("only registered users can change their password")
return
}
if password == "" {
return
}
existing := FindPassword(m.PersonUID)
if existing != nil {
return
}
pw := NewPassword(m.PersonUID, password)
if err := pw.Save(); err != nil {
log.Error(err)
}
}
// InvalidPassword returns true if the given password does not match the hash.
func (m *Person) InvalidPassword(password string) bool {
if !m.Registered() {
log.Warn("only registered users can change their password")
return true
}
if password == "" {
return true
}
pw := FindPassword(m.PersonUID)
if pw == nil {
return true
}
return pw.InvalidPassword(password)
}
// Role returns the user role for ACL permission checks.
func (m *Person) Role() acl.Role {
if m.RoleAdmin {
return acl.RoleAdmin
}
if m.RoleChild {
return acl.RoleChild
}
if m.RoleFamily {
return acl.RoleFamily
}
if m.RoleFriend {
return acl.RoleFriend
}
if m.RoleGuest {
return acl.RoleGuest
}
return acl.RoleDefault
}