Auth: Refactor user roles and auth providers in entity model #98
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
3465d0e348
commit
5b73101442
11 changed files with 84 additions and 13 deletions
23
.ldap.cfg
23
.ldap.cfg
|
@ -97,6 +97,25 @@ debug = true
|
||||||
action = "search"
|
action = "search"
|
||||||
object = "*"
|
object = "*"
|
||||||
|
|
||||||
|
[[users]]
|
||||||
|
name = "contributor"
|
||||||
|
givenname = "Contributor"
|
||||||
|
objectClass = "user"
|
||||||
|
displayName = "Contributor"
|
||||||
|
sn = "Contributor"
|
||||||
|
userPrincipalName = "contributor@example.com"
|
||||||
|
mail = "contributor@example.com"
|
||||||
|
uidnumber = 5009
|
||||||
|
primarygroup = 5509
|
||||||
|
loginShell = "/bin/bash"
|
||||||
|
otherGroups = [5508]
|
||||||
|
passsha256 = "4314c1fe282face45336b1422a3285c5ff31a39c8e24425615fa53a43b718493" # photoprism
|
||||||
|
[[users.customattributes]]
|
||||||
|
photoprismUploadPath = ["contrib"]
|
||||||
|
[[users.capabilities]]
|
||||||
|
action = "search"
|
||||||
|
object = "*"
|
||||||
|
|
||||||
[[users]]
|
[[users]]
|
||||||
name = "mail"
|
name = "mail"
|
||||||
objectClass = "user"
|
objectClass = "user"
|
||||||
|
@ -144,3 +163,7 @@ debug = true
|
||||||
[[groups]]
|
[[groups]]
|
||||||
name = "PhotoPrism-webdav"
|
name = "PhotoPrism-webdav"
|
||||||
gidnumber = 5508
|
gidnumber = 5508
|
||||||
|
|
||||||
|
[[groups]]
|
||||||
|
name = "PhotoPrism-contributor"
|
||||||
|
gidnumber = 5509
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -49,7 +50,15 @@ func AuthLocal(user *User, f form.Login, m *Session) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login allowed?
|
// Login allowed?
|
||||||
if !user.CanLogIn() {
|
if !user.Provider().IsDefault() && !user.Provider().IsLocal() {
|
||||||
|
message := fmt.Sprintf("%s authentication disabled", authn.ProviderLocal.String())
|
||||||
|
if m != nil {
|
||||||
|
event.AuditWarn([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name))
|
||||||
|
event.LoginError(m.IP(), "api", name, m.UserAgent, message)
|
||||||
|
m.Status = http.StatusUnauthorized
|
||||||
|
}
|
||||||
|
return i18n.Error(i18n.ErrInvalidCredentials)
|
||||||
|
} else if !user.CanLogIn() {
|
||||||
message := "account disabled"
|
message := "account disabled"
|
||||||
if m != nil {
|
if m != nil {
|
||||||
event.AuditWarn([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name))
|
event.AuditWarn([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name))
|
||||||
|
@ -102,7 +111,7 @@ func (m *Session) LogIn(f form.Login, c *gin.Context) (err error) {
|
||||||
m.SetProvider(provider)
|
m.SetProvider(provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Share token provided?
|
// Link token provided?
|
||||||
if f.HasToken() {
|
if f.HasToken() {
|
||||||
user = m.User()
|
user = m.User()
|
||||||
|
|
||||||
|
@ -127,7 +136,7 @@ func (m *Session) LogIn(f form.Login, c *gin.Context) (err error) {
|
||||||
return i18n.Error(i18n.ErrInvalidLink)
|
return i18n.Error(i18n.ErrInvalidLink)
|
||||||
} else {
|
} else {
|
||||||
m.SetData(data)
|
m.SetData(data)
|
||||||
m.SetProvider(authn.ProviderToken)
|
m.SetProvider(authn.ProviderLink)
|
||||||
event.AuditInfo([]string{m.IP(), "session %s", "token redeemed for %d shares"}, m.RefID, shares, data)
|
event.AuditInfo([]string{m.IP(), "session %s", "token redeemed for %d shares"}, m.RefID, shares, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -491,7 +491,7 @@ func (m *User) Provider() authn.ProviderType {
|
||||||
if m.AuthProvider != "" {
|
if m.AuthProvider != "" {
|
||||||
return authn.ProviderType(m.AuthProvider)
|
return authn.ProviderType(m.AuthProvider)
|
||||||
} else if m.ID == Visitor.ID {
|
} else if m.ID == Visitor.ID {
|
||||||
return authn.ProviderToken
|
return authn.ProviderLink
|
||||||
} else if m.ID == 1 {
|
} else if m.ID == 1 {
|
||||||
return authn.ProviderLocal
|
return authn.ProviderLocal
|
||||||
} else if m.UserName != "" && m.ID > 0 {
|
} else if m.UserName != "" && m.ID > 0 {
|
||||||
|
@ -595,6 +595,20 @@ func (m *User) FullName() string {
|
||||||
return clean.NameCapitalized(strings.ReplaceAll(m.Handle(), ".", " "))
|
return clean.NameCapitalized(strings.ReplaceAll(m.Handle(), ".", " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRole sets the user role specified as string.
|
||||||
|
func (m *User) SetRole(role string) *User {
|
||||||
|
role = clean.Role(role)
|
||||||
|
|
||||||
|
switch role {
|
||||||
|
case "", "0", "false", "nil", "null", "nan":
|
||||||
|
m.UserRole = acl.RoleUnknown.String()
|
||||||
|
default:
|
||||||
|
m.UserRole = acl.ValidRoles[role].String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
// AclRole returns the user role for ACL permission checks.
|
// AclRole returns the user role for ACL permission checks.
|
||||||
func (m *User) AclRole() acl.Role {
|
func (m *User) AclRole() acl.Role {
|
||||||
role := clean.Role(m.UserRole)
|
role := clean.Role(m.UserRole)
|
||||||
|
@ -842,7 +856,7 @@ func (m *User) SetFormValues(frm form.User) *User {
|
||||||
m.SuperAdmin = frm.SuperAdmin
|
m.SuperAdmin = frm.SuperAdmin
|
||||||
m.CanLogin = frm.CanLogin
|
m.CanLogin = frm.CanLogin
|
||||||
m.WebDAV = frm.WebDAV
|
m.WebDAV = frm.WebDAV
|
||||||
m.UserRole = frm.Role()
|
m.SetRole(frm.Role())
|
||||||
m.UserAttr = frm.Attr()
|
m.UserAttr = frm.Attr()
|
||||||
m.SetBasePath(frm.BasePath)
|
m.SetBasePath(frm.BasePath)
|
||||||
m.SetUploadPath(frm.UploadPath)
|
m.SetUploadPath(frm.UploadPath)
|
||||||
|
@ -1011,7 +1025,7 @@ func (m *User) SaveForm(f form.User, updateRights bool) error {
|
||||||
|
|
||||||
// Update user rights only if explicitly requested.
|
// Update user rights only if explicitly requested.
|
||||||
if updateRights {
|
if updateRights {
|
||||||
m.UserRole = f.Role()
|
m.SetRole(f.Role())
|
||||||
m.SuperAdmin = f.SuperAdmin
|
m.SuperAdmin = f.SuperAdmin
|
||||||
|
|
||||||
m.CanLogin = f.CanLogin
|
m.CanLogin = f.CanLogin
|
||||||
|
@ -1025,12 +1039,12 @@ func (m *User) SaveForm(f form.User, updateRights bool) error {
|
||||||
|
|
||||||
// Ensure super admins never have a non-admin role.
|
// Ensure super admins never have a non-admin role.
|
||||||
if m.SuperAdmin {
|
if m.SuperAdmin {
|
||||||
m.UserRole = acl.RoleAdmin.String()
|
m.SetRole(acl.RoleAdmin.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure that the initial admin user cannot lock itself out.
|
// Make sure that the initial admin user cannot lock itself out.
|
||||||
if m.ID == Admin.ID && (m.AclRole() != acl.RoleAdmin || !m.SuperAdmin || !m.CanLogin) {
|
if m.ID == Admin.ID && (m.AclRole() != acl.RoleAdmin || !m.SuperAdmin || !m.CanLogin) {
|
||||||
m.UserRole = acl.RoleAdmin.String()
|
m.SetRole(acl.RoleAdmin.String())
|
||||||
m.SuperAdmin = true
|
m.SuperAdmin = true
|
||||||
m.CanLogin = true
|
m.CanLogin = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ func (m *User) SetValuesFromCli(ctx *cli.Context) error {
|
||||||
|
|
||||||
// User role.
|
// User role.
|
||||||
if ctx.IsSet("role") {
|
if ctx.IsSet("role") {
|
||||||
m.UserRole = frm.Role()
|
m.SetRole(frm.Role())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Super-admin status.
|
// Super-admin status.
|
||||||
|
|
|
@ -51,7 +51,7 @@ var Visitor = User{
|
||||||
ID: -2,
|
ID: -2,
|
||||||
UserUID: "u000000000000002",
|
UserUID: "u000000000000002",
|
||||||
UserName: "",
|
UserName: "",
|
||||||
AuthProvider: authn.ProviderToken.String(),
|
AuthProvider: authn.ProviderLink.String(),
|
||||||
UserRole: acl.RoleVisitor.String(),
|
UserRole: acl.RoleVisitor.String(),
|
||||||
DisplayName: VisitorDisplayName,
|
DisplayName: VisitorDisplayName,
|
||||||
CanLogin: false,
|
CanLogin: false,
|
||||||
|
|
|
@ -1001,7 +1001,7 @@ func TestUser_Username(t *testing.T) {
|
||||||
|
|
||||||
func TestUser_Provider(t *testing.T) {
|
func TestUser_Provider(t *testing.T) {
|
||||||
t.Run("Visitor", func(t *testing.T) {
|
t.Run("Visitor", func(t *testing.T) {
|
||||||
assert.Equal(t, authn.ProviderToken, Visitor.Provider())
|
assert.Equal(t, authn.ProviderLink, Visitor.Provider())
|
||||||
})
|
})
|
||||||
t.Run("UnknownUser", func(t *testing.T) {
|
t.Run("UnknownUser", func(t *testing.T) {
|
||||||
assert.Equal(t, authn.ProviderNone, UnknownUser.Provider())
|
assert.Equal(t, authn.ProviderNone, UnknownUser.Provider())
|
||||||
|
|
|
@ -159,4 +159,10 @@ var DialectMySQL = Migrations{
|
||||||
Stage: "main",
|
Stage: "main",
|
||||||
Statements: []string{"UPDATE auth_users SET auth_provider = 'local' WHERE id = 1;", "UPDATE auth_users SET auth_provider = 'none' WHERE id = -1;", "UPDATE auth_users SET auth_provider = 'token' WHERE id = -2;", "UPDATE auth_users SET auth_provider = 'default' WHERE auth_provider = '' OR auth_provider = 'password' OR auth_provider IS NULL;"},
|
Statements: []string{"UPDATE auth_users SET auth_provider = 'local' WHERE id = 1;", "UPDATE auth_users SET auth_provider = 'none' WHERE id = -1;", "UPDATE auth_users SET auth_provider = 'token' WHERE id = -2;", "UPDATE auth_users SET auth_provider = 'default' WHERE auth_provider = '' OR auth_provider = 'password' OR auth_provider IS NULL;"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ID: "20230313-000001",
|
||||||
|
Dialect: "mysql",
|
||||||
|
Stage: "main",
|
||||||
|
Statements: []string{"UPDATE auth_users SET user_role = 'contributor' WHERE user_role = 'uploader';", "UPDATE auth_sessions SET auth_provider = 'link' WHERE auth_provider = 'token';"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,4 +87,10 @@ var DialectSQLite3 = Migrations{
|
||||||
Stage: "main",
|
Stage: "main",
|
||||||
Statements: []string{"UPDATE auth_users SET auth_provider = 'local' WHERE id = 1;", "UPDATE auth_users SET auth_provider = 'none' WHERE id = -1;", "UPDATE auth_users SET auth_provider = 'token' WHERE id = -2;", "UPDATE auth_users SET auth_provider = 'default' WHERE auth_provider = '' OR auth_provider = 'password' OR auth_provider IS NULL;"},
|
Statements: []string{"UPDATE auth_users SET auth_provider = 'local' WHERE id = 1;", "UPDATE auth_users SET auth_provider = 'none' WHERE id = -1;", "UPDATE auth_users SET auth_provider = 'token' WHERE id = -2;", "UPDATE auth_users SET auth_provider = 'default' WHERE auth_provider = '' OR auth_provider = 'password' OR auth_provider IS NULL;"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ID: "20230313-000001",
|
||||||
|
Dialect: "sqlite3",
|
||||||
|
Stage: "main",
|
||||||
|
Statements: []string{"UPDATE auth_users SET user_role = 'contributor' WHERE user_role = 'uploader';", "UPDATE auth_sessions SET auth_provider = 'link' WHERE auth_provider = 'token';"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
2
internal/migrate/mysql/20230313-000001.sql
Normal file
2
internal/migrate/mysql/20230313-000001.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
UPDATE auth_users SET user_role = 'contributor' WHERE user_role = 'uploader';
|
||||||
|
UPDATE auth_sessions SET auth_provider = 'link' WHERE auth_provider = 'token';
|
2
internal/migrate/sqlite3/20230313-000001.sql
Normal file
2
internal/migrate/sqlite3/20230313-000001.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
UPDATE auth_users SET user_role = 'contributor' WHERE user_role = 'uploader';
|
||||||
|
UPDATE auth_sessions SET auth_provider = 'link' WHERE auth_provider = 'token';
|
|
@ -14,7 +14,7 @@ const (
|
||||||
ProviderDefault ProviderType = "default"
|
ProviderDefault ProviderType = "default"
|
||||||
ProviderLocal ProviderType = "local"
|
ProviderLocal ProviderType = "local"
|
||||||
ProviderLDAP ProviderType = "ldap"
|
ProviderLDAP ProviderType = "ldap"
|
||||||
ProviderToken ProviderType = "token"
|
ProviderLink ProviderType = "link"
|
||||||
ProviderNone ProviderType = "none"
|
ProviderNone ProviderType = "none"
|
||||||
ProviderUnknown ProviderType = ""
|
ProviderUnknown ProviderType = ""
|
||||||
)
|
)
|
||||||
|
@ -39,11 +39,18 @@ func (t ProviderType) IsLocal() bool {
|
||||||
return list.Contains(LocalProviders, string(t))
|
return list.Contains(LocalProviders, string(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsDefault checks if this is the default provider.
|
||||||
|
func (t ProviderType) IsDefault() bool {
|
||||||
|
return t.String() == ProviderDefault.String()
|
||||||
|
}
|
||||||
|
|
||||||
// String returns the provider identifier as a string.
|
// String returns the provider identifier as a string.
|
||||||
func (t ProviderType) String() string {
|
func (t ProviderType) String() string {
|
||||||
switch t {
|
switch t {
|
||||||
case "":
|
case "":
|
||||||
return string(ProviderDefault)
|
return string(ProviderDefault)
|
||||||
|
case "token":
|
||||||
|
return string(ProviderLink)
|
||||||
case "password":
|
case "password":
|
||||||
return string(ProviderLocal)
|
return string(ProviderLocal)
|
||||||
default:
|
default:
|
||||||
|
@ -66,6 +73,8 @@ func Provider(s string) ProviderType {
|
||||||
switch s {
|
switch s {
|
||||||
case "", "-", "null", "nil", "0", "false":
|
case "", "-", "null", "nil", "0", "false":
|
||||||
return ProviderDefault
|
return ProviderDefault
|
||||||
|
case "token", "url":
|
||||||
|
return ProviderLink
|
||||||
case "pass", "passwd", "password":
|
case "pass", "passwd", "password":
|
||||||
return ProviderLocal
|
return ProviderLocal
|
||||||
case "ldap", "ad", "ldap/ad", "ldap\\ad":
|
case "ldap", "ad", "ldap/ad", "ldap\\ad":
|
||||||
|
|
Loading…
Reference in a new issue