mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -27,7 +27,7 @@ package acl
|
|||||||
// ACL represents an access control list based on Resource, Roles, and Permissions.
|
// ACL represents an access control list based on Resource, Roles, and Permissions.
|
||||||
type ACL map[Resource]Roles
|
type ACL map[Resource]Roles
|
||||||
|
|
||||||
// Deny checks whether the role must be denied access to the specified resource.
|
// Deny checks whether the Role must be denied access to the specified Resource.
|
||||||
func (acl ACL) Deny(resource Resource, role Role, perm Permission) bool {
|
func (acl ACL) Deny(resource Resource, role Role, perm Permission) bool {
|
||||||
return !acl.Allow(resource, role, perm)
|
return !acl.Allow(resource, role, perm)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,130 +8,130 @@ import (
|
|||||||
|
|
||||||
func TestACL_Allow(t *testing.T) {
|
func TestACL_Allow(t *testing.T) {
|
||||||
t.Run("ResourceSessions", func(t *testing.T) {
|
t.Run("ResourceSessions", func(t *testing.T) {
|
||||||
assert.True(t, Resources.Allow(ResourceSessions, RoleAdmin, AccessAll))
|
assert.True(t, Rules.Allow(ResourceSessions, RoleAdmin, AccessAll))
|
||||||
assert.True(t, Resources.Allow(ResourceSessions, RoleAdmin, AccessOwn))
|
assert.True(t, Rules.Allow(ResourceSessions, RoleAdmin, AccessOwn))
|
||||||
assert.False(t, Resources.Allow(ResourceSessions, RoleVisitor, AccessAll))
|
assert.False(t, Rules.Allow(ResourceSessions, RoleVisitor, AccessAll))
|
||||||
assert.True(t, Resources.Allow(ResourceSessions, RoleVisitor, AccessOwn))
|
assert.True(t, Rules.Allow(ResourceSessions, RoleVisitor, AccessOwn))
|
||||||
assert.False(t, Resources.Allow(ResourceSessions, RoleClient, AccessAll))
|
assert.False(t, Rules.Allow(ResourceSessions, RoleClient, AccessAll))
|
||||||
assert.True(t, Resources.Allow(ResourceSessions, RoleClient, AccessOwn))
|
assert.True(t, Rules.Allow(ResourceSessions, RoleClient, AccessOwn))
|
||||||
})
|
})
|
||||||
t.Run("ResourcePhotosRoleAdminActionModify", func(t *testing.T) {
|
t.Run("ResourcePhotosRoleAdminActionModify", func(t *testing.T) {
|
||||||
assert.True(t, Resources.Allow(ResourcePhotos, RoleAdmin, ActionUpdate))
|
assert.True(t, Rules.Allow(ResourcePhotos, RoleAdmin, ActionUpdate))
|
||||||
})
|
})
|
||||||
t.Run("ResourceDefaultRoleAdminActionDefault", func(t *testing.T) {
|
t.Run("ResourceDefaultRoleAdminActionDefault", func(t *testing.T) {
|
||||||
assert.True(t, Resources.Allow(ResourceDefault, RoleAdmin, FullAccess))
|
assert.True(t, Rules.Allow(ResourceDefault, RoleAdmin, FullAccess))
|
||||||
})
|
})
|
||||||
t.Run("ResourceDefaultRoleVisitorActionDefault", func(t *testing.T) {
|
t.Run("ResourceDefaultRoleVisitorActionDefault", func(t *testing.T) {
|
||||||
assert.False(t, Resources.Allow(ResourceDefault, RoleVisitor, FullAccess))
|
assert.False(t, Rules.Allow(ResourceDefault, RoleVisitor, FullAccess))
|
||||||
})
|
})
|
||||||
t.Run("ResourcePhotosRoleVisitorActionDefault", func(t *testing.T) {
|
t.Run("ResourcePhotosRoleVisitorActionDefault", func(t *testing.T) {
|
||||||
assert.False(t, Resources.Allow(ResourcePhotos, RoleVisitor, FullAccess))
|
assert.False(t, Rules.Allow(ResourcePhotos, RoleVisitor, FullAccess))
|
||||||
})
|
})
|
||||||
t.Run("ResourceAlbumsRoleVisitorAccessShared", func(t *testing.T) {
|
t.Run("ResourceAlbumsRoleVisitorAccessShared", func(t *testing.T) {
|
||||||
assert.True(t, Resources.Allow(ResourceAlbums, RoleVisitor, AccessShared))
|
assert.True(t, Rules.Allow(ResourceAlbums, RoleVisitor, AccessShared))
|
||||||
})
|
})
|
||||||
t.Run("ResourceAlbumsRoleVisitorActionDefault", func(t *testing.T) {
|
t.Run("ResourceAlbumsRoleVisitorActionDefault", func(t *testing.T) {
|
||||||
assert.False(t, Resources.Allow(ResourceAlbums, RoleVisitor, FullAccess))
|
assert.False(t, Rules.Allow(ResourceAlbums, RoleVisitor, FullAccess))
|
||||||
})
|
})
|
||||||
t.Run("WrongResourceRoleAdminActionDefault", func(t *testing.T) {
|
t.Run("WrongResourceRoleAdminActionDefault", func(t *testing.T) {
|
||||||
assert.True(t, Resources.Allow("wrong", RoleAdmin, FullAccess))
|
assert.True(t, Rules.Allow("wrong", RoleAdmin, FullAccess))
|
||||||
})
|
})
|
||||||
t.Run("WrongResourceRoleVisitorActionDefault", func(t *testing.T) {
|
t.Run("WrongResourceRoleVisitorActionDefault", func(t *testing.T) {
|
||||||
assert.False(t, Resources.Allow("wrong", RoleVisitor, FullAccess))
|
assert.False(t, Rules.Allow("wrong", RoleVisitor, FullAccess))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestACL_AllowAny(t *testing.T) {
|
func TestACL_AllowAny(t *testing.T) {
|
||||||
t.Run("Empty", func(t *testing.T) {
|
t.Run("Empty", func(t *testing.T) {
|
||||||
assert.False(t, Resources.AllowAny(ResourceAlbums, RoleVisitor, Permissions{}))
|
assert.False(t, Rules.AllowAny(ResourceAlbums, RoleVisitor, Permissions{}))
|
||||||
})
|
})
|
||||||
t.Run("VisitorAccess", func(t *testing.T) {
|
t.Run("VisitorAccess", func(t *testing.T) {
|
||||||
assert.True(t, Resources.AllowAny(ResourceAlbums, RoleVisitor, Permissions{AccessAll, AccessShared}))
|
assert.True(t, Rules.AllowAny(ResourceAlbums, RoleVisitor, Permissions{AccessAll, AccessShared}))
|
||||||
assert.True(t, Resources.AllowAny(ResourceAlbums, RoleVisitor, Permissions{AccessShared}))
|
assert.True(t, Rules.AllowAny(ResourceAlbums, RoleVisitor, Permissions{AccessShared}))
|
||||||
assert.False(t, Resources.AllowAny(ResourceAlbums, RoleVisitor, Permissions{AccessAll}))
|
assert.False(t, Rules.AllowAny(ResourceAlbums, RoleVisitor, Permissions{AccessAll}))
|
||||||
})
|
})
|
||||||
t.Run("ResourcePhotosRoleAdminActionModify", func(t *testing.T) {
|
t.Run("ResourcePhotosRoleAdminActionModify", func(t *testing.T) {
|
||||||
assert.True(t, Resources.AllowAny(ResourcePhotos, RoleAdmin, Permissions{ActionUpdate}))
|
assert.True(t, Rules.AllowAny(ResourcePhotos, RoleAdmin, Permissions{ActionUpdate}))
|
||||||
})
|
})
|
||||||
t.Run("ResourceDefaultRoleAdminActionDefault", func(t *testing.T) {
|
t.Run("ResourceDefaultRoleAdminActionDefault", func(t *testing.T) {
|
||||||
assert.True(t, Resources.AllowAny(ResourceDefault, RoleAdmin, Permissions{FullAccess}))
|
assert.True(t, Rules.AllowAny(ResourceDefault, RoleAdmin, Permissions{FullAccess}))
|
||||||
})
|
})
|
||||||
t.Run("ResourceDefaultRoleVisitorActionDefault", func(t *testing.T) {
|
t.Run("ResourceDefaultRoleVisitorActionDefault", func(t *testing.T) {
|
||||||
assert.False(t, Resources.AllowAny(ResourceDefault, RoleVisitor, Permissions{FullAccess}))
|
assert.False(t, Rules.AllowAny(ResourceDefault, RoleVisitor, Permissions{FullAccess}))
|
||||||
})
|
})
|
||||||
t.Run("ResourcePhotosRoleVisitorActionDefault", func(t *testing.T) {
|
t.Run("ResourcePhotosRoleVisitorActionDefault", func(t *testing.T) {
|
||||||
assert.False(t, Resources.AllowAny(ResourcePhotos, RoleVisitor, Permissions{FullAccess}))
|
assert.False(t, Rules.AllowAny(ResourcePhotos, RoleVisitor, Permissions{FullAccess}))
|
||||||
})
|
})
|
||||||
t.Run("ResourceAlbumsRoleVisitorAccessShared", func(t *testing.T) {
|
t.Run("ResourceAlbumsRoleVisitorAccessShared", func(t *testing.T) {
|
||||||
assert.True(t, Resources.AllowAny(ResourceAlbums, RoleVisitor, Permissions{AccessShared}))
|
assert.True(t, Rules.AllowAny(ResourceAlbums, RoleVisitor, Permissions{AccessShared}))
|
||||||
})
|
})
|
||||||
t.Run("ResourceAlbumsRoleVisitorActionDefault", func(t *testing.T) {
|
t.Run("ResourceAlbumsRoleVisitorActionDefault", func(t *testing.T) {
|
||||||
assert.False(t, Resources.AllowAny(ResourceAlbums, RoleVisitor, Permissions{FullAccess}))
|
assert.False(t, Rules.AllowAny(ResourceAlbums, RoleVisitor, Permissions{FullAccess}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestACL_AllowAll(t *testing.T) {
|
func TestACL_AllowAll(t *testing.T) {
|
||||||
t.Run("Empty", func(t *testing.T) {
|
t.Run("Empty", func(t *testing.T) {
|
||||||
assert.False(t, Resources.AllowAll(ResourceAlbums, RoleVisitor, Permissions{}))
|
assert.False(t, Rules.AllowAll(ResourceAlbums, RoleVisitor, Permissions{}))
|
||||||
})
|
})
|
||||||
t.Run("VisitorAccess", func(t *testing.T) {
|
t.Run("VisitorAccess", func(t *testing.T) {
|
||||||
assert.False(t, Resources.AllowAll(ResourceAlbums, RoleVisitor, Permissions{AccessAll, AccessShared}))
|
assert.False(t, Rules.AllowAll(ResourceAlbums, RoleVisitor, Permissions{AccessAll, AccessShared}))
|
||||||
assert.True(t, Resources.AllowAll(ResourceAlbums, RoleVisitor, Permissions{AccessShared}))
|
assert.True(t, Rules.AllowAll(ResourceAlbums, RoleVisitor, Permissions{AccessShared}))
|
||||||
assert.False(t, Resources.AllowAll(ResourceAlbums, RoleVisitor, Permissions{AccessAll}))
|
assert.False(t, Rules.AllowAll(ResourceAlbums, RoleVisitor, Permissions{AccessAll}))
|
||||||
})
|
})
|
||||||
t.Run("ResourcePhotosRoleAdminActionModify", func(t *testing.T) {
|
t.Run("ResourcePhotosRoleAdminActionModify", func(t *testing.T) {
|
||||||
assert.True(t, Resources.AllowAll(ResourcePhotos, RoleAdmin, Permissions{ActionUpdate}))
|
assert.True(t, Rules.AllowAll(ResourcePhotos, RoleAdmin, Permissions{ActionUpdate}))
|
||||||
})
|
})
|
||||||
t.Run("ResourceDefaultRoleAdminActionDefault", func(t *testing.T) {
|
t.Run("ResourceDefaultRoleAdminActionDefault", func(t *testing.T) {
|
||||||
assert.True(t, Resources.AllowAll(ResourceDefault, RoleAdmin, Permissions{FullAccess}))
|
assert.True(t, Rules.AllowAll(ResourceDefault, RoleAdmin, Permissions{FullAccess}))
|
||||||
})
|
})
|
||||||
t.Run("ResourceDefaultRoleVisitorActionDefault", func(t *testing.T) {
|
t.Run("ResourceDefaultRoleVisitorActionDefault", func(t *testing.T) {
|
||||||
assert.False(t, Resources.AllowAll(ResourceDefault, RoleVisitor, Permissions{FullAccess}))
|
assert.False(t, Rules.AllowAll(ResourceDefault, RoleVisitor, Permissions{FullAccess}))
|
||||||
})
|
})
|
||||||
t.Run("ResourcePhotosRoleVisitorActionDefault", func(t *testing.T) {
|
t.Run("ResourcePhotosRoleVisitorActionDefault", func(t *testing.T) {
|
||||||
assert.False(t, Resources.AllowAll(ResourcePhotos, RoleVisitor, Permissions{FullAccess}))
|
assert.False(t, Rules.AllowAll(ResourcePhotos, RoleVisitor, Permissions{FullAccess}))
|
||||||
})
|
})
|
||||||
t.Run("ResourceAlbumsRoleVisitorAccessShared", func(t *testing.T) {
|
t.Run("ResourceAlbumsRoleVisitorAccessShared", func(t *testing.T) {
|
||||||
assert.True(t, Resources.AllowAll(ResourceAlbums, RoleVisitor, Permissions{AccessShared}))
|
assert.True(t, Rules.AllowAll(ResourceAlbums, RoleVisitor, Permissions{AccessShared}))
|
||||||
})
|
})
|
||||||
t.Run("ResourceAlbumsRoleVisitorActionDefault", func(t *testing.T) {
|
t.Run("ResourceAlbumsRoleVisitorActionDefault", func(t *testing.T) {
|
||||||
assert.False(t, Resources.AllowAll(ResourceAlbums, RoleVisitor, Permissions{FullAccess}))
|
assert.False(t, Rules.AllowAll(ResourceAlbums, RoleVisitor, Permissions{FullAccess}))
|
||||||
})
|
})
|
||||||
t.Run("Empty", func(t *testing.T) {
|
t.Run("Empty", func(t *testing.T) {
|
||||||
assert.False(t, Resources.AllowAll(ResourceAlbums, RoleVisitor, Permissions{}))
|
assert.False(t, Rules.AllowAll(ResourceAlbums, RoleVisitor, Permissions{}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestACL_Deny(t *testing.T) {
|
func TestACL_Deny(t *testing.T) {
|
||||||
t.Run("ResourceDefaultRoleAdminActionDefault", func(t *testing.T) {
|
t.Run("ResourceDefaultRoleAdminActionDefault", func(t *testing.T) {
|
||||||
assert.False(t, Resources.Deny(ResourceDefault, RoleAdmin, FullAccess))
|
assert.False(t, Rules.Deny(ResourceDefault, RoleAdmin, FullAccess))
|
||||||
})
|
})
|
||||||
t.Run("ResourceDefaultRoleVisitorActionDefault", func(t *testing.T) {
|
t.Run("ResourceDefaultRoleVisitorActionDefault", func(t *testing.T) {
|
||||||
assert.True(t, Resources.Deny(ResourceDefault, RoleVisitor, FullAccess))
|
assert.True(t, Rules.Deny(ResourceDefault, RoleVisitor, FullAccess))
|
||||||
})
|
})
|
||||||
t.Run("ResourceAlbumsRoleVisitorActionAccessShared", func(t *testing.T) {
|
t.Run("ResourceAlbumsRoleVisitorActionAccessShared", func(t *testing.T) {
|
||||||
assert.False(t, Resources.Deny(ResourceAlbums, RoleVisitor, AccessShared))
|
assert.False(t, Rules.Deny(ResourceAlbums, RoleVisitor, AccessShared))
|
||||||
})
|
})
|
||||||
t.Run("ResourcePhotosRoleVisitorActionDefault", func(t *testing.T) {
|
t.Run("ResourcePhotosRoleVisitorActionDefault", func(t *testing.T) {
|
||||||
assert.True(t, Resources.Deny(ResourcePhotos, RoleVisitor, FullAccess))
|
assert.True(t, Rules.Deny(ResourcePhotos, RoleVisitor, FullAccess))
|
||||||
})
|
})
|
||||||
t.Run("ResourceAlbumsRoleVisitorActionDefault", func(t *testing.T) {
|
t.Run("ResourceAlbumsRoleVisitorActionDefault", func(t *testing.T) {
|
||||||
assert.True(t, Resources.Deny(ResourceAlbums, RoleVisitor, FullAccess))
|
assert.True(t, Rules.Deny(ResourceAlbums, RoleVisitor, FullAccess))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestACL_DenyAll(t *testing.T) {
|
func TestACL_DenyAll(t *testing.T) {
|
||||||
t.Run("ResourceFilesRoleVisitorActionDefault", func(t *testing.T) {
|
t.Run("ResourceFilesRoleVisitorActionDefault", func(t *testing.T) {
|
||||||
assert.True(t, Resources.DenyAll(ResourceFiles, RoleVisitor, Permissions{FullAccess, AccessShared, ActionView}))
|
assert.True(t, Rules.DenyAll(ResourceFiles, RoleVisitor, Permissions{FullAccess, AccessShared, ActionView}))
|
||||||
})
|
})
|
||||||
t.Run("ResourceFilesRoleAdminActionDefault", func(t *testing.T) {
|
t.Run("ResourceFilesRoleAdminActionDefault", func(t *testing.T) {
|
||||||
assert.False(t, Resources.DenyAll(ResourceFiles, RoleAdmin, Permissions{FullAccess, AccessShared, ActionView}))
|
assert.False(t, Rules.DenyAll(ResourceFiles, RoleAdmin, Permissions{FullAccess, AccessShared, ActionView}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestACL_Resources(t *testing.T) {
|
func TestACL_Resources(t *testing.T) {
|
||||||
t.Run("Resources", func(t *testing.T) {
|
t.Run("Rules", func(t *testing.T) {
|
||||||
result := Resources.Resources()
|
result := Rules.Resources()
|
||||||
assert.Len(t, result, len(ResourceNames)-1)
|
assert.Len(t, result, len(ResourceNames)-1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
84
internal/acl/const.go
Normal file
84
internal/acl/const.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package acl
|
||||||
|
|
||||||
|
// Roles that can be granted Permissions to use a Resource.
|
||||||
|
const (
|
||||||
|
RoleDefault Role = "default"
|
||||||
|
RoleAdmin Role = "admin"
|
||||||
|
RoleVisitor Role = "visitor"
|
||||||
|
RoleClient Role = "client"
|
||||||
|
RoleNone Role = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
// Permissions to use a Resource that can be granted to a Role.
|
||||||
|
const (
|
||||||
|
FullAccess Permission = "full_access"
|
||||||
|
AccessShared Permission = "access_shared"
|
||||||
|
AccessLibrary Permission = "access_library"
|
||||||
|
AccessPrivate Permission = "access_private"
|
||||||
|
AccessOwn Permission = "access_own"
|
||||||
|
AccessAll Permission = "access_all"
|
||||||
|
ActionSearch Permission = "search"
|
||||||
|
ActionView Permission = "view"
|
||||||
|
ActionUpload Permission = "upload"
|
||||||
|
ActionCreate Permission = "create"
|
||||||
|
ActionUpdate Permission = "update"
|
||||||
|
ActionDownload Permission = "download"
|
||||||
|
ActionShare Permission = "share"
|
||||||
|
ActionDelete Permission = "delete"
|
||||||
|
ActionRate Permission = "rate"
|
||||||
|
ActionReact Permission = "react"
|
||||||
|
ActionSubscribe Permission = "subscribe"
|
||||||
|
ActionManage Permission = "manage"
|
||||||
|
ActionManageOwn Permission = "manage_own"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Role can be given Permission to use a Resource.
|
||||||
|
const (
|
||||||
|
ResourceFiles Resource = "files"
|
||||||
|
ResourceFolders Resource = "folders"
|
||||||
|
ResourceShares Resource = "shares"
|
||||||
|
ResourcePhotos Resource = "photos"
|
||||||
|
ResourceVideos Resource = "videos"
|
||||||
|
ResourceFavorites Resource = "favorites"
|
||||||
|
ResourceAlbums Resource = "albums"
|
||||||
|
ResourceMoments Resource = "moments"
|
||||||
|
ResourceCalendar Resource = "calendar"
|
||||||
|
ResourcePeople Resource = "people"
|
||||||
|
ResourcePlaces Resource = "places"
|
||||||
|
ResourceLabels Resource = "labels"
|
||||||
|
ResourceConfig Resource = "config"
|
||||||
|
ResourceSettings Resource = "settings"
|
||||||
|
ResourcePasscode Resource = "passcode"
|
||||||
|
ResourcePassword Resource = "password"
|
||||||
|
ResourceServices Resource = "services"
|
||||||
|
ResourceUsers Resource = "users"
|
||||||
|
ResourceSessions Resource = "sessions"
|
||||||
|
ResourceLogs Resource = "logs"
|
||||||
|
ResourceWebDAV Resource = "webdav"
|
||||||
|
ResourceMetrics Resource = "metrics"
|
||||||
|
ResourceFeedback Resource = "feedback"
|
||||||
|
ResourceDefault Resource = "default"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Events for which a Role can be granted the ActionSubscribe Permission.
|
||||||
|
const (
|
||||||
|
ChannelUser Resource = "user"
|
||||||
|
ChannelSession Resource = "session"
|
||||||
|
ChannelAudit Resource = "audit"
|
||||||
|
ChannelLog Resource = "log"
|
||||||
|
ChannelNotify Resource = "notify"
|
||||||
|
ChannelIndex Resource = "index"
|
||||||
|
ChannelUpload Resource = "upload"
|
||||||
|
ChannelImport Resource = "import"
|
||||||
|
ChannelConfig Resource = "config"
|
||||||
|
ChannelCount Resource = "count"
|
||||||
|
ChannelPhotos Resource = "photos"
|
||||||
|
ChannelCameras Resource = "cameras"
|
||||||
|
ChannelLenses Resource = "lenses"
|
||||||
|
ChannelCountries Resource = "countries"
|
||||||
|
ChannelAlbums Resource = "albums"
|
||||||
|
ChannelLabels Resource = "labels"
|
||||||
|
ChannelSubjects Resource = "subjects"
|
||||||
|
ChannelPeople Resource = "people"
|
||||||
|
ChannelSync Resource = "sync"
|
||||||
|
)
|
||||||
@@ -11,6 +11,7 @@ var (
|
|||||||
AccessOwn: true,
|
AccessOwn: true,
|
||||||
AccessShared: true,
|
AccessShared: true,
|
||||||
AccessLibrary: true,
|
AccessLibrary: true,
|
||||||
|
ActionView: true,
|
||||||
ActionCreate: true,
|
ActionCreate: true,
|
||||||
ActionUpdate: true,
|
ActionUpdate: true,
|
||||||
ActionDelete: true,
|
ActionDelete: true,
|
||||||
@@ -21,22 +22,66 @@ var (
|
|||||||
ActionManage: true,
|
ActionManage: true,
|
||||||
ActionSubscribe: true,
|
ActionSubscribe: true,
|
||||||
}
|
}
|
||||||
GrantSubscribeAll = Grant{
|
GrantOwn = Grant{
|
||||||
AccessAll: true,
|
|
||||||
ActionSubscribe: true,
|
|
||||||
}
|
|
||||||
GrantSubscribeOwn = Grant{
|
|
||||||
AccessOwn: true,
|
AccessOwn: true,
|
||||||
|
ActionView: true,
|
||||||
|
ActionCreate: true,
|
||||||
|
ActionUpdate: true,
|
||||||
|
ActionDelete: true,
|
||||||
ActionSubscribe: true,
|
ActionSubscribe: true,
|
||||||
}
|
}
|
||||||
GrantViewAll = Grant{
|
GrantAll = Grant{
|
||||||
AccessAll: true,
|
AccessAll: true,
|
||||||
ActionView: true,
|
AccessOwn: true,
|
||||||
|
ActionView: true,
|
||||||
|
ActionCreate: true,
|
||||||
|
ActionUpdate: true,
|
||||||
|
ActionDelete: true,
|
||||||
|
ActionSubscribe: true,
|
||||||
|
}
|
||||||
|
GrantManageOwn = Grant{
|
||||||
|
AccessOwn: true,
|
||||||
|
ActionView: true,
|
||||||
|
ActionCreate: true,
|
||||||
|
ActionUpdate: true,
|
||||||
|
ActionDelete: true,
|
||||||
|
ActionSubscribe: true,
|
||||||
|
ActionManageOwn: true,
|
||||||
|
}
|
||||||
|
GrantConfigureOwn = Grant{
|
||||||
|
AccessOwn: true,
|
||||||
|
ActionCreate: true,
|
||||||
|
ActionUpdate: true,
|
||||||
|
ActionDelete: true,
|
||||||
|
}
|
||||||
|
GrantUpdateOwn = Grant{
|
||||||
|
AccessOwn: true,
|
||||||
|
ActionUpdate: true,
|
||||||
}
|
}
|
||||||
GrantViewOwn = Grant{
|
GrantViewOwn = Grant{
|
||||||
AccessOwn: true,
|
AccessOwn: true,
|
||||||
ActionView: true,
|
ActionView: true,
|
||||||
}
|
}
|
||||||
|
GrantViewUpdateOwn = Grant{
|
||||||
|
AccessOwn: true,
|
||||||
|
ActionView: true,
|
||||||
|
ActionUpdate: true,
|
||||||
|
}
|
||||||
|
GrantViewLibrary = Grant{
|
||||||
|
AccessLibrary: true,
|
||||||
|
ActionView: true,
|
||||||
|
}
|
||||||
|
GrantViewAll = Grant{
|
||||||
|
AccessAll: true,
|
||||||
|
AccessOwn: true,
|
||||||
|
ActionView: true,
|
||||||
|
}
|
||||||
|
GrantViewUpdateAll = Grant{
|
||||||
|
AccessAll: true,
|
||||||
|
AccessOwn: true,
|
||||||
|
ActionView: true,
|
||||||
|
ActionUpdate: true,
|
||||||
|
}
|
||||||
GrantViewShared = Grant{
|
GrantViewShared = Grant{
|
||||||
AccessShared: true,
|
AccessShared: true,
|
||||||
ActionView: true,
|
ActionView: true,
|
||||||
@@ -48,10 +93,30 @@ var (
|
|||||||
ActionView: true,
|
ActionView: true,
|
||||||
ActionDownload: true,
|
ActionDownload: true,
|
||||||
}
|
}
|
||||||
|
GrantSearchAll = Grant{
|
||||||
|
AccessAll: true,
|
||||||
|
ActionView: true,
|
||||||
|
ActionSearch: true,
|
||||||
|
}
|
||||||
|
GrantSubscribeOwn = Grant{
|
||||||
|
AccessOwn: true,
|
||||||
|
ActionSubscribe: true,
|
||||||
|
}
|
||||||
|
GrantSubscribeAll = Grant{
|
||||||
|
AccessAll: true,
|
||||||
|
ActionSubscribe: true,
|
||||||
|
}
|
||||||
GrantNone = Grant{}
|
GrantNone = Grant{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Allow checks whether the permission is granted.
|
// GrantDefaults defines default grants for all supported roles.
|
||||||
|
var GrantDefaults = Roles{
|
||||||
|
RoleAdmin: GrantFullAccess,
|
||||||
|
RoleVisitor: GrantViewShared,
|
||||||
|
RoleClient: GrantFullAccess,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow checks if this Grant includes the specified Permission.
|
||||||
func (grant Grant) Allow(perm Permission) bool {
|
func (grant Grant) Allow(perm Permission) bool {
|
||||||
if result, ok := grant[perm]; ok {
|
if result, ok := grant[perm]; ok {
|
||||||
return result
|
return result
|
||||||
@@ -62,9 +127,13 @@ func (grant Grant) Allow(perm Permission) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// GrantDefaults defines default grants for all supported roles.
|
// DenyAny checks if any of the Permissions are not covered by this Grant.
|
||||||
var GrantDefaults = Roles{
|
func (grant Grant) DenyAny(perms Permissions) bool {
|
||||||
RoleAdmin: GrantFullAccess,
|
for i := range perms {
|
||||||
RoleVisitor: GrantViewShared,
|
if !grant.Allow(perms[i]) {
|
||||||
RoleClient: GrantFullAccess,
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,3 +47,45 @@ func TestGrant_Allow(t *testing.T) {
|
|||||||
assert.True(t, GrantViewOwn.Allow(AccessOwn))
|
assert.True(t, GrantViewOwn.Allow(AccessOwn))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGrant_DenyAny(t *testing.T) {
|
||||||
|
t.Run("GrantFullAccessAll", func(t *testing.T) {
|
||||||
|
assert.False(t, GrantFullAccess.DenyAny(Permissions{AccessAll}))
|
||||||
|
})
|
||||||
|
t.Run("GrantFullAccessDownload", func(t *testing.T) {
|
||||||
|
assert.False(t, GrantFullAccess.DenyAny(Permissions{ActionDownload}))
|
||||||
|
})
|
||||||
|
t.Run("GrantFullAccessDelete", func(t *testing.T) {
|
||||||
|
assert.False(t, GrantFullAccess.DenyAny(Permissions{ActionDelete}))
|
||||||
|
})
|
||||||
|
t.Run("GrantFullAccessLibrary", func(t *testing.T) {
|
||||||
|
assert.False(t, GrantFullAccess.DenyAny(Permissions{AccessLibrary}))
|
||||||
|
})
|
||||||
|
t.Run("UnknownAction", func(t *testing.T) {
|
||||||
|
assert.True(t, GrantViewAll.DenyAny(Permissions{"lovecats"}))
|
||||||
|
})
|
||||||
|
t.Run("ViewAllView", func(t *testing.T) {
|
||||||
|
assert.False(t, GrantViewAll.DenyAny(Permissions{ActionView}))
|
||||||
|
})
|
||||||
|
t.Run("ViewAllAccessAll", func(t *testing.T) {
|
||||||
|
assert.False(t, GrantViewAll.DenyAny(Permissions{AccessAll}))
|
||||||
|
})
|
||||||
|
t.Run("ViewAllDownload", func(t *testing.T) {
|
||||||
|
assert.True(t, GrantViewAll.DenyAny(Permissions{ActionDownload}))
|
||||||
|
})
|
||||||
|
t.Run("ViewAllShare", func(t *testing.T) {
|
||||||
|
assert.True(t, GrantViewAll.DenyAny(Permissions{ActionShare}))
|
||||||
|
})
|
||||||
|
t.Run("ViewOwnShare", func(t *testing.T) {
|
||||||
|
assert.True(t, GrantViewOwn.DenyAny(Permissions{ActionShare}))
|
||||||
|
})
|
||||||
|
t.Run("ViewOwnView", func(t *testing.T) {
|
||||||
|
assert.False(t, GrantViewOwn.DenyAny(Permissions{ActionView}))
|
||||||
|
})
|
||||||
|
t.Run("ViewOwnAccessAll", func(t *testing.T) {
|
||||||
|
assert.True(t, GrantViewOwn.DenyAny(Permissions{AccessAll}))
|
||||||
|
})
|
||||||
|
t.Run("ViewOwnAccessOwn", func(t *testing.T) {
|
||||||
|
assert.False(t, GrantViewOwn.DenyAny(Permissions{AccessOwn}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import (
|
|||||||
|
|
||||||
func TestACL_Grants(t *testing.T) {
|
func TestACL_Grants(t *testing.T) {
|
||||||
t.Run("RoleAdmin", func(t *testing.T) {
|
t.Run("RoleAdmin", func(t *testing.T) {
|
||||||
result := Resources.Grants(RoleAdmin)
|
result := Rules.Grants(RoleAdmin)
|
||||||
assert.True(t, result[ResourcePhotos][ActionManage])
|
assert.True(t, result[ResourcePhotos][ActionManage])
|
||||||
assert.True(t, result[ResourceConfig][ActionManage])
|
assert.True(t, result[ResourceConfig][ActionManage])
|
||||||
})
|
})
|
||||||
t.Run("RoleVisitor", func(t *testing.T) {
|
t.Run("RoleVisitor", func(t *testing.T) {
|
||||||
result := Resources.Grants(RoleVisitor)
|
result := Rules.Grants(RoleVisitor)
|
||||||
assert.False(t, result[ResourcePhotos][ActionUpdate])
|
assert.False(t, result[ResourcePhotos][ActionUpdate])
|
||||||
assert.False(t, result[ResourceConfig][ActionManage])
|
assert.False(t, result[ResourceConfig][ActionManage])
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,28 +5,6 @@ import "strings"
|
|||||||
// Permission represents a single ability.
|
// Permission represents a single ability.
|
||||||
type Permission string
|
type Permission string
|
||||||
|
|
||||||
// Permissions that can be granted to roles.
|
|
||||||
const (
|
|
||||||
FullAccess Permission = "full_access"
|
|
||||||
AccessShared Permission = "access_shared"
|
|
||||||
AccessLibrary Permission = "access_library"
|
|
||||||
AccessPrivate Permission = "access_private"
|
|
||||||
AccessOwn Permission = "access_own"
|
|
||||||
AccessAll Permission = "access_all"
|
|
||||||
ActionSearch Permission = "search"
|
|
||||||
ActionView Permission = "view"
|
|
||||||
ActionUpload Permission = "upload"
|
|
||||||
ActionCreate Permission = "create"
|
|
||||||
ActionUpdate Permission = "update"
|
|
||||||
ActionDownload Permission = "download"
|
|
||||||
ActionShare Permission = "share"
|
|
||||||
ActionDelete Permission = "delete"
|
|
||||||
ActionRate Permission = "rate"
|
|
||||||
ActionReact Permission = "react"
|
|
||||||
ActionManage Permission = "manage"
|
|
||||||
ActionSubscribe Permission = "subscribe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// String returns the type as string.
|
// String returns the type as string.
|
||||||
func (p Permission) String() string {
|
func (p Permission) String() string {
|
||||||
return strings.ReplaceAll(string(p), "_", " ")
|
return strings.ReplaceAll(string(p), "_", " ")
|
||||||
@@ -46,17 +24,3 @@ func (p Permission) Equal(s string) bool {
|
|||||||
func (p Permission) NotEqual(s string) bool {
|
func (p Permission) NotEqual(s string) bool {
|
||||||
return !p.Equal(s)
|
return !p.Equal(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Permissions is a list of permissions.
|
|
||||||
type Permissions []Permission
|
|
||||||
|
|
||||||
// String returns the permissions as a comma-separated string.
|
|
||||||
func (perm Permissions) String() string {
|
|
||||||
s := make([]string, len(perm))
|
|
||||||
|
|
||||||
for i := range perm {
|
|
||||||
s[i] = perm[i].String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(s, ", ")
|
|
||||||
}
|
|
||||||
|
|||||||
17
internal/acl/permissions.go
Normal file
17
internal/acl/permissions.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package acl
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// Permissions represents a list of permissions.
|
||||||
|
type Permissions []Permission
|
||||||
|
|
||||||
|
// String returns the permissions as a comma-separated string.
|
||||||
|
func (perm Permissions) String() string {
|
||||||
|
s := make([]string, len(perm))
|
||||||
|
|
||||||
|
for i := range perm {
|
||||||
|
s[i] = perm[i].String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(s, ", ")
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package acl
|
|
||||||
|
|
||||||
// Events that Roles can be granted Permission to listen to.
|
|
||||||
const (
|
|
||||||
ChannelUser Resource = "user"
|
|
||||||
ChannelSession Resource = "session"
|
|
||||||
ChannelAudit Resource = "audit"
|
|
||||||
ChannelLog Resource = "log"
|
|
||||||
ChannelNotify Resource = "notify"
|
|
||||||
ChannelIndex Resource = "index"
|
|
||||||
ChannelUpload Resource = "upload"
|
|
||||||
ChannelImport Resource = "import"
|
|
||||||
ChannelConfig Resource = "config"
|
|
||||||
ChannelCount Resource = "count"
|
|
||||||
ChannelPhotos Resource = "photos"
|
|
||||||
ChannelCameras Resource = "cameras"
|
|
||||||
ChannelLenses Resource = "lenses"
|
|
||||||
ChannelCountries Resource = "countries"
|
|
||||||
ChannelAlbums Resource = "albums"
|
|
||||||
ChannelLabels Resource = "labels"
|
|
||||||
ChannelSubjects Resource = "subjects"
|
|
||||||
ChannelPeople Resource = "people"
|
|
||||||
ChannelSync Resource = "sync"
|
|
||||||
)
|
|
||||||
@@ -1,33 +1,5 @@
|
|||||||
package acl
|
package acl
|
||||||
|
|
||||||
// Resources that Roles can be granted Permission.
|
|
||||||
const (
|
|
||||||
ResourceFiles Resource = "files"
|
|
||||||
ResourceFolders Resource = "folders"
|
|
||||||
ResourceShares Resource = "shares"
|
|
||||||
ResourcePhotos Resource = "photos"
|
|
||||||
ResourceVideos Resource = "videos"
|
|
||||||
ResourceFavorites Resource = "favorites"
|
|
||||||
ResourceAlbums Resource = "albums"
|
|
||||||
ResourceMoments Resource = "moments"
|
|
||||||
ResourceCalendar Resource = "calendar"
|
|
||||||
ResourcePeople Resource = "people"
|
|
||||||
ResourcePlaces Resource = "places"
|
|
||||||
ResourceLabels Resource = "labels"
|
|
||||||
ResourceConfig Resource = "config"
|
|
||||||
ResourceSettings Resource = "settings"
|
|
||||||
ResourcePasscode Resource = "passcode"
|
|
||||||
ResourcePassword Resource = "password"
|
|
||||||
ResourceServices Resource = "services"
|
|
||||||
ResourceUsers Resource = "users"
|
|
||||||
ResourceSessions Resource = "sessions"
|
|
||||||
ResourceLogs Resource = "logs"
|
|
||||||
ResourceWebDAV Resource = "webdav"
|
|
||||||
ResourceMetrics Resource = "metrics"
|
|
||||||
ResourceFeedback Resource = "feedback"
|
|
||||||
ResourceDefault Resource = "default"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ResourceNames contains a list of all specified resources.
|
// ResourceNames contains a list of all specified resources.
|
||||||
var ResourceNames = []Resource{
|
var ResourceNames = []Resource{
|
||||||
ResourceFiles,
|
ResourceFiles,
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
package acl
|
package acl
|
||||||
|
|
||||||
// Roles that can be assigned to users.
|
|
||||||
const (
|
|
||||||
RoleDefault Role = "default"
|
|
||||||
RoleAdmin Role = "admin"
|
|
||||||
RoleVisitor Role = "visitor"
|
|
||||||
RoleClient Role = "client"
|
|
||||||
RoleNone Role = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
// RoleStrings represents user role names mapped to roles.
|
// RoleStrings represents user role names mapped to roles.
|
||||||
type RoleStrings = map[string]Role
|
type RoleStrings = map[string]Role
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package acl
|
package acl
|
||||||
|
|
||||||
// Resources specifies granted permissions by Resource and Role.
|
// Rules specifies granted permissions by Resource and Role.
|
||||||
var Resources = ACL{
|
var Rules = ACL{
|
||||||
ResourceFiles: Roles{
|
ResourceFiles: Roles{
|
||||||
RoleAdmin: GrantFullAccess,
|
RoleAdmin: GrantFullAccess,
|
||||||
RoleClient: GrantFullAccess,
|
RoleClient: GrantFullAccess,
|
||||||
@@ -51,8 +51,8 @@ var Resources = ACL{
|
|||||||
},
|
},
|
||||||
ResourceSettings: Roles{
|
ResourceSettings: Roles{
|
||||||
RoleAdmin: GrantFullAccess,
|
RoleAdmin: GrantFullAccess,
|
||||||
RoleVisitor: Grant{AccessOwn: true, ActionView: true},
|
RoleVisitor: GrantViewOwn,
|
||||||
RoleClient: Grant{AccessOwn: true, ActionView: true, ActionUpdate: true},
|
RoleClient: GrantViewUpdateOwn,
|
||||||
},
|
},
|
||||||
ResourceServices: Roles{
|
ResourceServices: Roles{
|
||||||
RoleAdmin: GrantFullAccess,
|
RoleAdmin: GrantFullAccess,
|
||||||
@@ -64,12 +64,12 @@ var Resources = ACL{
|
|||||||
RoleAdmin: GrantFullAccess,
|
RoleAdmin: GrantFullAccess,
|
||||||
},
|
},
|
||||||
ResourceUsers: Roles{
|
ResourceUsers: Roles{
|
||||||
RoleAdmin: Grant{AccessAll: true, AccessOwn: true, ActionView: true, ActionCreate: true, ActionUpdate: true, ActionDelete: true, ActionSubscribe: true},
|
RoleAdmin: GrantAll,
|
||||||
RoleClient: Grant{AccessOwn: true, ActionView: true},
|
RoleClient: GrantViewOwn,
|
||||||
},
|
},
|
||||||
ResourceSessions: Roles{
|
ResourceSessions: Roles{
|
||||||
RoleAdmin: GrantFullAccess,
|
RoleAdmin: GrantFullAccess,
|
||||||
RoleDefault: Grant{AccessOwn: true, ActionView: true, ActionCreate: true, ActionUpdate: true, ActionDelete: true, ActionSubscribe: true},
|
RoleDefault: GrantOwn,
|
||||||
},
|
},
|
||||||
ResourceLogs: Roles{
|
ResourceLogs: Roles{
|
||||||
RoleAdmin: GrantFullAccess,
|
RoleAdmin: GrantFullAccess,
|
||||||
37
internal/acl/scopes.go
Normal file
37
internal/acl/scopes.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package acl
|
||||||
|
|
||||||
|
// Permission scopes to Grant multiple Permissions for a Resource.
|
||||||
|
const (
|
||||||
|
ScopeRead Permission = "read"
|
||||||
|
ScopeWrite Permission = "write"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
GrantScopeRead = Grant{
|
||||||
|
AccessShared: true,
|
||||||
|
AccessLibrary: true,
|
||||||
|
AccessPrivate: true,
|
||||||
|
AccessOwn: true,
|
||||||
|
AccessAll: true,
|
||||||
|
ActionSearch: true,
|
||||||
|
ActionView: true,
|
||||||
|
ActionDownload: true,
|
||||||
|
ActionSubscribe: true,
|
||||||
|
}
|
||||||
|
GrantScopeWrite = Grant{
|
||||||
|
AccessShared: true,
|
||||||
|
AccessLibrary: true,
|
||||||
|
AccessPrivate: true,
|
||||||
|
AccessOwn: true,
|
||||||
|
AccessAll: true,
|
||||||
|
ActionUpload: true,
|
||||||
|
ActionCreate: true,
|
||||||
|
ActionUpdate: true,
|
||||||
|
ActionShare: true,
|
||||||
|
ActionDelete: true,
|
||||||
|
ActionRate: true,
|
||||||
|
ActionReact: true,
|
||||||
|
ActionManage: true,
|
||||||
|
ActionManageOwn: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
37
internal/acl/scopes_test.go
Normal file
37
internal/acl/scopes_test.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package acl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGrantScopeRead(t *testing.T) {
|
||||||
|
t.Run("ActionView", func(t *testing.T) {
|
||||||
|
assert.True(t, GrantScopeRead.Allow(ActionView))
|
||||||
|
assert.False(t, GrantScopeRead.DenyAny(Permissions{ActionView}))
|
||||||
|
})
|
||||||
|
t.Run("ActionUpdate", func(t *testing.T) {
|
||||||
|
assert.False(t, GrantScopeRead.Allow(ActionUpdate))
|
||||||
|
assert.True(t, GrantScopeRead.DenyAny(Permissions{ActionUpdate}))
|
||||||
|
})
|
||||||
|
t.Run("AccessAll", func(t *testing.T) {
|
||||||
|
assert.True(t, GrantScopeRead.Allow(AccessAll))
|
||||||
|
assert.False(t, GrantScopeRead.DenyAny(Permissions{AccessAll}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGrantScopeWrite(t *testing.T) {
|
||||||
|
t.Run("ActionView", func(t *testing.T) {
|
||||||
|
assert.False(t, GrantScopeWrite.Allow(ActionView))
|
||||||
|
assert.True(t, GrantScopeWrite.DenyAny(Permissions{ActionView}))
|
||||||
|
})
|
||||||
|
t.Run("ActionUpdate", func(t *testing.T) {
|
||||||
|
assert.True(t, GrantScopeWrite.Allow(ActionUpdate))
|
||||||
|
assert.False(t, GrantScopeWrite.DenyAny(Permissions{ActionUpdate}))
|
||||||
|
})
|
||||||
|
t.Run("AccessAll", func(t *testing.T) {
|
||||||
|
assert.True(t, GrantScopeWrite.Allow(AccessAll))
|
||||||
|
assert.False(t, GrantScopeWrite.DenyAny(Permissions{AccessAll}))
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -12,13 +12,13 @@ import (
|
|||||||
|
|
||||||
// Auth checks if the user is authorized to access a resource with the given permission
|
// Auth checks if the user is authorized to access a resource with the given permission
|
||||||
// and returns the session or nil otherwise.
|
// and returns the session or nil otherwise.
|
||||||
func Auth(c *gin.Context, resource acl.Resource, grant acl.Permission) *entity.Session {
|
func Auth(c *gin.Context, resource acl.Resource, perm acl.Permission) *entity.Session {
|
||||||
return AuthAny(c, resource, acl.Permissions{grant})
|
return AuthAny(c, resource, acl.Permissions{perm})
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthAny checks if the user is authorized to access a resource with any of the specified permissions
|
// AuthAny checks if the user is authorized to access a resource with any of the specified permissions
|
||||||
// and returns the session or nil otherwise.
|
// and returns the session or nil otherwise.
|
||||||
func AuthAny(c *gin.Context, resource acl.Resource, grants acl.Permissions) (s *entity.Session) {
|
func AuthAny(c *gin.Context, resource acl.Resource, perms acl.Permissions) (s *entity.Session) {
|
||||||
// Prevent CDNs from caching responses that require authentication.
|
// Prevent CDNs from caching responses that require authentication.
|
||||||
if header.IsCdn(c.Request) {
|
if header.IsCdn(c.Request) {
|
||||||
return entity.SessionStatusForbidden()
|
return entity.SessionStatusForbidden()
|
||||||
@@ -30,7 +30,7 @@ func AuthAny(c *gin.Context, resource acl.Resource, grants acl.Permissions) (s *
|
|||||||
|
|
||||||
// Find active session to perform authorization check or deny if no session was found.
|
// Find active session to perform authorization check or deny if no session was found.
|
||||||
if s = Session(clientIp, authToken); s == nil {
|
if s = Session(clientIp, authToken); s == nil {
|
||||||
event.AuditWarn([]string{clientIp, "%s %s without authentication", "denied"}, grants.String(), string(resource))
|
event.AuditWarn([]string{clientIp, "%s %s without authentication", "denied"}, perms.String(), string(resource))
|
||||||
return entity.SessionStatusUnauthorized()
|
return entity.SessionStatusUnauthorized()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,33 +41,33 @@ func AuthAny(c *gin.Context, resource acl.Resource, grants acl.Permissions) (s *
|
|||||||
// If the request is from a client application, check its authorization based
|
// If the request is from a client application, check its authorization based
|
||||||
// on the allowed scope, the ACL, and the user account it belongs to (if any).
|
// on the allowed scope, the ACL, and the user account it belongs to (if any).
|
||||||
if s.IsClient() {
|
if s.IsClient() {
|
||||||
// Check ACL resource name against the permitted scope.
|
// Check the resource and required permissions against the session scope.
|
||||||
if !s.HasScope(resource.String()) {
|
if s.ScopeExcludes(resource, perms) {
|
||||||
event.AuditErr([]string{clientIp, "client %s", "session %s", "access %s", "denied"}, clean.Log(s.ClientInfo()), s.RefID, string(resource))
|
event.AuditErr([]string{clientIp, "client %s", "session %s", "access %s", "denied"}, clean.Log(s.ClientInfo()), s.RefID, string(resource))
|
||||||
return entity.SessionStatusForbidden()
|
return entity.SessionStatusForbidden()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform an authorization check based on the ACL defaults for client applications.
|
// Check request authorization against client application ACL rules.
|
||||||
if acl.Resources.DenyAll(resource, s.ClientRole(), grants) {
|
if acl.Rules.DenyAll(resource, s.ClientRole(), perms) {
|
||||||
event.AuditErr([]string{clientIp, "client %s", "session %s", "%s %s", "denied"}, clean.Log(s.ClientInfo()), s.RefID, grants.String(), string(resource))
|
event.AuditErr([]string{clientIp, "client %s", "session %s", "%s %s", "denied"}, clean.Log(s.ClientInfo()), s.RefID, perms.String(), string(resource))
|
||||||
return entity.SessionStatusForbidden()
|
return entity.SessionStatusForbidden()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additionally check the user authorization if the client belongs to a user account.
|
// Also check the request authorization against the user's ACL rules?
|
||||||
if s.NoUser() {
|
if s.NoUser() {
|
||||||
// Allow access based on the ACL defaults for client applications.
|
// Allow access based on the ACL defaults for client applications.
|
||||||
event.AuditInfo([]string{clientIp, "client %s", "session %s", "%s %s", "granted"}, clean.Log(s.ClientInfo()), s.RefID, grants.String(), string(resource))
|
event.AuditInfo([]string{clientIp, "client %s", "session %s", "%s %s", "granted"}, clean.Log(s.ClientInfo()), s.RefID, perms.String(), string(resource))
|
||||||
} else if u := s.User(); !u.IsDisabled() && !u.IsUnknown() && u.IsRegistered() {
|
} else if u := s.User(); !u.IsDisabled() && !u.IsUnknown() && u.IsRegistered() {
|
||||||
if acl.Resources.DenyAll(resource, u.AclRole(), grants) {
|
if acl.Rules.DenyAll(resource, u.AclRole(), perms) {
|
||||||
event.AuditErr([]string{clientIp, "client %s", "session %s", "%s %s as %s", "denied"}, clean.Log(s.ClientInfo()), s.RefID, grants.String(), string(resource), u.String())
|
event.AuditErr([]string{clientIp, "client %s", "session %s", "%s %s as %s", "denied"}, clean.Log(s.ClientInfo()), s.RefID, perms.String(), string(resource), u.String())
|
||||||
return entity.SessionStatusForbidden()
|
return entity.SessionStatusForbidden()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow access based on the user role.
|
// Allow access based on the user role.
|
||||||
event.AuditInfo([]string{clientIp, "client %s", "session %s", "%s %s as %s", "granted"}, clean.Log(s.ClientInfo()), s.RefID, grants.String(), string(resource), u.String())
|
event.AuditInfo([]string{clientIp, "client %s", "session %s", "%s %s as %s", "granted"}, clean.Log(s.ClientInfo()), s.RefID, perms.String(), string(resource), u.String())
|
||||||
} else {
|
} else {
|
||||||
// Deny access if it is not a regular user account or the account has been disabled.
|
// Deny access if it is not a regular user account or the account has been disabled.
|
||||||
event.AuditErr([]string{clientIp, "client %s", "session %s", "%s %s as unauthorized user", "denied"}, clean.Log(s.ClientInfo()), s.RefID, grants.String(), string(resource))
|
event.AuditErr([]string{clientIp, "client %s", "session %s", "%s %s as unauthorized user", "denied"}, clean.Log(s.ClientInfo()), s.RefID, perms.String(), string(resource))
|
||||||
return entity.SessionStatusForbidden()
|
return entity.SessionStatusForbidden()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,13 +76,13 @@ func AuthAny(c *gin.Context, resource acl.Resource, grants acl.Permissions) (s *
|
|||||||
|
|
||||||
// Otherwise, perform a regular ACL authorization check based on the user role.
|
// Otherwise, perform a regular ACL authorization check based on the user role.
|
||||||
if u := s.User(); u.IsUnknown() || u.IsDisabled() {
|
if u := s.User(); u.IsUnknown() || u.IsDisabled() {
|
||||||
event.AuditWarn([]string{clientIp, "session %s", "%s %s as unauthorized user", "denied"}, s.RefID, grants.String(), string(resource))
|
event.AuditWarn([]string{clientIp, "session %s", "%s %s as unauthorized user", "denied"}, s.RefID, perms.String(), string(resource))
|
||||||
return entity.SessionStatusUnauthorized()
|
return entity.SessionStatusUnauthorized()
|
||||||
} else if acl.Resources.DenyAll(resource, u.AclRole(), grants) {
|
} else if acl.Rules.DenyAll(resource, u.AclRole(), perms) {
|
||||||
event.AuditErr([]string{clientIp, "session %s", "%s %s as %s", "denied"}, s.RefID, grants.String(), string(resource), u.AclRole().String())
|
event.AuditErr([]string{clientIp, "session %s", "%s %s as %s", "denied"}, s.RefID, perms.String(), string(resource), u.AclRole().String())
|
||||||
return entity.SessionStatusForbidden()
|
return entity.SessionStatusForbidden()
|
||||||
} else {
|
} else {
|
||||||
event.AuditInfo([]string{clientIp, "session %s", "%s %s as %s", "granted"}, s.RefID, grants.String(), string(resource), u.AclRole().String())
|
event.AuditInfo([]string{clientIp, "session %s", "%s %s as %s", "granted"}, s.RefID, perms.String(), string(resource), u.AclRole().String())
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ func BatchPhotosDelete(router *gin.RouterGroup) {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Abort if user wants to delete all but does not have sufficient privileges.
|
// Abort if user wants to delete all but does not have sufficient privileges.
|
||||||
if f.All && !acl.Resources.AllowAll(acl.ResourcePhotos, s.UserRole(), acl.Permissions{acl.AccessAll, acl.ActionManage}) {
|
if f.All && !acl.Rules.AllowAll(acl.ResourcePhotos, s.UserRole(), acl.Permissions{acl.AccessAll, acl.ActionManage}) {
|
||||||
AbortForbidden(c)
|
AbortForbidden(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,8 +91,8 @@ func SaveSettings(router *gin.RouterGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if acl.Resources.DenyAll(acl.ResourceSettings, s.UserRole(), acl.Permissions{acl.ActionUpdate, acl.ActionManage}) {
|
if acl.Rules.DenyAll(acl.ResourceSettings, s.UserRole(), acl.Permissions{acl.ActionUpdate, acl.ActionManage}) {
|
||||||
c.JSON(http.StatusOK, user.Settings().Apply(settings).ApplyTo(conf.Settings().ApplyACL(acl.Resources, user.AclRole())))
|
c.JSON(http.StatusOK, user.Settings().Apply(settings).ApplyTo(conf.Settings().ApplyACL(acl.Rules, user.AclRole())))
|
||||||
return
|
return
|
||||||
} else if err := user.Settings().Apply(settings).Save(); err != nil {
|
} else if err := user.Settings().Apply(settings).Save(); err != nil {
|
||||||
log.Debugf("config: %s (save user settings)", err)
|
log.Debugf("config: %s (save user settings)", err)
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func SearchFolders(router *gin.RouterGroup, urlPath, rootName, rootPath string)
|
|||||||
// Exclude private content?
|
// Exclude private content?
|
||||||
if !get.Config().Settings().Features.Private {
|
if !get.Config().Settings().Features.Private {
|
||||||
f.Public = false
|
f.Public = false
|
||||||
} else if acl.Resources.Deny(acl.ResourcePhotos, aclRole, acl.AccessPrivate) {
|
} else if acl.Rules.Deny(acl.ResourcePhotos, aclRole, acl.AccessPrivate) {
|
||||||
f.Public = true
|
f.Public = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func StartImport(router *gin.RouterGroup) {
|
|||||||
if token := path.Base(srcFolder); token != "" && path.Dir(srcFolder) == UploadPath {
|
if token := path.Base(srcFolder); token != "" && path.Dir(srcFolder) == UploadPath {
|
||||||
srcFolder = path.Join(UploadPath, s.RefID+token)
|
srcFolder = path.Join(UploadPath, s.RefID+token)
|
||||||
event.AuditInfo([]string{ClientIP(c), "session %s", "import uploads from %s as %s", "granted"}, s.RefID, clean.Log(srcFolder), s.UserRole().String())
|
event.AuditInfo([]string{ClientIP(c), "session %s", "import uploads from %s as %s", "granted"}, s.RefID, clean.Log(srcFolder), s.UserRole().String())
|
||||||
} else if acl.Resources.Deny(acl.ResourceFiles, s.UserRole(), acl.ActionManage) {
|
} else if acl.Rules.Deny(acl.ResourceFiles, s.UserRole(), acl.ActionManage) {
|
||||||
event.AuditErr([]string{ClientIP(c), "session %s", "import files from %s as %s", "denied"}, s.RefID, clean.Log(srcFolder), s.UserRole().String())
|
event.AuditErr([]string{ClientIP(c), "session %s", "import files from %s as %s", "denied"}, s.RefID, clean.Log(srcFolder), s.UserRole().String())
|
||||||
AbortForbidden(c)
|
AbortForbidden(c)
|
||||||
return
|
return
|
||||||
@@ -99,7 +99,7 @@ func StartImport(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
// Add imported files to albums if allowed.
|
// Add imported files to albums if allowed.
|
||||||
if len(f.Albums) > 0 &&
|
if len(f.Albums) > 0 &&
|
||||||
acl.Resources.AllowAny(acl.ResourceAlbums, s.UserRole(), acl.Permissions{acl.ActionCreate, acl.ActionUpload}) {
|
acl.Rules.AllowAny(acl.ResourceAlbums, s.UserRole(), acl.Permissions{acl.ActionCreate, acl.ActionUpload}) {
|
||||||
log.Debugf("import: adding files to album %s", clean.Log(strings.Join(f.Albums, " and ")))
|
log.Debugf("import: adding files to album %s", clean.Log(strings.Join(f.Albums, " and ")))
|
||||||
opt.Albums = f.Albums
|
opt.Albums = f.Albums
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func SearchPhotos(router *gin.RouterGroup) {
|
|||||||
// Ignore private flag if feature is disabled.
|
// Ignore private flag if feature is disabled.
|
||||||
if f.Scope == "" &&
|
if f.Scope == "" &&
|
||||||
settings.Features.Review &&
|
settings.Features.Review &&
|
||||||
acl.Resources.Deny(acl.ResourcePhotos, s.UserRole(), acl.ActionManage) {
|
acl.Rules.Deny(acl.ResourcePhotos, s.UserRole(), acl.ActionManage) {
|
||||||
f.Quality = 3
|
f.Quality = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func SearchGeo(router *gin.RouterGroup) {
|
|||||||
// Ignore private flag if feature is disabled.
|
// Ignore private flag if feature is disabled.
|
||||||
if f.Scope == "" &&
|
if f.Scope == "" &&
|
||||||
settings.Features.Review &&
|
settings.Features.Review &&
|
||||||
acl.Resources.Deny(acl.ResourcePhotos, s.UserRole(), acl.ActionManage) {
|
acl.Rules.Deny(acl.ResourcePhotos, s.UserRole(), acl.ActionManage) {
|
||||||
f.Quality = 3
|
f.Quality = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ func LikePhoto(router *gin.RouterGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if get.Config().Experimental() && acl.Resources.Allow(acl.ResourcePhotos, s.UserRole(), acl.ActionReact) {
|
if get.Config().Experimental() && acl.Rules.Allow(acl.ResourcePhotos, s.UserRole(), acl.ActionReact) {
|
||||||
logWarn("react", m.React(s.User(), react.Find("love")))
|
logWarn("react", m.React(s.User(), react.Find("love")))
|
||||||
}
|
}
|
||||||
|
|
||||||
if acl.Resources.Allow(acl.ResourcePhotos, s.UserRole(), acl.ActionUpdate) {
|
if acl.Rules.Allow(acl.ResourcePhotos, s.UserRole(), acl.ActionUpdate) {
|
||||||
err = m.SetFavorite(true)
|
err = m.SetFavorite(true)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -71,11 +71,11 @@ func DislikePhoto(router *gin.RouterGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if get.Config().Experimental() && acl.Resources.Allow(acl.ResourcePhotos, s.UserRole(), acl.ActionReact) {
|
if get.Config().Experimental() && acl.Rules.Allow(acl.ResourcePhotos, s.UserRole(), acl.ActionReact) {
|
||||||
logWarn("react", m.UnReact(s.User()))
|
logWarn("react", m.UnReact(s.User()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if acl.Resources.Allow(acl.ResourcePhotos, s.UserRole(), acl.ActionUpdate) {
|
if acl.Rules.Allow(acl.ResourcePhotos, s.UserRole(), acl.ActionUpdate) {
|
||||||
err = m.SetFavorite(false)
|
err = m.SetFavorite(false)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func DeleteSession(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
// Only admins may delete other sessions by ref id.
|
// Only admins may delete other sessions by ref id.
|
||||||
if rnd.IsRefID(id) {
|
if rnd.IsRefID(id) {
|
||||||
if !acl.Resources.AllowAll(acl.ResourceSessions, s.UserRole(), acl.Permissions{acl.AccessAll, acl.ActionManage}) {
|
if !acl.Rules.AllowAll(acl.ResourceSessions, s.UserRole(), acl.Permissions{acl.AccessAll, acl.ActionManage}) {
|
||||||
event.AuditErr([]string{clientIp, "session %s", "delete %s as %s", "denied"}, s.RefID, acl.ResourceSessions.String(), s.UserRole())
|
event.AuditErr([]string{clientIp, "session %s", "delete %s as %s", "denied"}, s.RefID, acl.ResourceSessions.String(), s.UserRole())
|
||||||
Abort(c, http.StatusForbidden, i18n.ErrForbidden)
|
Abort(c, http.StatusForbidden, i18n.ErrForbidden)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func UploadUserAvatar(router *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the session user is has user management privileges.
|
// Check if the session user is has user management privileges.
|
||||||
isAdmin := acl.Resources.AllowAll(acl.ResourceUsers, s.UserRole(), acl.Permissions{acl.AccessAll, acl.ActionManage})
|
isAdmin := acl.Rules.AllowAll(acl.ResourceUsers, s.UserRole(), acl.Permissions{acl.AccessAll, acl.ActionManage})
|
||||||
uid := clean.UID(c.Param("uid"))
|
uid := clean.UID(c.Param("uid"))
|
||||||
|
|
||||||
// Users may only change their own avatar.
|
// Users may only change their own avatar.
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func UpdateUserPassword(router *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the current user has management privileges.
|
// Check if the current user has management privileges.
|
||||||
isAdmin := acl.Resources.AllowAll(acl.ResourceUsers, s.UserRole(), acl.Permissions{acl.AccessAll, acl.ActionManage})
|
isAdmin := acl.Rules.AllowAll(acl.ResourceUsers, s.UserRole(), acl.Permissions{acl.AccessAll, acl.ActionManage})
|
||||||
isSuperAdmin := isAdmin && s.User().IsSuperAdmin()
|
isSuperAdmin := isAdmin && s.User().IsSuperAdmin()
|
||||||
uid := clean.UID(c.Param("uid"))
|
uid := clean.UID(c.Param("uid"))
|
||||||
|
|
||||||
|
|||||||
67
internal/api/users_sessions.go
Normal file
67
internal/api/users_sessions.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/acl"
|
||||||
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
|
"github.com/photoprism/photoprism/internal/get"
|
||||||
|
"github.com/photoprism/photoprism/internal/search"
|
||||||
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FindUserSessions finds user sessions and returns them as JSON.
|
||||||
|
//
|
||||||
|
// GET /api/v1/users/:uid/sessions
|
||||||
|
func FindUserSessions(router *gin.RouterGroup) {
|
||||||
|
router.GET("/users/:uid/sessions", func(c *gin.Context) {
|
||||||
|
// Check if the session user is has user management privileges.
|
||||||
|
s := Auth(c, acl.ResourceSessions, acl.ActionManageOwn)
|
||||||
|
|
||||||
|
if s.Abort(c) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get global config.
|
||||||
|
conf := get.Config()
|
||||||
|
|
||||||
|
// Check feature flags and authorization.
|
||||||
|
if conf.Public() || conf.Demo() || !s.HasRegisteredUser() || conf.DisableSettings() {
|
||||||
|
c.JSON(http.StatusNotFound, entity.Users{})
|
||||||
|
return
|
||||||
|
} else if !rnd.IsUID(s.UserUID, entity.UserUID) || s.UserUID != c.Param("uid") {
|
||||||
|
c.JSON(http.StatusForbidden, entity.Users{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var f form.SearchSessions
|
||||||
|
|
||||||
|
// Init search form.
|
||||||
|
err := c.MustBindWith(&f, binding.Form)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
AbortBadRequest(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by user.
|
||||||
|
f.UID = s.UserUID
|
||||||
|
|
||||||
|
// Perform search.
|
||||||
|
result, err := search.Sessions(f)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
AbortBadRequest(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
AddLimitHeader(c, f.Count)
|
||||||
|
AddOffsetHeader(c, f.Offset)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
48
internal/api/users_sessions_test.go
Normal file
48
internal/api/users_sessions_test.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFindUserSessions(t *testing.T) {
|
||||||
|
t.Run("Public", func(t *testing.T) {
|
||||||
|
app, router, _ := NewApiTest()
|
||||||
|
FindUserSessions(router)
|
||||||
|
r := PerformRequest(app, "GET", "/api/v1/users/uqxc08w3d0ej2283/sessions")
|
||||||
|
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||||
|
})
|
||||||
|
t.Run("Unauthorized", func(t *testing.T) {
|
||||||
|
app, router, conf := NewApiTest()
|
||||||
|
conf.SetAuthMode(config.AuthModePasswd)
|
||||||
|
defer conf.SetAuthMode(config.AuthModePublic)
|
||||||
|
FindUserSessions(router)
|
||||||
|
|
||||||
|
r := PerformRequest(app, "GET", "/api/v1/users/uqxc08w3d0ej2283/sessions")
|
||||||
|
assert.Equal(t, http.StatusUnauthorized, r.Code)
|
||||||
|
})
|
||||||
|
t.Run("InvalidUID", func(t *testing.T) {
|
||||||
|
app, router, conf := NewApiTest()
|
||||||
|
conf.SetAuthMode(config.AuthModePasswd)
|
||||||
|
defer conf.SetAuthMode(config.AuthModePublic)
|
||||||
|
FindUserSessions(router)
|
||||||
|
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
|
||||||
|
|
||||||
|
r := AuthenticatedRequest(app, "GET", "/api/v1/users/uqxetseacy5eo9z2/sessions?count=100", sessId)
|
||||||
|
assert.Equal(t, http.StatusForbidden, r.Code)
|
||||||
|
})
|
||||||
|
t.Run("Success", func(t *testing.T) {
|
||||||
|
app, router, conf := NewApiTest()
|
||||||
|
conf.SetAuthMode(config.AuthModePasswd)
|
||||||
|
defer conf.SetAuthMode(config.AuthModePublic)
|
||||||
|
FindUserSessions(router)
|
||||||
|
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
|
||||||
|
|
||||||
|
r := AuthenticatedRequest(app, "GET", "/api/v1/users/uqxetse3cy5eo9z2/sessions?count=100", sessId)
|
||||||
|
assert.Equal(t, http.StatusOK, r.Code)
|
||||||
|
// t.Logf("FindUserSessions/Success: %s", r.Body.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -61,7 +61,7 @@ func UpdateUser(router *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the session user is has user management privileges.
|
// Check if the session user is has user management privileges.
|
||||||
isAdmin := acl.Resources.AllowAll(acl.ResourceUsers, s.UserRole(), acl.Permissions{acl.AccessAll, acl.ActionManage})
|
isAdmin := acl.Rules.AllowAll(acl.ResourceUsers, s.UserRole(), acl.Permissions{acl.AccessAll, acl.ActionManage})
|
||||||
privilegeLevelChange := isAdmin && m.PrivilegeLevelChange(f)
|
privilegeLevelChange := isAdmin && m.PrivilegeLevelChange(f)
|
||||||
|
|
||||||
// Get user from session.
|
// Get user from session.
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ func ProcessUserUpload(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
// Add imported files to albums if allowed.
|
// Add imported files to albums if allowed.
|
||||||
if len(f.Albums) > 0 &&
|
if len(f.Albums) > 0 &&
|
||||||
acl.Resources.AllowAny(acl.ResourceAlbums, s.UserRole(), acl.Permissions{acl.ActionCreate, acl.ActionUpload}) {
|
acl.Rules.AllowAny(acl.ResourceAlbums, s.UserRole(), acl.Permissions{acl.ActionCreate, acl.ActionUpload}) {
|
||||||
log.Debugf("upload: adding files to album %s", clean.Log(strings.Join(f.Albums, " and ")))
|
log.Debugf("upload: adding files to album %s", clean.Log(strings.Join(f.Albums, " and ")))
|
||||||
opt.Albums = f.Albums
|
opt.Albums = f.Albums
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,14 +221,14 @@ func (c *Config) Flags() (flags []string) {
|
|||||||
// ClientPublic returns config values for use by the JavaScript UI and other clients.
|
// ClientPublic returns config values for use by the JavaScript UI and other clients.
|
||||||
func (c *Config) ClientPublic() ClientConfig {
|
func (c *Config) ClientPublic() ClientConfig {
|
||||||
if c.Public() {
|
if c.Public() {
|
||||||
return c.ClientUser(true).ApplyACL(acl.Resources, acl.RoleAdmin)
|
return c.ClientUser(true).ApplyACL(acl.Rules, acl.RoleAdmin)
|
||||||
}
|
}
|
||||||
|
|
||||||
a := c.ClientAssets()
|
a := c.ClientAssets()
|
||||||
|
|
||||||
cfg := ClientConfig{
|
cfg := ClientConfig{
|
||||||
Settings: c.PublicSettings(),
|
Settings: c.PublicSettings(),
|
||||||
ACL: acl.Resources.Grants(acl.RoleNone),
|
ACL: acl.Rules.Grants(acl.RoleNone),
|
||||||
Disable: ClientDisable{
|
Disable: ClientDisable{
|
||||||
WebDAV: true,
|
WebDAV: true,
|
||||||
Settings: c.DisableSettings(),
|
Settings: c.DisableSettings(),
|
||||||
@@ -317,7 +317,7 @@ func (c *Config) ClientShare() ClientConfig {
|
|||||||
|
|
||||||
cfg := ClientConfig{
|
cfg := ClientConfig{
|
||||||
Settings: c.ShareSettings(),
|
Settings: c.ShareSettings(),
|
||||||
ACL: acl.Resources.Grants(acl.RoleVisitor),
|
ACL: acl.Rules.Grants(acl.RoleVisitor),
|
||||||
Disable: ClientDisable{
|
Disable: ClientDisable{
|
||||||
WebDAV: c.DisableWebDAV(),
|
WebDAV: c.DisableWebDAV(),
|
||||||
Settings: c.DisableSettings(),
|
Settings: c.DisableSettings(),
|
||||||
@@ -677,18 +677,18 @@ func (c *Config) ClientUser(withSettings bool) ClientConfig {
|
|||||||
|
|
||||||
// ClientRole provides the client config values for the specified user role.
|
// ClientRole provides the client config values for the specified user role.
|
||||||
func (c *Config) ClientRole(role acl.Role) ClientConfig {
|
func (c *Config) ClientRole(role acl.Role) ClientConfig {
|
||||||
return c.ClientUser(true).ApplyACL(acl.Resources, role)
|
return c.ClientUser(true).ApplyACL(acl.Rules, role)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientSession provides the client config values for the specified session.
|
// ClientSession provides the client config values for the specified session.
|
||||||
func (c *Config) ClientSession(sess *entity.Session) (cfg ClientConfig) {
|
func (c *Config) ClientSession(sess *entity.Session) (cfg ClientConfig) {
|
||||||
if sess.NoUser() && sess.IsClient() {
|
if sess.NoUser() && sess.IsClient() {
|
||||||
cfg = c.ClientUser(false).ApplyACL(acl.Resources, sess.ClientRole())
|
cfg = c.ClientUser(false).ApplyACL(acl.Rules, sess.ClientRole())
|
||||||
cfg.Settings = c.SessionSettings(sess)
|
cfg.Settings = c.SessionSettings(sess)
|
||||||
} else if sess.User().IsVisitor() {
|
} else if sess.User().IsVisitor() {
|
||||||
cfg = c.ClientShare()
|
cfg = c.ClientShare()
|
||||||
} else if sess.User().IsRegistered() {
|
} else if sess.User().IsRegistered() {
|
||||||
cfg = c.ClientUser(false).ApplyACL(acl.Resources, sess.UserRole())
|
cfg = c.ClientUser(false).ApplyACL(acl.Rules, sess.UserRole())
|
||||||
cfg.Settings = c.SessionSettings(sess)
|
cfg.Settings = c.SessionSettings(sess)
|
||||||
} else {
|
} else {
|
||||||
cfg = c.ClientPublic()
|
cfg = c.ClientPublic()
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func (c *Config) SessionSettings(sess *entity.Session) *customize.Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sess.NoUser() && sess.IsClient() {
|
if sess.NoUser() && sess.IsClient() {
|
||||||
return c.Settings().ApplyACL(acl.Resources, sess.ClientRole()).ApplyScope(sess.Scope())
|
return c.Settings().ApplyACL(acl.Rules, sess.ClientRole()).ApplyScope(sess.Scope())
|
||||||
}
|
}
|
||||||
|
|
||||||
user := sess.User()
|
user := sess.User()
|
||||||
@@ -84,7 +84,7 @@ func (c *Config) SessionSettings(sess *entity.Session) *customize.Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply role-based permissions and user settings to a copy of the global app settings.
|
// Apply role-based permissions and user settings to a copy of the global app settings.
|
||||||
return user.Settings().ApplyTo(c.Settings().ApplyACL(acl.Resources, user.AclRole())).ApplyScope(sess.Scope())
|
return user.Settings().ApplyTo(c.Settings().ApplyACL(acl.Rules, user.AclRole())).ApplyScope(sess.Scope())
|
||||||
}
|
}
|
||||||
|
|
||||||
// PublicSettings returns the public app settings.
|
// PublicSettings returns the public app settings.
|
||||||
@@ -102,7 +102,7 @@ func (c *Config) PublicSettings() *customize.Settings {
|
|||||||
|
|
||||||
// ShareSettings returns the app settings for share link visitors.
|
// ShareSettings returns the app settings for share link visitors.
|
||||||
func (c *Config) ShareSettings() *customize.Settings {
|
func (c *Config) ShareSettings() *customize.Settings {
|
||||||
settings := c.Settings().ApplyACL(acl.Resources, acl.RoleVisitor)
|
settings := c.Settings().ApplyACL(acl.Rules, acl.RoleVisitor)
|
||||||
|
|
||||||
return &customize.Settings{
|
return &customize.Settings{
|
||||||
UI: settings.UI,
|
UI: settings.UI,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func TestSettings_ApplyACL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, original, s.Features)
|
assert.Equal(t, original, s.Features)
|
||||||
r := s.ApplyACL(acl.Resources, acl.RoleAdmin)
|
r := s.ApplyACL(acl.Rules, acl.RoleAdmin)
|
||||||
|
|
||||||
t.Logf("RoleAdmin: %#v", r)
|
t.Logf("RoleAdmin: %#v", r)
|
||||||
assert.Equal(t, expected, r.Features)
|
assert.Equal(t, expected, r.Features)
|
||||||
@@ -85,7 +85,7 @@ func TestSettings_ApplyACL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, original, s.Features)
|
assert.Equal(t, original, s.Features)
|
||||||
r := s.ApplyACL(acl.Resources, acl.RoleVisitor)
|
r := s.ApplyACL(acl.Rules, acl.RoleVisitor)
|
||||||
t.Logf("RoleVisitor: %#v", r)
|
t.Logf("RoleVisitor: %#v", r)
|
||||||
assert.Equal(t, expected, r.Features)
|
assert.Equal(t, expected, r.Features)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import (
|
|||||||
|
|
||||||
func TestSettings_ApplyScope(t *testing.T) {
|
func TestSettings_ApplyScope(t *testing.T) {
|
||||||
original := NewDefaultSettings().Features
|
original := NewDefaultSettings().Features
|
||||||
admin := NewDefaultSettings().ApplyACL(acl.Resources, acl.RoleAdmin)
|
admin := NewDefaultSettings().ApplyACL(acl.Rules, acl.RoleAdmin)
|
||||||
client := NewDefaultSettings().ApplyACL(acl.Resources, acl.RoleClient)
|
client := NewDefaultSettings().ApplyACL(acl.Rules, acl.RoleClient)
|
||||||
visitor := NewDefaultSettings().ApplyACL(acl.Resources, acl.RoleVisitor)
|
visitor := NewDefaultSettings().ApplyACL(acl.Rules, acl.RoleVisitor)
|
||||||
|
|
||||||
t.Run("AdminUnscoped", func(t *testing.T) {
|
t.Run("AdminUnscoped", func(t *testing.T) {
|
||||||
s := NewDefaultSettings()
|
s := NewDefaultSettings()
|
||||||
|
|||||||
@@ -518,9 +518,47 @@ func (m *Session) Scope() string {
|
|||||||
return clean.Scope(m.AuthScope)
|
return clean.Scope(m.AuthScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasScope checks if the session has the given authorization scope.
|
// ScopeAllows checks if the scope does not exclude access to specified resource.
|
||||||
func (m *Session) HasScope(scope string) bool {
|
func (m *Session) ScopeAllows(resource acl.Resource, perms acl.Permissions) bool {
|
||||||
return list.ParseAttr(m.Scope()).Contains(scope)
|
// Get scope string.
|
||||||
|
scope := m.Scope()
|
||||||
|
|
||||||
|
// Skip detailed check and allow all if scope is "*".
|
||||||
|
if scope == list.All {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip resource check if scope includes all read operations.
|
||||||
|
if scope == acl.ScopeRead.String() {
|
||||||
|
return !acl.GrantScopeRead.DenyAny(perms)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse scope to check for resources and permissions.
|
||||||
|
attr := list.ParseAttr(scope)
|
||||||
|
|
||||||
|
// Check if resource is within scope.
|
||||||
|
if granted := attr.Contains(resource.String()); !granted {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if permission is within scope.
|
||||||
|
if len(perms) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if scope is limited to read or write operations.
|
||||||
|
if a := attr.Find(acl.ScopeRead.String()); a.Value == list.True && acl.GrantScopeRead.DenyAny(perms) {
|
||||||
|
return false
|
||||||
|
} else if a = attr.Find(acl.ScopeWrite.String()); a.Value == list.True && acl.GrantScopeWrite.DenyAny(perms) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScopeExcludes checks if the scope does not include access to specified resource.
|
||||||
|
func (m *Session) ScopeExcludes(resource acl.Resource, perms acl.Permissions) bool {
|
||||||
|
return !m.ScopeAllows(resource, perms)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetScope sets a custom authentication scope.
|
// SetScope sets a custom authentication scope.
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ func AuthLocal(user *User, f form.Login, m *Session, c *gin.Context) (provider a
|
|||||||
event.LoginError(clientIp, "api", userName, m.UserAgent, message)
|
event.LoginError(clientIp, "api", userName, m.UserAgent, message)
|
||||||
m.Status = http.StatusUnauthorized
|
m.Status = http.StatusUnauthorized
|
||||||
return provider, method, i18n.Error(i18n.ErrInvalidCredentials)
|
return provider, method, i18n.Error(i18n.ErrInvalidCredentials)
|
||||||
} else if !authSess.IsClient() || !authSess.HasScope(acl.ResourceSessions.String()) {
|
} else if !authSess.IsClient() || authSess.ScopeExcludes(acl.ResourceSessions, acl.Permissions{acl.ActionCreate}) {
|
||||||
message := "unauthorized"
|
message := "unauthorized"
|
||||||
limiter.Login.Reserve(clientIp)
|
limiter.Login.Reserve(clientIp)
|
||||||
event.AuditErr([]string{clientIp, "session %s", "login as %s with app password", message}, m.RefID, clean.LogQuote(userName))
|
event.AuditErr([]string{clientIp, "session %s", "login as %s with app password", message}, m.RefID, clean.LogQuote(userName))
|
||||||
|
|||||||
@@ -110,8 +110,8 @@ func TestAuthSession(t *testing.T) {
|
|||||||
assert.True(t, authSess.IsRegistered())
|
assert.True(t, authSess.IsRegistered())
|
||||||
assert.True(t, authSess.HasUser())
|
assert.True(t, authSess.HasUser())
|
||||||
|
|
||||||
assert.True(t, authSess.HasScope(acl.ResourceWebDAV.String()))
|
assert.True(t, authSess.ScopeAllows(acl.ResourceWebDAV, acl.Permissions{acl.ActionCreate}))
|
||||||
assert.True(t, authSess.HasScope(acl.ResourceSessions.String()))
|
assert.True(t, authSess.ScopeAllows(acl.ResourceSessions, acl.Permissions{acl.ActionCreate}))
|
||||||
})
|
})
|
||||||
t.Run("AliceTokenWebdav", func(t *testing.T) {
|
t.Run("AliceTokenWebdav", func(t *testing.T) {
|
||||||
s := SessionFixtures.Get("alice_token_webdav")
|
s := SessionFixtures.Get("alice_token_webdav")
|
||||||
@@ -148,8 +148,8 @@ func TestAuthSession(t *testing.T) {
|
|||||||
assert.True(t, authSess.IsRegistered())
|
assert.True(t, authSess.IsRegistered())
|
||||||
assert.True(t, authSess.HasUser())
|
assert.True(t, authSess.HasUser())
|
||||||
|
|
||||||
assert.True(t, authSess.HasScope(acl.ResourceWebDAV.String()))
|
assert.True(t, authSess.ScopeAllows(acl.ResourceWebDAV, acl.Permissions{acl.ActionCreate}))
|
||||||
assert.False(t, authSess.HasScope(acl.ResourceSessions.String()))
|
assert.False(t, authSess.ScopeAllows(acl.ResourceSessions, acl.Permissions{acl.ActionCreate}))
|
||||||
})
|
})
|
||||||
t.Run("EmptyPassword", func(t *testing.T) {
|
t.Run("EmptyPassword", func(t *testing.T) {
|
||||||
// Create test request form.
|
// Create test request form.
|
||||||
|
|||||||
@@ -544,6 +544,96 @@ func TestSession_SetAuthID(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSession_ScopeAllows(t *testing.T) {
|
||||||
|
t.Run("AnyScope", func(t *testing.T) {
|
||||||
|
s := &Session{
|
||||||
|
UserName: "test",
|
||||||
|
RefID: "sessxkkcxxxz",
|
||||||
|
AuthScope: "*",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, s.ScopeAllows("", nil))
|
||||||
|
})
|
||||||
|
t.Run("ReadScope", func(t *testing.T) {
|
||||||
|
s := &Session{
|
||||||
|
UserName: "test",
|
||||||
|
RefID: "sessxkkcxxxz",
|
||||||
|
AuthScope: "read",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, s.ScopeAllows("metrics", nil))
|
||||||
|
assert.True(t, s.ScopeAllows("sessions", nil))
|
||||||
|
assert.True(t, s.ScopeAllows("metrics", acl.Permissions{acl.ActionView, acl.AccessAll}))
|
||||||
|
assert.False(t, s.ScopeAllows("metrics", acl.Permissions{acl.ActionUpdate}))
|
||||||
|
assert.False(t, s.ScopeAllows("metrics", acl.Permissions{acl.ActionUpdate}))
|
||||||
|
assert.False(t, s.ScopeAllows("settings", acl.Permissions{acl.ActionUpdate}))
|
||||||
|
assert.False(t, s.ScopeAllows("settings", acl.Permissions{acl.ActionCreate}))
|
||||||
|
assert.False(t, s.ScopeAllows("sessions", acl.Permissions{acl.ActionDelete}))
|
||||||
|
})
|
||||||
|
t.Run("ReadAny", func(t *testing.T) {
|
||||||
|
s := &Session{
|
||||||
|
UserName: "test",
|
||||||
|
RefID: "sessxkkcxxxz",
|
||||||
|
AuthScope: "read *",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, s.ScopeAllows("metrics", nil))
|
||||||
|
assert.True(t, s.ScopeAllows("sessions", nil))
|
||||||
|
assert.True(t, s.ScopeAllows("metrics", acl.Permissions{acl.ActionView, acl.AccessAll}))
|
||||||
|
assert.False(t, s.ScopeAllows("metrics", acl.Permissions{acl.ActionUpdate}))
|
||||||
|
assert.False(t, s.ScopeAllows("metrics", acl.Permissions{acl.ActionUpdate}))
|
||||||
|
assert.False(t, s.ScopeAllows("settings", acl.Permissions{acl.ActionUpdate}))
|
||||||
|
assert.False(t, s.ScopeAllows("settings", acl.Permissions{acl.ActionCreate}))
|
||||||
|
assert.False(t, s.ScopeAllows("sessions", acl.Permissions{acl.ActionDelete}))
|
||||||
|
})
|
||||||
|
t.Run("ReadSettings", func(t *testing.T) {
|
||||||
|
s := &Session{
|
||||||
|
UserName: "test",
|
||||||
|
RefID: "sessxkkcxxxz",
|
||||||
|
AuthScope: "read settings",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, s.ScopeAllows("settings", acl.Permissions{acl.ActionView}))
|
||||||
|
assert.False(t, s.ScopeAllows("metrics", nil))
|
||||||
|
assert.False(t, s.ScopeAllows("sessions", nil))
|
||||||
|
assert.False(t, s.ScopeAllows("metrics", acl.Permissions{acl.ActionView, acl.AccessAll}))
|
||||||
|
assert.False(t, s.ScopeAllows("metrics", acl.Permissions{acl.ActionUpdate}))
|
||||||
|
assert.False(t, s.ScopeAllows("metrics", acl.Permissions{acl.ActionUpdate}))
|
||||||
|
assert.False(t, s.ScopeAllows("settings", acl.Permissions{acl.ActionUpdate}))
|
||||||
|
assert.False(t, s.ScopeAllows("sessions", acl.Permissions{acl.ActionDelete}))
|
||||||
|
assert.False(t, s.ScopeAllows("sessions", acl.Permissions{acl.ActionDelete}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSession_ScopeExcludes(t *testing.T) {
|
||||||
|
t.Run("Empty", func(t *testing.T) {
|
||||||
|
s := &Session{
|
||||||
|
UserName: "test",
|
||||||
|
RefID: "sessxkkcxxxz",
|
||||||
|
AuthScope: "*",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.False(t, s.ScopeExcludes("", nil))
|
||||||
|
})
|
||||||
|
t.Run("ReadSettings", func(t *testing.T) {
|
||||||
|
s := &Session{
|
||||||
|
UserName: "test",
|
||||||
|
RefID: "sessxkkcxxxz",
|
||||||
|
AuthScope: "read settings",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.False(t, s.ScopeExcludes("settings", acl.Permissions{acl.ActionView}))
|
||||||
|
assert.True(t, s.ScopeExcludes("metrics", nil))
|
||||||
|
assert.True(t, s.ScopeExcludes("sessions", nil))
|
||||||
|
assert.True(t, s.ScopeExcludes("metrics", acl.Permissions{acl.ActionView, acl.AccessAll}))
|
||||||
|
assert.True(t, s.ScopeExcludes("metrics", acl.Permissions{acl.ActionUpdate}))
|
||||||
|
assert.True(t, s.ScopeExcludes("metrics", acl.Permissions{acl.ActionUpdate}))
|
||||||
|
assert.True(t, s.ScopeExcludes("settings", acl.Permissions{acl.ActionUpdate}))
|
||||||
|
assert.True(t, s.ScopeExcludes("sessions", acl.Permissions{acl.ActionDelete}))
|
||||||
|
assert.True(t, s.ScopeExcludes("sessions", acl.Permissions{acl.ActionDelete}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestSession_SetScope(t *testing.T) {
|
func TestSession_SetScope(t *testing.T) {
|
||||||
t.Run("EmptyScope", func(t *testing.T) {
|
t.Run("EmptyScope", func(t *testing.T) {
|
||||||
s := &Session{
|
s := &Session{
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ func (m *User) CanLogIn() bool {
|
|||||||
} else if m.IsDisabled() || m.IsUnknown() || !m.IsRegistered() {
|
} else if m.IsDisabled() || m.IsUnknown() || !m.IsRegistered() {
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
return acl.Resources.Allow(acl.ResourceConfig, m.AclRole(), acl.AccessOwn)
|
return acl.Rules.Allow(acl.ResourceConfig, m.AclRole(), acl.AccessOwn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,7 +425,7 @@ func (m *User) CanUseWebDAV() bool {
|
|||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
// Check if the ACL allows downloading files via WebDAV based on the user role.
|
// Check if the ACL allows downloading files via WebDAV based on the user role.
|
||||||
return acl.Resources.Allow(acl.ResourceWebDAV, m.AclRole(), acl.ActionDownload)
|
return acl.Rules.Allow(acl.ResourceWebDAV, m.AclRole(), acl.ActionDownload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,7 +439,7 @@ func (m *User) CanUpload() bool {
|
|||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
// Check if the ACL allows uploading photos based on the user role.
|
// Check if the ACL allows uploading photos based on the user role.
|
||||||
return acl.Resources.Allow(acl.ResourcePhotos, m.AclRole(), acl.ActionUpload)
|
return acl.Rules.Allow(acl.ResourcePhotos, m.AclRole(), acl.ActionUpload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -790,11 +790,11 @@ func (m *User) IsVisitor() bool {
|
|||||||
|
|
||||||
// HasSharedAccessOnly checks if the user as only access to shared resources.
|
// HasSharedAccessOnly checks if the user as only access to shared resources.
|
||||||
func (m *User) HasSharedAccessOnly(resource acl.Resource) bool {
|
func (m *User) HasSharedAccessOnly(resource acl.Resource) bool {
|
||||||
if acl.Resources.Deny(resource, m.AclRole(), acl.AccessShared) {
|
if acl.Rules.Deny(resource, m.AclRole(), acl.AccessShared) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return acl.Resources.DenyAll(resource, m.AclRole(), acl.Permissions{acl.AccessAll, acl.AccessLibrary})
|
return acl.Rules.DenyAll(resource, m.AclRole(), acl.Permissions{acl.AccessAll, acl.AccessLibrary})
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUnknown checks if the user is unknown.
|
// IsUnknown checks if the user is unknown.
|
||||||
|
|||||||
@@ -57,19 +57,19 @@ func UserAlbums(f form.SearchAlbums, sess *entity.Session) (results AlbumResults
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check user permissions.
|
// Check user permissions.
|
||||||
if acl.Resources.DenyAll(aclResource, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary, acl.AccessShared, acl.AccessOwn}) {
|
if acl.Rules.DenyAll(aclResource, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary, acl.AccessShared, acl.AccessOwn}) {
|
||||||
return AlbumResults{}, ErrForbidden
|
return AlbumResults{}, ErrForbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit results by UID, owner and path.
|
// Limit results by UID, owner and path.
|
||||||
if sess.IsVisitor() || sess.NotRegistered() {
|
if sess.IsVisitor() || sess.NotRegistered() {
|
||||||
s = s.Where("albums.album_uid IN (?) OR albums.published_at > ?", sess.SharedUIDs(), entity.TimeStamp())
|
s = s.Where("albums.album_uid IN (?) OR albums.published_at > ?", sess.SharedUIDs(), entity.TimeStamp())
|
||||||
} else if acl.Resources.DenyAll(aclResource, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary}) {
|
} else if acl.Rules.DenyAll(aclResource, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary}) {
|
||||||
s = s.Where("albums.album_uid IN (?) OR albums.created_by = ? OR albums.published_at > ?", sess.SharedUIDs(), user.UserUID, entity.TimeStamp())
|
s = s.Where("albums.album_uid IN (?) OR albums.created_by = ? OR albums.published_at > ?", sess.SharedUIDs(), user.UserUID, entity.TimeStamp())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exclude private content?
|
// Exclude private content?
|
||||||
if acl.Resources.Deny(acl.ResourcePhotos, aclRole, acl.AccessPrivate) || acl.Resources.Deny(aclResource, aclRole, acl.AccessPrivate) {
|
if acl.Rules.Deny(acl.ResourcePhotos, aclRole, acl.AccessPrivate) || acl.Rules.Deny(aclResource, aclRole, acl.AccessPrivate) {
|
||||||
f.Public = true
|
f.Public = true
|
||||||
f.Private = false
|
f.Private = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,31 +129,31 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
|||||||
aclRole := user.AclRole()
|
aclRole := user.AclRole()
|
||||||
|
|
||||||
// Exclude private content.
|
// Exclude private content.
|
||||||
if acl.Resources.Deny(acl.ResourcePhotos, aclRole, acl.AccessPrivate) {
|
if acl.Rules.Deny(acl.ResourcePhotos, aclRole, acl.AccessPrivate) {
|
||||||
f.Public = true
|
f.Public = true
|
||||||
f.Private = false
|
f.Private = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exclude archived content.
|
// Exclude archived content.
|
||||||
if acl.Resources.Deny(acl.ResourcePhotos, aclRole, acl.ActionDelete) {
|
if acl.Rules.Deny(acl.ResourcePhotos, aclRole, acl.ActionDelete) {
|
||||||
f.Archived = false
|
f.Archived = false
|
||||||
f.Review = false
|
f.Review = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exclude hidden files.
|
// Exclude hidden files.
|
||||||
if acl.Resources.Deny(acl.ResourceFiles, aclRole, acl.AccessAll) {
|
if acl.Rules.Deny(acl.ResourceFiles, aclRole, acl.AccessAll) {
|
||||||
f.Hidden = false
|
f.Hidden = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visitors and other restricted users can only access shared content.
|
// Visitors and other restricted users can only access shared content.
|
||||||
if f.Scope != "" && !sess.HasShare(f.Scope) && (sess.User().HasSharedAccessOnly(acl.ResourcePhotos) || sess.NotRegistered()) ||
|
if f.Scope != "" && !sess.HasShare(f.Scope) && (sess.User().HasSharedAccessOnly(acl.ResourcePhotos) || sess.NotRegistered()) ||
|
||||||
f.Scope == "" && acl.Resources.Deny(acl.ResourcePhotos, aclRole, acl.ActionSearch) {
|
f.Scope == "" && acl.Rules.Deny(acl.ResourcePhotos, aclRole, acl.ActionSearch) {
|
||||||
event.AuditErr([]string{sess.IP(), "session %s", "%s %s as %s", "denied"}, sess.RefID, acl.ActionSearch.String(), string(acl.ResourcePhotos), aclRole)
|
event.AuditErr([]string{sess.IP(), "session %s", "%s %s as %s", "denied"}, sess.RefID, acl.ActionSearch.String(), string(acl.ResourcePhotos), aclRole)
|
||||||
return PhotoResults{}, 0, ErrForbidden
|
return PhotoResults{}, 0, ErrForbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit results for external users.
|
// Limit results for external users.
|
||||||
if f.Scope == "" && acl.Resources.DenyAll(acl.ResourcePhotos, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary}) {
|
if f.Scope == "" && acl.Rules.DenyAll(acl.ResourcePhotos, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary}) {
|
||||||
sharedAlbums := "photos.photo_uid IN (SELECT photo_uid FROM photos_albums WHERE hidden = 0 AND missing = 0 AND album_uid IN (?)) OR "
|
sharedAlbums := "photos.photo_uid IN (SELECT photo_uid FROM photos_albums WHERE hidden = 0 AND missing = 0 AND album_uid IN (?)) OR "
|
||||||
|
|
||||||
if sess.IsVisitor() || sess.NotRegistered() {
|
if sess.IsVisitor() || sess.NotRegistered() {
|
||||||
|
|||||||
@@ -117,26 +117,26 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
|||||||
aclRole := user.AclRole()
|
aclRole := user.AclRole()
|
||||||
|
|
||||||
// Exclude private content.
|
// Exclude private content.
|
||||||
if acl.Resources.Deny(acl.ResourcePlaces, aclRole, acl.AccessPrivate) {
|
if acl.Rules.Deny(acl.ResourcePlaces, aclRole, acl.AccessPrivate) {
|
||||||
f.Public = true
|
f.Public = true
|
||||||
f.Private = false
|
f.Private = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exclude archived content.
|
// Exclude archived content.
|
||||||
if acl.Resources.Deny(acl.ResourcePlaces, aclRole, acl.ActionDelete) {
|
if acl.Rules.Deny(acl.ResourcePlaces, aclRole, acl.ActionDelete) {
|
||||||
f.Archived = false
|
f.Archived = false
|
||||||
f.Review = false
|
f.Review = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visitors and other restricted users can only access shared content.
|
// Visitors and other restricted users can only access shared content.
|
||||||
if f.Scope != "" && !sess.HasShare(f.Scope) && (sess.User().HasSharedAccessOnly(acl.ResourcePlaces) || sess.NotRegistered()) ||
|
if f.Scope != "" && !sess.HasShare(f.Scope) && (sess.User().HasSharedAccessOnly(acl.ResourcePlaces) || sess.NotRegistered()) ||
|
||||||
f.Scope == "" && acl.Resources.Deny(acl.ResourcePlaces, aclRole, acl.ActionSearch) {
|
f.Scope == "" && acl.Rules.Deny(acl.ResourcePlaces, aclRole, acl.ActionSearch) {
|
||||||
event.AuditErr([]string{sess.IP(), "session %s", "%s %s as %s", "denied"}, sess.RefID, acl.ActionSearch.String(), string(acl.ResourcePlaces), aclRole)
|
event.AuditErr([]string{sess.IP(), "session %s", "%s %s as %s", "denied"}, sess.RefID, acl.ActionSearch.String(), string(acl.ResourcePlaces), aclRole)
|
||||||
return GeoResults{}, ErrForbidden
|
return GeoResults{}, ErrForbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit results for external users.
|
// Limit results for external users.
|
||||||
if f.Scope == "" && acl.Resources.DenyAll(acl.ResourcePlaces, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary}) {
|
if f.Scope == "" && acl.Rules.DenyAll(acl.ResourcePlaces, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary}) {
|
||||||
sharedAlbums := "photos.photo_uid IN (SELECT photo_uid FROM photos_albums WHERE hidden = 0 AND missing = 0 AND album_uid IN (?)) OR "
|
sharedAlbums := "photos.photo_uid IN (SELECT photo_uid FROM photos_albums WHERE hidden = 0 AND missing = 0 AND album_uid IN (?)) OR "
|
||||||
|
|
||||||
if sess.IsVisitor() || sess.NotRegistered() {
|
if sess.IsVisitor() || sess.NotRegistered() {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/entity"
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
"github.com/photoprism/photoprism/internal/form"
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
"github.com/photoprism/photoprism/pkg/rnd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Sessions finds user sessions.
|
// Sessions finds user sessions.
|
||||||
@@ -13,20 +12,24 @@ func Sessions(f form.SearchSessions) (result entity.Sessions, err error) {
|
|||||||
result = entity.Sessions{}
|
result = entity.Sessions{}
|
||||||
stmt := Db()
|
stmt := Db()
|
||||||
|
|
||||||
|
userUid := strings.TrimSpace(f.UID)
|
||||||
search := strings.TrimSpace(f.Query)
|
search := strings.TrimSpace(f.Query)
|
||||||
uid := strings.TrimSpace(f.UID)
|
|
||||||
sortOrder := f.Order
|
sortOrder := f.Order
|
||||||
limit := f.Count
|
limit := f.Count
|
||||||
offset := f.Offset
|
offset := f.Offset
|
||||||
|
|
||||||
if search == "all" {
|
// Filter by user UID?
|
||||||
// Don't filter.
|
if userUid != "" {
|
||||||
} else if rnd.IsUID(uid, entity.UserUID) {
|
stmt = stmt.Where("user_uid = ?", userUid)
|
||||||
stmt = stmt.Where("user_uid = ?", search)
|
}
|
||||||
} else if search != "" {
|
|
||||||
|
// Filter by user name and/or auth provider name?
|
||||||
|
if search != "" && search != "all" {
|
||||||
stmt = stmt.Where("user_name LIKE ? OR auth_provider LIKE ?", search+"%", search+"%")
|
stmt = stmt.Where("user_name LIKE ? OR auth_provider LIKE ?", search+"%", search+"%")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort results?
|
||||||
if sortOrder == "" {
|
if sortOrder == "" {
|
||||||
sortOrder = "last_active DESC, user_name"
|
sortOrder = "last_active DESC, user_name"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
|||||||
api.UploadUserFiles(APIv1)
|
api.UploadUserFiles(APIv1)
|
||||||
api.ProcessUserUpload(APIv1)
|
api.ProcessUserUpload(APIv1)
|
||||||
api.UploadUserAvatar(APIv1)
|
api.UploadUserAvatar(APIv1)
|
||||||
|
api.FindUserSessions(APIv1)
|
||||||
api.CreateUserPasscode(APIv1)
|
api.CreateUserPasscode(APIv1)
|
||||||
api.ConfirmUserPasscode(APIv1)
|
api.ConfirmUserPasscode(APIv1)
|
||||||
api.ActivateUserPasscode(APIv1)
|
api.ActivateUserPasscode(APIv1)
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ func WebDAVAuth(conf *config.Config) gin.HandlerFunc {
|
|||||||
event.AuditErr([]string{clientIp, "session %s", "access webdav without user account", "denied"}, sess.RefID)
|
event.AuditErr([]string{clientIp, "session %s", "access webdav without user account", "denied"}, sess.RefID)
|
||||||
WebDAVAbortUnauthorized(c)
|
WebDAVAbortUnauthorized(c)
|
||||||
return
|
return
|
||||||
} else if sess.IsClient() && !sess.HasScope(acl.ResourceWebDAV.String()) {
|
} else if sess.IsClient() && sess.ScopeExcludes(acl.ResourceWebDAV, nil) {
|
||||||
// Log error if the client is allowed to access webdav based on its scope.
|
// Log error if the client is allowed to access webdav based on its scope.
|
||||||
message := "denied"
|
message := "denied"
|
||||||
event.AuditWarn([]string{clientIp, "client %s", "session %s", "access webdav as %s", message}, clean.Log(sess.ClientInfo()), sess.RefID, clean.LogQuote(user.Username()))
|
event.AuditWarn([]string{clientIp, "client %s", "session %s", "access webdav as %s", message}, clean.Log(sess.ClientInfo()), sess.RefID, clean.LogQuote(user.Username()))
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ func TestWebDAVAuthSession(t *testing.T) {
|
|||||||
assert.True(t, sess.HasUser())
|
assert.True(t, sess.HasUser())
|
||||||
assert.Equal(t, user.UserUID, sess.UserUID)
|
assert.Equal(t, user.UserUID, sess.UserUID)
|
||||||
assert.Equal(t, entity.UserFixtures.Get("alice").UserUID, sess.UserUID)
|
assert.Equal(t, entity.UserFixtures.Get("alice").UserUID, sess.UserUID)
|
||||||
assert.True(t, sess.HasScope(acl.ResourceWebDAV.String()))
|
assert.True(t, sess.ScopeAllows(acl.ResourceWebDAV, acl.Permissions{acl.ActionView}))
|
||||||
assert.False(t, cached)
|
assert.False(t, cached)
|
||||||
|
|
||||||
assert.Equal(t, s.ID, sid)
|
assert.Equal(t, s.ID, sid)
|
||||||
@@ -222,7 +222,7 @@ func TestWebDAVAuthSession(t *testing.T) {
|
|||||||
assert.Equal(t, entity.UserFixtures.Get("alice").UserUID, user.UserUID)
|
assert.Equal(t, entity.UserFixtures.Get("alice").UserUID, user.UserUID)
|
||||||
assert.Equal(t, entity.UserFixtures.Get("alice").UserUID, sess.UserUID)
|
assert.Equal(t, entity.UserFixtures.Get("alice").UserUID, sess.UserUID)
|
||||||
assert.True(t, user.CanUseWebDAV())
|
assert.True(t, user.CanUseWebDAV())
|
||||||
assert.False(t, sess.HasScope(acl.ResourceWebDAV.String()))
|
assert.False(t, sess.ScopeAllows(acl.ResourceWebDAV, acl.Permissions{acl.ActionView}))
|
||||||
|
|
||||||
// WebDAVAuthSession should not set a status code or any headers.
|
// WebDAVAuthSession should not set a status code or any headers.
|
||||||
assert.Equal(t, http.StatusOK, c.Writer.Status())
|
assert.Equal(t, http.StatusOK, c.Writer.Status())
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func NewOAuthAuthorizationServer(conf *config.Config) *OAuthAuthorizationServer
|
|||||||
Issuer: conf.SiteUrl(),
|
Issuer: conf.SiteUrl(),
|
||||||
AuthorizationEndpoint: "",
|
AuthorizationEndpoint: "",
|
||||||
TokenEndpoint: fmt.Sprintf("%sapi/v1/oauth/token", conf.SiteUrl()),
|
TokenEndpoint: fmt.Sprintf("%sapi/v1/oauth/token", conf.SiteUrl()),
|
||||||
ScopesSupported: acl.Resources.Resources(),
|
ScopesSupported: acl.Rules.Resources(),
|
||||||
ResponseTypesSupported: OAuthResponseTypes,
|
ResponseTypesSupported: OAuthResponseTypes,
|
||||||
GrantTypesSupported: OAuthGrantTypes,
|
GrantTypesSupported: OAuthGrantTypes,
|
||||||
TokenEndpointAuthMethodsSupported: OAuthTokenEndpointAuthMethods,
|
TokenEndpointAuthMethodsSupported: OAuthTokenEndpointAuthMethods,
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func NewOpenIDConfiguration(conf *config.Config) *OpenIDConfiguration {
|
|||||||
GrantTypesSupported: OAuthGrantTypes,
|
GrantTypesSupported: OAuthGrantTypes,
|
||||||
SubjectTypesSupported: []string{},
|
SubjectTypesSupported: []string{},
|
||||||
IdTokenSigningAlgValuesSupported: []string{},
|
IdTokenSigningAlgValuesSupported: []string{},
|
||||||
ScopesSupported: acl.Resources.Resources(),
|
ScopesSupported: acl.Rules.Resources(),
|
||||||
TokenEndpointAuthMethodsSupported: OAuthTokenEndpointAuthMethods,
|
TokenEndpointAuthMethodsSupported: OAuthTokenEndpointAuthMethods,
|
||||||
ClaimsSupported: []string{},
|
ClaimsSupported: []string{},
|
||||||
CodeChallengeMethodsSupported: []string{},
|
CodeChallengeMethodsSupported: []string{},
|
||||||
|
|||||||
@@ -74,7 +74,9 @@ func (f *KeyValue) Parse(s string) *KeyValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Default?
|
// Default?
|
||||||
if v = Value(v); v == "" {
|
if f.Key == All {
|
||||||
|
return f
|
||||||
|
} else if v = Value(v); v == "" {
|
||||||
f.Value = True
|
f.Value = True
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
@@ -95,6 +97,10 @@ func (f *KeyValue) String() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if f.Key == All {
|
||||||
|
return All
|
||||||
|
}
|
||||||
|
|
||||||
if Bool[strings.ToLower(f.Value)] == True {
|
if Bool[strings.ToLower(f.Value)] == True {
|
||||||
return f.Key
|
return f.Key
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,45 +59,72 @@ func (list Attr) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort sorts the attributes by key.
|
// Sort sorts the attributes by key.
|
||||||
func (list Attr) Sort() {
|
func (list Attr) Sort() Attr {
|
||||||
sort.Slice(list, func(i, j int) bool {
|
sort.Slice(list, func(i, j int) bool {
|
||||||
if list[i].Key == list[j].Key {
|
if list[i].Key == list[j].Key {
|
||||||
return list[i].Value < list[j].Value
|
return list[i].Value < list[j].Value
|
||||||
|
} else if list[i].Key == All {
|
||||||
|
return false
|
||||||
|
} else if list[j].Key == All {
|
||||||
|
return true
|
||||||
} else {
|
} else {
|
||||||
return list[i].Key < list[j].Key
|
return list[i].Key < list[j].Key
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains tests if the list contains the attribute provided as string.
|
// Contains tests if the list contains the attribute provided as string.
|
||||||
func (list Attr) Contains(s string) bool {
|
func (list Attr) Contains(s string) bool {
|
||||||
if len(list) == 0 || s == "" {
|
attr := list.Find(s)
|
||||||
|
|
||||||
|
if attr.Key == "" || attr.Value == False {
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find returns the matching KeyValue attribute if found.
|
||||||
|
func (list Attr) Find(s string) (a KeyValue) {
|
||||||
|
if len(list) == 0 || s == "" {
|
||||||
|
return a
|
||||||
} else if s == All {
|
} else if s == All {
|
||||||
return true
|
return KeyValue{Key: All, Value: ""}
|
||||||
}
|
}
|
||||||
|
|
||||||
attr := ParseKeyValue(s)
|
attr := ParseKeyValue(s)
|
||||||
|
|
||||||
// Abort if attribute is invalid.
|
// Return nil if key is invalid or all.
|
||||||
if attr.Key == "" {
|
if attr.Key == "" {
|
||||||
return false
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find matches.
|
// Find and return first match.
|
||||||
if attr.Value == "" || attr.Value == All {
|
if attr.Value == "" || attr.Value == All {
|
||||||
for i := range list {
|
for i := range list {
|
||||||
if strings.EqualFold(attr.Key, list[i].Key) || list[i].Key == All {
|
if strings.EqualFold(attr.Key, list[i].Key) {
|
||||||
return true
|
return *list[i]
|
||||||
|
} else if list[i].Key == All {
|
||||||
|
a = *list[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for i := range list {
|
for i := range list {
|
||||||
if strings.EqualFold(attr.Key, list[i].Key) && (attr.Value == list[i].Value || list[i].Value == All) || list[i].Key == All {
|
if strings.EqualFold(attr.Key, list[i].Key) {
|
||||||
return true
|
if attr.Value == True && list[i].Value == False {
|
||||||
|
return KeyValue{Key: "", Value: ""}
|
||||||
|
} else if attr.Value == list[i].Value {
|
||||||
|
return *list[i]
|
||||||
|
} else if list[i].Value == All {
|
||||||
|
a = *list[i]
|
||||||
|
}
|
||||||
|
} else if list[i].Key == All && attr.Value != False {
|
||||||
|
a = *list[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return a
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,14 @@ func TestParseAttr(t *testing.T) {
|
|||||||
|
|
||||||
assert.Len(t, attr, 3)
|
assert.Len(t, attr, 3)
|
||||||
assert.Equal(t, Attr{{Key: "foo", Value: "true"}, {Key: "bar", Value: "true"}, {Key: "baz", Value: "true"}}, attr)
|
assert.Equal(t, Attr{{Key: "foo", Value: "true"}, {Key: "bar", Value: "true"}, {Key: "baz", Value: "true"}}, attr)
|
||||||
|
assert.Equal(t, Attr{{Key: "bar", Value: "true"}, {Key: "baz", Value: "true"}, {Key: "foo", Value: "true"}}, attr.Sort())
|
||||||
})
|
})
|
||||||
t.Run("WhitespaceKeys", func(t *testing.T) {
|
t.Run("WhitespaceKeys", func(t *testing.T) {
|
||||||
attr := ParseAttr(" foo bar baz ")
|
attr := ParseAttr(" foo bar baz ")
|
||||||
|
|
||||||
assert.Len(t, attr, 3)
|
assert.Len(t, attr, 3)
|
||||||
assert.Equal(t, Attr{{Key: "foo", Value: "true"}, {Key: "bar", Value: "true"}, {Key: "baz", Value: "true"}}, attr)
|
assert.Equal(t, Attr{{Key: "foo", Value: "true"}, {Key: "bar", Value: "true"}, {Key: "baz", Value: "true"}}, attr)
|
||||||
|
assert.Equal(t, Attr{{Key: "bar", Value: "true"}, {Key: "baz", Value: "true"}, {Key: "foo", Value: "true"}}, attr.Sort())
|
||||||
})
|
})
|
||||||
t.Run("Values", func(t *testing.T) {
|
t.Run("Values", func(t *testing.T) {
|
||||||
attr := ParseAttr("foo:yes bar:disable baz:true biZZ:false BIG CAT:FISH berghain:berlin:germany hello:off")
|
attr := ParseAttr("foo:yes bar:disable baz:true biZZ:false BIG CAT:FISH berghain:berlin:germany hello:off")
|
||||||
@@ -41,6 +43,18 @@ func TestParseAttr(t *testing.T) {
|
|||||||
{Key: "hello", Value: "false"},
|
{Key: "hello", Value: "false"},
|
||||||
}, attr,
|
}, attr,
|
||||||
)
|
)
|
||||||
|
assert.Equal(t,
|
||||||
|
Attr{
|
||||||
|
{Key: "BIG", Value: "true"},
|
||||||
|
{Key: "CAT", Value: "FISH"},
|
||||||
|
{Key: "bar", Value: "false"},
|
||||||
|
{Key: "baz", Value: "true"},
|
||||||
|
{Key: "berghain", Value: "berlin:germany"},
|
||||||
|
{Key: "biZZ", Value: "false"},
|
||||||
|
{Key: "foo", Value: "true"},
|
||||||
|
{Key: "hello", Value: "false"},
|
||||||
|
}, attr.Sort(),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
t.Run("Scopes", func(t *testing.T) {
|
t.Run("Scopes", func(t *testing.T) {
|
||||||
attr := ParseAttr("files files.read:true photos photos.create:false albums:true people.view:true config.view:false")
|
attr := ParseAttr("files files.read:true photos photos.create:false albums:true people.view:true config.view:false")
|
||||||
@@ -55,6 +69,15 @@ func TestParseAttr(t *testing.T) {
|
|||||||
{Key: "people.view", Value: "true"},
|
{Key: "people.view", Value: "true"},
|
||||||
{Key: "config.view", Value: "false"},
|
{Key: "config.view", Value: "false"},
|
||||||
}, attr)
|
}, attr)
|
||||||
|
assert.Equal(t, Attr{
|
||||||
|
{Key: "albums", Value: "true"},
|
||||||
|
{Key: "config.view", Value: "false"},
|
||||||
|
{Key: "files", Value: "true"},
|
||||||
|
{Key: "files.read", Value: "true"},
|
||||||
|
{Key: "people.view", Value: "true"},
|
||||||
|
{Key: "photos", Value: "true"},
|
||||||
|
{Key: "photos.create", Value: "false"},
|
||||||
|
}, attr.Sort())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +86,7 @@ func TestParseKeyValue(t *testing.T) {
|
|||||||
kv := ParseKeyValue("*")
|
kv := ParseKeyValue("*")
|
||||||
|
|
||||||
assert.Equal(t, "*", kv.Key)
|
assert.Equal(t, "*", kv.Key)
|
||||||
assert.Equal(t, "true", kv.Value)
|
assert.Equal(t, "", kv.Value)
|
||||||
})
|
})
|
||||||
t.Run("Scope", func(t *testing.T) {
|
t.Run("Scope", func(t *testing.T) {
|
||||||
kv := ParseKeyValue("files.read:true")
|
kv := ParseKeyValue("files.read:true")
|
||||||
@@ -88,11 +111,11 @@ func TestAttr_String(t *testing.T) {
|
|||||||
assert.Equal(t, s, attr.String())
|
assert.Equal(t, s, attr.String())
|
||||||
})
|
})
|
||||||
t.Run("Random", func(t *testing.T) {
|
t.Run("Random", func(t *testing.T) {
|
||||||
s := " admin.conversations.removeCustomRetention admin.usergroups:read me:yes FOOt0-2U 6VU #$#%$ cm,Nu"
|
s := " admin.conversations.removeCustomRetention * admin.usergroups:read me:yes FOOt0-2U 6VU #$#%$ cm,Nu"
|
||||||
attr := ParseAttr(s)
|
attr := ParseAttr(s)
|
||||||
|
|
||||||
assert.Len(t, attr, 6)
|
assert.Len(t, attr, 7)
|
||||||
assert.Equal(t, "6VU FOOt0-2U admin.conversations.removeCustomRetention admin.usergroups:read cmNu me", attr.String())
|
assert.Equal(t, "6VU FOOt0-2U admin.conversations.removeCustomRetention admin.usergroups:read cmNu me *", attr.String())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,3 +155,94 @@ func TestAttr_Contains(t *testing.T) {
|
|||||||
assert.True(t, attr.Contains("people.view"))
|
assert.True(t, attr.Contains("people.view"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAttr_Find(t *testing.T) {
|
||||||
|
t.Run("Any", func(t *testing.T) {
|
||||||
|
s := "*"
|
||||||
|
attr := ParseAttr(s)
|
||||||
|
|
||||||
|
assert.Len(t, attr, 1)
|
||||||
|
result := attr.Find("metrics")
|
||||||
|
|
||||||
|
assert.Equal(t, All, result.Key)
|
||||||
|
assert.Equal(t, "", result.Value)
|
||||||
|
})
|
||||||
|
t.Run("Empty", func(t *testing.T) {
|
||||||
|
s := "*"
|
||||||
|
attr := ParseAttr(s)
|
||||||
|
|
||||||
|
assert.Len(t, attr, 1)
|
||||||
|
result := attr.Find("")
|
||||||
|
assert.Equal(t, "", result.Key)
|
||||||
|
assert.Equal(t, "", result.Value)
|
||||||
|
})
|
||||||
|
t.Run("All", func(t *testing.T) {
|
||||||
|
s := "*"
|
||||||
|
attr := ParseAttr(s)
|
||||||
|
|
||||||
|
assert.Len(t, attr, 1)
|
||||||
|
result := attr.Find("*")
|
||||||
|
assert.Equal(t, All, result.Key)
|
||||||
|
assert.Equal(t, "", result.Value)
|
||||||
|
})
|
||||||
|
t.Run("ValueAll", func(t *testing.T) {
|
||||||
|
s := "*"
|
||||||
|
attr := ParseAttr(s)
|
||||||
|
|
||||||
|
assert.Len(t, attr, 1)
|
||||||
|
result := attr.Find("6VU:*")
|
||||||
|
assert.Equal(t, All, result.Key)
|
||||||
|
assert.Equal(t, "", result.Value)
|
||||||
|
})
|
||||||
|
t.Run("Scopes", func(t *testing.T) {
|
||||||
|
attr := ParseAttr("files files.read:true photos photos.create:false albums:true people.view:true config.view:false")
|
||||||
|
|
||||||
|
assert.Len(t, attr, 7)
|
||||||
|
result := attr.Find("people.view")
|
||||||
|
assert.Equal(t, "people.view", result.Key)
|
||||||
|
assert.Equal(t, True, result.Value)
|
||||||
|
})
|
||||||
|
t.Run("ReadAll", func(t *testing.T) {
|
||||||
|
s := "read *"
|
||||||
|
attr := ParseAttr(s)
|
||||||
|
|
||||||
|
assert.Len(t, attr, 2)
|
||||||
|
result := attr.Find("read")
|
||||||
|
assert.Equal(t, "read", result.Key)
|
||||||
|
assert.Equal(t, True, result.Value)
|
||||||
|
})
|
||||||
|
t.Run("ReadFalse", func(t *testing.T) {
|
||||||
|
s := "read:false *"
|
||||||
|
attr := ParseAttr(s)
|
||||||
|
|
||||||
|
assert.Len(t, attr, 2)
|
||||||
|
result := attr.Find("read:*")
|
||||||
|
assert.Equal(t, "read", result.Key)
|
||||||
|
assert.Equal(t, False, result.Value)
|
||||||
|
result = attr.Find("read:false")
|
||||||
|
assert.Equal(t, "read", result.Key)
|
||||||
|
assert.Equal(t, False, result.Value)
|
||||||
|
})
|
||||||
|
t.Run("ReadOther", func(t *testing.T) {
|
||||||
|
s := "read:other *"
|
||||||
|
attr := ParseAttr(s)
|
||||||
|
|
||||||
|
assert.Len(t, attr, 2)
|
||||||
|
|
||||||
|
result := attr.Find("read")
|
||||||
|
assert.Equal(t, All, result.Key)
|
||||||
|
assert.Equal(t, "", result.Value)
|
||||||
|
|
||||||
|
result = attr.Find("read:other")
|
||||||
|
assert.Equal(t, "read", result.Key)
|
||||||
|
assert.Equal(t, "other", result.Value)
|
||||||
|
|
||||||
|
result = attr.Find("read:true")
|
||||||
|
assert.Equal(t, All, result.Key)
|
||||||
|
assert.Equal(t, "", result.Value)
|
||||||
|
|
||||||
|
result = attr.Find("read:false")
|
||||||
|
assert.Equal(t, "", result.Key)
|
||||||
|
assert.Equal(t, "", result.Value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user