feat(perms): add separate REQUEST_MOVIE and REQUEST_TV permissions (#1474)

* feat(perms): add separate REQUEST_MOVIE and REQUEST_TV permissions

* fix(perms): do not require regular request perms for 4K requests
pull/1510/head
TheCatLady 3 years ago committed by GitHub
parent ed99e4976d
commit 91b9e0f679
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -17,6 +17,8 @@ export enum Permission {
AUTO_APPROVE_4K = 32768,
AUTO_APPROVE_4K_MOVIE = 65536,
AUTO_APPROVE_4K_TV = 131072,
REQUEST_MOVIE = 262144,
REQUEST_TV = 524288,
}
export interface PermissionCheckOptions {

@ -139,10 +139,7 @@ requestRoutes.get('/', async (req, res, next) => {
}
});
requestRoutes.post(
'/',
isAuthenticated(Permission.REQUEST),
async (req, res, next) => {
requestRoutes.post('/', async (req, res, next) => {
const tmdb = new TheMovieDb();
const mediaRepository = getRepository(Media);
const requestRepository = getRepository(MediaRequest);
@ -175,11 +172,12 @@ requestRoutes.post(
});
}
if (req.body.is4k) {
if (
req.body.mediaType === MediaType.MOVIE &&
!req.user?.hasPermission(
[Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE],
req.body.is4k
? [Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE]
: [Permission.REQUEST, Permission.REQUEST_MOVIE],
{
type: 'or',
}
@ -187,12 +185,16 @@ requestRoutes.post(
) {
return next({
status: 403,
message: 'You do not have permission to make 4K movie requests.',
message: `You do not have permission to make ${
req.body.is4k ? '4K ' : ''
}movie requests.`,
});
} else if (
req.body.mediaType === MediaType.TV &&
!req.user?.hasPermission(
[Permission.REQUEST_4K, Permission.REQUEST_4K_TV],
req.body.is4k
? [Permission.REQUEST_4K, Permission.REQUEST_4K_TV]
: [Permission.REQUEST, Permission.REQUEST_TV],
{
type: 'or',
}
@ -200,10 +202,11 @@ requestRoutes.post(
) {
return next({
status: 403,
message: 'You do not have permission to make 4K series requests.',
message: `You do not have permission to make ${
req.body.is4k ? '4K ' : ''
}series requests.`,
});
}
}
const quotas = await requestUser.getQuota();
@ -418,8 +421,7 @@ requestRoutes.post(
} catch (e) {
next({ status: 500, message: e.message });
}
}
);
});
requestRoutes.get('/count', async (_req, res, next) => {
const requestRepository = getRepository(MediaRequest);

@ -108,11 +108,18 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
}
const hasRequestable =
hasPermission([Permission.REQUEST, Permission.REQUEST_MOVIE], {
type: 'or',
}) &&
data.parts.filter(
(part) => !part.mediaInfo || part.mediaInfo.status === MediaStatus.UNKNOWN
).length > 0;
const hasRequestable4k =
settings.currentSettings.movie4kEnabled &&
hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE], {
type: 'or',
}) &&
data.parts.filter(
(part) =>
!part.mediaInfo || part.mediaInfo.status4k === MediaStatus.UNKNOWN
@ -323,14 +330,7 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
</span>
</div>
<div className="media-actions">
{hasPermission(Permission.REQUEST) &&
(hasRequestable ||
(settings.currentSettings.movie4kEnabled &&
hasPermission(
[Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE],
{ type: 'or' }
) &&
hasRequestable4k)) && (
{(hasRequestable || hasRequestable4k) && (
<ButtonWithDropdown
buttonType="primary"
onClick={() => {
@ -350,13 +350,7 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
</>
}
>
{settings.currentSettings.movie4kEnabled &&
hasPermission(
[Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE],
{ type: 'or' }
) &&
hasRequestable &&
hasRequestable4k && (
{hasRequestable && hasRequestable4k && (
<ButtonWithDropdown.Item
buttonType="primary"
onClick={() => {

@ -70,9 +70,9 @@ const messages = defineMessages({
openradarr4k: 'Open Movie in 4K Radarr',
downloadstatus: 'Download Status',
playonplex: 'Play on Plex',
play4konplex: 'Play 4K on Plex',
play4konplex: 'Play in 4K on Plex',
markavailable: 'Mark as Available',
mark4kavailable: 'Mark 4K as Available',
mark4kavailable: 'Mark as Available in 4K',
});
interface MovieDetailsProps {
@ -112,7 +112,12 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
const mediaLinks: PlayButtonLink[] = [];
if (data.mediaInfo?.plexUrl) {
if (
data.mediaInfo?.plexUrl &&
hasPermission([Permission.REQUEST, Permission.REQUEST_MOVIE], {
type: 'or',
})
) {
mediaLinks.push({
text: intl.formatMessage(messages.playonplex),
url: data.mediaInfo?.plexUrl,

@ -12,42 +12,41 @@ export const messages = defineMessages({
'Grant permission to manage Overseerr users. Users with this permission cannot modify users with or grant the Admin privilege.',
settings: 'Manage Settings',
settingsDescription:
'Grant permission to modify all Overseerr settings. A user must have this permission to grant it to others.',
'Grant permission to modify Overseerr settings. A user must have this permission to grant it to others.',
managerequests: 'Manage Requests',
managerequestsDescription:
'Grant permission to manage Overseerr requests (includes approving and denying requests). All requests made by a user with this permission will be automatically approved.',
'Grant permission to manage Overseerr requests. All requests made by a user with this permission will be automatically approved.',
request: 'Request',
requestDescription: 'Grant permission to request movies and series.',
vote: 'Vote',
voteDescription:
'Grant permission to vote on requests (voting not yet implemented).',
requestDescription: 'Grant permission to request non-4K media.',
requestMovies: 'Request Movies',
requestMoviesDescription: 'Grant permission to request non-4K movies.',
requestTv: 'Request Series',
requestTvDescription: 'Grant permission to request non-4K series.',
autoapprove: 'Auto-Approve',
autoapproveDescription:
'Grant automatic approval for all non-4K requests made by this user.',
autoapproveDescription: 'Grant automatic approval for all non-4K requests.',
autoapproveMovies: 'Auto-Approve Movies',
autoapproveMoviesDescription:
'Grant automatic approval for non-4K movie requests made by this user.',
'Grant automatic approval for non-4K movie requests.',
autoapproveSeries: 'Auto-Approve Series',
autoapproveSeriesDescription:
'Grant automatic approval for non-4K series requests made by this user.',
'Grant automatic approval for non-4K series requests.',
autoapprove4k: 'Auto-Approve 4K',
autoapprove4kDescription:
'Grant automatic approval for all 4K requests made by this user.',
autoapprove4kDescription: 'Grant automatic approval for all 4K requests.',
autoapprove4kMovies: 'Auto-Approve 4K Movies',
autoapprove4kMoviesDescription:
'Grant automatic approval for 4K movie requests made by this user.',
'Grant automatic approval for 4K movie requests.',
autoapprove4kSeries: 'Auto-Approve 4K Series',
autoapprove4kSeriesDescription:
'Grant automatic approval for 4K series requests made by this user.',
'Grant automatic approval for 4K series requests.',
request4k: 'Request 4K',
request4kDescription: 'Grant permission to request 4K movies and series.',
request4kDescription: 'Grant permission to request 4K media.',
request4kMovies: 'Request 4K Movies',
request4kMoviesDescription: 'Grant permission to request 4K movies.',
request4kTv: 'Request 4K Series',
request4kTvDescription: 'Grant permission to request 4K Series.',
request4kTvDescription: 'Grant permission to request 4K series.',
advancedrequest: 'Advanced Requests',
advancedrequestDescription:
'Grant permission to use advanced request options (e.g., changing servers, profiles, or paths).',
'Grant permission to use advanced request options.',
viewrequests: 'View Requests',
viewrequestsDescription: "Grant permission to view other users' requests.",
});
@ -111,27 +110,18 @@ export const PermissionEdit: React.FC<PermissionEditProps> = ({
name: intl.formatMessage(messages.request),
description: intl.formatMessage(messages.requestDescription),
permission: Permission.REQUEST,
},
{
id: 'request4k',
name: intl.formatMessage(messages.request4k),
description: intl.formatMessage(messages.request4kDescription),
permission: Permission.REQUEST_4K,
requires: [{ permissions: [Permission.REQUEST] }],
children: [
{
id: 'request4k-movies',
name: intl.formatMessage(messages.request4kMovies),
description: intl.formatMessage(messages.request4kMoviesDescription),
permission: Permission.REQUEST_4K_MOVIE,
requires: [{ permissions: [Permission.REQUEST] }],
id: 'request-movies',
name: intl.formatMessage(messages.requestMovies),
description: intl.formatMessage(messages.requestMoviesDescription),
permission: Permission.REQUEST_MOVIE,
},
{
id: 'request4k-tv',
name: intl.formatMessage(messages.request4kTv),
description: intl.formatMessage(messages.request4kTvDescription),
permission: Permission.REQUEST_4K_TV,
requires: [{ permissions: [Permission.REQUEST] }],
id: 'request-tv',
name: intl.formatMessage(messages.requestTv),
description: intl.formatMessage(messages.requestTvDescription),
permission: Permission.REQUEST_TV,
},
],
},
@ -149,7 +139,12 @@ export const PermissionEdit: React.FC<PermissionEditProps> = ({
messages.autoapproveMoviesDescription
),
permission: Permission.AUTO_APPROVE_MOVIE,
requires: [{ permissions: [Permission.REQUEST] }],
requires: [
{
permissions: [Permission.REQUEST, Permission.REQUEST_MOVIE],
type: 'or',
},
],
},
{
id: 'autoapprovetv',
@ -158,7 +153,32 @@ export const PermissionEdit: React.FC<PermissionEditProps> = ({
messages.autoapproveSeriesDescription
),
permission: Permission.AUTO_APPROVE_TV,
requires: [{ permissions: [Permission.REQUEST] }],
requires: [
{
permissions: [Permission.REQUEST, Permission.REQUEST_TV],
type: 'or',
},
],
},
],
},
{
id: 'request4k',
name: intl.formatMessage(messages.request4k),
description: intl.formatMessage(messages.request4kDescription),
permission: Permission.REQUEST_4K,
children: [
{
id: 'request4k-movies',
name: intl.formatMessage(messages.request4kMovies),
description: intl.formatMessage(messages.request4kMoviesDescription),
permission: Permission.REQUEST_4K_MOVIE,
},
{
id: 'request4k-tv',
name: intl.formatMessage(messages.request4kTv),
description: intl.formatMessage(messages.request4kTvDescription),
permission: Permission.REQUEST_4K_TV,
},
],
},
@ -169,8 +189,7 @@ export const PermissionEdit: React.FC<PermissionEditProps> = ({
permission: Permission.AUTO_APPROVE_4K,
requires: [
{
permissions: [Permission.REQUEST, Permission.REQUEST_4K],
type: 'and',
permissions: [Permission.REQUEST_4K],
},
],
children: [
@ -182,9 +201,6 @@ export const PermissionEdit: React.FC<PermissionEditProps> = ({
),
permission: Permission.AUTO_APPROVE_4K_MOVIE,
requires: [
{
permissions: [Permission.REQUEST],
},
{
permissions: [Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE],
type: 'or',
@ -199,9 +215,6 @@ export const PermissionEdit: React.FC<PermissionEditProps> = ({
),
permission: Permission.AUTO_APPROVE_4K_TV,
requires: [
{
permissions: [Permission.REQUEST],
},
{
permissions: [Permission.REQUEST_4K, Permission.REQUEST_4K_TV],
type: 'or',

@ -1,7 +1,7 @@
import React from 'react';
import { hasPermission } from '../../../server/lib/permissions';
import { Permission, User } from '../../hooks/useUser';
import useSettings from '../../hooks/useSettings';
import { Permission, User } from '../../hooks/useUser';
export interface PermissionItem {
id: string;

@ -123,7 +123,15 @@ const RequestButton: React.FC<RequestButtonProps> = ({
const buttons: ButtonOption[] = [];
if (
(!media || media.status === MediaStatus.UNKNOWN) &&
hasPermission(Permission.REQUEST)
hasPermission(
[
Permission.REQUEST,
mediaType === 'movie'
? Permission.REQUEST_MOVIE
: Permission.REQUEST_TV,
],
{ type: 'or' }
)
) {
buttons.push({
id: 'request',
@ -138,9 +146,15 @@ const RequestButton: React.FC<RequestButtonProps> = ({
if (
(!media || media.status4k === MediaStatus.UNKNOWN) &&
(hasPermission(Permission.REQUEST_4K) ||
(mediaType === 'movie' && hasPermission(Permission.REQUEST_4K_MOVIE)) ||
(mediaType === 'tv' && hasPermission(Permission.REQUEST_4K_TV))) &&
hasPermission(
[
Permission.REQUEST_4K,
mediaType === 'movie'
? Permission.REQUEST_4K_MOVIE
: Permission.REQUEST_4K_TV,
],
{ type: 'or' }
) &&
((settings.currentSettings.movie4kEnabled && mediaType === 'movie') ||
(settings.currentSettings.series4kEnabled && mediaType === 'tv'))
) {
@ -302,7 +316,9 @@ const RequestButton: React.FC<RequestButtonProps> = ({
if (
mediaType === 'tv' &&
(!activeRequest || activeRequest.requestedBy.id !== user?.id) &&
hasPermission(Permission.REQUEST) &&
hasPermission([Permission.REQUEST, Permission.REQUEST_TV], {
type: 'or',
}) &&
media &&
media.status !== MediaStatus.AVAILABLE &&
media.status !== MediaStatus.UNKNOWN &&

@ -69,6 +69,14 @@ const TitleCard: React.FC<TitleCardProps> = ({
const closeModal = useCallback(() => setShowRequestModal(false), []);
const showRequestButton = hasPermission(
[
Permission.REQUEST,
mediaType === 'movie' ? Permission.REQUEST_MOVIE : Permission.REQUEST_TV,
],
{ type: 'or' }
);
return (
<div className={canExpand ? 'w-full' : 'w-36 sm:w-36 md:w-44'}>
<RequestModal
@ -185,7 +193,7 @@ const TitleCard: React.FC<TitleCardProps> = ({
<div className="flex items-end w-full h-full">
<div
className={`px-2 text-white ${
!hasPermission(Permission.REQUEST) ||
!showRequestButton ||
(currentStatus && currentStatus !== MediaStatus.UNKNOWN)
? 'pb-2'
: 'pb-11'
@ -209,7 +217,7 @@ const TitleCard: React.FC<TitleCardProps> = ({
className="text-xs whitespace-normal"
style={{
WebkitLineClamp:
!hasPermission(Permission.REQUEST) ||
!showRequestButton ||
(currentStatus &&
currentStatus !== MediaStatus.UNKNOWN)
? 5
@ -228,7 +236,7 @@ const TitleCard: React.FC<TitleCardProps> = ({
</Link>
<div className="absolute bottom-0 left-0 right-0 flex justify-between px-2 py-2">
{hasPermission(Permission.REQUEST) &&
{showRequestButton &&
(!currentStatus || currentStatus === MediaStatus.UNKNOWN) && (
<Button
buttonType="primary"

@ -63,7 +63,7 @@ const messages = defineMessages({
manageModalNoRequests: 'No requests.',
manageModalClearMedia: 'Clear Media Data',
manageModalClearMediaWarning:
'* This will irreversibly remove all data for this TV series, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.',
'* This will irreversibly remove all data for this series, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.',
originaltitle: 'Original Title',
showtype: 'Series Type',
anime: 'Anime',
@ -73,9 +73,9 @@ const messages = defineMessages({
opensonarr4k: 'Open Series in 4K Sonarr',
downloadstatus: 'Download Status',
playonplex: 'Play on Plex',
play4konplex: 'Play 4K on Plex',
play4konplex: 'Play in 4K on Plex',
markavailable: 'Mark as Available',
mark4kavailable: 'Mark 4K as Available',
mark4kavailable: 'Mark as Available in 4K',
allseasonsmarkedavailable: '* All seasons will be marked as available.',
seasons: '{seasonCount, plural, one {# Season} other {# Seasons}}',
episodeRuntime: 'Episode Runtime',

@ -69,7 +69,7 @@
"components.MovieDetails.manageModalNoRequests": "No requests.",
"components.MovieDetails.manageModalRequests": "Requests",
"components.MovieDetails.manageModalTitle": "Manage Movie",
"components.MovieDetails.mark4kavailable": "Mark 4K as Available",
"components.MovieDetails.mark4kavailable": "Mark as Available in 4K",
"components.MovieDetails.markavailable": "Mark as Available",
"components.MovieDetails.openradarr": "Open Movie in Radarr",
"components.MovieDetails.openradarr4k": "Open Movie in 4K Radarr",
@ -77,7 +77,7 @@
"components.MovieDetails.originaltitle": "Original Title",
"components.MovieDetails.overview": "Overview",
"components.MovieDetails.overviewunavailable": "Overview unavailable.",
"components.MovieDetails.play4konplex": "Play 4K on Plex",
"components.MovieDetails.play4konplex": "Play in 4K on Plex",
"components.MovieDetails.playonplex": "Play on Plex",
"components.MovieDetails.recommendations": "Recommendations",
"components.MovieDetails.releasedate": "Release Date",
@ -103,37 +103,39 @@
"components.PermissionEdit.admin": "Admin",
"components.PermissionEdit.adminDescription": "Full administrator access. Bypasses all other permission checks.",
"components.PermissionEdit.advancedrequest": "Advanced Requests",
"components.PermissionEdit.advancedrequestDescription": "Grant permission to use advanced request options (e.g., changing servers, profiles, or paths).",
"components.PermissionEdit.advancedrequestDescription": "Grant permission to use advanced request options.",
"components.PermissionEdit.autoapprove": "Auto-Approve",
"components.PermissionEdit.autoapprove4k": "Auto-Approve 4K",
"components.PermissionEdit.autoapprove4kDescription": "Grant automatic approval for all 4K requests made by this user.",
"components.PermissionEdit.autoapprove4kDescription": "Grant automatic approval for all 4K requests.",
"components.PermissionEdit.autoapprove4kMovies": "Auto-Approve 4K Movies",
"components.PermissionEdit.autoapprove4kMoviesDescription": "Grant automatic approval for 4K movie requests made by this user.",
"components.PermissionEdit.autoapprove4kMoviesDescription": "Grant automatic approval for 4K movie requests.",
"components.PermissionEdit.autoapprove4kSeries": "Auto-Approve 4K Series",
"components.PermissionEdit.autoapprove4kSeriesDescription": "Grant automatic approval for 4K series requests made by this user.",
"components.PermissionEdit.autoapproveDescription": "Grant automatic approval for all non-4K requests made by this user.",
"components.PermissionEdit.autoapprove4kSeriesDescription": "Grant automatic approval for 4K series requests.",
"components.PermissionEdit.autoapproveDescription": "Grant automatic approval for all non-4K requests.",
"components.PermissionEdit.autoapproveMovies": "Auto-Approve Movies",
"components.PermissionEdit.autoapproveMoviesDescription": "Grant automatic approval for non-4K movie requests made by this user.",
"components.PermissionEdit.autoapproveMoviesDescription": "Grant automatic approval for non-4K movie requests.",
"components.PermissionEdit.autoapproveSeries": "Auto-Approve Series",
"components.PermissionEdit.autoapproveSeriesDescription": "Grant automatic approval for non-4K series requests made by this user.",
"components.PermissionEdit.autoapproveSeriesDescription": "Grant automatic approval for non-4K series requests.",
"components.PermissionEdit.managerequests": "Manage Requests",
"components.PermissionEdit.managerequestsDescription": "Grant permission to manage Overseerr requests (includes approving and denying requests). All requests made by a user with this permission will be automatically approved.",
"components.PermissionEdit.managerequestsDescription": "Grant permission to manage Overseerr requests. All requests made by a user with this permission will be automatically approved.",
"components.PermissionEdit.request": "Request",
"components.PermissionEdit.request4k": "Request 4K",
"components.PermissionEdit.request4kDescription": "Grant permission to request 4K movies and series.",
"components.PermissionEdit.request4kDescription": "Grant permission to request 4K media.",
"components.PermissionEdit.request4kMovies": "Request 4K Movies",
"components.PermissionEdit.request4kMoviesDescription": "Grant permission to request 4K movies.",
"components.PermissionEdit.request4kTv": "Request 4K Series",
"components.PermissionEdit.request4kTvDescription": "Grant permission to request 4K Series.",
"components.PermissionEdit.requestDescription": "Grant permission to request movies and series.",
"components.PermissionEdit.request4kTvDescription": "Grant permission to request 4K series.",
"components.PermissionEdit.requestDescription": "Grant permission to request non-4K media.",
"components.PermissionEdit.requestMovies": "Request Movies",
"components.PermissionEdit.requestMoviesDescription": "Grant permission to request non-4K movies.",
"components.PermissionEdit.requestTv": "Request Series",
"components.PermissionEdit.requestTvDescription": "Grant permission to request non-4K series.",
"components.PermissionEdit.settings": "Manage Settings",
"components.PermissionEdit.settingsDescription": "Grant permission to modify all Overseerr settings. A user must have this permission to grant it to others.",
"components.PermissionEdit.settingsDescription": "Grant permission to modify Overseerr settings. A user must have this permission to grant it to others.",
"components.PermissionEdit.users": "Manage Users",
"components.PermissionEdit.usersDescription": "Grant permission to manage Overseerr users. Users with this permission cannot modify users with or grant the Admin privilege.",
"components.PermissionEdit.viewrequests": "View Requests",
"components.PermissionEdit.viewrequestsDescription": "Grant permission to view other users' requests.",
"components.PermissionEdit.vote": "Vote",
"components.PermissionEdit.voteDescription": "Grant permission to vote on requests (voting not yet implemented).",
"components.PersonDetails.alsoknownas": "Also Known As: {names}",
"components.PersonDetails.appearsin": "Appearances",
"components.PersonDetails.ascharacter": "as {character}",
@ -658,11 +660,11 @@
"components.TvDetails.episodeRuntimeMinutes": "{runtime} minutes",
"components.TvDetails.firstAirDate": "First Air Date",
"components.TvDetails.manageModalClearMedia": "Clear Media Data",
"components.TvDetails.manageModalClearMediaWarning": "* This will irreversibly remove all data for this TV series, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.",
"components.TvDetails.manageModalClearMediaWarning": "* This will irreversibly remove all data for this series, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.",
"components.TvDetails.manageModalNoRequests": "No requests.",
"components.TvDetails.manageModalRequests": "Requests",
"components.TvDetails.manageModalTitle": "Manage Series",
"components.TvDetails.mark4kavailable": "Mark 4K as Available",
"components.TvDetails.mark4kavailable": "Mark as Available in 4K",
"components.TvDetails.markavailable": "Mark as Available",
"components.TvDetails.network": "{networkCount, plural, one {Network} other {Networks}}",
"components.TvDetails.nextAirDate": "Next Air Date",
@ -672,7 +674,7 @@
"components.TvDetails.originaltitle": "Original Title",
"components.TvDetails.overview": "Overview",
"components.TvDetails.overviewunavailable": "Overview unavailable.",
"components.TvDetails.play4konplex": "Play 4K on Plex",
"components.TvDetails.play4konplex": "Play in 4K on Plex",
"components.TvDetails.playonplex": "Play on Plex",
"components.TvDetails.recommendations": "Recommendations",
"components.TvDetails.seasons": "{seasonCount, plural, one {# Season} other {# Seasons}}",

Loading…
Cancel
Save