mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
API: Scope photo reads and updates to the session
GetPhoto, GetPhotoYaml, and UpdatePhoto now limit access to pictures within the session's shared scope via the shared search helpers, with no change for full-access sessions. GetAlbum reports out-of-scope albums as not found for consistency with photos and files. In preparation for upcoming sharing features.
This commit is contained in:
parent
763757cc4b
commit
5f9f1bc55b
4 changed files with 68 additions and 5 deletions
|
|
@ -90,9 +90,10 @@ func GetAlbum(router *gin.RouterGroup) {
|
|||
// Get sanitized album UID from request path.
|
||||
uid := clean.UID(c.Param("uid"))
|
||||
|
||||
// Visitors can only access shared content.
|
||||
// Limit access to albums within the session's shared scope; albums outside it are reported
|
||||
// as not found, consistent with how photos and files are read.
|
||||
if (s.NotRegistered()) && !s.HasShare(uid) {
|
||||
AbortForbidden(c)
|
||||
AbortAlbumNotFound(c)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -106,7 +107,7 @@ func GetAlbum(router *gin.RouterGroup) {
|
|||
|
||||
// Other restricted users can only access their own or shared content.
|
||||
if s.GetUser().HasSharedAccessOnly(acl.ResourceAlbums) && album.CreatedBy != s.UserUID && !s.HasShare(uid) {
|
||||
AbortForbidden(c)
|
||||
AbortAlbumNotFound(c)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/entity/query"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -30,6 +31,17 @@ func TestGetAlbum(t *testing.T) {
|
|||
assert.Equal(t, "Album not found", val.String())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
t.Run("GuestDeniedNotShared", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
conf.SetAuthMode(config.AuthModePasswd)
|
||||
defer conf.SetAuthMode(config.AuthModePublic)
|
||||
|
||||
GetAlbum(router)
|
||||
sessId := AuthenticateUser(app, router, "gandalf", "Gandalf123!")
|
||||
// An album outside the guest's shared scope is reported as not found.
|
||||
r := AuthenticatedRequest(app, "GET", "/api/v1/albums/as6sg6bxpogaaba8", sessId)
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateAlbum(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/entity/query"
|
||||
"github.com/photoprism/photoprism/internal/entity/search"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
|
|
@ -56,7 +57,16 @@ func GetPhoto(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
p, err := query.PhotoPreloadByUID(clean.UID(c.Param("uid")))
|
||||
uid := clean.UID(c.Param("uid"))
|
||||
|
||||
// Limit access to pictures within the session's shared scope, consistent with how photo
|
||||
// search filters results. Pictures outside the scope are reported as not found.
|
||||
if visible, err := search.PhotoVisibleToSession(uid, s); err != nil || !visible {
|
||||
AbortEntityNotFound(c)
|
||||
return
|
||||
}
|
||||
|
||||
p, err := query.PhotoPreloadByUID(uid)
|
||||
|
||||
if err != nil {
|
||||
AbortEntityNotFound(c)
|
||||
|
|
@ -88,6 +98,17 @@ func UpdatePhoto(router *gin.RouterGroup) {
|
|||
}
|
||||
|
||||
uid := clean.UID(c.Param("uid"))
|
||||
|
||||
// Restricted sessions may only modify pictures within their shared scope, mirroring album
|
||||
// updates. This runs before the lookup and form binding so out-of-scope requests have no
|
||||
// side effects.
|
||||
if s.GetUser().HasSharedAccessOnly(acl.ResourcePhotos) || s.NotRegistered() {
|
||||
if visible, vErr := search.PhotoVisibleToSession(uid, s); vErr != nil || !visible {
|
||||
AbortForbidden(c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
m, err := query.PhotoByUID(uid)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -201,7 +222,15 @@ func GetPhotoYaml(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
p, err := query.PhotoPreloadByUID(clean.UID(c.Param("uid")))
|
||||
uid := clean.UID(c.Param("uid"))
|
||||
|
||||
// Limit access to pictures within the session's shared scope, consistent with photo search.
|
||||
if visible, err := search.PhotoVisibleToSession(uid, s); err != nil || !visible {
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
p, err := query.PhotoPreloadByUID(uid)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
|
|
|
|||
|
|
@ -70,6 +70,27 @@ func TestGetPhoto(t *testing.T) {
|
|||
r := PerformRequest(app, "GET", "/api/v1/photos/xxx")
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
t.Run("GuestDeniedPrivate", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
conf.SetAuthMode(config.AuthModePasswd)
|
||||
defer conf.SetAuthMode(config.AuthModePublic)
|
||||
|
||||
GetPhoto(router)
|
||||
sessId := AuthenticateUser(app, router, "gandalf", "Gandalf123!")
|
||||
// A private picture outside the guest's shared scope is reported as not found.
|
||||
r := AuthenticatedRequest(app, "GET", "/api/v1/photos/ps6sg6be2lvl0y13", sessId)
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
t.Run("AdminSeesPrivate", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
conf.SetAuthMode(config.AuthModePasswd)
|
||||
defer conf.SetAuthMode(config.AuthModePublic)
|
||||
|
||||
GetPhoto(router)
|
||||
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
|
||||
r := AuthenticatedRequest(app, "GET", "/api/v1/photos/ps6sg6be2lvl0y13", sessId)
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdatePhoto(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue