mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Add:Restrict user permissions by tag
This commit is contained in:
		
							parent
							
								
									f8d0384155
								
							
						
					
					
						commit
						27f1bd90f9
					
				| @ -77,6 +77,19 @@ | ||||
|             <div v-if="!newUser.permissions.accessAllLibraries" class="my-4"> | ||||
|               <ui-multi-select-dropdown v-model="newUser.librariesAccessible" :items="libraryItems" label="Libraries Accessible to User" /> | ||||
|             </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 class="flex pt-4"> | ||||
| @ -103,7 +116,9 @@ export default { | ||||
|       processing: false, | ||||
|       newUser: {}, | ||||
|       isNew: true, | ||||
|       accountTypes: ['guest', 'user', 'admin'] | ||||
|       accountTypes: ['guest', 'user', 'admin'], | ||||
|       tags: [], | ||||
|       loadingTags: false | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
| @ -135,9 +150,40 @@ export default { | ||||
|     }, | ||||
|     libraryItems() { | ||||
|       return this.libraries.map((lib) => ({ text: lib.name, value: lib.id })) | ||||
|     }, | ||||
|     itemTags() { | ||||
|       return this.tags.map((t) => { | ||||
|         return { | ||||
|           text: t, | ||||
|           value: t | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|   }, | ||||
|   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) { | ||||
|       if (!val && !this.newUser.librariesAccessible.length) { | ||||
|         this.newUser.librariesAccessible = this.libraries.map((l) => l.id) | ||||
| @ -223,20 +269,22 @@ export default { | ||||
|         download: type !== 'guest', | ||||
|         update: type === 'admin', | ||||
|         delete: type === 'admin', | ||||
|         upload: type === 'admin' | ||||
|         upload: type === 'admin', | ||||
|         accessAllLibraries: true, | ||||
|         accessAllTags: true | ||||
|       } | ||||
|     }, | ||||
|     init() { | ||||
|       this.isNew = !this.account | ||||
|       if (this.account) { | ||||
|         var librariesAccessible = this.account.librariesAccessible || [] | ||||
|         this.newUser = { | ||||
|           username: this.account.username, | ||||
|           password: this.account.password, | ||||
|           type: this.account.type, | ||||
|           isActive: this.account.isActive, | ||||
|           permissions: { ...this.account.permissions }, | ||||
|           librariesAccessible: [...librariesAccessible] | ||||
|           librariesAccessible: [...(this.account.librariesAccessible || [])], | ||||
|           itemTagsAccessible: [...(this.account.itemTagsAccessible || [])] | ||||
|         } | ||||
|       } else { | ||||
|         this.newUser = { | ||||
| @ -249,7 +297,8 @@ export default { | ||||
|             update: false, | ||||
|             delete: false, | ||||
|             upload: false, | ||||
|             accessAllLibraries: true | ||||
|             accessAllLibraries: true, | ||||
|             accessAllTags: true | ||||
|           }, | ||||
|           librariesAccessible: [] | ||||
|         } | ||||
|  | ||||
| @ -233,7 +233,9 @@ class LibraryController { | ||||
|     if (!req.params.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) { | ||||
|       return res.status(404).send('Series not found') | ||||
|     } | ||||
| @ -530,7 +532,9 @@ class LibraryController { | ||||
|       return res.status(404).send('Library not found') | ||||
|     } | ||||
|     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() | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -262,6 +262,11 @@ class LibraryItemController { | ||||
|       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) { | ||||
|       Logger.warn(`[LibraryItemController] User attempted to delete without permission`, req.user) | ||||
|       return res.sendStatus(403) | ||||
|  | ||||
| @ -170,5 +170,21 @@ class MiscController { | ||||
|     } | ||||
|     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() | ||||
| @ -20,6 +20,7 @@ class User { | ||||
|     this.settings = {} | ||||
|     this.permissions = {} | ||||
|     this.librariesAccessible = [] // Library IDs (Empty if ALL libraries)
 | ||||
|     this.itemTagsAccessible = [] // Empty if ALL item tags accessible
 | ||||
| 
 | ||||
|     if (user) { | ||||
|       this.construct(user) | ||||
| @ -44,6 +45,9 @@ class User { | ||||
|   get canAccessAllLibraries() { | ||||
|     return !!this.permissions.accessAllLibraries && this.isActive | ||||
|   } | ||||
|   get canAccessAllTags() { | ||||
|     return !!this.permissions.accessAllTags && this.isActive | ||||
|   } | ||||
|   get hasPw() { | ||||
|     return !!this.pash && !!this.pash.length | ||||
|   } | ||||
| @ -68,7 +72,8 @@ class User { | ||||
|       update: true, | ||||
|       delete: this.type === 'root', | ||||
|       upload: this.type === 'root' || this.type === 'admin', | ||||
|       accessAllLibraries: true | ||||
|       accessAllLibraries: true, | ||||
|       accessAllTags: true | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -87,7 +92,8 @@ class User { | ||||
|       createdAt: this.createdAt, | ||||
|       settings: this.settings, | ||||
|       permissions: this.permissions, | ||||
|       librariesAccessible: [...this.librariesAccessible] | ||||
|       librariesAccessible: [...this.librariesAccessible], | ||||
|       itemTagsAccessible: [...this.itemTagsAccessible] | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -105,7 +111,8 @@ class User { | ||||
|       createdAt: this.createdAt, | ||||
|       settings: this.settings, | ||||
|       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
 | ||||
|     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) { | ||||
| @ -190,6 +200,19 @@ class User { | ||||
|         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 | ||||
|   } | ||||
| 
 | ||||
| @ -270,6 +293,11 @@ class User { | ||||
|     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) { | ||||
|     return this.bookmarks.find(bm => bm.libraryItemId === libraryItemId && bm.time == time) | ||||
|   } | ||||
|  | ||||
| @ -192,6 +192,7 @@ class ApiRouter { | ||||
|     this.router.get('/search/books', MiscController.findBooks.bind(this)) | ||||
|     this.router.get('/search/podcast', MiscController.findPodcasts.bind(this)) | ||||
|     this.router.get('/search/authors', MiscController.findAuthor.bind(this)) | ||||
|     this.router.get('/tags', MiscController.getAllTags.bind(this)) | ||||
| 
 | ||||
|     // OLD
 | ||||
|     // this.router.post('/syncUserAudiobookData', this.syncUserAudiobookData.bind(this))
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user