Backend: Refactor user entity and add pro package
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
1516f556c2
commit
46b9239026
38 changed files with 1037 additions and 652 deletions
9
frontend/package-lock.json
generated
9
frontend/package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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 "";
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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)})
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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", `{}`)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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())
|
||||
|
|
3
internal/config/testdata/credentials.yml
vendored
3
internal/config/testdata/credentials.yml
vendored
|
@ -1,3 +0,0 @@
|
|||
key: f60f5b25d59c397989e3cd374f81cdd7710a4fca
|
||||
secret: photoprism
|
||||
session: Zm9vYmFy
|
|
@ -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)
|
||||
|
|
|
@ -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
323
internal/entity/user.go
Normal 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
|
||||
}
|
|
@ -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())
|
||||
})
|
||||
}
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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
200
internal/pro/config.go
Normal 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
38
internal/pro/pro.go
Normal 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
249
internal/pro/pro_test.go
Normal 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
23
internal/pro/request.go
Normal 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
22
internal/pro/session.go
Normal 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
5
internal/pro/testdata/pro1.yml
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
key: b32e9ccdc90eb7c0f6f1b9fbc82b8a2b0e993304
|
||||
secret: 5991ea36a9611e9e00a8360c10b91567
|
||||
session: 3ef5685c6391a568731c8fc94ccad82d92dea60476c8b672990047c822248f45366fc0e8e812ad15e0b5ae1eb20e866235c56b
|
||||
status: unregistered
|
||||
version: develop
|
5
internal/pro/testdata/pro2.yml
vendored
Normal file
5
internal/pro/testdata/pro2.yml
vendored
Normal 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
5
internal/pro/testdata/pro3.yml
vendored
Executable file
|
@ -0,0 +1,5 @@
|
|||
key: 8dd8b115d052f91ac74b1c2475e305009366c487
|
||||
secret: ddf4ce46afbf6c16a6bd8555ab1e4efb
|
||||
session: 7607796238c26b2d95007957b05c72d63f504346576bc2aa064a6dc54344de47d2ab38422bd1d061c067a16ef517e6054d8b7f5336c120431935518277fed45e49472aaf740cac1bc33ab2e362c767007a59e953e9973709
|
||||
status: unregistered
|
||||
version: develop
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue