Auth: Fix client role lookup in auth_session.go

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2025-10-28 19:34:55 +01:00
parent b988ba046c
commit 1408e99135
2 changed files with 75 additions and 10 deletions

View file

@ -5,6 +5,7 @@ import (
"fmt"
"net"
"net/http"
"strings"
"time"
"github.com/dustin/go-humanize/english"
@ -276,17 +277,25 @@ func (m *Session) GetClient() *Client {
return &Client{}
} else if m.client != nil {
return m.client
} else if c := FindClientByUID(m.ClientUID); c != nil {
}
// Get client ID.
uid := m.ClientUID
if c := FindClientByUID(uid); c != nil {
m.SetClient(c)
return m.client
}
// Get client role.
role := m.clientRole(false)
return &Client{
UserUID: m.UserUID,
UserName: m.UserName,
ClientUID: m.ClientUID,
ClientUID: uid,
ClientName: m.GetClientName(),
ClientRole: m.GetClientRole().String(),
ClientRole: role.String(),
AuthScope: m.Scope(),
AuthMethod: m.AuthMethod,
}
@ -299,13 +308,7 @@ func (m *Session) GetClientName() string {
// GetClientRole returns the client ACL role.
func (m *Session) GetClientRole() acl.Role {
if m.HasClient() {
return m.GetClient().AclRole()
} else if m.IsClient() {
return acl.RoleClient
}
return acl.RoleNone
return m.clientRole(true)
}
// GetClientInfo returns the client identifier string.
@ -338,6 +341,43 @@ func (m *Session) IsClient() bool {
return authn.Provider(m.AuthProvider).IsClient()
}
// clientRole resolves the client role for this session. When resolve is true it
// may perform a one-time lookup via FindClientByUID; callers that already
// depend on GetClientRole must pass resolve=false to avoid the recursive loop
// that previously caused stack overflows between GetClient() and GetClientRole().
func (m *Session) clientRole(resolve bool) acl.Role {
if m == nil {
return acl.RoleNone
}
if c := m.client; c != nil {
return c.AclRole()
}
if !resolve {
// Skip lookup to avoid recursive loop.
} else if uid := m.ClientUID; uid != "" {
if c := FindClientByUID(uid); c != nil {
m.SetClient(c)
return c.AclRole()
}
}
if m.IsClient() {
if authn.MethodJWT.NotEqual(m.AuthMethod) {
// Do nothing.
} else if role, _, hasRole := strings.Cut(m.AuthIssuer, ":"); !hasRole {
// Do nothing.
} else if aclRole, roleFound := acl.ClientRoles[role]; roleFound {
return aclRole
}
return acl.RoleClient
}
return acl.RoleNone
}
// GetUser returns the related user entity.
func (m *Session) GetUser() *User {
if m == nil {

View file

@ -349,6 +349,31 @@ func TestSession_ClientRole(t *testing.T) {
m := &Session{}
assert.Equal(t, acl.RoleNone, m.GetClientRole())
})
t.Run("MissingClientEntityPortal", func(t *testing.T) {
m := &Session{
ClientUID: "cs5cpu17n6gj2zzz",
AuthProvider: authn.ProviderClient.String(),
AuthMethod: authn.MethodJWT.String(),
AuthIssuer: "portal:cbaa0276-07d3-43ac-b420-25e2601b0ad4",
}
role := m.GetClientRole()
assert.Equal(t, acl.RolePortal, role)
client := m.GetClient()
assert.Equal(t, "cs5cpu17n6gj2zzz", client.ClientUID)
assert.Equal(t, acl.RolePortal, client.AclRole())
})
t.Run("MissingClientEntityDefault", func(t *testing.T) {
m := &Session{
ClientUID: "cs5cpu17n6gj2xxx",
AuthProvider: authn.ProviderClient.String(),
AuthMethod: authn.MethodJWT.String(),
AuthIssuer: "https://example.com/oauth",
}
role := m.GetClientRole()
assert.Equal(t, acl.RoleClient, role)
})
}
func TestSession_ClientInfo(t *testing.T) {