Skip to content

Commit fa487ed

Browse files
committed
Albums: Fix GET /albums/{uid} and GET /photos API permission checks
Signed-off-by: Michael Mayer <[email protected]>
1 parent 102ca2e commit fa487ed

File tree

3 files changed

+84
-72
lines changed

3 files changed

+84
-72
lines changed

internal/api/albums.go

Lines changed: 62 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,27 @@ func GetAlbum(router *gin.RouterGroup) {
5555
// Get sanitized album UID from request path.
5656
uid := clean.UID(c.Param("uid"))
5757

58-
// Visitors and other restricted users can only access shared content.
59-
if (s.User().HasSharedAccessOnly(acl.ResourceAlbums) || s.NotRegistered()) && !s.HasShare(uid) {
58+
// Visitors can only access shared content.
59+
if (s.NotRegistered()) && !s.HasShare(uid) {
6060
AbortForbidden(c)
6161
return
6262
}
6363

6464
// Find album by UID.
65-
a, err := query.AlbumByUID(uid)
65+
album, err := query.AlbumByUID(uid)
6666

6767
if err != nil {
6868
AbortAlbumNotFound(c)
6969
return
7070
}
7171

72-
c.JSON(http.StatusOK, a)
72+
// Other restricted users can only access their own or shared content.
73+
if album.CreatedBy != s.UserUID && s.User().HasSharedAccessOnly(acl.ResourceAlbums) {
74+
AbortForbidden(c)
75+
return
76+
}
77+
78+
c.JSON(http.StatusOK, album)
7379
})
7480
}
7581

@@ -103,25 +109,25 @@ func CreateAlbum(router *gin.RouterGroup) {
103109
albumMutex.Lock()
104110
defer albumMutex.Unlock()
105111

106-
a := entity.NewUserAlbum(frm.AlbumTitle, entity.AlbumManual, s.UserUID)
107-
a.AlbumFavorite = frm.AlbumFavorite
112+
album := entity.NewUserAlbum(frm.AlbumTitle, entity.AlbumManual, s.UserUID)
113+
album.AlbumFavorite = frm.AlbumFavorite
108114

109115
// Existing album?
110-
if found := a.Find(); found == nil {
116+
if found := album.Find(); found == nil {
111117
// Not found, create new album.
112-
if err := a.Create(); err != nil {
118+
if err := album.Create(); err != nil {
113119
// Report unexpected error.
114120
log.Errorf("album: %s (create)", err)
115121
AbortUnexpectedError(c)
116122
return
117123
}
118124
} else {
119125
// Exists, restore if necessary.
120-
a = found
121-
if !a.Deleted() {
122-
c.JSON(http.StatusOK, a)
126+
album = found
127+
if !album.Deleted() {
128+
c.JSON(http.StatusOK, album)
123129
return
124-
} else if err := a.Restore(); err != nil {
130+
} else if err := album.Restore(); err != nil {
125131
// Report unexpected error.
126132
log.Errorf("album: %s (restore)", err)
127133
AbortUnexpectedError(c)
@@ -132,10 +138,10 @@ func CreateAlbum(router *gin.RouterGroup) {
132138
UpdateClientConfig()
133139

134140
// Update album YAML backup.
135-
SaveAlbumYaml(*a)
141+
SaveAlbumYaml(*album)
136142

137143
// Return as JSON.
138-
c.JSON(http.StatusOK, a)
144+
c.JSON(http.StatusOK, album)
139145
})
140146
}
141147

@@ -169,14 +175,14 @@ func UpdateAlbum(router *gin.RouterGroup) {
169175
}
170176

171177
// Find album by UID.
172-
a, err := query.AlbumByUID(uid)
178+
album, err := query.AlbumByUID(uid)
173179

174180
if err != nil {
175181
AbortAlbumNotFound(c)
176182
return
177183
}
178184

179-
frm, err := form.NewAlbum(a)
185+
frm, err := form.NewAlbum(album)
180186

181187
if err != nil {
182188
log.Error(err)
@@ -194,7 +200,7 @@ func UpdateAlbum(router *gin.RouterGroup) {
194200
albumMutex.Lock()
195201
defer albumMutex.Unlock()
196202

197-
if err = a.SaveForm(frm); err != nil {
203+
if err = album.SaveForm(frm); err != nil {
198204
log.Error(err)
199205
AbortSaveFailed(c)
200206
return
@@ -207,9 +213,9 @@ func UpdateAlbum(router *gin.RouterGroup) {
207213
UpdateClientConfig()
208214

209215
// Update album YAML backup.
210-
SaveAlbumYaml(a)
216+
SaveAlbumYaml(album)
211217

212-
c.JSON(http.StatusOK, a)
218+
c.JSON(http.StatusOK, album)
213219
})
214220
}
215221

@@ -241,7 +247,7 @@ func DeleteAlbum(router *gin.RouterGroup) {
241247
}
242248

243249
// Find album by UID.
244-
a, err := query.AlbumByUID(uid)
250+
album, err := query.AlbumByUID(uid)
245251

246252
if err != nil {
247253
AbortAlbumNotFound(c)
@@ -252,28 +258,28 @@ func DeleteAlbum(router *gin.RouterGroup) {
252258
defer albumMutex.Unlock()
253259

254260
// Regular, manually created album?
255-
if a.IsDefault() {
261+
if album.IsDefault() {
256262
// Soft delete manually created albums.
257-
err = a.Delete()
263+
err = album.Delete()
258264

259265
// Also update album YAML backup.
260266
if err != nil {
261267
log.Errorf("album: %s (delete)", err)
262268
AbortDeleteFailed(c)
263269
return
264270
} else {
265-
SaveAlbumYaml(a)
271+
SaveAlbumYaml(album)
266272
}
267273
} else {
268274
// Permanently delete automatically created albums.
269-
err = a.DeletePermanently()
275+
err = album.DeletePermanently()
270276

271277
// Also remove YAML backup file, if it exists.
272278
if err != nil {
273279
log.Errorf("album: %s (delete permanently)", err)
274280
AbortDeleteFailed(c)
275281
return
276-
} else if fileName, relName, nameErr := a.YamlFileName(get.Config().BackupAlbumsPath()); nameErr != nil {
282+
} else if fileName, relName, nameErr := album.YamlFileName(get.Config().BackupAlbumsPath()); nameErr != nil {
277283
log.Warnf("album: %s (delete %s)", err, clean.Log(relName))
278284
} else if !fs.FileExists(fileName) {
279285
// Do nothing.
@@ -284,7 +290,7 @@ func DeleteAlbum(router *gin.RouterGroup) {
284290

285291
UpdateClientConfig()
286292

287-
c.JSON(http.StatusOK, a)
293+
c.JSON(http.StatusOK, album)
288294
})
289295
}
290296

@@ -316,14 +322,14 @@ func LikeAlbum(router *gin.RouterGroup) {
316322
}
317323

318324
// Find album by UID.
319-
a, err := query.AlbumByUID(uid)
325+
album, err := query.AlbumByUID(uid)
320326

321327
if err != nil {
322328
AbortAlbumNotFound(c)
323329
return
324330
}
325331

326-
if err := a.Update("AlbumFavorite", true); err != nil {
332+
if err = album.Update("AlbumFavorite", true); err != nil {
327333
Abort(c, http.StatusInternalServerError, i18n.ErrSaveFailed)
328334
return
329335
}
@@ -333,7 +339,7 @@ func LikeAlbum(router *gin.RouterGroup) {
333339
PublishAlbumEvent(StatusUpdated, uid, c)
334340

335341
// Update album YAML backup.
336-
SaveAlbumYaml(a)
342+
SaveAlbumYaml(album)
337343

338344
c.JSON(http.StatusOK, i18n.NewResponse(http.StatusOK, i18n.MsgChangesSaved))
339345
})
@@ -367,14 +373,14 @@ func DislikeAlbum(router *gin.RouterGroup) {
367373
}
368374

369375
// Find album by UID.
370-
a, err := query.AlbumByUID(uid)
376+
album, err := query.AlbumByUID(uid)
371377

372378
if err != nil {
373379
AbortAlbumNotFound(c)
374380
return
375381
}
376382

377-
if err = a.Update("AlbumFavorite", false); err != nil {
383+
if err = album.Update("AlbumFavorite", false); err != nil {
378384
Abort(c, http.StatusInternalServerError, i18n.ErrSaveFailed)
379385
return
380386
}
@@ -384,7 +390,7 @@ func DislikeAlbum(router *gin.RouterGroup) {
384390
PublishAlbumEvent(StatusUpdated, uid, c)
385391

386392
// Update album YAML backup.
387-
SaveAlbumYaml(a)
393+
SaveAlbumYaml(album)
388394

389395
c.JSON(http.StatusOK, i18n.NewResponse(http.StatusOK, i18n.MsgChangesSaved))
390396
})
@@ -420,7 +426,7 @@ func CloneAlbums(router *gin.RouterGroup) {
420426
}
421427

422428
// Find album by UID.
423-
a, err := query.AlbumByUID(uid)
429+
album, err := query.AlbumByUID(uid)
424430

425431
if err != nil {
426432
AbortAlbumNotFound(c)
@@ -452,19 +458,19 @@ func CloneAlbums(router *gin.RouterGroup) {
452458
continue
453459
}
454460

455-
added = append(added, a.AddPhotos(photos)...)
461+
added = append(added, album.AddPhotos(photos)...)
456462
}
457463

458464
if len(added) > 0 {
459-
event.SuccessMsg(i18n.MsgSelectionAddedTo, clean.Log(a.Title()))
465+
event.SuccessMsg(i18n.MsgSelectionAddedTo, clean.Log(album.Title()))
460466

461-
PublishAlbumEvent(StatusUpdated, a.AlbumUID, c)
467+
PublishAlbumEvent(StatusUpdated, album.AlbumUID, c)
462468

463469
// Update album YAML backup.
464-
SaveAlbumYaml(a)
470+
SaveAlbumYaml(album)
465471
}
466472

467-
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": i18n.Msg(i18n.MsgAlbumCloned), "album": a, "added": added})
473+
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": i18n.Msg(i18n.MsgAlbumCloned), "album": album, "added": added})
468474
})
469475
}
470476

@@ -506,12 +512,12 @@ func AddPhotosToAlbum(router *gin.RouterGroup) {
506512
}
507513

508514
// Find album by UID.
509-
a, err := query.AlbumByUID(uid)
515+
album, err := query.AlbumByUID(uid)
510516

511517
if err != nil {
512518
AbortAlbumNotFound(c)
513519
return
514-
} else if !a.HasID() {
520+
} else if !album.HasID() {
515521
AbortAlbumNotFound(c)
516522
return
517523
} else if frm.Empty() {
@@ -530,21 +536,21 @@ func AddPhotosToAlbum(router *gin.RouterGroup) {
530536

531537
conf := get.Config()
532538

533-
added := a.AddPhotos(photos)
539+
added := album.AddPhotos(photos)
534540

535541
if len(added) > 0 {
536542
if len(added) == 1 {
537-
event.SuccessMsg(i18n.MsgEntryAddedTo, clean.Log(a.Title()))
543+
event.SuccessMsg(i18n.MsgEntryAddedTo, clean.Log(album.Title()))
538544
} else {
539-
event.SuccessMsg(i18n.MsgEntriesAddedTo, len(added), clean.Log(a.Title()))
545+
event.SuccessMsg(i18n.MsgEntriesAddedTo, len(added), clean.Log(album.Title()))
540546
}
541547

542-
RemoveFromAlbumCoverCache(a.AlbumUID)
548+
RemoveFromAlbumCoverCache(album.AlbumUID)
543549

544-
PublishAlbumEvent(StatusUpdated, a.AlbumUID, c)
550+
PublishAlbumEvent(StatusUpdated, album.AlbumUID, c)
545551

546552
// Update album YAML backup.
547-
SaveAlbumYaml(a)
553+
SaveAlbumYaml(album)
548554

549555
// Auto-approve photos that have been added to an album,
550556
// see https://github.com/photoprism/photoprism/issues/4229
@@ -575,7 +581,7 @@ func AddPhotosToAlbum(router *gin.RouterGroup) {
575581
}
576582
}
577583

578-
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": i18n.Msg(i18n.MsgChangesSaved), "album": a, "photos": photos.UIDs(), "added": added})
584+
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": i18n.Msg(i18n.MsgChangesSaved), "album": album, "photos": photos.UIDs(), "added": added})
579585
})
580586
}
581587

@@ -622,33 +628,33 @@ func RemovePhotosFromAlbum(router *gin.RouterGroup) {
622628
}
623629

624630
// Find album by UID.
625-
a, err := query.AlbumByUID(uid)
631+
album, err := query.AlbumByUID(uid)
626632

627633
if err != nil {
628634
AbortAlbumNotFound(c)
629635
return
630-
} else if !a.HasID() {
636+
} else if !album.HasID() {
631637
AbortAlbumNotFound(c)
632638
return
633639
}
634640

635-
removed := a.RemovePhotos(frm.Photos)
641+
removed := album.RemovePhotos(frm.Photos)
636642

637643
if len(removed) > 0 {
638644
if len(removed) == 1 {
639-
event.SuccessMsg(i18n.MsgEntryRemovedFrom, clean.Log(a.Title()))
645+
event.SuccessMsg(i18n.MsgEntryRemovedFrom, clean.Log(album.Title()))
640646
} else {
641-
event.SuccessMsg(i18n.MsgEntriesRemovedFrom, len(removed), clean.Log(a.Title()))
647+
event.SuccessMsg(i18n.MsgEntriesRemovedFrom, len(removed), clean.Log(album.Title()))
642648
}
643649

644-
RemoveFromAlbumCoverCache(a.AlbumUID)
650+
RemoveFromAlbumCoverCache(album.AlbumUID)
645651

646-
PublishAlbumEvent(StatusUpdated, a.AlbumUID, c)
652+
PublishAlbumEvent(StatusUpdated, album.AlbumUID, c)
647653

648654
// Update album YAML backup.
649-
SaveAlbumYaml(a)
655+
SaveAlbumYaml(album)
650656
}
651657

652-
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": i18n.Msg(i18n.MsgChangesSaved), "album": a, "photos": frm.Photos, "removed": removed})
658+
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": i18n.Msg(i18n.MsgChangesSaved), "album": album, "photos": frm.Photos, "removed": removed})
653659
})
654660
}

internal/entity/search/photos.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,23 +95,26 @@ func searchPhotos(frm form.SearchPhotos, sess *entity.Session, resultCols string
9595
frm.Album = ""
9696
}
9797

98+
var album entity.Album
99+
var albumErr error
100+
98101
// Limit search results to a specific UID scope, e.g. when sharing.
99102
if txt.NotEmpty(frm.Scope) {
100103
frm.Scope = strings.ToLower(frm.Scope)
101104

102105
if idType, idPrefix := rnd.IdType(frm.Scope); idType != rnd.TypeUID || idPrefix != entity.AlbumUID {
103106
return PhotoResults{}, 0, ErrInvalidId
104-
} else if a, err := entity.CachedAlbumByUID(frm.Scope); err != nil || a.AlbumUID == "" {
107+
} else if album, albumErr = entity.CachedAlbumByUID(frm.Scope); albumErr != nil || album.AlbumUID == "" {
105108
return PhotoResults{}, 0, ErrInvalidId
106-
} else if a.AlbumFilter == "" {
109+
} else if album.AlbumFilter == "" {
107110
s = s.Joins("JOIN photos_albums ON photos_albums.photo_uid = files.photo_uid").
108-
Where("photos_albums.hidden = 0 AND photos_albums.album_uid = ?", a.AlbumUID)
109-
} else if formErr := form.Unserialize(&frm, a.AlbumFilter); formErr != nil {
110-
log.Debugf("search: %s (%s)", clean.Error(formErr), clean.Log(a.AlbumFilter))
111+
Where("photos_albums.hidden = 0 AND photos_albums.album_uid = ?", album.AlbumUID)
112+
} else if formErr := form.Unserialize(&frm, album.AlbumFilter); formErr != nil {
113+
log.Debugf("search: %s (%s)", clean.Error(formErr), clean.Log(album.AlbumFilter))
111114
return PhotoResults{}, 0, ErrBadFilter
112115
} else {
113-
frm.Filter = a.AlbumFilter
114-
s = s.Where("files.photo_uid NOT IN (SELECT photo_uid FROM photos_albums pa WHERE pa.hidden = 1 AND pa.album_uid = ?)", a.AlbumUID)
116+
frm.Filter = album.AlbumFilter
117+
s = s.Where("files.photo_uid NOT IN (SELECT photo_uid FROM photos_albums pa WHERE pa.hidden = 1 AND pa.album_uid = ?)", album.AlbumUID)
115118
}
116119

117120
// Enforce search distance range (km).
@@ -147,7 +150,7 @@ func searchPhotos(frm form.SearchPhotos, sess *entity.Session, resultCols string
147150
}
148151

149152
// Visitors and other restricted users can only access shared content.
150-
if frm.Scope != "" && !sess.HasShare(frm.Scope) && (sess.User().HasSharedAccessOnly(acl.ResourcePhotos) || sess.NotRegistered()) ||
153+
if frm.Scope != "" && album.CreatedBy != user.UserUID && !sess.HasShare(frm.Scope) && (sess.User().HasSharedAccessOnly(acl.ResourcePhotos) || sess.NotRegistered()) ||
151154
frm.Scope == "" && acl.Rules.Deny(acl.ResourcePhotos, aclRole, acl.ActionSearch) {
152155
event.AuditErr([]string{sess.IP(), "session %s", "%s %s as %s", authn.Denied}, sess.RefID, acl.ActionSearch.String(), string(acl.ResourcePhotos), aclRole)
153156
return PhotoResults{}, 0, ErrForbidden

0 commit comments

Comments
 (0)