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
25
.ldap.cfg
25
.ldap.cfg
|
@ -97,6 +97,25 @@ debug = true
|
|||
action = "search"
|
||||
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]]
|
||||
name = "mail"
|
||||
objectClass = "user"
|
||||
|
@ -143,4 +162,8 @@ debug = true
|
|||
|
||||
[[groups]]
|
||||
name = "PhotoPrism-webdav"
|
||||
gidnumber = 5508
|
||||
gidnumber = 5508
|
||||
|
||||
[[groups]]
|
||||
name = "PhotoPrism-contributor"
|
||||
gidnumber = 5509
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
|
@ -49,7 +50,15 @@ func AuthLocal(user *User, f form.Login, m *Session) (err error) {
|
|||
}
|
||||
|
||||
// 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"
|
||||
if m != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
// Share token provided?
|
||||
// Link token provided?
|
||||
if f.HasToken() {
|
||||
user = m.User()
|
||||
|
||||
|
@ -127,7 +136,7 @@ func (m *Session) LogIn(f form.Login, c *gin.Context) (err error) {
|
|||
return i18n.Error(i18n.ErrInvalidLink)
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -491,7 +491,7 @@ func (m *User) Provider() authn.ProviderType {
|
|||
if m.AuthProvider != "" {
|
||||
return authn.ProviderType(m.AuthProvider)
|
||||
} else if m.ID == Visitor.ID {
|
||||
return authn.ProviderToken
|
||||
return authn.ProviderLink
|
||||
} else if m.ID == 1 {
|
||||
return authn.ProviderLocal
|
||||
} else if m.UserName != "" && m.ID > 0 {
|
||||
|
@ -595,6 +595,20 @@ func (m *User) FullName() string {
|
|||
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.
|
||||
func (m *User) AclRole() acl.Role {
|
||||
role := clean.Role(m.UserRole)
|
||||
|
@ -842,7 +856,7 @@ func (m *User) SetFormValues(frm form.User) *User {
|
|||
m.SuperAdmin = frm.SuperAdmin
|
||||
m.CanLogin = frm.CanLogin
|
||||
m.WebDAV = frm.WebDAV
|
||||
m.UserRole = frm.Role()
|
||||
m.SetRole(frm.Role())
|
||||
m.UserAttr = frm.Attr()
|
||||
m.SetBasePath(frm.BasePath)
|
||||
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.
|
||||
if updateRights {
|
||||
m.UserRole = f.Role()
|
||||
m.SetRole(f.Role())
|
||||
m.SuperAdmin = f.SuperAdmin
|
||||
|
||||
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.
|
||||
if m.SuperAdmin {
|
||||
m.UserRole = acl.RoleAdmin.String()
|
||||
m.SetRole(acl.RoleAdmin.String())
|
||||
}
|
||||
|
||||
// Make sure that the initial admin user cannot lock itself out.
|
||||
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.CanLogin = true
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ func (m *User) SetValuesFromCli(ctx *cli.Context) error {
|
|||
|
||||
// User role.
|
||||
if ctx.IsSet("role") {
|
||||
m.UserRole = frm.Role()
|
||||
m.SetRole(frm.Role())
|
||||
}
|
||||
|
||||
// Super-admin status.
|
||||
|
|
|
@ -51,7 +51,7 @@ var Visitor = User{
|
|||
ID: -2,
|
||||
UserUID: "u000000000000002",
|
||||
UserName: "",
|
||||
AuthProvider: authn.ProviderToken.String(),
|
||||
AuthProvider: authn.ProviderLink.String(),
|
||||
UserRole: acl.RoleVisitor.String(),
|
||||
DisplayName: VisitorDisplayName,
|
||||
CanLogin: false,
|
||||
|
|
|
@ -1001,7 +1001,7 @@ func TestUser_Username(t *testing.T) {
|
|||
|
||||
func TestUser_Provider(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) {
|
||||
assert.Equal(t, authn.ProviderNone, UnknownUser.Provider())
|
||||
|
|
|
@ -159,4 +159,10 @@ var DialectMySQL = Migrations{
|
|||
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;"},
|
||||
},
|
||||
{
|
||||
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",
|
||||
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"
|
||||
ProviderLocal ProviderType = "local"
|
||||
ProviderLDAP ProviderType = "ldap"
|
||||
ProviderToken ProviderType = "token"
|
||||
ProviderLink ProviderType = "link"
|
||||
ProviderNone ProviderType = "none"
|
||||
ProviderUnknown ProviderType = ""
|
||||
)
|
||||
|
@ -39,11 +39,18 @@ func (t ProviderType) IsLocal() bool {
|
|||
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.
|
||||
func (t ProviderType) String() string {
|
||||
switch t {
|
||||
case "":
|
||||
return string(ProviderDefault)
|
||||
case "token":
|
||||
return string(ProviderLink)
|
||||
case "password":
|
||||
return string(ProviderLocal)
|
||||
default:
|
||||
|
@ -66,6 +73,8 @@ func Provider(s string) ProviderType {
|
|||
switch s {
|
||||
case "", "-", "null", "nil", "0", "false":
|
||||
return ProviderDefault
|
||||
case "token", "url":
|
||||
return ProviderLink
|
||||
case "pass", "passwd", "password":
|
||||
return ProviderLocal
|
||||
case "ldap", "ad", "ldap/ad", "ldap\\ad":
|
||||
|
|
Loading…
Reference in a new issue