Add:Restrict user permissions by tag

This commit is contained in:
advplyr 2022-03-20 06:29:08 -05:00
parent f8d0384155
commit 27f1bd90f9
6 changed files with 114 additions and 11 deletions

View File

@ -77,6 +77,19 @@
<div v-if="!newUser.permissions.accessAllLibraries" class="my-4"> <div v-if="!newUser.permissions.accessAllLibraries" class="my-4">
<ui-multi-select-dropdown v-model="newUser.librariesAccessible" :items="libraryItems" label="Libraries Accessible to User" /> <ui-multi-select-dropdown v-model="newUser.librariesAccessible" :items="libraryItems" label="Libraries Accessible to User" />
</div> </div>
<div class="flex items-cen~ter my-2 max-w-md">
<div class="w-1/2">
<p>Can Access All Tags</p>
</div>
<div class="w-1/2">
<ui-toggle-switch v-model="newUser.permissions.accessAllTags" @input="accessAllTagsToggled" />
</div>
</div>
<div v-if="!newUser.permissions.accessAllTags" class="my-4">
<ui-multi-select-dropdown v-model="newUser.itemsTagsAccessible" :items="itemTags" label="Tags Accessible to User" />
</div>
</div> </div>
<div class="flex pt-4"> <div class="flex pt-4">
@ -103,7 +116,9 @@ export default {
processing: false, processing: false,
newUser: {}, newUser: {},
isNew: true, isNew: true,
accountTypes: ['guest', 'user', 'admin'] accountTypes: ['guest', 'user', 'admin'],
tags: [],
loadingTags: false
} }
}, },
watch: { watch: {
@ -135,9 +150,40 @@ export default {
}, },
libraryItems() { libraryItems() {
return this.libraries.map((lib) => ({ text: lib.name, value: lib.id })) return this.libraries.map((lib) => ({ text: lib.name, value: lib.id }))
},
itemTags() {
return this.tags.map((t) => {
return {
text: t,
value: t
}
})
} }
}, },
methods: { methods: {
accessAllTagsToggled(val) {
if (!val) {
this.fetchAllTags()
}
if (!val && !this.newUser.itemTagsAccessible.length) {
this.newUser.itemTagsAccessible = this.libraries.map((l) => l.id)
} else if (val && this.newUser.itemTagsAccessible.length) {
this.newUser.itemTagsAccessible = []
}
},
fetchAllTags() {
this.loadingTags = true
this.$axios
.$get(`/api/tags`)
.then((tags) => {
this.tags = tags
this.loadingTags = false
})
.catch((error) => {
console.error('Failed to load tags', error)
this.loadingTags = false
})
},
accessAllLibrariesToggled(val) { accessAllLibrariesToggled(val) {
if (!val && !this.newUser.librariesAccessible.length) { if (!val && !this.newUser.librariesAccessible.length) {
this.newUser.librariesAccessible = this.libraries.map((l) => l.id) this.newUser.librariesAccessible = this.libraries.map((l) => l.id)
@ -223,20 +269,22 @@ export default {
download: type !== 'guest', download: type !== 'guest',
update: type === 'admin', update: type === 'admin',
delete: type === 'admin', delete: type === 'admin',
upload: type === 'admin' upload: type === 'admin',
accessAllLibraries: true,
accessAllTags: true
} }
}, },
init() { init() {
this.isNew = !this.account this.isNew = !this.account
if (this.account) { if (this.account) {
var librariesAccessible = this.account.librariesAccessible || []
this.newUser = { this.newUser = {
username: this.account.username, username: this.account.username,
password: this.account.password, password: this.account.password,
type: this.account.type, type: this.account.type,
isActive: this.account.isActive, isActive: this.account.isActive,
permissions: { ...this.account.permissions }, permissions: { ...this.account.permissions },
librariesAccessible: [...librariesAccessible] librariesAccessible: [...(this.account.librariesAccessible || [])],
itemTagsAccessible: [...(this.account.itemTagsAccessible || [])]
} }
} else { } else {
this.newUser = { this.newUser = {
@ -249,7 +297,8 @@ export default {
update: false, update: false,
delete: false, delete: false,
upload: false, upload: false,
accessAllLibraries: true accessAllLibraries: true,
accessAllTags: true
}, },
librariesAccessible: [] librariesAccessible: []
} }

View File

@ -233,7 +233,9 @@ class LibraryController {
if (!req.params.series) { if (!req.params.series) {
return res.status(403).send('Invalid series') return res.status(403).send('Invalid series')
} }
var libraryItems = this.db.libraryItems.filter(li => li.libraryId === req.library.id && li.book.series === req.params.series) var libraryItems = this.db.libraryItems.filter(li => {
return li.libraryId === req.library.id && li.book.series === req.params.series
})
if (!libraryItems.length) { if (!libraryItems.length) {
return res.status(404).send('Series not found') return res.status(404).send('Series not found')
} }
@ -530,7 +532,9 @@ class LibraryController {
return res.status(404).send('Library not found') return res.status(404).send('Library not found')
} }
req.library = library req.library = library
req.libraryItems = this.db.libraryItems.filter(li => li.libraryId === library.id) req.libraryItems = this.db.libraryItems.filter(li => {
return li.libraryId === library.id && req.user.checkCanAccessLibraryItemWithTags(li.media.tags)
})
next() next()
} }
} }

View File

@ -262,6 +262,11 @@ class LibraryItemController {
return res.sendStatus(403) return res.sendStatus(403)
} }
// Check user can access this library item
if (!req.user.checkCanAccessLibraryItemWithTags(item.media.tags)) {
return res.sendStatus(403)
}
if (req.method == 'DELETE' && !req.user.canDelete) { if (req.method == 'DELETE' && !req.user.canDelete) {
Logger.warn(`[LibraryItemController] User attempted to delete without permission`, req.user) Logger.warn(`[LibraryItemController] User attempted to delete without permission`, req.user)
return res.sendStatus(403) return res.sendStatus(403)

View File

@ -170,5 +170,21 @@ class MiscController {
} }
res.json({ user: req.user }) res.json({ user: req.user })
} }
getAllTags(req, res) {
if (!req.user.isRoot) {
Logger.error(`[MiscController] Non-root user attempted to getAllTags`)
return res.sendStatus(404)
}
var tags = []
this.db.libraryItems.forEach((li) => {
if (li.media.tags && li.media.tags.length) {
li.media.tags.forEach((tag) => {
if (!tags.includes(tag)) tags.push(tag)
})
}
})
res.json(tags)
}
} }
module.exports = new MiscController() module.exports = new MiscController()

View File

@ -20,6 +20,7 @@ class User {
this.settings = {} this.settings = {}
this.permissions = {} this.permissions = {}
this.librariesAccessible = [] // Library IDs (Empty if ALL libraries) this.librariesAccessible = [] // Library IDs (Empty if ALL libraries)
this.itemTagsAccessible = [] // Empty if ALL item tags accessible
if (user) { if (user) {
this.construct(user) this.construct(user)
@ -44,6 +45,9 @@ class User {
get canAccessAllLibraries() { get canAccessAllLibraries() {
return !!this.permissions.accessAllLibraries && this.isActive return !!this.permissions.accessAllLibraries && this.isActive
} }
get canAccessAllTags() {
return !!this.permissions.accessAllTags && this.isActive
}
get hasPw() { get hasPw() {
return !!this.pash && !!this.pash.length return !!this.pash && !!this.pash.length
} }
@ -68,7 +72,8 @@ class User {
update: true, update: true,
delete: this.type === 'root', delete: this.type === 'root',
upload: this.type === 'root' || this.type === 'admin', upload: this.type === 'root' || this.type === 'admin',
accessAllLibraries: true accessAllLibraries: true,
accessAllTags: true
} }
} }
@ -87,7 +92,8 @@ class User {
createdAt: this.createdAt, createdAt: this.createdAt,
settings: this.settings, settings: this.settings,
permissions: this.permissions, permissions: this.permissions,
librariesAccessible: [...this.librariesAccessible] librariesAccessible: [...this.librariesAccessible],
itemTagsAccessible: [...this.itemTagsAccessible]
} }
} }
@ -105,7 +111,8 @@ class User {
createdAt: this.createdAt, createdAt: this.createdAt,
settings: this.settings, settings: this.settings,
permissions: this.permissions, permissions: this.permissions,
librariesAccessible: [...this.librariesAccessible] librariesAccessible: [...this.librariesAccessible],
itemTagsAccessible: [...this.itemTagsAccessible]
} }
} }
@ -151,8 +158,11 @@ class User {
// Library restriction permissions added v1.4.14, defaults to all libraries // Library restriction permissions added v1.4.14, defaults to all libraries
if (this.permissions.accessAllLibraries === undefined) this.permissions.accessAllLibraries = true if (this.permissions.accessAllLibraries === undefined) this.permissions.accessAllLibraries = true
// Library restriction permissions added v2.0, defaults to all libraries
if (this.permissions.accessAllTags === undefined) this.permissions.accessAllTags = true
this.librariesAccessible = (user.librariesAccessible || []).map(l => l) this.librariesAccessible = [...(user.librariesAccessible || [])]
this.itemTagsAccessible = [...(user.itemTagsAccessible || [])]
} }
update(payload) { update(payload) {
@ -190,6 +200,19 @@ class User {
this.librariesAccessible = [] this.librariesAccessible = []
} }
} }
// Update accessible libraries
if (payload.itemTagsAccessible !== undefined) {
if (payload.itemTagsAccessible.length) {
if (payload.itemTagsAccessible.join(',') !== this.itemTagsAccessible.join(',')) {
hasUpdates = true
this.itemTagsAccessible = [...payload.itemTagsAccessible]
}
} else if (this.itemTagsAccessible.length > 0) {
hasUpdates = true
this.itemTagsAccessible = []
}
}
return hasUpdates return hasUpdates
} }
@ -270,6 +293,11 @@ class User {
return this.librariesAccessible.includes(libraryId) return this.librariesAccessible.includes(libraryId)
} }
checkCanAccessLibraryItemWithTags(tags) {
if (this.permissions.accessAllTags || !tags || !tags.length) return true
return this.itemTagsAccessible.some(tag => tags.includes(tag))
}
findBookmark(libraryItemId, time) { findBookmark(libraryItemId, time) {
return this.bookmarks.find(bm => bm.libraryItemId === libraryItemId && bm.time == time) return this.bookmarks.find(bm => bm.libraryItemId === libraryItemId && bm.time == time)
} }

View File

@ -192,6 +192,7 @@ class ApiRouter {
this.router.get('/search/books', MiscController.findBooks.bind(this)) this.router.get('/search/books', MiscController.findBooks.bind(this))
this.router.get('/search/podcast', MiscController.findPodcasts.bind(this)) this.router.get('/search/podcast', MiscController.findPodcasts.bind(this))
this.router.get('/search/authors', MiscController.findAuthor.bind(this)) this.router.get('/search/authors', MiscController.findAuthor.bind(this))
this.router.get('/tags', MiscController.getAllTags.bind(this))
// OLD // OLD
// this.router.post('/syncUserAudiobookData', this.syncUserAudiobookData.bind(this)) // this.router.post('/syncUserAudiobookData', this.syncUserAudiobookData.bind(this))