Backend: Refactor user entity and add pro package

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2020-10-03 13:50:30 +02:00
parent 1516f556c2
commit 46b9239026
38 changed files with 1037 additions and 652 deletions

View file

@ -2035,7 +2035,8 @@
},
"minimist": {
"version": "1.2.0",
"resolved": ""
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
}
}
},
@ -11807,7 +11808,8 @@
},
"minimist": {
"version": "1.2.0",
"resolved": ""
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
},
"postcss": {
"version": "7.0.21",
@ -13724,7 +13726,8 @@
},
"minimist": {
"version": "1.2.0",
"resolved": ""
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
},
"parse-json": {
"version": "2.2.0",

View file

@ -142,9 +142,9 @@ export default class Session {
return "";
}
getGivenName() {
getNickName() {
if (this.isUser()) {
return this.user.GivenName;
return this.user.NickName;
}
return "";
@ -152,7 +152,7 @@ export default class Session {
getFullName() {
if (this.isUser()) {
return this.user.GivenName + " " + this.user.FamilyName;
return this.user.FullName;
}
return "";

View file

@ -38,34 +38,36 @@ export class User extends RestModel {
return {
UID: "",
Address: {},
ParentUID: "",
MotherUID: "",
FatherUID: "",
GlobalUID: "",
DisplayName: "",
DisplayLocation: "",
DisplayBio: "",
NamePrefix: "",
GivenName: "",
FamilyName: "",
NameSuffix: "",
FullName: "",
NickName: "",
MaidenName: "",
ArtistName: "",
UserName: "",
UserStatus: "",
UserDisabled: false,
UserSettings: "",
PrimaryEmail: "",
EmailConfirmed: false,
BackupEmail: "",
PersonURL: "",
PersonPhone: "",
PersonStatus: "",
PersonAvatar: "",
CompanyURL: "",
CompanyPhone: "",
PersonLocation: "",
PersonBio: "",
BusinessURL: "",
BusinessPhone: "",
BusinessEmail: "",
CompanyName: "",
DepartmentName: "",
JobTitle: "",
BirthYear: -1,
BirthMonth: -1,
BirthDay: -1,
UserName: "",
UserSettings: "",
TermsAccepted: false,
IsActive: false,
IsConfirmed: false,
IsArtist: false,
IsSubject: false,
RoleAdmin: false,
@ -75,13 +77,16 @@ export class User extends RestModel {
RoleFriend: false,
WebDAV: false,
StoragePath: "",
CanInvite: false,
InviteToken: "",
InvitedBy: "",
CreatedAt: "",
UpdatedAt: "",
};
}
getEntityName() {
return this.GivenName + " " + this.FamilyName;
return this.FullName ? this.FullName : this.UserName;
}
getRegisterForm() {

View file

@ -186,11 +186,11 @@ describe('common/session', () => {
const storage = new StorageShim();
const session = new Session(storage, config);
assert.isFalse(session.user.hasId());
const values = {"user": {ID: 5, GivenName: "Max", FamilyName: "Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
const values = {"user": {ID: 5, NickName: "Foo", FullName: "Max Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
session.setData();
assert.equal(session.user.GivenName, "");
assert.equal(session.user.FullName, "");
session.setData(values);
assert.equal(session.user.GivenName, "Max");
assert.equal(session.user.FullName, "Max Last");
assert.equal(session.user.RoleAdmin, true);
const result = session.getUser();
assert.equal(result.ID, 5);
@ -202,27 +202,27 @@ describe('common/session', () => {
it('should get user email', () => {
const storage = new StorageShim();
const session = new Session(storage, config);
const values = {"user": {ID: 5, GivenName: "Max", FamilyName: "Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
const values = {"user": {ID: 5, NickName: "Foo", FullName: "Max Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
session.setData(values);
const result = session.getEmail();
assert.equal(result, "test@test.com");
const values2 = {"user": {GivenName: "Max", FamilyName: "Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
const values2 = {"user": {NickName: "Foo", FullName: "Max Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
session.setData(values2);
const result2 = session.getEmail();
assert.equal(result2, "");
session.deleteData();
});
it('should get user firstname', () => {
it('should get user nick name', () => {
const storage = new StorageShim();
const session = new Session(storage, config);
const values = {"user": {ID: 5, GivenName: "Max", FamilyName: "Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
const values = {"user": {ID: 5, NickName: "Foo", FullName: "Max Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
session.setData(values);
const result = session.getGivenName();
assert.equal(result, "Max");
const values2 = {"user": {GivenName: "Max", FamilyName: "Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
const result = session.getNickName();
assert.equal(result, "Foo");
const values2 = {"user": {NickName: "Bar", FullName: "Max Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
session.setData(values2);
const result2 = session.getGivenName();
const result2 = session.getNickName();
assert.equal(result2, "");
session.deleteData();
});
@ -230,11 +230,11 @@ describe('common/session', () => {
it('should get user full name', () => {
const storage = new StorageShim();
const session = new Session(storage, config);
const values = {"user": {ID: 5, GivenName: "Max", FamilyName: "Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
const values = {"user": {ID: 5, NickName: "Foo", FullName: "Max Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
session.setData(values);
const result = session.getFullName();
assert.equal(result, "Max Last");
const values2 = {"user": {GivenName: "Max", FamilyName: "Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
const values2 = {"user": {NickName: "Bar", FullName: "Max New", PrimaryEmail: "test@test.com", RoleAdmin: true}};
session.setData(values2);
const result2 = session.getFullName();
assert.equal(result2, "");
@ -244,7 +244,7 @@ describe('common/session', () => {
it('should test whether user is set', () => {
const storage = new StorageShim();
const session = new Session(storage, config);
const values = {"user": {ID: 5, GivenName: "Max", FamilyName: "Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
const values = {"user": {ID: 5, NickName: "Foo", FullName: "Max Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
session.setData(values);
const result = session.isUser();
assert.equal(result, true);
@ -254,7 +254,7 @@ describe('common/session', () => {
it('should test whether user is admin', () => {
const storage = new StorageShim();
const session = new Session(storage, config);
const values = {"user": {ID: 5, GivenName: "Max", FamilyName: "Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
const values = {"user": {ID: 5, NickName: "Foo", FullName: "Max Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
session.setData(values);
const result = session.isAdmin();
assert.equal(result, true);
@ -264,7 +264,7 @@ describe('common/session', () => {
it('should test whether user is anonymous', () => {
const storage = new StorageShim();
const session = new Session(storage, config);
const values = {"user": {ID: 5, GivenName: "Max", FamilyName: "Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
const values = {"user": {ID: 5, NickName: "Foo", FullName: "Max Last", PrimaryEmail: "test@test.com", RoleAdmin: true}};
session.setData(values);
const result = session.isAnonymous();
assert.equal(result, false);

View file

@ -9,14 +9,14 @@ describe("model/user", () => {
const mock = new MockAdapter(Api);
it("should get entity name", () => {
const values = {ID: 5, GivenName: "Max", FamilyName: "Last", PrimaryEmail: "test@test.com", Role: "admin"};
const values = {ID: 5, FullName: "Max Last", PrimaryEmail: "test@test.com", RoleAdmin: true};
const user = new User(values);
const result = user.getEntityName();
assert.equal(result, "Max Last");
});
it("should get id", () => {
const values = {ID: 5, GivenName: "Max", FamilyName: "Last", PrimaryEmail: "test@test.com", Role: "admin"};
const values = {ID: 5, FullName: "Max Last", PrimaryEmail: "test@test.com", RoleAdmin: true};
const user = new User(values);
const result = user.getId();
assert.equal(result, 5);
@ -34,7 +34,7 @@ describe("model/user", () => {
it("should get register form", async() => {
mock.onAny("users/52/register").reply(200, "registerForm");
const values = {ID: 52, GivenName: "Max"};
const values = {ID: 52, FullName: "Max Last"};
const user = new User(values);
const result = await user.getRegisterForm();
assert.equal(result.definition, "registerForm");
@ -43,7 +43,7 @@ describe("model/user", () => {
it("should get profile form", async() => {
mock.onAny("users/53/profile").reply(200, "profileForm");
const values = {ID: 53, GivenName: "Max"};
const values = {ID: 53, FullName: "Max Last"};
const user = new User(values);
const result = await user.getProfileForm();
assert.equal(result.definition, "profileForm");
@ -52,20 +52,18 @@ describe("model/user", () => {
it("should get change password", async() => {
mock.onPut("users/54/password").reply(200, {password: "old", new_password: "new"});
const values = {ID: 54, GivenName: "Max", FamilyName: "Last", PrimaryEmail: "test@test.com", Role: "admin"};
const values = {ID: 54, FullName: "Max Last", PrimaryEmail: "test@test.com", RoleAdmin: true};
const user = new User(values);
const result = await user.changePassword("old", "new");
assert.equal(result.new_password, "new");
});
it("should save profile", async() => {
mock.onPost("users/55/profile").reply(200, {GivenName: "MaxNew", FamilyName: "LastNew"});
const values = {ID: 55, GivenName: "Max", FamilyName: "Last", PrimaryEmail: "test@test.com", Role: "admin"};
mock.onPost("users/55/profile").reply(200, {FullName: "Max New",});
const values = {ID: 55, FullName: "Max Last", PrimaryEmail: "test@test.com", RoleAdmin: true};
const user = new User(values);
assert.equal(user.GivenName, "Max");
assert.equal(user.FamilyName, "Last");
assert.equal(user.FullName, "Max Last");
await user.saveProfile();
assert.equal(user.GivenName, "MaxNew");
assert.equal(user.FamilyName, "LastNew");
assert.equal(user.FullName, "Max New");
});
});

View file

@ -54,7 +54,7 @@ func CreateSession(router *gin.RouterGroup) {
data.User = entity.Guest
}
} else if f.HasCredentials() {
user := entity.FindPersonByUserName(f.UserName)
user := entity.FindUserByName(f.UserName)
if user == nil {
c.AbortWithStatusJSON(400, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})

View file

@ -29,7 +29,7 @@ func ChangePassword(router *gin.RouterGroup) {
}
uid := c.Param("uid")
m := entity.FindPersonByUID(uid)
m := entity.FindUserByUID(uid)
if m == nil {
Abort(c, http.StatusNotFound, i18n.ErrUserNotFound)

View file

@ -7,7 +7,7 @@ import (
)
func TestChangePassword(t *testing.T) {
t.Run("not existing person", func(t *testing.T) {
t.Run("not existing user", func(t *testing.T) {
app, router, _ := NewApiTest()
ChangePassword(router)
r := PerformRequestWithBody(app, "PUT", "/api/v1/users/xxx/password", `{}`)

View file

@ -30,9 +30,9 @@ type clientInfo struct {
}
var wsAuth = struct {
user map[string]entity.Person
user map[string]entity.User
mutex sync.RWMutex
}{user: make(map[string]entity.Person)}
}{user: make(map[string]entity.User)}
func wsReader(ws *websocket.Conn, writeMutex *sync.Mutex, connId string, conf *config.Config) {
defer ws.Close()
@ -106,7 +106,7 @@ func wsWriter(ws *websocket.Conn, writeMutex *sync.Mutex, connId string) {
ws.Close()
wsAuth.mutex.Lock()
wsAuth.user[connId] = entity.UnknownPerson
wsAuth.user[connId] = entity.UnknownUser
wsAuth.mutex.Unlock()
}()
@ -125,7 +125,7 @@ func wsWriter(ws *websocket.Conn, writeMutex *sync.Mutex, connId string) {
case msg := <-s.Receiver:
wsAuth.mutex.RLock()
user := entity.UnknownPerson
user := entity.UnknownUser
if hit, ok := wsAuth.user[connId]; ok {
user = hit
@ -181,7 +181,7 @@ func Websocket(router *gin.RouterGroup) {
if conf.Public() {
wsAuth.user[connId] = entity.Admin
} else {
wsAuth.user[connId] = entity.UnknownPerson
wsAuth.user[connId] = entity.UnknownUser
}
wsAuth.mutex.Unlock()

View file

@ -12,8 +12,9 @@ import (
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/maps/places"
"github.com/photoprism/photoprism/internal/mutex"
"github.com/photoprism/photoprism/internal/pro"
"github.com/photoprism/photoprism/internal/pro/places"
"github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/sirupsen/logrus"
@ -25,12 +26,12 @@ var once sync.Once
// Config holds database, cache and all parameters of photoprism
type Config struct {
once sync.Once
db *gorm.DB
params *Params
settings *Settings
credentials *Credentials
token string
once sync.Once
db *gorm.DB
params *Params
settings *Settings
pro *pro.Config
token string
}
func init() {
@ -70,7 +71,7 @@ func NewConfig(ctx *cli.Context) *Config {
}
c.initSettings()
c.initCredentials()
c.initPro()
return c
}
@ -86,7 +87,7 @@ func (c *Config) Propagate() {
places.UserAgent = c.UserAgent()
c.Settings().Propagate()
c.Credentials().Propagate()
c.Pro().Propagate()
}
// Init initialises the database connection and dependencies.
@ -267,3 +268,24 @@ func (c *Config) OriginalsLimit() int64 {
// Megabyte.
return c.params.OriginalsLimit * 1024 * 1024
}
// initPro initializes photoprism.pro api credentials.
func (c *Config) initPro() {
c.pro = pro.NewConfig(c.Version())
p := c.ProConfigFile()
if err := c.pro.Load(p); err == nil {
// Do nothing.
} else if err := c.pro.Refresh(); err != nil {
log.Errorf("pro: %s", err)
} else if err := c.pro.Save(p); err != nil {
log.Errorf("pro: %s", err)
}
c.pro.Propagate()
}
// Config returns the photoprism.pro api credentials.
func (c *Config) Pro() *pro.Config {
return c.pro
}

View file

@ -1,108 +0,0 @@
package config
import (
"crypto/sha1"
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/photoprism/photoprism/internal/maps/places"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/txt"
"gopkg.in/yaml.v2"
)
// Credentials represents api credentials for hosted services like maps & places.
type Credentials struct {
Key string `json:"key" yaml:"key"`
Secret string `json:"secret" yaml:"secret"`
Session string `json:"session" yaml:"session"`
}
// NewCredentials creates a new Credentials instance.
func NewCredentials() *Credentials {
return &Credentials{
Key: "",
Secret: "",
Session: "",
}
}
// Propagate updates api credentials in other packages.
func (a *Credentials) Propagate() {
places.Key = a.Key
places.Secret = a.Secret
}
// Sanitize verifies and sanitizes api credentials;
func (a *Credentials) Sanitize() {
a.Key = strings.ToLower(a.Key)
if a.Secret != "" {
if a.Key != fmt.Sprintf("%x", sha1.Sum([]byte(a.Secret))) {
a.Secret = ""
a.Session = ""
}
}
}
// Load api credentials from a file.
func (a *Credentials) Load(fileName string) error {
if !fs.FileExists(fileName) {
return fmt.Errorf("credentials file not found: %s", txt.Quote(fileName))
}
yamlConfig, err := ioutil.ReadFile(fileName)
if err != nil {
return err
}
if err := yaml.Unmarshal(yamlConfig, a); err != nil {
return err
}
a.Sanitize()
a.Propagate()
return nil
}
// Save api credentials to a file.
func (a *Credentials) Save(fileName string) error {
a.Sanitize()
data, err := yaml.Marshal(a)
if err != nil {
return err
}
a.Propagate()
if err := ioutil.WriteFile(fileName, data, os.ModePerm); err != nil {
return err
}
a.Propagate()
return nil
}
// initCredentials initializes the api credentials.
func (c *Config) initCredentials() {
c.credentials = NewCredentials()
p := c.CredentialsFile()
if err := c.credentials.Load(p); err != nil {
log.Traceln(err)
}
c.credentials.Propagate()
}
// Credentials returns the api key instance.
func (c *Config) Credentials() *Credentials {
return c.credentials
}

View file

@ -1,84 +0,0 @@
package config
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewCredentials(t *testing.T) {
c := NewCredentials()
assert.IsType(t, &Credentials{}, c)
}
func TestCredentials_Load(t *testing.T) {
t.Run("existing filename", func(t *testing.T) {
c := NewCredentials()
if err := c.Load("testdata/credentials.yml"); err != nil {
t.Fatal(err)
}
assert.Equal(t, "f60f5b25d59c397989e3cd374f81cdd7710a4fca", c.Key)
assert.Equal(t, "photoprism", c.Secret)
assert.Equal(t, "Zm9vYmFy", c.Session)
})
t.Run("not existing filename", func(t *testing.T) {
c := NewCredentials()
if err := c.Load("testdata/credentials_xxx.yml"); err == nil {
t.Fatal("file should not exist")
}
assert.Equal(t, "", c.Key)
assert.Equal(t, "", c.Secret)
assert.Equal(t, "", c.Session)
})
}
func TestCredentials_Save(t *testing.T) {
t.Run("existing filename", func(t *testing.T) {
assert.FileExists(t, "testdata/credentials.yml")
c := NewCredentials()
if err := c.Load("testdata/credentials.yml"); err != nil {
t.Fatal(err)
}
assert.Equal(t, "f60f5b25d59c397989e3cd374f81cdd7710a4fca", c.Key)
assert.Equal(t, "photoprism", c.Secret)
assert.Equal(t, "Zm9vYmFy", c.Session)
if err := c.Save("testdata/credentials.yml"); err != nil {
t.Fatal(err)
}
assert.FileExists(t, "testdata/credentials.yml")
})
t.Run("not existing filename", func(t *testing.T) {
c := NewCredentials()
c.Key = "F60F5B25D59C397989E3CD374F81CDD7710A4FCA"
c.Secret = "foo"
c.Session = "bar"
assert.Equal(t, "F60F5B25D59C397989E3CD374F81CDD7710A4FCA", c.Key)
assert.Equal(t, "foo", c.Secret)
assert.Equal(t, "bar", c.Session)
if err := c.Save("testdata/credentials_new.yml"); err != nil {
t.Fatal(err)
}
assert.Equal(t, "f60f5b25d59c397989e3cd374f81cdd7710a4fca", c.Key)
assert.Equal(t, "", c.Secret)
assert.Equal(t, "", c.Session)
assert.FileExists(t, "testdata/credentials_new.yml")
if err := os.Remove("testdata/credentials_new.yml"); err != nil {
t.Fatal(err)
}
})
}

View file

@ -137,9 +137,9 @@ func (c *Config) ConfigFile() string {
return c.params.ConfigFile
}
// CredentialsFile returns the api credentials file name for hosted services like maps & places.
func (c *Config) CredentialsFile() string {
return filepath.Join(c.SettingsPath(), "credentials.yml")
// ProConfigFile returns the photoprism.pro api config file name.
func (c *Config) ProConfigFile() string {
return filepath.Join(c.SettingsPath(), "pro.yml")
}
// SettingsFile returns the user settings file name.

View file

@ -130,7 +130,7 @@ func NewTestConfig() *Config {
}
c.initSettings()
c.initCredentials()
c.initPro()
if err := c.Init(context.Background()); err != nil {
log.Fatalf("config: %s", err.Error())
@ -151,7 +151,7 @@ func NewTestErrorConfig() *Config {
c := &Config{params: NewTestParamsError()}
c.initSettings()
c.initCredentials()
c.initPro()
if err := c.Init(context.Background()); err != nil {
log.Fatalf("config: %s", err.Error())

View file

@ -1,3 +0,0 @@
key: f60f5b25d59c397989e3cd374f81cdd7710a4fca
secret: photoprism
session: Zm9vYmFy

View file

@ -31,7 +31,7 @@ type Types map[string]interface{}
var Entities = Types{
"errors": &Error{},
"addresses": &Address{},
"people": &Person{},
"users": &User{},
"accounts": &Account{},
"folders": &Folder{},
"duplicates": &Duplicate{},
@ -86,7 +86,7 @@ func (list Types) WaitForMigration() {
func (list Types) Truncate() {
for name := range list {
if err := Db().Exec(fmt.Sprintf("DELETE FROM %s WHERE 1", name)).Error; err == nil {
log.Debugf("entity: removed all data from %s", name)
// log.Debugf("entity: removed all data from %s", name)
break
} else if err.Error() != "record not found" {
log.Debugf("entity: %s in %s", err, name)

View file

@ -1,318 +0,0 @@
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:"-" yaml:"-"`
Address *Address `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false;PRELOAD:true;" json:"Address,omitempty" yaml:"Address,omitempty"`
AddressID int `gorm:"default:1" json:"-" yaml:"-"`
PersonUID string `gorm:"type:VARBINARY(42);unique_index;" json:"UID" yaml:"UID"`
ParentUID string `gorm:"type:VARBINARY(42);" json:"ParentUID" yaml:"ParentUID,omitempty"`
GlobalUID string `gorm:"type:VARBINARY(42);index;" json:"GlobalUID" yaml:"GlobalUID,omitempty"`
DisplayName string `gorm:"size:128;" json:"DisplayName" yaml:"DisplayName,omitempty"`
DisplayLocation string `gorm:"size:128;" 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"`
PrimaryEmail string `gorm:"size:255;index;" json:"PrimaryEmail" yaml:"PrimaryEmail,omitempty"`
BackupEmail string `gorm:"size:255;" json:"BackupEmail" yaml:"BackupEmail,omitempty"`
PersonURL string `gorm:"type:VARBINARY(255);" json:"PersonURL" yaml:"PersonURL,omitempty"`
PersonPhone string `gorm:"size:32;" json:"PersonPhone" yaml:"PersonPhone,omitempty"`
PersonStatus string `gorm:"type:VARBINARY(32);" json:"PersonStatus" yaml:"PersonStatus,omitempty"`
PersonAvatar string `gorm:"type:VARBINARY(255);" json:"PersonAvatar" yaml:"PersonAvatar,omitempty"`
PersonAccounts string `gorm:"type:LONGTEXT;" json:"-" yaml:"-"`
CompanyURL string `gorm:"type:VARBINARY(255);" json:"CompanyURL" yaml:"CompanyURL,omitempty"`
CompanyPhone string `gorm:"size:32;" json:"CompanyPhone" yaml:"CompanyPhone,omitempty"`
CompanyName string `gorm:"size:128;" json:"CompanyName" yaml:"CompanyName,omitempty"`
DepartmentName string `gorm:"size:128;" json:"DepartmentName" yaml:"DepartmentName,omitempty"`
JobTitle string `gorm:"size:64;" json:"JobTitle" yaml:"JobTitle,omitempty"`
BirthYear int `json:"BirthYear" yaml:"BirthYear,omitempty"`
BirthMonth int `json:"BirthMonth" yaml:"BirthMonth,omitempty"`
BirthDay int `json:"BirthDay" yaml:"BirthDay,omitempty"`
UserName string `gorm:"size:64;" json:"UserName" yaml:"UserName,omitempty"`
UserSettings string `gorm:"type:LONGTEXT;" json:"-" yaml:"-"`
TermsAccepted bool `json:"TermsAccepted" yaml:"TermsAccepted,omitempty"`
IsActive bool `json:"IsActive" yaml:"IsActive,omitempty"`
IsConfirmed bool `json:"IsConfirmed" yaml:"IsConfirmed,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"`
WebDAV bool `gorm:"column:webdav" json:"WebDAV" yaml:"WebDAV,omitempty"`
StoragePath string `gorm:"column:storage_path;type:VARBINARY(255);" json:"StoragePath" yaml:"StoragePath,omitempty"`
ConfirmToken string `gorm:"type:VARBINARY(128);" json:"-" yaml:"-"`
ResetToken string `gorm:"type:VARBINARY(128);" json:"-" yaml:"-"`
ApiToken string `gorm:"column:api_token;type:VARBINARY(128);" json:"-" yaml:"-"`
ApiSecret string `gorm:"column:api_secret;type:VARBINARY(128);" json:"-" yaml:"-"`
LoginAttempts int `json:"-" yaml:"-"`
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,
AddressID: 1,
UserName: "admin",
DisplayName: "Admin",
RoleAdmin: true,
IsActive: true,
}
// Anonymous, public user without own account.
var UnknownPerson = Person{
ID: -1,
AddressID: 1,
PersonUID: "u000000000000001",
UserName: "",
DisplayName: "Anonymous",
RoleAdmin: false,
RoleGuest: false,
IsActive: false,
}
// Guest user without own account for link sharing.
var Guest = Person{
ID: -2,
AddressID: 1,
PersonUID: "u000000000000002",
UserName: "",
DisplayName: "Guest",
RoleAdmin: false,
RoleGuest: true,
IsActive: 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().Preload("Address").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().Preload("Address").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().Preload("Address").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
}
time.Sleep(time.Second * 5 * time.Duration(m.LoginAttempts))
pw := FindPassword(m.PersonUID)
if pw == nil {
return true
}
if pw.InvalidPassword(password) {
if err := Db().Model(m).UpdateColumn("login_attempts", gorm.Expr("login_attempts + ?", 1)).Error; err != nil {
log.Errorf("person: %s (update login attempts)", err)
}
return true
}
if err := Db().Model(m).Updates(map[string]interface{}{"login_attempts": 0, "login_at": Timestamp()}).Error; err != nil {
log.Errorf("person: %s (update last login)", err)
}
return false
}
// 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
}

323
internal/entity/user.go Normal file
View file

@ -0,0 +1,323 @@
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 Users []User
// User represents a person that may optionally log in as user.
type User struct {
ID int `gorm:"primary_key" json:"-" yaml:"-"`
Address *Address `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false;PRELOAD:true;" json:"Address,omitempty" yaml:"Address,omitempty"`
AddressID int `gorm:"default:1" json:"-" yaml:"-"`
UserUID string `gorm:"type:VARBINARY(42);unique_index;" json:"UID" yaml:"UID"`
MotherUID string `gorm:"type:VARBINARY(42);" json:"MotherUID" yaml:"MotherUID,omitempty"`
FatherUID string `gorm:"type:VARBINARY(42);" json:"FatherUID" yaml:"FatherUID,omitempty"`
GlobalUID string `gorm:"type:VARBINARY(42);index;" json:"GlobalUID" yaml:"GlobalUID,omitempty"`
FullName string `gorm:"size:128;" json:"FullName" yaml:"FullName,omitempty"`
NickName string `gorm:"size:64;" json:"NickName" yaml:"NickName,omitempty"`
MaidenName string `gorm:"size:64;" json:"MaidenName" yaml:"MaidenName,omitempty"`
ArtistName string `gorm:"size:64;" json:"ArtistName" yaml:"ArtistName,omitempty"`
UserName string `gorm:"size:64;" json:"UserName" yaml:"UserName,omitempty"`
UserStatus string `gorm:"size:32;" json:"UserStatus" yaml:"UserStatus,omitempty"`
UserDisabled bool `json:"UserDisabled" yaml:"UserDisabled,omitempty"`
UserSettings string `gorm:"type:LONGTEXT;" json:"-" yaml:"-"`
PrimaryEmail string `gorm:"size:255;index;" json:"PrimaryEmail" yaml:"PrimaryEmail,omitempty"`
EmailConfirmed bool `json:"EmailConfirmed" yaml:"EmailConfirmed,omitempty"`
BackupEmail string `gorm:"size:255;" json:"BackupEmail" yaml:"BackupEmail,omitempty"`
PersonURL string `gorm:"type:VARBINARY(255);" json:"PersonURL" yaml:"PersonURL,omitempty"`
PersonPhone string `gorm:"size:32;" json:"PersonPhone" yaml:"PersonPhone,omitempty"`
PersonStatus string `gorm:"size:32;" json:"PersonStatus" yaml:"PersonStatus,omitempty"`
PersonAvatar string `gorm:"type:VARBINARY(255);" json:"PersonAvatar" yaml:"PersonAvatar,omitempty"`
PersonLocation string `gorm:"size:128;" json:"PersonLocation" yaml:"PersonLocation,omitempty"`
PersonBio string `gorm:"type:TEXT;" json:"PersonBio" yaml:"PersonBio,omitempty"`
PersonAccounts string `gorm:"type:LONGTEXT;" json:"-" yaml:"-"`
BusinessURL string `gorm:"type:VARBINARY(255);" json:"BusinessURL" yaml:"BusinessURL,omitempty"`
BusinessPhone string `gorm:"size:32;" json:"BusinessPhone" yaml:"BusinessPhone,omitempty"`
BusinessEmail string `gorm:"size:255;" json:"BusinessEmail" yaml:"BusinessEmail,omitempty"`
CompanyName string `gorm:"size:128;" json:"CompanyName" yaml:"CompanyName,omitempty"`
DepartmentName string `gorm:"size:128;" json:"DepartmentName" yaml:"DepartmentName,omitempty"`
JobTitle string `gorm:"size:64;" json:"JobTitle" yaml:"JobTitle,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"`
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"`
WebDAV bool `gorm:"column:webdav" json:"WebDAV" yaml:"WebDAV,omitempty"`
StoragePath string `gorm:"column:storage_path;type:VARBINARY(255);" json:"StoragePath" yaml:"StoragePath,omitempty"`
CanInvite bool `json:"CanInvite" yaml:"CanInvite,omitempty"`
InviteToken string `gorm:"type:VARBINARY(32);" json:"-" yaml:"-"`
InvitedBy string `gorm:"type:VARBINARY(32);" json:"-" yaml:"-"`
ConfirmToken string `gorm:"type:VARBINARY(64);" json:"-" yaml:"-"`
ResetToken string `gorm:"type:VARBINARY(64);" json:"-" yaml:"-"`
ApiToken string `gorm:"column:api_token;type:VARBINARY(128);" json:"-" yaml:"-"`
ApiSecret string `gorm:"column:api_secret;type:VARBINARY(128);" json:"-" yaml:"-"`
LoginAttempts int `json:"-" yaml:"-"`
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 (User) TableName() string {
return "users"
}
// Default admin user.
var Admin = User{
ID: 1,
AddressID: 1,
UserName: "admin",
FullName: "Admin",
RoleAdmin: true,
UserDisabled: false,
}
// Anonymous, public user without own account.
var UnknownUser = User{
ID: -1,
AddressID: 1,
UserUID: "u000000000000001",
UserName: "",
FullName: "Anonymous",
RoleAdmin: false,
RoleGuest: false,
UserDisabled: true,
}
// Guest user without own account for link sharing.
var Guest = User{
ID: -2,
AddressID: 1,
UserUID: "u000000000000002",
UserName: "",
FullName: "Guest",
RoleAdmin: false,
RoleGuest: true,
UserDisabled: true,
}
// CreateDefaultUsers initializes the database with default user accounts.
func CreateDefaultUsers() {
if user := FirstOrCreateUser(&Admin); user != nil {
Admin = *user
}
if user := FirstOrCreateUser(&UnknownUser); user != nil {
UnknownUser = *user
}
if user := FirstOrCreateUser(&Guest); user != nil {
Guest = *user
}
}
// Create inserts a new row to the database.
func (m *User) Create() error {
return Db().Create(m).Error
}
// Saves the new row to the database.
func (m *User) Save() error {
return Db().Save(m).Error
}
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
func (m *User) BeforeCreate(scope *gorm.Scope) error {
if rnd.IsUID(m.UserUID, 'u') {
return nil
}
return scope.SetColumn("UserUID", rnd.PPID('u'))
}
// FirstOrCreateUser returns an existing row, inserts a new row or nil in case of errors.
func FirstOrCreateUser(m *User) *User {
result := User{}
if err := Db().Preload("Address").Where("id = ? OR user_uid = ?", m.ID, m.UserUID).First(&result).Error; err == nil {
return &result
} else if err := m.Create(); err != nil {
log.Debugf("user: %s", err)
return nil
}
return m
}
// FindUserByName returns an existing user or nil if not found.
func FindUserByName(userName string) *User {
if userName == "" {
return nil
}
result := User{}
if err := Db().Preload("Address").Where("user_name = ?", userName).First(&result).Error; err == nil {
return &result
} else {
log.Debugf("user %s not found", txt.Quote(userName))
return nil
}
}
// FindUserByUID returns an existing user or nil if not found.
func FindUserByUID(uid string) *User {
if uid == "" {
return nil
}
result := User{}
if err := Db().Preload("Address").Where("user_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 *User) String() string {
if m.UserName != "" {
return m.UserName
}
if m.FullName != "" {
return m.FullName
}
return m.UserUID
}
// User returns true if the user has a user name.
func (m *User) Registered() bool {
return m.UserName != "" && rnd.IsPPID(m.UserUID, 'u')
}
// Admin returns true if the user is an admin with user name.
func (m *User) Admin() bool {
return m.Registered() && m.RoleAdmin
}
// Anonymous returns true if the user is unknown.
func (m *User) Anonymous() bool {
return !rnd.IsPPID(m.UserUID, 'u') || m.ID == UnknownUser.ID || m.UserUID == UnknownUser.UserUID
}
// Guest returns true if the user is a guest.
func (m *User) Guest() bool {
return m.RoleGuest
}
// SetPassword sets a new password stored as hash.
func (m *User) 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.UserUID, password)
return pw.Save()
}
// InitPassword sets the initial user password stored as hash.
func (m *User) InitPassword(password string) {
if !m.Registered() {
log.Warn("only registered users can change their password")
return
}
if password == "" {
return
}
existing := FindPassword(m.UserUID)
if existing != nil {
return
}
pw := NewPassword(m.UserUID, password)
if err := pw.Save(); err != nil {
log.Error(err)
}
}
// InvalidPassword returns true if the given password does not match the hash.
func (m *User) InvalidPassword(password string) bool {
if !m.Registered() {
log.Warn("only registered users can change their password")
return true
}
if password == "" {
return true
}
time.Sleep(time.Second * 5 * time.Duration(m.LoginAttempts))
pw := FindPassword(m.UserUID)
if pw == nil {
return true
}
if pw.InvalidPassword(password) {
if err := Db().Model(m).UpdateColumn("login_attempts", gorm.Expr("login_attempts + ?", 1)).Error; err != nil {
log.Errorf("user: %s (update login attempts)", err)
}
return true
}
if err := Db().Model(m).Updates(map[string]interface{}{"login_attempts": 0, "login_at": Timestamp()}).Error; err != nil {
log.Errorf("user: %s (update last login)", err)
}
return false
}
// Role returns the user role for ACL permission checks.
func (m *User) 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
}

View file

@ -7,24 +7,24 @@ import (
"github.com/stretchr/testify/assert"
)
func TestFindPersonByUserName(t *testing.T) {
func TestFindUserByName(t *testing.T) {
t.Run("admin", func(t *testing.T) {
m := FindPersonByUserName("admin")
m := FindUserByName("admin")
if m == nil {
t.Fatal("result should not be nil")
}
assert.Equal(t, 1, m.ID)
assert.NotEmpty(t, m.PersonUID)
assert.NotEmpty(t, m.UserUID)
assert.Equal(t, "admin", m.UserName)
assert.Equal(t, "Admin", m.DisplayName)
assert.Equal(t, "Admin", m.FullName)
assert.NotEmpty(t, m.CreatedAt)
assert.NotEmpty(t, m.UpdatedAt)
})
t.Run("unknown", func(t *testing.T) {
m := FindPersonByUserName("")
m := FindUserByName("")
if m != nil {
t.Fatal("result should be nil")
@ -32,7 +32,7 @@ func TestFindPersonByUserName(t *testing.T) {
})
t.Run("not found", func(t *testing.T) {
m := FindPersonByUserName("xxx")
m := FindUserByName("xxx")
if m != nil {
t.Fatal("result should be nil")
@ -40,9 +40,9 @@ func TestFindPersonByUserName(t *testing.T) {
})
}
func TestPerson_InvalidPassword(t *testing.T) {
func TestUser_InvalidPassword(t *testing.T) {
t.Run("admin", func(t *testing.T) {
m := FindPersonByUserName("admin")
m := FindUserByName("admin")
if m == nil {
t.Fatal("result should not be nil")
@ -51,24 +51,24 @@ func TestPerson_InvalidPassword(t *testing.T) {
assert.False(t, m.InvalidPassword("photoprism"))
})
t.Run("no password existing", func(t *testing.T) {
p := Person{PersonUID: "u000000000000010", UserName: "Hans", DisplayName: ""}
p := User{UserUID: "u000000000000010", UserName: "Hans", FullName: ""}
p.Save()
assert.True(t, p.InvalidPassword("abcdef"))
})
t.Run("not registered", func(t *testing.T) {
p := Person{PersonUID: "u12", UserName: "", DisplayName: ""}
p := User{UserUID: "u12", UserName: "", FullName: ""}
assert.True(t, p.InvalidPassword("abcdef"))
})
t.Run("password empty", func(t *testing.T) {
p := Person{PersonUID: "u000000000000011", UserName: "User", DisplayName: ""}
p := User{UserUID: "u000000000000011", UserName: "User", FullName: ""}
assert.True(t, p.InvalidPassword(""))
})
}
func TestPerson_Save(t *testing.T) {
func TestUser_Save(t *testing.T) {
t.Run("success", func(t *testing.T) {
p := Person{}
p := User{}
err := p.Save()
@ -78,11 +78,11 @@ func TestPerson_Save(t *testing.T) {
})
}
func TestFirstOrCreatePerson(t *testing.T) {
func TestFirstOrCreateUser(t *testing.T) {
t.Run("not existing", func(t *testing.T) {
p := &Person{ID: 555}
p := &User{ID: 555}
result := FirstOrCreatePerson(p)
result := FirstOrCreateUser(p)
if result == nil {
t.Fatal("result should not be nil")
}
@ -91,14 +91,14 @@ func TestFirstOrCreatePerson(t *testing.T) {
})
t.Run("existing", func(t *testing.T) {
p := &Person{ID: 1234}
p := &User{ID: 1234}
err := p.Save()
if err != nil {
t.Fatal(err)
}
result := FirstOrCreatePerson(p)
result := FirstOrCreateUser(p)
if result == nil {
t.Fatal("result should not be nil")
@ -107,24 +107,24 @@ func TestFirstOrCreatePerson(t *testing.T) {
})
}
func TestFindPersonByUID(t *testing.T) {
func TestFindUserByUID(t *testing.T) {
t.Run("guest", func(t *testing.T) {
m := FindPersonByUID("u000000000000002")
m := FindUserByUID("u000000000000002")
if m == nil {
t.Fatal("result should not be nil")
}
assert.Equal(t, -2, m.ID)
assert.NotEmpty(t, m.PersonUID)
assert.NotEmpty(t, m.UserUID)
assert.Equal(t, "", m.UserName)
assert.Equal(t, "Guest", m.DisplayName)
assert.Equal(t, "Guest", m.FullName)
assert.NotEmpty(t, m.CreatedAt)
assert.NotEmpty(t, m.UpdatedAt)
})
t.Run("unknown", func(t *testing.T) {
m := FindPersonByUID("")
m := FindUserByUID("")
if m != nil {
t.Fatal("result should be nil")
@ -132,7 +132,7 @@ func TestFindPersonByUID(t *testing.T) {
})
t.Run("not found", func(t *testing.T) {
m := FindPersonByUID("xxx")
m := FindUserByUID("xxx")
if m != nil {
t.Fatal("result should be nil")
@ -140,75 +140,75 @@ func TestFindPersonByUID(t *testing.T) {
})
}
func TestPerson_String(t *testing.T) {
func TestUser_String(t *testing.T) {
t.Run("return UID", func(t *testing.T) {
p := Person{PersonUID: "abc123", UserName: "", DisplayName: ""}
p := User{UserUID: "abc123", UserName: "", FullName: ""}
assert.Equal(t, "abc123", p.String())
})
t.Run("return display name", func(t *testing.T) {
p := Person{PersonUID: "abc123", UserName: "", DisplayName: "Test"}
p := User{UserUID: "abc123", UserName: "", FullName: "Test"}
assert.Equal(t, "Test", p.String())
})
t.Run("return user name", func(t *testing.T) {
p := Person{PersonUID: "abc123", UserName: "Super-User", DisplayName: "Test"}
p := User{UserUID: "abc123", UserName: "Super-User", FullName: "Test"}
assert.Equal(t, "Super-User", p.String())
})
}
func TestPerson_Admin(t *testing.T) {
func TestUser_Admin(t *testing.T) {
t.Run("true", func(t *testing.T) {
p := Person{PersonUID: "u000000000000008", UserName: "Hanna", DisplayName: "", RoleAdmin: true}
p := User{UserUID: "u000000000000008", UserName: "Hanna", FullName: "", RoleAdmin: true}
assert.True(t, p.Admin())
})
t.Run("false", func(t *testing.T) {
p := Person{PersonUID: "u000000000000008", UserName: "Hanna", DisplayName: "", RoleAdmin: false}
p := User{UserUID: "u000000000000008", UserName: "Hanna", FullName: "", RoleAdmin: false}
assert.False(t, p.Admin())
})
}
func TestPerson_Anonymous(t *testing.T) {
func TestUser_Anonymous(t *testing.T) {
t.Run("true", func(t *testing.T) {
p := Person{PersonUID: "", UserName: "Hanna", DisplayName: ""}
p := User{UserUID: "", UserName: "Hanna", FullName: ""}
assert.True(t, p.Anonymous())
})
t.Run("false", func(t *testing.T) {
p := Person{PersonUID: "u000000000000008", UserName: "Hanna", DisplayName: "", RoleAdmin: true}
p := User{UserUID: "u000000000000008", UserName: "Hanna", FullName: "", RoleAdmin: true}
assert.False(t, p.Anonymous())
})
}
func TestPerson_Guest(t *testing.T) {
func TestUser_Guest(t *testing.T) {
t.Run("true", func(t *testing.T) {
p := Person{PersonUID: "", UserName: "Hanna", DisplayName: "", RoleGuest: true}
p := User{UserUID: "", UserName: "Hanna", FullName: "", RoleGuest: true}
assert.True(t, p.Guest())
})
t.Run("false", func(t *testing.T) {
p := Person{PersonUID: "", UserName: "Hanna", DisplayName: ""}
p := User{UserUID: "", UserName: "Hanna", FullName: ""}
assert.False(t, p.Guest())
})
}
func TestPerson_SetPassword(t *testing.T) {
func TestUser_SetPassword(t *testing.T) {
t.Run("success", func(t *testing.T) {
p := Person{PersonUID: "u000000000000008", UserName: "Hanna", DisplayName: ""}
p := User{UserUID: "u000000000000008", UserName: "Hanna", FullName: ""}
if err := p.SetPassword("abcdefgt"); err != nil {
t.Fatal(err)
}
})
t.Run("not registered", func(t *testing.T) {
p := Person{PersonUID: "", UserName: "Hanna", DisplayName: ""}
p := User{UserUID: "", UserName: "Hanna", FullName: ""}
assert.Error(t, p.SetPassword("abchjy"))
})
t.Run("password too short", func(t *testing.T) {
p := Person{PersonUID: "u000000000000008", UserName: "Hanna", DisplayName: ""}
p := User{UserUID: "u000000000000008", UserName: "Hanna", FullName: ""}
assert.Error(t, p.SetPassword("abc"))
})
}
func TestPerson_InitPassword(t *testing.T) {
func TestUser_InitPassword(t *testing.T) {
t.Run("success", func(t *testing.T) {
p := Person{PersonUID: "u000000000000009", UserName: "Hanna", DisplayName: ""}
p := User{UserUID: "u000000000000009", UserName: "Hanna", FullName: ""}
assert.Nil(t, FindPassword("u000000000000009"))
p.InitPassword("abcdek")
m := FindPassword("u000000000000009")
@ -218,7 +218,7 @@ func TestPerson_InitPassword(t *testing.T) {
}
})
t.Run("already existing", func(t *testing.T) {
p := Person{PersonUID: "u000000000000010", UserName: "Hans", DisplayName: ""}
p := User{UserUID: "u000000000000010", UserName: "Hans", FullName: ""}
p.Save()
p.SetPassword("hutfdt")
assert.NotNil(t, FindPassword("u000000000000010"))
@ -230,42 +230,42 @@ func TestPerson_InitPassword(t *testing.T) {
}
})
t.Run("not registered", func(t *testing.T) {
p := Person{PersonUID: "u12", UserName: "", DisplayName: ""}
p := User{UserUID: "u12", UserName: "", FullName: ""}
assert.Nil(t, FindPassword("u12"))
p.InitPassword("dcjygkh")
assert.Nil(t, FindPassword("u12"))
})
t.Run("password empty", func(t *testing.T) {
p := Person{PersonUID: "u000000000000011", UserName: "User", DisplayName: ""}
p := User{UserUID: "u000000000000011", UserName: "User", FullName: ""}
assert.Nil(t, FindPassword("u000000000000011"))
p.InitPassword("")
assert.Nil(t, FindPassword("u000000000000011"))
})
}
func TestPerson_Role(t *testing.T) {
func TestUser_Role(t *testing.T) {
t.Run("admin", func(t *testing.T) {
p := Person{PersonUID: "u000000000000008", UserName: "Hanna", DisplayName: "", RoleAdmin: true}
p := User{UserUID: "u000000000000008", UserName: "Hanna", FullName: "", RoleAdmin: true}
assert.Equal(t, acl.Role("admin"), p.Role())
})
t.Run("child", func(t *testing.T) {
p := Person{PersonUID: "u000000000000008", UserName: "Hanna", DisplayName: "", RoleChild: true}
p := User{UserUID: "u000000000000008", UserName: "Hanna", FullName: "", RoleChild: true}
assert.Equal(t, acl.Role("child"), p.Role())
})
t.Run("family", func(t *testing.T) {
p := Person{PersonUID: "u000000000000008", UserName: "Hanna", DisplayName: "", RoleFamily: true}
p := User{UserUID: "u000000000000008", UserName: "Hanna", FullName: "", RoleFamily: true}
assert.Equal(t, acl.Role("family"), p.Role())
})
t.Run("friend", func(t *testing.T) {
p := Person{PersonUID: "u000000000000008", UserName: "Hanna", DisplayName: "", RoleFriend: true}
p := User{UserUID: "u000000000000008", UserName: "Hanna", FullName: "", RoleFriend: true}
assert.Equal(t, acl.Role("friend"), p.Role())
})
t.Run("guest", func(t *testing.T) {
p := Person{PersonUID: "u000000000000008", UserName: "Hanna", DisplayName: "", RoleGuest: true}
p := User{UserUID: "u000000000000008", UserName: "Hanna", FullName: "", RoleGuest: true}
assert.Equal(t, acl.Role("guest"), p.Role())
})
t.Run("default", func(t *testing.T) {
p := Person{PersonUID: "u000000000000008", UserName: "Hanna", DisplayName: ""}
p := User{UserUID: "u000000000000008", UserName: "Hanna", FullName: ""}
assert.Equal(t, acl.Role("*"), p.Role())
})
}

View file

@ -5,7 +5,7 @@ import (
"strings"
"github.com/photoprism/photoprism/internal/maps/osm"
"github.com/photoprism/photoprism/internal/maps/places"
"github.com/photoprism/photoprism/internal/pro/places"
"github.com/photoprism/photoprism/pkg/s2"
)

View file

@ -4,7 +4,7 @@ import (
"strings"
"testing"
"github.com/photoprism/photoprism/internal/maps/places"
"github.com/photoprism/photoprism/internal/pro/places"
"github.com/photoprism/photoprism/pkg/s2"
"github.com/stretchr/testify/assert"
)

View file

@ -346,7 +346,7 @@ func TestMediaFileCanonicalName(t *testing.T) {
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "20180111_110938_B6B8AB4F", mediaFile.CanonicalName())
assert.Equal(t, "20180111_110938_7D8F8A23", mediaFile.CanonicalName())
}
func TestMediaFileCanonicalNameFromFile(t *testing.T) {

200
internal/pro/config.go Normal file
View file

@ -0,0 +1,200 @@
package pro
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"time"
"github.com/photoprism/photoprism/internal/pro/places"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/txt"
"gopkg.in/yaml.v2"
)
// Config represents photoprism.pro api credentials for maps & geodata.
type Config struct {
Key string `json:"key" yaml:"key"`
Secret string `json:"secret" yaml:"secret"`
Session string `json:"session" yaml:"session"`
Status string `json:"status" yaml:"status"`
Version string `json:"version" yaml:"version"`
}
// NewConfig creates a new photoprism.pro api credentials instance.
func NewConfig(version string) *Config {
return &Config{
Key: "",
Secret: "",
Session: "",
Status: "",
Version: version,
}
}
// Propagate updates photoprism.pro api credentials in other packages.
func (p *Config) Propagate() {
places.Key = p.Key
places.Secret = p.Secret
}
// Sanitize verifies and sanitizes photoprism.pro api credentials.
func (p *Config) Sanitize() {
p.Key = strings.ToLower(p.Key)
if p.Secret != "" {
if p.Key != fmt.Sprintf("%x", sha1.Sum([]byte(p.Secret))) {
p.Key = ""
p.Secret = ""
p.Session = ""
p.Status = ""
}
}
}
// DecodeSession decodes photoprism.pro api session data.
func (p *Config) DecodeSession() (Session, error) {
p.Sanitize()
result := Session{}
if p.Session == "" {
return result, fmt.Errorf("empty session")
}
s, err := hex.DecodeString(p.Session)
if err != nil {
return result, err
}
hash := sha256.New()
hash.Write([]byte(p.Secret))
var b []byte
block, err := aes.NewCipher(hash.Sum(b))
if err != nil {
return result, err
}
iv := s[:aes.BlockSize]
plaintext := make([]byte, len(s))
stream := cipher.NewCTR(block, iv)
stream.XORKeyStream(plaintext, s[aes.BlockSize:])
plaintext = bytes.Trim(plaintext, "\x00")
if err := json.Unmarshal(plaintext, &result); err != nil {
return result, err
}
return result, nil
}
// Refresh updates photoprism.pro api credentials.
func (p *Config) Refresh() (err error) {
p.Sanitize()
client := &http.Client{Timeout: 60 * time.Second}
url := ApiURL
method := http.MethodPost
var req *http.Request
if p.Key != "" {
url = fmt.Sprintf(ApiURL+"/%s", p.Key)
method = http.MethodPut
log.Debugf("pro: updating api key for maps & places")
} else {
log.Debugf("pro: requesting api key for maps & places")
}
if j, err := json.Marshal(NewRequest(p.Version)); err != nil {
return err
} else if req, err = http.NewRequest(method, url, bytes.NewReader(j)); err != nil {
return err
}
req.Header.Add("Content-Type", "application/json")
var r *http.Response
for i := 0; i < 3; i++ {
r, err = client.Do(req)
if err == nil {
break
}
}
if err != nil {
log.Errorf("pro: %s", err.Error())
return err
} else if r.StatusCode >= 400 {
err = fmt.Errorf("api key request for maps & places failed with code %d", r.StatusCode)
return err
}
err = json.NewDecoder(r.Body).Decode(p)
if err != nil {
log.Errorf("pro: %s", err.Error())
return err
}
return nil
}
// Load photoprism.pro api credentials from a YAML file.
func (p *Config) Load(fileName string) error {
if !fs.FileExists(fileName) {
return fmt.Errorf("api key file not found: %s", txt.Quote(fileName))
}
yamlConfig, err := ioutil.ReadFile(fileName)
if err != nil {
return err
}
if err := yaml.Unmarshal(yamlConfig, p); err != nil {
return err
}
p.Sanitize()
p.Propagate()
return nil
}
// Save photoprism.pro api credentials to a YAML file.
func (p *Config) Save(fileName string) error {
p.Sanitize()
data, err := yaml.Marshal(p)
if err != nil {
return err
}
p.Propagate()
if err := ioutil.WriteFile(fileName, data, os.ModePerm); err != nil {
return err
}
p.Propagate()
return nil
}

38
internal/pro/pro.go Normal file
View file

@ -0,0 +1,38 @@
/*
Package pro contains photoprism.pro api config & clients.
Copyright (c) 2018 - 2020 Michael Mayer <hello@photoprism.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
PhotoPrism® is a registered trademark of Michael Mayer. You may use it as required
to describe our software, run your own server, for educational purposes, but not for
offering commercial goods, products, or services without prior written permission.
In other words, please ask.
Feel free to send an e-mail to hello@photoprism.org if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.org/developer-guide/
*/
package pro
import (
"github.com/photoprism/photoprism/internal/event"
)
var log = event.Log

249
internal/pro/pro_test.go Normal file
View file

@ -0,0 +1,249 @@
package pro
import (
"crypto/rand"
"encoding/binary"
"encoding/json"
"fmt"
"os"
"strconv"
"testing"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
func TestMain(m *testing.M) {
log = logrus.StandardLogger()
log.SetLevel(logrus.DebugLevel)
ApiURL = "https://api-int.photoprism.pro/v1/hello"
code := m.Run()
os.Exit(code)
}
// Token returns a random token with length of up to 10 characters.
func Token(size uint) string {
if size > 10 || size < 1 {
panic(fmt.Sprintf("size out of range: %d", size))
}
result := make([]byte, 0, 14)
b := make([]byte, 8)
if _, err := rand.Read(b); err != nil {
panic(err)
}
randomInt := binary.BigEndian.Uint64(b)
result = append(result, strconv.FormatUint(randomInt, 36)...)
for i := len(result); i < cap(result); i++ {
result = append(result, byte(123-(cap(result)-i)))
}
return string(result[:size])
}
func TestNewPro(t *testing.T) {
c := NewConfig("develop")
assert.IsType(t, &Config{}, c)
}
func TestNewProRequest(t *testing.T) {
r := NewRequest("develop")
assert.IsType(t, &Request{}, r)
t.Logf("Request: %+v", r)
if j, err := json.Marshal(r); err != nil {
t.Fatal(err)
} else {
t.Logf("JSON: %s", j)
}
}
func TestConfig_Refresh(t *testing.T) {
t.Run("success", func(t *testing.T) {
fileName := fmt.Sprintf("testdata/pro.%s.yml", Token(8))
c := NewConfig("develop")
if err := c.Refresh(); err != nil {
t.Fatal(err)
}
assert.Len(t, c.Key, 40)
assert.Len(t, c.Secret, 32)
assert.Equal(t, "develop", c.Version)
if sess, err := c.DecodeSession(); err != nil {
t.Fatal(err)
} else if sess.Expired() {
t.Fatalf("session expired: %+v", sess)
}
if err := c.Save(fileName); err != nil {
t.Fatal(err)
}
defer os.Remove(fileName)
assert.FileExists(t, fileName)
if err := c.Refresh(); err != nil {
t.Fatal(err)
}
assert.Len(t, c.Key, 40)
assert.Len(t, c.Secret, 32)
assert.Equal(t, "develop", c.Version)
if sess, err := c.DecodeSession(); err != nil {
t.Fatal(err)
} else if sess.Expired() {
t.Fatal("session expired")
}
if err := c.Save(fileName); err != nil {
t.Fatal(err)
}
assert.FileExists(t, fileName)
})
}
func TestConfig_DecodeSession(t *testing.T) {
t.Run("pro3.yml", func(t *testing.T) {
c := NewConfig("develop")
if err := c.Load("testdata/pro3.yml"); err != nil {
t.Fatal(err)
}
assert.Equal(t, "8dd8b115d052f91ac74b1c2475e305009366c487", c.Key)
assert.Equal(t, "ddf4ce46afbf6c16a6bd8555ab1e4efb", c.Secret)
assert.Equal(t, "7607796238c26b2d95007957b05c72d63f504346576bc2aa064a6dc54344de47d2ab38422bd1d061c067a16ef517e6054d8b7f5336c120431935518277fed45e49472aaf740cac1bc33ab2e362c767007a59e953e9973709", c.Session)
assert.Equal(t, "unregistered", c.Status)
assert.Equal(t, "develop", c.Version)
if sess, err := c.DecodeSession(); err != nil {
t.Fatal(err)
} else if sess.Expired() {
t.Logf("expired session: %+v", sess)
} else {
t.Errorf("session should be expired: %+v", sess)
}
})
}
func TestConfig_Load(t *testing.T) {
t.Run("pro1.yml", func(t *testing.T) {
c := NewConfig("develop")
if err := c.Load("testdata/pro1.yml"); err != nil {
t.Fatal(err)
}
assert.Equal(t, "b32e9ccdc90eb7c0f6f1b9fbc82b8a2b0e993304", c.Key)
assert.Equal(t, "5991ea36a9611e9e00a8360c10b91567", c.Secret)
assert.Equal(t, "3ef5685c6391a568731c8fc94ccad82d92dea60476c8b672990047c822248f45366fc0e8e812ad15e0b5ae1eb20e866235c56b", c.Session)
assert.Equal(t, "unregistered", c.Status)
assert.Equal(t, "develop", c.Version)
})
t.Run("pro2.yml", func(t *testing.T) {
c := NewConfig("develop")
if err := c.Load("testdata/pro2.yml"); err != nil {
t.Fatal(err)
}
assert.Equal(t, "ab66cb5cfb3658dbea0a1433df048d900934ac68", c.Key)
assert.Equal(t, "6b0f8440fe307d3120b3a4366350094b", c.Secret)
assert.Equal(t, "c0ca88fc3094b70a1947b5b10f980a420cd6b1542a20f6f26ecc6a16f340473b9fb16b80be1078e86d886b3a8d46bf8184d147", c.Session)
assert.Equal(t, "unregistered", c.Status)
assert.Equal(t, "200925-f8e2b580-Darwin-i386-DEBUG", c.Version)
})
t.Run("not existing filename", func(t *testing.T) {
c := NewConfig("develop")
if err := c.Load("testdata/pro_xxx.yml"); err == nil {
t.Fatal("file should not exist")
}
assert.Equal(t, "", c.Key)
assert.Equal(t, "", c.Secret)
assert.Equal(t, "", c.Session)
})
}
func TestConfig_Save(t *testing.T) {
t.Run("existing filename", func(t *testing.T) {
assert.FileExists(t, "testdata/pro1.yml")
c := NewConfig("develop")
if err := c.Load("testdata/pro1.yml"); err != nil {
t.Fatal(err)
}
assert.Equal(t, "b32e9ccdc90eb7c0f6f1b9fbc82b8a2b0e993304", c.Key)
assert.Equal(t, "5991ea36a9611e9e00a8360c10b91567", c.Secret)
assert.Equal(t, "3ef5685c6391a568731c8fc94ccad82d92dea60476c8b672990047c822248f45366fc0e8e812ad15e0b5ae1eb20e866235c56b", c.Session)
assert.Equal(t, "unregistered", c.Status)
assert.Equal(t, "develop", c.Version)
if err := c.Save("testdata/pro-save.yml"); err != nil {
t.Fatal(err)
}
defer os.Remove("testdata/pro-save.yml")
assert.Equal(t, "b32e9ccdc90eb7c0f6f1b9fbc82b8a2b0e993304", c.Key)
assert.Equal(t, "5991ea36a9611e9e00a8360c10b91567", c.Secret)
assert.Equal(t, "3ef5685c6391a568731c8fc94ccad82d92dea60476c8b672990047c822248f45366fc0e8e812ad15e0b5ae1eb20e866235c56b", c.Session)
assert.Equal(t, "unregistered", c.Status)
assert.Equal(t, "develop", c.Version)
assert.FileExists(t, "testdata/pro-save.yml")
if err := c.Load("testdata/pro-save.yml"); err != nil {
t.Fatal(err)
}
assert.Equal(t, "b32e9ccdc90eb7c0f6f1b9fbc82b8a2b0e993304", c.Key)
assert.Equal(t, "5991ea36a9611e9e00a8360c10b91567", c.Secret)
assert.Equal(t, "3ef5685c6391a568731c8fc94ccad82d92dea60476c8b672990047c822248f45366fc0e8e812ad15e0b5ae1eb20e866235c56b", c.Session)
assert.Equal(t, "unregistered", c.Status)
assert.Equal(t, "develop", c.Version)
})
t.Run("not existing filename", func(t *testing.T) {
c := NewConfig("develop")
c.Key = "F60F5B25D59C397989E3CD374F81CDD7710A4FCA"
c.Secret = "foo"
c.Session = "bar"
assert.Equal(t, "F60F5B25D59C397989E3CD374F81CDD7710A4FCA", c.Key)
assert.Equal(t, "foo", c.Secret)
assert.Equal(t, "bar", c.Session)
if err := c.Save("testdata/pro_new.yml"); err != nil {
t.Fatal(err)
}
assert.Equal(t, "", c.Key)
assert.Equal(t, "", c.Secret)
assert.Equal(t, "", c.Session)
assert.FileExists(t, "testdata/pro_new.yml")
if err := os.Remove("testdata/pro_new.yml"); err != nil {
t.Fatal(err)
}
})
}

23
internal/pro/request.go Normal file
View file

@ -0,0 +1,23 @@
package pro
import "runtime"
var ApiURL = "https://api.photoprism.pro/v1/hello"
// photoprism.pro api credentials request incl basic runtime specs for statistical evaluation.
type Request struct {
ClientVersion string `json:"ClientVersion"`
ClientOS string `json:"ClientOS"`
ClientArch string `json:"ClientArch"`
ClientCPU int `json:"ClientCPU"`
}
// NewRequest creates a new photoprism.pro key request instance.
func NewRequest(version string) *Request {
return &Request{
ClientVersion: version,
ClientOS: runtime.GOOS,
ClientArch: runtime.GOARCH,
ClientCPU: runtime.NumCPU(),
}
}

22
internal/pro/session.go Normal file
View file

@ -0,0 +1,22 @@
package pro
import "time"
// Session represents photoprism.pro api session data.
type Session struct {
MaptilerKey string
ExpiresAt string
}
// Expired tests if the api session is expired.
func (p *Session) Expired() bool {
if p.ExpiresAt == "" {
return true
} else if date, err := time.Parse("2006-01-02T15:04:05", p.ExpiresAt); err != nil {
return true
} else if date.Before(time.Now()) {
return true
}
return false
}

5
internal/pro/testdata/pro1.yml vendored Normal file
View file

@ -0,0 +1,5 @@
key: b32e9ccdc90eb7c0f6f1b9fbc82b8a2b0e993304
secret: 5991ea36a9611e9e00a8360c10b91567
session: 3ef5685c6391a568731c8fc94ccad82d92dea60476c8b672990047c822248f45366fc0e8e812ad15e0b5ae1eb20e866235c56b
status: unregistered
version: develop

5
internal/pro/testdata/pro2.yml vendored Normal file
View file

@ -0,0 +1,5 @@
key: ab66cb5cfb3658dbea0a1433df048d900934ac68
secret: 6b0f8440fe307d3120b3a4366350094b
session: c0ca88fc3094b70a1947b5b10f980a420cd6b1542a20f6f26ecc6a16f340473b9fb16b80be1078e86d886b3a8d46bf8184d147
status: unregistered
version: 200925-f8e2b580-Darwin-i386-DEBUG

5
internal/pro/testdata/pro3.yml vendored Executable file
View file

@ -0,0 +1,5 @@
key: 8dd8b115d052f91ac74b1c2475e305009366c487
secret: ddf4ce46afbf6c16a6bd8555ab1e4efb
session: 7607796238c26b2d95007957b05c72d63f504346576bc2aa064a6dc54344de47d2ab38422bd1d061c067a16ef517e6054d8b7f5336c120431935518277fed45e49472aaf740cac1bc33ab2e362c767007a59e953e9973709
status: unregistered
version: develop

View file

@ -12,9 +12,9 @@ import (
)
var basicAuth = struct {
user map[string]entity.Person
user map[string]entity.User
mutex sync.RWMutex
}{user: make(map[string]entity.Person)}
}{user: make(map[string]entity.User)}
func GetCredentials(c *gin.Context) (username, password, raw string) {
data := c.GetHeader("Authorization")
@ -53,11 +53,11 @@ func BasicAuth() gin.HandlerFunc {
defer basicAuth.mutex.Unlock()
if user, ok := basicAuth.user[raw]; ok {
c.Set(gin.AuthUserKey, user.PersonUID)
c.Set(gin.AuthUserKey, user.UserUID)
return
}
user := entity.FindPersonByUserName(username)
user := entity.FindUserByName(username)
if user != nil {
invalid = user.InvalidPassword(password)
@ -71,6 +71,6 @@ func BasicAuth() gin.HandlerFunc {
basicAuth.user[raw] = *user
c.Set(gin.AuthUserKey, user.PersonUID)
c.Set(gin.AuthUserKey, user.UserUID)
}
}

View file

@ -19,17 +19,17 @@ func (list UIDs) String() string {
}
type Data struct {
User entity.Person `json:"user"` // Session user, guest or anonymous person.
Tokens []string `json:"tokens"` // Slice of secret share tokens.
Shares UIDs `json:"shares"` // Slice of shared entity UIDs.
User entity.User `json:"user"` // Session user, guest or anonymous person.
Tokens []string `json:"tokens"` // Slice of secret share tokens.
Shares UIDs `json:"shares"` // Slice of shared entity UIDs.
}
func (s Data) Saved() Saved {
return Saved{User: s.User.PersonUID, Tokens: s.Tokens}
return Saved{User: s.User.UserUID, Tokens: s.Tokens}
}
func (s Data) Invalid() bool {
return s.User.ID == 0 || s.User.PersonUID == "" || (s.Guest() && s.NoShares())
return s.User.ID == 0 || s.User.UserUID == "" || (s.Guest() && s.NoShares())
}
func (s Data) Valid() bool {

View file

@ -34,7 +34,7 @@ func New(expiration time.Duration, cachePath string) *Session {
log.Errorf("session: %s", err)
} else {
for key, saved := range savedItems {
user := entity.FindPersonByUID(saved.User)
user := entity.FindUserByUID(saved.User)
if user == nil {
continue