feat: new permission to allow users to see other users requests

closes #840
pull/845/head
sct 3 years ago
parent 06e941171a
commit 033ba9d41b

@ -8,7 +8,11 @@ import {
RelationCount, RelationCount,
AfterLoad, AfterLoad,
} from 'typeorm'; } from 'typeorm';
import { Permission, hasPermission } from '../lib/permissions'; import {
Permission,
hasPermission,
PermissionCheckOptions,
} from '../lib/permissions';
import { MediaRequest } from './MediaRequest'; import { MediaRequest } from './MediaRequest';
import bcrypt from 'bcrypt'; import bcrypt from 'bcrypt';
import path from 'path'; import path from 'path';
@ -85,8 +89,11 @@ export class User {
return filtered; return filtered;
} }
public hasPermission(permissions: Permission | Permission[]): boolean { public hasPermission(
return !!hasPermission(permissions, this.permissions); permissions: Permission | Permission[],
options?: PermissionCheckOptions
): boolean {
return !!hasPermission(permissions, this.permissions, options);
} }
public passwordMatch(password: string): Promise<boolean> { public passwordMatch(password: string): Promise<boolean> {

@ -13,6 +13,11 @@ export enum Permission {
REQUEST_4K_MOVIE = 2048, REQUEST_4K_MOVIE = 2048,
REQUEST_4K_TV = 4096, REQUEST_4K_TV = 4096,
REQUEST_ADVANCED = 8192, REQUEST_ADVANCED = 8192,
REQUEST_VIEW = 16384,
}
export interface PermissionCheckOptions {
type: 'and' | 'or';
} }
/** /**
@ -22,10 +27,12 @@ export enum Permission {
* *
* @param permissions Single permission or array of permissions * @param permissions Single permission or array of permissions
* @param value users current permission value * @param value users current permission value
* @param options Extra options to control permission check behavior (mainly for arrays)
*/ */
export const hasPermission = ( export const hasPermission = (
permissions: Permission | Permission[], permissions: Permission | Permission[],
value: number value: number,
options: PermissionCheckOptions = { type: 'and' }
): boolean => { ): boolean => {
let total = 0; let total = 0;
@ -35,8 +42,15 @@ export const hasPermission = (
} }
if (Array.isArray(permissions)) { if (Array.isArray(permissions)) {
// Combine all permission values into one if (value & Permission.ADMIN) {
total = permissions.reduce((a, v) => a + v, 0); return true;
}
switch (options.type) {
case 'and':
return permissions.every((permission) => !!(value & permission));
case 'or':
return permissions.some((permission) => !!(value & permission));
}
} else { } else {
total = permissions; total = permissions;
} }

@ -1,6 +1,6 @@
import { getRepository } from 'typeorm'; import { getRepository } from 'typeorm';
import { User } from '../entity/User'; import { User } from '../entity/User';
import { Permission } from '../lib/permissions'; import { Permission, PermissionCheckOptions } from '../lib/permissions';
import { getSettings } from '../lib/settings'; import { getSettings } from '../lib/settings';
export const checkUser: Middleware = async (req, _res, next) => { export const checkUser: Middleware = async (req, _res, next) => {
@ -34,10 +34,11 @@ export const checkUser: Middleware = async (req, _res, next) => {
}; };
export const isAuthenticated = ( export const isAuthenticated = (
permissions?: Permission | Permission[] permissions?: Permission | Permission[],
options?: PermissionCheckOptions
): Middleware => { ): Middleware => {
const authMiddleware: Middleware = (req, res, next) => { const authMiddleware: Middleware = (req, res, next) => {
if (!req.user || !req.user.hasPermission(permissions ?? 0)) { if (!req.user || !req.user.hasPermission(permissions ?? 0, options)) {
res.status(403).json({ res.status(403).json({
status: 403, status: 403,
error: 'You do not have permission to access this endpoint', error: 'You do not have permission to access this endpoint',

@ -57,7 +57,8 @@ requestRoutes.get('/', async (req, res, next) => {
} }
const [requests, requestCount] = req.user?.hasPermission( const [requests, requestCount] = req.user?.hasPermission(
Permission.MANAGE_REQUESTS [Permission.MANAGE_REQUESTS, Permission.REQUEST_VIEW],
{ type: 'or' }
) )
? await requestRepository.findAndCount({ ? await requestRepository.findAndCount({
order: sortFilter, order: sortFilter,
@ -102,10 +103,10 @@ requestRoutes.post(
if ( if (
req.body.userId && req.body.userId &&
!( !req.user?.hasPermission([
req.user?.hasPermission(Permission.MANAGE_USERS) && Permission.MANAGE_USERS,
req.user?.hasPermission(Permission.MANAGE_REQUESTS) Permission.MANAGE_REQUESTS,
) ])
) { ) {
return next({ return next({
status: 403, status: 403,

@ -39,6 +39,8 @@ export const messages = defineMessages({
advancedrequest: 'Advanced Requests', advancedrequest: 'Advanced Requests',
advancedrequestDescription: advancedrequestDescription:
'Grants permission to use advanced request options. (Ex. Changing servers/profiles/paths)', 'Grants permission to use advanced request options. (Ex. Changing servers/profiles/paths)',
viewrequests: 'View Requests',
viewrequestsDescription: "Grants permission to view other user's requests.",
}); });
interface PermissionEditProps { interface PermissionEditProps {
@ -85,6 +87,12 @@ export const PermissionEdit: React.FC<PermissionEditProps> = ({
description: intl.formatMessage(messages.advancedrequestDescription), description: intl.formatMessage(messages.advancedrequestDescription),
permission: Permission.REQUEST_ADVANCED, permission: Permission.REQUEST_ADVANCED,
}, },
{
id: 'viewrequests',
name: intl.formatMessage(messages.viewrequests),
description: intl.formatMessage(messages.viewrequestsDescription),
permission: Permission.REQUEST_VIEW,
},
], ],
}, },
{ {

@ -322,8 +322,7 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
</div> </div>
</> </>
)} )}
{hasPermission(Permission.MANAGE_REQUESTS) && {hasPermission([Permission.MANAGE_REQUESTS, Permission.MANAGE_USERS]) &&
hasPermission(Permission.MANAGE_USERS) &&
selectedUser && ( selectedUser && (
<div className="mt-0 sm:mt-2"> <div className="mt-0 sm:mt-2">
<Listbox <Listbox

@ -1,5 +1,9 @@
import useSwr from 'swr'; import useSwr from 'swr';
import { hasPermission, Permission } from '../../server/lib/permissions'; import {
hasPermission,
Permission,
PermissionCheckOptions,
} from '../../server/lib/permissions';
import { UserType } from '../../server/constants/user'; import { UserType } from '../../server/constants/user';
export { Permission, UserType }; export { Permission, UserType };
@ -20,7 +24,10 @@ interface UserHookResponse {
loading: boolean; loading: boolean;
error: string; error: string;
revalidate: () => Promise<boolean>; revalidate: () => Promise<boolean>;
hasPermission: (permission: Permission | Permission[]) => boolean; hasPermission: (
permission: Permission | Permission[],
options?: PermissionCheckOptions
) => boolean;
} }
export const useUser = ({ export const useUser = ({
@ -37,8 +44,11 @@ export const useUser = ({
} }
); );
const checkPermission = (permission: Permission | Permission[]): boolean => { const checkPermission = (
return hasPermission(permission, data?.permissions ?? 0); permission: Permission | Permission[],
options?: PermissionCheckOptions
): boolean => {
return hasPermission(permission, data?.permissions ?? 0, options);
}; };
return { return {

@ -114,6 +114,8 @@
"components.PermissionEdit.settingsDescription": "Grants permission to modify all Overseerr settings. A user must have this permission to grant it to others.", "components.PermissionEdit.settingsDescription": "Grants permission to modify all Overseerr settings. A user must have this permission to grant it to others.",
"components.PermissionEdit.users": "Manage Users", "components.PermissionEdit.users": "Manage Users",
"components.PermissionEdit.usersDescription": "Grants permission to manage Overseerr users. Users with this permission cannot modify users with Administrator privilege, or grant it.", "components.PermissionEdit.usersDescription": "Grants permission to manage Overseerr users. Users with this permission cannot modify users with Administrator privilege, or grant it.",
"components.PermissionEdit.viewrequests": "View Requests",
"components.PermissionEdit.viewrequestsDescription": "Grants permission to view other user's requests.",
"components.PermissionEdit.vote": "Vote", "components.PermissionEdit.vote": "Vote",
"components.PermissionEdit.voteDescription": "Grants permission to vote on requests (voting not yet implemented)", "components.PermissionEdit.voteDescription": "Grants permission to vote on requests (voting not yet implemented)",
"components.PersonDetails.appearsin": "Appears in", "components.PersonDetails.appearsin": "Appears in",

Loading…
Cancel
Save