OIDC: Add handling to AuthID so that SQLite doesn't corrupt on save with long numbers

This commit is contained in:
Keith Martin 2025-11-07 00:19:16 +10:00
parent 6d0016b1df
commit d90182833e
2 changed files with 101 additions and 1 deletions

View file

@ -52,7 +52,7 @@ type User struct {
AuthProvider string `gorm:"type:VARBINARY(128);default:'';" json:"AuthProvider" yaml:"AuthProvider,omitempty"`
AuthMethod string `gorm:"type:VARBINARY(128);default:'';" json:"AuthMethod" yaml:"AuthMethod,omitempty"`
AuthIssuer string `gorm:"type:VARBINARY(255);default:'';" json:"AuthIssuer,omitempty" yaml:"AuthIssuer,omitempty"`
AuthID string `gorm:"type:VARBINARY(255);index;default:'';" json:"AuthID" yaml:"AuthID,omitempty"`
AuthID string `gorm:"type:VARBINARY(264);index;default:'';" json:"AuthID" yaml:"AuthID,omitempty"`
UserName string `gorm:"size:200;index;" json:"Name" yaml:"Name,omitempty"`
DisplayName string `gorm:"size:200;" json:"DisplayName" yaml:"DisplayName,omitempty"`
UserEmail string `gorm:"size:255;index;" json:"Email" yaml:"Email,omitempty"`
@ -382,6 +382,20 @@ func (m *User) Updates(values interface{}) error {
return UnscopedDb().Model(m).Updates(values).Error
}
// Wraps the AuthID field so that SQLite will save it correctly
func (m *User) wrapAuthID() {
if m.AuthID != "" && !strings.HasPrefix(m.AuthID, "<pp>") && !strings.HasSuffix(m.AuthID, "</pp>") {
m.AuthID = fmt.Sprintf("<pp>%s</pp>", m.AuthID)
}
}
// Unwraps the AuthID field so that PhotoPrism can use it correctly
func (m *User) unwrapAuthID() {
if m.AuthID != "" && strings.HasPrefix(m.AuthID, "<pp>") && strings.HasSuffix(m.AuthID, "</pp>") {
m.AuthID = strings.TrimSuffix(strings.TrimPrefix(m.AuthID, "<pp>"), "</pp>")
}
}
// BeforeCreate sets a random UID if needed before inserting a new row to the database.
func (m *User) BeforeCreate(scope *gorm.Scope) error {
if m.UserSettings != nil {
@ -403,10 +417,42 @@ func (m *User) BeforeCreate(scope *gorm.Scope) error {
return nil
}
m.wrapAuthID()
m.UserUID = rnd.GenerateUID(UserUID)
return scope.SetColumn("UserUID", m.UserUID)
}
// BeforeSave ensures that the AuthID will save correctly on SQLite
func (m *User) BeforeSave(scope *gorm.Scope) error {
m.wrapAuthID()
return nil
}
// BeforeUpdate ensures that the AuthID will save correctly on SQLite
func (m *User) BeforeUpdate(scope *gorm.Scope) error {
m.wrapAuthID()
return nil
}
// AfterSave ensures that the AuthID will not have the prefix and suffix added so that it will save correctly on SQLite
func (m *User) AfterSave(scope *gorm.Scope) error {
m.unwrapAuthID()
return nil
}
// AfterUpdate ensures that the AuthID will not have the prefix and suffix added so that it will save correctly on SQLite
func (m *User) AfterUpdate(scope *gorm.Scope) error {
m.unwrapAuthID()
return nil
}
// AfterFind ensures that the AuthID will not have the prefix and suffix added so that it will save correctly on SQLite
func (m *User) AfterFind(scope *gorm.Scope) error {
m.unwrapAuthID()
return nil
}
// IsExpired checks if the user account has expired.
func (m *User) IsExpired() bool {
if m.ExpiresAt == nil {

View file

@ -70,6 +70,25 @@ func TestOidcUser(t *testing.T) {
assert.Equal(t, "jane.doe", m.UserName)
assert.Equal(t, "Jane Doe", m.DisplayName)
})
t.Run("LongNumberAsSubject", func(t *testing.T) {
info := &oidc.UserInfo{}
info.Name = "Jane Doe"
info.GivenName = "Jane"
info.FamilyName = "Doe"
info.Email = "jane@doe.com"
info.EmailVerified = true
info.Subject = "12345678901234567890"
info.PreferredUsername = "Jane Doe"
m := OidcUser(info, "", "jane.doe")
assert.Equal(t, "oidc", m.AuthProvider)
assert.Equal(t, "", m.AuthIssuer)
assert.Equal(t, "12345678901234567890", m.AuthID)
assert.Equal(t, "jane@doe.com", m.UserEmail)
assert.Equal(t, "jane.doe", m.UserName)
assert.Equal(t, "Jane Doe", m.DisplayName)
})
t.Run("NoUsername", func(t *testing.T) {
info := &oidc.UserInfo{}
info.Name = "Jane Doe"
@ -1808,6 +1827,7 @@ func TestUser_SetAuthID(t *testing.T) {
func TestUser_UpdateAuthID(t *testing.T) {
uuid := rnd.UUID()
issuer := "http://dummy-oidc:9998"
longnumber := "12345678901234567890"
t.Run("UUID", func(t *testing.T) {
m := UserFixtures.Get("friend")
@ -1833,6 +1853,20 @@ func TestUser_UpdateAuthID(t *testing.T) {
err := m.UpdateAuthID(uuid, "")
assert.Error(t, err)
})
t.Run("LongNumber", func(t *testing.T) {
m := UserFixtures.Get("friend")
m.SetAuthID("", issuer)
assert.Equal(t, "", m.AuthID)
assert.Equal(t, "", m.AuthIssuer)
m.SetAuthID(longnumber, issuer)
assert.Equal(t, longnumber, m.AuthID)
assert.Equal(t, issuer, m.AuthIssuer)
err := m.UpdateAuthID(longnumber, "")
assert.NoError(t, err)
assert.Equal(t, longnumber, m.AuthID)
assert.Equal(t, "", m.AuthIssuer)
})
}
func TestUser_AuthInfo(t *testing.T) {
@ -2356,3 +2390,23 @@ func TestUser_SetValuesFromCliScope(t *testing.T) {
require.NoError(t, user.SetValuesFromCli(ctx))
assert.Equal(t, "videos:view", user.UserScope)
}
func TestUser_AuthIDSQLite(t *testing.T) {
user := FindLocalUser("alice")
require.NotNil(t, user)
original := user.AuthID
t.Cleanup(func() {
user.AuthID = original
user.Save()
})
expected := "012345678901234567890123456789"
user.AuthID = expected
user.Save()
user2 := FindLocalUser("alice")
require.NotNil(t, user2)
assert.Equal(t, expected, user2.AuthID)
}