Also check Seerr parent permissions (#1631)

## Description
When saving Seerr permissions on the WebUI, if a "parent" permission is
checked, only its bit is flipped and its children's bits are not. So
clients must check if a user has the permission _or_ its parent.

For example, if just "Request 4K" is checked, the flags for "Request 4K
Movies" and "Request 4K TV" will not set explicitly.

### Related issues
Fixes #1630

### Testing
Emulator & unit tests, also manually reviewed API requests/responses on
Seerr's web UI

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-06-27 14:28:57 -04:00 • committed by GitHub
parent a8ade9171e
commit 21e720c420
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 92 additions and 14 deletions

View file

@ -7,6 +7,7 @@ import com.github.damontecres.wholphin.services.SeerrUserConfig
*/ */
enum class SeerrPermission( enum class SeerrPermission(
private val flag: Int, private val flag: Int,
vararg val parents: SeerrPermission,
) { ) {
// Source: https://github.com/seerr-team/seerr/blob/develop/server/lib/permissions.ts // Source: https://github.com/seerr-team/seerr/blob/develop/server/lib/permissions.ts
NONE(0), NONE(0),
@ -17,28 +18,28 @@ enum class SeerrPermission(
REQUEST(32), REQUEST(32),
VOTE(64), VOTE(64),
AUTO_APPROVE(128), AUTO_APPROVE(128),
AUTO_APPROVE_MOVIE(256), AUTO_APPROVE_MOVIE(256, AUTO_APPROVE),
AUTO_APPROVE_TV(512), AUTO_APPROVE_TV(512, AUTO_APPROVE),
REQUEST_4K(1024), REQUEST_4K(1024),
REQUEST_4K_MOVIE(2048), REQUEST_4K_MOVIE(2048, REQUEST_4K),
REQUEST_4K_TV(4096), REQUEST_4K_TV(4096, REQUEST_4K),
REQUEST_ADVANCED(8192), REQUEST_ADVANCED(8192),
REQUEST_VIEW(16384), REQUEST_VIEW(16384),
AUTO_APPROVE_4K(32768), AUTO_APPROVE_4K(32768),
AUTO_APPROVE_4K_MOVIE(65536), AUTO_APPROVE_4K_MOVIE(65536, AUTO_APPROVE_4K),
AUTO_APPROVE_4K_TV(131072), AUTO_APPROVE_4K_TV(131072, AUTO_APPROVE_4K),
REQUEST_MOVIE(262144), REQUEST_MOVIE(262144, REQUEST),
REQUEST_TV(524288), REQUEST_TV(524288, REQUEST),
MANAGE_ISSUES(1048576), MANAGE_ISSUES(1048576),
VIEW_ISSUES(2097152), VIEW_ISSUES(2097152, MANAGE_ISSUES),
CREATE_ISSUES(4194304), CREATE_ISSUES(4194304, MANAGE_ISSUES),
AUTO_REQUEST(8388608), AUTO_REQUEST(8388608),
AUTO_REQUEST_MOVIE(16777216), AUTO_REQUEST_MOVIE(16777216, AUTO_REQUEST),
AUTO_REQUEST_TV(33554432), AUTO_REQUEST_TV(33554432, AUTO_REQUEST),
RECENT_VIEW(67108864), RECENT_VIEW(67108864),
WATCHLIST_VIEW(134217728), WATCHLIST_VIEW(134217728),
MANAGE_BLACKLIST(268435456), MANAGE_BLACKLIST(268435456),
VIEW_BLACKLIST(1073741824), VIEW_BLACKLIST(1073741824, MANAGE_BLACKLIST),
; ;
internal fun hasPermission(permissions: Int) = flag.and(permissions) == flag internal fun hasPermission(permissions: Int) = flag.and(permissions) == flag
@ -48,5 +49,8 @@ enum class SeerrPermission(
* Check whether the user has the given permissions (or is an admin) * Check whether the user has the given permissions (or is an admin)
*/ */
fun SeerrUserConfig?.hasPermission(permission: SeerrPermission): Boolean { fun SeerrUserConfig?.hasPermission(permission: SeerrPermission): Boolean {
return permission.hasPermission(this?.permissions ?: return false) || SeerrPermission.ADMIN.hasPermission(permissions) if (this?.permissions == null) return false
return permission.hasPermission(permissions) ||
permission.parents.any { hasPermission(it) } ||
SeerrPermission.ADMIN.hasPermission(permissions)
} }

View file

@ -0,0 +1,74 @@
package com.github.damontecres.wholphin.test
import com.github.damontecres.wholphin.data.model.SeerrPermission
import com.github.damontecres.wholphin.data.model.hasPermission
import com.github.damontecres.wholphin.services.SeerrUserConfig
import org.junit.Assert
import org.junit.Test
class TestSeerrPermission {
@Test
fun `Is Admin`() {
val user =
SeerrUserConfig(
id = 0,
permissions = 2,
)
Assert.assertTrue(user.hasPermission(SeerrPermission.ADMIN))
Assert.assertTrue(user.hasPermission(SeerrPermission.REQUEST_4K_MOVIE))
Assert.assertTrue(user.hasPermission(SeerrPermission.REQUEST_4K_TV))
Assert.assertTrue(user.hasPermission(SeerrPermission.REQUEST_4K))
}
@Test
fun `Request any 4k has request movie 4k`() {
val user =
SeerrUserConfig(
id = 0,
permissions = 1024,
)
Assert.assertFalse(user.hasPermission(SeerrPermission.ADMIN))
Assert.assertTrue(user.hasPermission(SeerrPermission.REQUEST_4K_MOVIE))
Assert.assertTrue(user.hasPermission(SeerrPermission.REQUEST_4K_TV))
Assert.assertTrue(user.hasPermission(SeerrPermission.REQUEST_4K))
}
@Test
fun `Request movie 4k and does not have request tv 4k`() {
val user =
SeerrUserConfig(
id = 0,
permissions = 2048,
)
Assert.assertFalse(user.hasPermission(SeerrPermission.ADMIN))
Assert.assertTrue(user.hasPermission(SeerrPermission.REQUEST_4K_MOVIE))
Assert.assertFalse(user.hasPermission(SeerrPermission.REQUEST_4K_TV))
Assert.assertFalse(user.hasPermission(SeerrPermission.REQUEST_4K))
}
@Test
fun `Has explicit request 4k and children`() {
val user =
SeerrUserConfig(
id = 0,
permissions = 7168,
)
Assert.assertFalse(user.hasPermission(SeerrPermission.ADMIN))
Assert.assertTrue(user.hasPermission(SeerrPermission.REQUEST_4K_MOVIE))
Assert.assertTrue(user.hasPermission(SeerrPermission.REQUEST_4K_TV))
Assert.assertTrue(user.hasPermission(SeerrPermission.REQUEST_4K))
}
@Test
fun `Has explicit request 4k children`() {
val user =
SeerrUserConfig(
id = 0,
permissions = 6144,
)
Assert.assertFalse(user.hasPermission(SeerrPermission.ADMIN))
Assert.assertTrue(user.hasPermission(SeerrPermission.REQUEST_4K_MOVIE))
Assert.assertTrue(user.hasPermission(SeerrPermission.REQUEST_4K_TV))
Assert.assertFalse(user.hasPermission(SeerrPermission.REQUEST_4K))
}
}