2022-09-02 21:30:50 +02:00
package entity
import (
"errors"
"fmt"
"net/mail"
2022-09-30 00:42:19 +02:00
"path"
2022-09-28 09:01:17 +02:00
"strings"
2022-09-02 21:30:50 +02:00
"time"
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/acl"
2022-09-28 09:01:17 +02:00
"github.com/photoprism/photoprism/internal/event"
2022-09-02 21:30:50 +02:00
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/rnd"
)
2022-09-28 09:01:17 +02:00
// User identifier prefixes.
const (
2022-09-30 19:15:10 +02:00
UserUID = byte ( 'u' )
UserPrefix = "user"
OwnerUnknown = ""
2022-09-28 09:01:17 +02:00
)
// LenNameMin specifies the minimum length of the username in characters.
var LenNameMin = 3
// LenPasswordMin specifies the minimum length of the password in characters.
var LenPasswordMin = 4
2022-09-02 21:30:50 +02:00
// Users represents a list of users.
type Users [ ] User
// User represents a person that may optionally log in as user.
type User struct {
2022-09-28 09:01:17 +02:00
ID int ` gorm:"primary_key" json:"-" yaml:"-" `
2022-09-30 19:15:10 +02:00
UUID string ` gorm:"type:VARBINARY(64);column:user_uuid;index;" json:"UUID,omitempty" yaml:"UUID,omitempty" `
2022-10-02 11:38:30 +02:00
UserUID string ` gorm:"type:VARBINARY(42);column:user_uid;unique_index;" json:"UID" yaml:"UID" `
2022-09-28 09:01:17 +02:00
AuthProvider string ` gorm:"type:VARBINARY(128);default:'';" json:"AuthProvider,omitempty" yaml:"AuthProvider,omitempty" `
AuthID string ` gorm:"type:VARBINARY(128);index;default:'';" json:"AuthID,omitempty" yaml:"AuthID,omitempty" `
UserName string ` gorm:"size:64;index;" json:"Name" yaml:"Name,omitempty" `
DisplayName string ` gorm:"size:200;" json:"DisplayName" yaml:"DisplayName,omitempty" `
UserEmail string ` gorm:"size:255;index;" json:"Email" yaml:"Email,omitempty" `
BackupEmail string ` gorm:"size:255;" json:"BackupEmail,omitempty" yaml:"BackupEmail,omitempty" `
UserRole string ` gorm:"size:64;default:'restricted';" json:"Role,omitempty" yaml:"Role,omitempty" `
UserAttr string ` gorm:"size:1024;" json:"Attr,omitempty" yaml:"Attr,omitempty" `
SuperAdmin bool ` json:"SuperAdmin,omitempty" yaml:"SuperAdmin,omitempty" `
CanLogin bool ` json:"CanLogin,omitempty" yaml:"CanLogin,omitempty" `
LoginAt * time . Time ` json:"LoginAt,omitempty" yaml:"LoginAt,omitempty" `
2022-10-02 11:38:30 +02:00
ExpiresAt * time . Time ` sql:"index" json:"ExpiresAt,omitempty" yaml:"ExpiresAt,omitempty" `
2022-09-30 00:42:19 +02:00
BasePath string ` gorm:"type:VARBINARY(1024);" json:"BasePath,omitempty" yaml:"BasePath,omitempty" `
UploadPath string ` gorm:"type:VARBINARY(1024);" json:"UploadPath,omitempty" yaml:"UploadPath,omitempty" `
2022-09-28 09:01:17 +02:00
CanSync bool ` json:"CanSync,omitempty" yaml:"CanSync,omitempty" `
CanInvite bool ` json:"CanInvite,omitempty" yaml:"CanInvite,omitempty" `
InviteToken string ` gorm:"type:VARBINARY(64);index;" json:"-" yaml:"-" `
InvitedBy string ` gorm:"size:64;" json:"-" yaml:"-" `
VerifyToken string ` gorm:"type:VARBINARY(64);" json:"-" yaml:"-" `
VerifiedAt * time . Time ` json:"VerifiedAt,omitempty" yaml:"VerifiedAt,omitempty" `
ConsentAt * time . Time ` json:"ConsentAt,omitempty" yaml:"ConsentAt,omitempty" `
BornAt * time . Time ` sql:"index" json:"BornAt,omitempty" yaml:"BornAt,omitempty" `
UserDetails * UserDetails ` gorm:"PRELOAD:true;foreignkey:UserUID;association_foreignkey:UserUID;" json:"Details,omitempty" yaml:"Details,omitempty" `
UserSettings * UserSettings ` gorm:"PRELOAD:true;foreignkey:UserUID;association_foreignkey:UserUID;" json:"Settings,omitempty" yaml:"Settings,omitempty" `
ResetToken string ` gorm:"type:VARBINARY(64);" json:"-" yaml:"-" `
PreviewToken string ` gorm:"type:VARBINARY(64);column:preview_token;" json:"-" yaml:"-" `
DownloadToken string ` gorm:"type:VARBINARY(64);column:download_token;" json:"-" yaml:"-" `
Thumb string ` gorm:"type:VARBINARY(128);index;default:'';" json:"Thumb,omitempty" yaml:"Thumb,omitempty" `
ThumbSrc string ` gorm:"type:VARBINARY(8);default:'';" json:"ThumbSrc,omitempty" yaml:"ThumbSrc,omitempty" `
RefID string ` gorm:"type:VARBINARY(16);" json:"-" yaml:"-" `
CreatedAt time . Time ` json:"CreatedAt" yaml:"-" `
UpdatedAt time . Time ` json:"UpdatedAt" yaml:"-" `
DeletedAt * time . Time ` sql:"index" json:"DeletedAt,omitempty" yaml:"-" `
2022-10-02 11:38:30 +02:00
Shares Shares ` gorm:"-" json:"Shares,omitempty" yaml:"Shares,omitempty" `
2022-09-28 09:01:17 +02:00
}
// TableName returns the entity table name.
2022-09-02 21:30:50 +02:00
func ( User ) TableName ( ) string {
2022-10-02 22:09:02 +02:00
return "auth_users"
2022-09-28 09:01:17 +02:00
}
// NewUser creates a new user and returns it.
func NewUser ( ) ( m * User ) {
uid := rnd . GenerateUID ( UserUID )
return & User {
UserUID : uid ,
UserDetails : NewUserDetails ( uid ) ,
UserSettings : NewUserSettings ( uid ) ,
RefID : rnd . RefID ( UserPrefix ) ,
}
}
// FirstOrCreateUser returns an existing record, inserts a new record, or returns nil in case of an error.
func FirstOrCreateUser ( m * User ) * User {
result := User { }
if err := Db ( ) . Where ( "id = ? OR (user_uid = ? AND user_uid <> '') OR (user_name = ? AND user_name <> '')" , m . ID , m . UserUID , m . UserName ) . First ( & result ) . Error ; err == nil {
return & result
} else if err = m . Create ( ) ; err != nil {
event . AuditErr ( [ ] string { "user" , "failed to create" , "%s" } , err )
return nil
}
return m
}
// FindUserByName finds a user by its username or returns nil if it was not found.
func FindUserByName ( name string ) * User {
name = clean . Username ( name )
if name == "" {
return nil
}
m := & User { }
// Find matching record.
if Db ( ) . First ( m , "user_name = ?" , name ) . RecordNotFound ( ) {
return nil
}
// Fetch related settings and details.
return m . LoadRelated ( )
}
// FindUserByUID returns an existing user or nil if not found.
func FindUserByUID ( uid string ) * User {
2022-10-02 11:38:30 +02:00
if rnd . InvalidUID ( uid , UserUID ) {
2022-09-28 09:01:17 +02:00
return nil
}
m := & User { }
// Find matching record.
if UnscopedDb ( ) . First ( m , "user_uid = ?" , uid ) . RecordNotFound ( ) {
return nil
}
// Fetch related settings and details.
return m . LoadRelated ( )
}
// UID returns the unique id as string.
func ( m * User ) UID ( ) string {
2022-10-02 11:38:30 +02:00
if m == nil {
return ""
}
2022-09-28 09:01:17 +02:00
return m . UserUID
2022-09-02 21:30:50 +02:00
}
2022-10-02 11:38:30 +02:00
// SameUID checks if the given uid matches the own uid.
func ( m * User ) SameUID ( uid string ) bool {
if m == nil {
return false
} else if m . UserUID == "" || rnd . InvalidUID ( uid , UserUID ) {
return false
}
return m . UserUID == uid
}
2022-09-02 21:30:50 +02:00
// InitAccount sets the name and password of the initial admin account.
func ( m * User ) InitAccount ( login , password string ) ( updated bool ) {
if ! m . IsRegistered ( ) {
log . Warn ( "only registered users can change their password" )
return false
}
// Password must not be empty.
if password == "" {
return false
}
existing := FindPassword ( m . UserUID )
if existing != nil {
return false
}
pw := NewPassword ( m . UserUID , password )
2022-09-02 22:55:57 +02:00
// Save password.
2022-09-02 21:30:50 +02:00
if err := pw . Save ( ) ; err != nil {
2022-10-02 11:38:30 +02:00
event . AuditErr ( [ ] string { "user %s" , "failed to change password" , "%s" } , m . RefID , err )
2022-09-02 21:30:50 +02:00
return false
}
2022-09-02 22:55:57 +02:00
// Change username.
if err := m . UpdateName ( login ) ; err != nil {
2022-10-02 11:38:30 +02:00
event . AuditErr ( [ ] string { "user %s" , "failed to change username to %s" , "%s" } , m . RefID , clean . Log ( login ) , err )
2022-09-02 22:55:57 +02:00
}
2022-09-02 21:30:50 +02:00
return true
}
// Create new entity in the database.
2022-09-28 09:01:17 +02:00
func ( m * User ) Create ( ) ( err error ) {
err = Db ( ) . Create ( m ) . Error
2022-09-02 21:30:50 +02:00
2022-09-28 09:01:17 +02:00
if err == nil {
m . SaveRelated ( )
}
2022-09-02 21:30:50 +02:00
2022-09-28 09:01:17 +02:00
return err
2022-09-02 21:30:50 +02:00
}
2022-10-02 11:38:30 +02:00
// Save updates the record in the database or inserts a new record if it does not already exist.
2022-09-28 09:01:17 +02:00
func ( m * User ) Save ( ) ( err error ) {
err = Db ( ) . Save ( m ) . Error
2022-09-02 21:30:50 +02:00
2022-09-28 09:01:17 +02:00
if err == nil {
m . SaveRelated ( )
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
return err
}
2022-09-02 21:30:50 +02:00
2022-09-28 09:01:17 +02:00
// Delete marks the entity as deleted.
func ( m * User ) Delete ( ) error {
if m . ID <= 1 {
return fmt . Errorf ( "cannot delete system user" )
}
2022-09-02 21:30:50 +02:00
2022-09-28 09:01:17 +02:00
return Db ( ) . Delete ( m ) . Error
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
// Deleted checks if the user account has been deleted.
func ( m * User ) Deleted ( ) bool {
if m . DeletedAt == nil {
return false
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
return ! m . DeletedAt . IsZero ( )
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
// LoadRelated loads related settings and details.
func ( m * User ) LoadRelated ( ) * User {
m . Settings ( )
m . Details ( )
2022-09-02 21:30:50 +02:00
2022-09-28 09:01:17 +02:00
return m
}
2022-09-02 21:30:50 +02:00
2022-09-28 09:01:17 +02:00
// SaveRelated saves related settings and details.
func ( m * User ) SaveRelated ( ) * User {
if err := m . Settings ( ) . Save ( ) ; err != nil {
2022-10-02 11:38:30 +02:00
event . AuditErr ( [ ] string { "user %s" , "failed to save settings" , "%s" } , m . RefID , err )
2022-09-28 09:01:17 +02:00
}
if err := m . Details ( ) . Save ( ) ; err != nil {
2022-10-02 11:38:30 +02:00
event . AuditErr ( [ ] string { "user %s" , "failed to save details" , "%s" } , m . RefID , err )
2022-09-02 21:30:50 +02:00
}
return m
}
2022-09-28 09:01:17 +02:00
// Updates multiple properties in the database.
func ( m * User ) Updates ( values interface { } ) error {
return UnscopedDb ( ) . Model ( m ) . Updates ( values ) . Error
}
// BeforeCreate sets a random UID if needed before inserting a new row to the database.
func ( m * User ) BeforeCreate ( scope * gorm . Scope ) error {
if m . UserSettings != nil {
m . UserSettings . UserUID = m . UserUID
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
if m . UserDetails != nil {
m . UserDetails . UserUID = m . UserUID
}
2022-09-02 21:30:50 +02:00
2022-09-28 09:01:17 +02:00
if rnd . InvalidRefID ( m . RefID ) {
m . RefID = rnd . RefID ( UserPrefix )
2022-10-02 11:38:30 +02:00
Log ( "user" , "set ref id" , scope . SetColumn ( "RefID" , m . RefID ) )
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
if rnd . IsUnique ( m . UserUID , UserUID ) {
2022-09-02 21:30:50 +02:00
return nil
}
2022-09-28 09:01:17 +02:00
m . UserUID = rnd . GenerateUID ( UserUID )
return scope . SetColumn ( "UserUID" , m . UserUID )
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
// Expired checks if the user account has expired.
func ( m * User ) Expired ( ) bool {
if m . ExpiresAt == nil {
return false
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
return m . ExpiresAt . Before ( time . Now ( ) )
}
2022-09-02 21:30:50 +02:00
2022-09-28 09:01:17 +02:00
// Disabled checks if the user account has been deleted or has expired.
func ( m * User ) Disabled ( ) bool {
return m . Deleted ( ) || m . Expired ( )
2022-09-02 21:30:50 +02:00
}
2022-09-30 00:42:19 +02:00
// LoginAllowed checks if the user is allowed to log in and use the web UI.
2022-09-28 09:01:17 +02:00
func ( m * User ) LoginAllowed ( ) bool {
if role := m . AclRole ( ) ; m . Disabled ( ) || ! m . CanLogin || m . UserName == "" || role == acl . RoleUnauthorized {
return false
} else {
return acl . Resources . Allow ( acl . ResourceConfig , role , acl . AccessOwn )
2022-09-02 21:30:50 +02:00
}
}
2022-09-30 00:42:19 +02:00
// SyncAllowed checks whether the user is allowed to use WebDAV to synchronize files.
2022-09-28 09:01:17 +02:00
func ( m * User ) SyncAllowed ( ) bool {
if role := m . AclRole ( ) ; m . Disabled ( ) || ! m . CanSync || m . UserName == "" || role == acl . RoleUnauthorized {
2022-09-02 21:30:50 +02:00
return false
2022-09-28 09:01:17 +02:00
} else {
return acl . Resources . Allow ( acl . ResourcePhotos , role , acl . ActionUpload )
2022-09-02 21:30:50 +02:00
}
}
2022-09-30 00:42:19 +02:00
// UploadAllowed checks if the user is allowed to upload files.
func ( m * User ) UploadAllowed ( ) bool {
if role := m . AclRole ( ) ; m . Disabled ( ) || role == acl . RoleUnauthorized {
return false
} else {
return acl . Resources . Allow ( acl . ResourcePhotos , role , acl . ActionUpload )
}
}
// SetBasePath changes the user's base folder.
func ( m * User ) SetBasePath ( dir string ) * User {
m . BasePath = clean . UserPath ( dir )
return m
}
// SetUploadPath changes the user's upload folder.
func ( m * User ) SetUploadPath ( dir string ) * User {
if m . BasePath == "" {
m . UploadPath = clean . UserPath ( dir )
} else {
m . UploadPath = path . Join ( m . BasePath , clean . UserPath ( dir ) )
}
return m
}
2022-09-02 21:30:50 +02:00
// String returns an identifier that can be used in logs.
func ( m * User ) String ( ) string {
2022-09-28 09:01:17 +02:00
if n := m . Name ( ) ; n != "" {
2022-09-02 21:30:50 +02:00
return clean . Log ( n )
2022-09-28 09:01:17 +02:00
} else if n = m . FullName ( ) ; n != "" {
2022-09-02 21:30:50 +02:00
return clean . Log ( n )
}
return clean . Log ( m . UserUID )
}
2022-09-28 09:01:17 +02:00
// Name returns the user's login name for authentication.
func ( m * User ) Name ( ) string {
return clean . Username ( m . UserName )
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
// SetName sets the login username to the specified string.
func ( m * User ) SetName ( login string ) ( err error ) {
login = clean . Username ( login )
2022-09-02 21:30:50 +02:00
// Empty?
if login == "" {
2022-09-28 09:01:17 +02:00
return fmt . Errorf ( "username cannot be empty" )
2022-09-02 21:30:50 +02:00
}
// Update username and slug.
2022-09-28 09:01:17 +02:00
m . UserName = login
2022-09-02 21:30:50 +02:00
// Update display name.
2022-09-02 22:55:57 +02:00
if m . DisplayName == "" || m . DisplayName == AdminDisplayName {
2022-09-30 00:42:19 +02:00
m . DisplayName = clean . NameCapitalized ( login )
2022-09-02 21:30:50 +02:00
}
return nil
}
// UpdateName changes the login username and saves it to the database.
func ( m * User ) UpdateName ( login string ) ( err error ) {
2022-09-28 09:01:17 +02:00
if err = m . SetName ( login ) ; err != nil {
2022-09-02 21:30:50 +02:00
return err
}
// Save to database.
return m . Updates ( Values {
2022-09-28 09:01:17 +02:00
"UserName" : m . UserName ,
2022-09-02 21:30:50 +02:00
"DisplayName" : m . DisplayName ,
} )
}
2022-09-28 09:01:17 +02:00
// Email returns the user's login email for authentication.
func ( m * User ) Email ( ) string {
return clean . Email ( m . UserEmail )
}
// FullName returns the name of the user for display purposes.
func ( m * User ) FullName ( ) string {
switch {
case m . DisplayName != "" :
return m . DisplayName
default :
return m . UserName
}
}
2022-09-02 21:30:50 +02:00
// AclRole returns the user role for ACL permission checks.
func ( m * User ) AclRole ( ) acl . Role {
role := clean . Role ( m . UserRole )
switch {
case m . SuperAdmin :
return acl . RoleAdmin
case role == "" :
2022-09-28 09:01:17 +02:00
return acl . RoleUnauthorized
case m . UserName == "" :
return acl . RoleVisitor
2022-09-02 21:30:50 +02:00
default :
2022-09-28 09:01:17 +02:00
return acl . ValidRoles [ role ]
}
}
// Settings returns the user settings and initializes them if necessary.
func ( m * User ) Settings ( ) * UserSettings {
if m . UserSettings != nil {
m . UserSettings . UserUID = m . UserUID
return m . UserSettings
} else if m . UID ( ) == "" {
m . UserSettings = & UserSettings { }
return m . UserSettings
} else if err := CreateUserSettings ( m ) ; err != nil {
m . UserSettings = NewUserSettings ( m . UserUID )
}
return m . UserSettings
}
// Details returns user profile information and initializes it if needed.
func ( m * User ) Details ( ) * UserDetails {
if m . UserDetails != nil {
m . UserDetails . UserUID = m . UserUID
return m . UserDetails
} else if m . UID ( ) == "" {
m . UserDetails = & UserDetails { }
return m . UserDetails
} else if err := CreateUserDetails ( m ) ; err != nil {
m . UserDetails = NewUserDetails ( m . UserUID )
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
return m . UserDetails
}
// Attr returns optional user account attributes as sanitized string.
// Example: https://learn.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties
func ( m * User ) Attr ( ) string {
return clean . Attr ( m . UserAttr )
2022-09-02 21:30:50 +02:00
}
// IsRegistered checks if the user is registered e.g. has a username.
func ( m * User ) IsRegistered ( ) bool {
2022-10-02 11:38:30 +02:00
if m == nil {
return false
}
return m . UserName != "" && rnd . IsUID ( m . UserUID , UserUID ) && ! m . IsVisitor ( )
}
// NotRegistered checks if the user is not registered with an own account.
func ( m * User ) NotRegistered ( ) bool {
return ! m . IsRegistered ( )
2022-09-02 21:30:50 +02:00
}
// IsAdmin checks if the user is an admin with username.
func ( m * User ) IsAdmin ( ) bool {
2022-10-02 11:38:30 +02:00
if m == nil {
return false
}
2022-09-30 19:15:10 +02:00
return m . IsRegistered ( ) && ( m . SuperAdmin || m . AclRole ( ) == acl . RoleAdmin )
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
// IsVisitor checks if the user is a sharing link visitor.
func ( m * User ) IsVisitor ( ) bool {
return m . AclRole ( ) == acl . RoleVisitor || m . ID == Visitor . ID
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
// IsUnknown checks if the user is unknown.
func ( m * User ) IsUnknown ( ) bool {
return ! rnd . IsUID ( m . UserUID , UserUID ) || m . ID == UnknownUser . ID || m . UserUID == UnknownUser . UserUID
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
// DeleteSessions deletes all active user sessions except those passed as argument.
func ( m * User ) DeleteSessions ( omit [ ] string ) ( deleted int ) {
if m . UserUID == "" {
return 0
}
// Find all user sessions except the session ids passed as argument.
stmt := Db ( ) . Where ( "user_uid = ? AND id NOT IN (?)" , m . UserUID , omit )
sess := Sessions { }
if err := stmt . Find ( & sess ) . Error ; err != nil {
2022-10-02 11:38:30 +02:00
event . AuditErr ( [ ] string { "user %s" , "failed to invalidate sessions" , "%s" } , m . RefID , err )
2022-09-28 09:01:17 +02:00
return 0
}
// This will also remove the session from the cache.
for _ , s := range sess {
if err := s . Delete ( ) ; err != nil {
2022-10-02 11:38:30 +02:00
event . AuditWarn ( [ ] string { "user %s" , "failed to invalidate session %s" , "%s" } , m . RefID , clean . Log ( s . RefID ) , err )
2022-09-28 09:01:17 +02:00
} else {
deleted ++
}
}
2022-09-02 21:30:50 +02:00
2022-09-28 09:01:17 +02:00
// Return number of deleted sessions for logs.
return deleted
2022-09-02 21:30:50 +02:00
}
// SetPassword sets a new password stored as hash.
func ( m * User ) SetPassword ( password string ) error {
if ! m . IsRegistered ( ) {
2022-09-28 09:01:17 +02:00
return fmt . Errorf ( "only registered users may change their password" )
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
if len ( password ) < LenPasswordMin {
return fmt . Errorf ( "password must have at least %d characters" , LenPasswordMin )
2022-09-02 21:30:50 +02:00
}
pw := NewPassword ( m . UserUID , password )
return pw . Save ( )
}
// InvalidPassword returns true if the given password does not match the hash.
func ( m * User ) InvalidPassword ( password string ) bool {
2022-09-28 09:01:17 +02:00
// Registered user?
2022-09-02 21:30:50 +02:00
if ! m . IsRegistered ( ) {
2022-09-28 09:01:17 +02:00
log . Warn ( "only registered users may change their password" )
2022-09-02 21:30:50 +02:00
return true
}
2022-09-28 09:01:17 +02:00
// Empty password?
2022-09-02 21:30:50 +02:00
if password == "" {
return true
}
2022-09-28 09:01:17 +02:00
// Fetch password.
2022-09-02 21:30:50 +02:00
pw := FindPassword ( m . UserUID )
2022-09-28 09:01:17 +02:00
// Found?
2022-09-02 21:30:50 +02:00
if pw == nil {
return true
}
2022-09-28 09:01:17 +02:00
// Invalid?
2022-09-02 21:30:50 +02:00
if pw . InvalidPassword ( password ) {
return true
}
return false
}
2022-09-28 09:01:17 +02:00
// Validate checks if username, email and role are valid and returns an error otherwise.
func ( m * User ) Validate ( ) ( err error ) {
// Empty name?
if m . Name ( ) == "" {
return errors . New ( "username cannot be empty" )
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
// Name too short?
if len ( m . Name ( ) ) < LenNameMin {
return fmt . Errorf ( "username must have at least %d characters" , LenNameMin )
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
// Validate user role.
if acl . ValidRoles [ m . UserRole ] == "" {
return fmt . Errorf ( "role %s is invalid" , clean . LogQuote ( m . UserRole ) )
}
// Check if the username is unique.
var duplicate = User { }
2022-09-02 21:30:50 +02:00
2022-09-28 09:01:17 +02:00
if err = Db ( ) .
Where ( "user_name = ? AND id <> ?" , m . UserName , m . ID ) .
First ( & duplicate ) . Error ; err == nil {
return fmt . Errorf ( "username %s already exists" , clean . LogQuote ( m . UserName ) )
2022-09-02 21:30:50 +02:00
} else if err != gorm . ErrRecordNotFound {
return err
}
2022-09-28 09:01:17 +02:00
// Skip email check?
if m . UserEmail == "" {
2022-09-02 21:30:50 +02:00
return nil
}
2022-09-28 09:01:17 +02:00
// Parse and validate email address.
if a , err := mail . ParseAddress ( m . UserEmail ) ; err != nil {
return fmt . Errorf ( "email %s is invalid" , clean . LogQuote ( m . UserEmail ) )
} else if email := a . Address ; ! strings . ContainsRune ( email , '.' ) {
return fmt . Errorf ( "email %s does not have a fully qualified domain" , clean . LogQuote ( m . UserEmail ) )
2022-09-02 21:30:50 +02:00
} else {
2022-09-28 09:01:17 +02:00
m . UserEmail = email
2022-09-02 21:30:50 +02:00
}
2022-09-28 09:01:17 +02:00
// Check if the email is unique.
if err = Db ( ) .
Where ( "user_email = ? AND id <> ?" , m . UserEmail , m . ID ) .
First ( & duplicate ) . Error ; err == nil {
return fmt . Errorf ( "email %s already exists" , clean . Log ( m . UserEmail ) )
2022-09-02 21:30:50 +02:00
} else if err != gorm . ErrRecordNotFound {
return err
}
return nil
}
2022-09-28 09:01:17 +02:00
// SetFormValues sets the values specified in the form.
func ( m * User ) SetFormValues ( frm form . User ) * User {
m . UserName = frm . Name ( )
m . UserEmail = frm . Email ( )
m . DisplayName = frm . DisplayName
m . SuperAdmin = frm . SuperAdmin
m . CanLogin = frm . CanLogin
m . CanSync = frm . CanSync
m . UserRole = frm . Role ( )
m . UserAttr = frm . Attr ( )
2022-09-30 00:42:19 +02:00
m . SetBasePath ( frm . BasePath )
m . SetUploadPath ( frm . UploadPath )
2022-09-02 21:30:50 +02:00
2022-09-28 09:01:17 +02:00
return m
2022-09-02 21:30:50 +02:00
}
2022-10-02 11:38:30 +02:00
// RefreshShares updates the list of shares.
func ( m * User ) RefreshShares ( ) * User {
m . Shares = FindShares ( m . UID ( ) )
return m
}
// NoShares checks if the user has no shares yet.
func ( m * User ) NoShares ( ) bool {
if ! m . IsRegistered ( ) {
return true
}
return m . Shares . Empty ( )
}
// HasShares checks if the user has any shares.
func ( m * User ) HasShares ( ) bool {
return ! m . NoShares ( )
}
// HasShare if a uid was shared with the user.
func ( m * User ) HasShare ( uid string ) bool {
if ! m . IsRegistered ( ) || m . NoShares ( ) {
return false
}
return m . Shares . Contains ( uid )
}
// SharedUIDs returns shared entity UIDs.
func ( m * User ) SharedUIDs ( ) UIDs {
if m . IsRegistered ( ) && m . Shares . Empty ( ) {
m . RefreshShares ( )
}
return m . Shares . UIDs ( )
}
// RedeemToken updates shared entity UIDs using the specified token.
func ( m * User ) RedeemToken ( token string ) ( n int ) {
if ! m . IsRegistered ( ) {
return 0
}
// Find links.
links := FindValidLinks ( token , "" )
// Found?
if n = len ( links ) ; n == 0 {
return n
}
// Find shares.
for _ , link := range links {
if found := FindShare ( Share { UserUID : m . UID ( ) , ShareUID : link . ShareUID } ) ; found == nil {
share := NewShare ( m . UID ( ) , link . ShareUID , link . Perm , link . ExpiresAt ( ) )
share . LinkUID = link . LinkUID
share . Comment = link . Comment
if err := share . Save ( ) ; err != nil {
event . AuditErr ( [ ] string { "user %s" , "token %s" , "failed to redeem shares" , "%s" } , m . RefID , clean . Log ( token ) , err )
} else {
link . Redeem ( )
}
} else if err := found . UpdateLink ( link ) ; err != nil {
event . AuditErr ( [ ] string { "user %s" , "token %s" , "failed to update shares" , "%s" } , m . RefID , clean . Log ( token ) , err )
}
}
return n
}